DNS, DKIM and ACME improvements - part 4
This commit is contained in:
304
tests/src/automation/acme.rs
Normal file
304
tests/src/automation/acme.rs
Normal file
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::utils::server::TestServer;
|
||||
use common::network::dns::update::DNS_RECORDS;
|
||||
use dns_update::DnsRecord;
|
||||
use jmap_proto::error::set::SetErrorType;
|
||||
use registry::{
|
||||
schema::{
|
||||
enums::{AcmeChallengeType, AcmeRenewBefore, DnsRecordType},
|
||||
prelude::{ObjectType, Property},
|
||||
structs::{
|
||||
AcmeProvider, Certificate, CertificateManagement, CertificateManagementProperties,
|
||||
DkimManagement, DnsManagement, DnsManagementProperties, DnsServer, DnsServerCloudflare,
|
||||
Domain, SecretKey, SecretKeyValue, Task, TaskDomainManagement,
|
||||
},
|
||||
},
|
||||
types::map::Map,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
pub async fn test(test: &TestServer) {
|
||||
println!("Running ACME tests...");
|
||||
let account = test.account("admin@example.org");
|
||||
|
||||
// Create test Pebble and In Memory DNS servers
|
||||
let pebble_dns_id = account
|
||||
.registry_create_object(DnsServer::Cloudflare(DnsServerCloudflare {
|
||||
email: "test@pebble.org".to_string().into(),
|
||||
secret: SecretKey::Value(SecretKeyValue {
|
||||
secret: "secret".into(),
|
||||
}),
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
let in_memory_dns_id = account
|
||||
.registry_create_object(DnsServer::Cloudflare(DnsServerCloudflare {
|
||||
email: "test@memory.org".to_string().into(),
|
||||
secret: SecretKey::Value(SecretKeyValue {
|
||||
secret: "secret".into(),
|
||||
}),
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
// ACME provider creation should fail without a contact email
|
||||
account
|
||||
.registry_create_object_expect_err(AcmeProvider {
|
||||
directory: "https://localhost:14000/dir".to_string(),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.assert_type(SetErrorType::InvalidProperties)
|
||||
.assert_description_contains("At least one contact email is required");
|
||||
|
||||
// Create an ACME provider using TLS-ALPN-01 challenge
|
||||
let tls_acme_id = account
|
||||
.registry_create_object(AcmeProvider {
|
||||
directory: "https://localhost:14000/dir".to_string(),
|
||||
contact: Map::new(vec!["mailto:hello@tls.org".to_string()]),
|
||||
challenge_type: AcmeChallengeType::TlsAlpn01,
|
||||
renew_before: AcmeRenewBefore::R12,
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
let provider = account.registry_get::<AcmeProvider>(tls_acme_id).await;
|
||||
assert_eq!(provider.directory, "https://localhost:14000/dir");
|
||||
assert_eq!(
|
||||
provider.contact,
|
||||
Map::new(vec!["mailto:hello@tls.org".to_string()])
|
||||
);
|
||||
assert!(
|
||||
provider.account_uri.starts_with("https://localhost:14000"),
|
||||
"Provider {:?} has invalid account URI",
|
||||
provider
|
||||
);
|
||||
|
||||
// Create a domain and trigger TLS-ALPN-01 ACME renewal
|
||||
let domain_id = account
|
||||
.registry_create_object(Domain {
|
||||
name: "tls.org".to_string(),
|
||||
certificate_management: CertificateManagement::Automatic(
|
||||
CertificateManagementProperties {
|
||||
acme_provider_id: tls_acme_id,
|
||||
subject_alternative_names: Default::default(),
|
||||
},
|
||||
),
|
||||
dkim_management: DkimManagement::Manual,
|
||||
dns_management: DnsManagement::Automatic(DnsManagementProperties {
|
||||
dns_server_id: in_memory_dns_id,
|
||||
publish_records: Map::new(vec![
|
||||
DnsRecordType::Tlsa,
|
||||
DnsRecordType::AutoConfig,
|
||||
DnsRecordType::AutoConfigLegacy,
|
||||
DnsRecordType::AutoDiscover,
|
||||
DnsRecordType::MtaSts,
|
||||
]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
test.wait_for_tasks_skip_not_due().await;
|
||||
let certificate = account
|
||||
.registry_get_all::<Certificate>()
|
||||
.await
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.1;
|
||||
let mut sans = certificate.subject_alternative_names.into_inner();
|
||||
sans.sort();
|
||||
assert_eq!(
|
||||
sans,
|
||||
vec![
|
||||
"autoconfig.tls.org".to_string(),
|
||||
"autodiscover.tls.org".to_string(),
|
||||
"mta-sts.tls.org".to_string(),
|
||||
"ua-auto-config.tls.org".to_string()
|
||||
]
|
||||
);
|
||||
|
||||
// Make sure the TLSA records were added to the in-memory DNS server
|
||||
let records = DNS_RECORDS.lock().unwrap().clone();
|
||||
for record in [
|
||||
"_443._tcp.mta-sts.tls.org.",
|
||||
"_443._tcp.autoconfig.tls.org.",
|
||||
"_443._tcp.ua-auto-config.tls.org.",
|
||||
"_443._tcp.autodiscover.tls.org.",
|
||||
] {
|
||||
if records
|
||||
.iter()
|
||||
.find(|r| r.name == record && matches!(r.record, DnsRecord::TLSA(_)))
|
||||
.is_none()
|
||||
{
|
||||
panic!(
|
||||
"Expected TLSA record for {} not found in DNS records: {:?}",
|
||||
record, records
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure a task was created to renew the certificate before it expires
|
||||
let tasks = account.registry_get_all::<Task>().await;
|
||||
assert_eq!(
|
||||
tasks.len(),
|
||||
1,
|
||||
"Expected 1 task, found {}: {:?}",
|
||||
tasks.len(),
|
||||
tasks
|
||||
);
|
||||
let task = tasks.into_iter().next().unwrap().1;
|
||||
if let Task::AcmeRenewal(TaskDomainManagement {
|
||||
domain_id: task_domain_id,
|
||||
..
|
||||
}) = task
|
||||
{
|
||||
assert_eq!(
|
||||
task_domain_id, domain_id,
|
||||
"ACME renewal task has incorrect domain ID"
|
||||
);
|
||||
} else {
|
||||
panic!("Expected ACME renewal task, found: {:?}", task);
|
||||
}
|
||||
let not_valid_after = certificate.not_valid_after.timestamp();
|
||||
let not_valid_before = certificate.not_valid_before.timestamp();
|
||||
let length = not_valid_after - not_valid_before;
|
||||
assert_eq!(
|
||||
not_valid_after - length / 2,
|
||||
task.due_timestamp() as i64,
|
||||
"ACME renewal task has incorrect due timestamp, expected around {} but found {}",
|
||||
not_valid_after - length / 2,
|
||||
task.due_timestamp() as i64
|
||||
);
|
||||
account.registry_destroy_all(ObjectType::Certificate).await;
|
||||
account.registry_destroy_all(ObjectType::Task).await;
|
||||
|
||||
// Test ACME using HTTP-01 challenge and the server domain "mail.example.org"
|
||||
let http_acme_id = account
|
||||
.registry_create_object(AcmeProvider {
|
||||
directory: "https://localhost:14000/dir".to_string(),
|
||||
contact: Map::new(vec!["mailto:hello@example.org".to_string()]),
|
||||
challenge_type: AcmeChallengeType::Http01,
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
let domain_id = account.find_or_create_domain("example.org").await;
|
||||
account.registry_update_object(ObjectType::Domain, domain_id, json!({
|
||||
Property::CertificateManagement: CertificateManagement::Automatic(CertificateManagementProperties {
|
||||
acme_provider_id: http_acme_id,
|
||||
subject_alternative_names: Default::default(),
|
||||
}),
|
||||
})).await;
|
||||
test.wait_for_tasks_skip_not_due().await;
|
||||
let certificate = account
|
||||
.registry_get_all::<Certificate>()
|
||||
.await
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.1;
|
||||
let mut sans = certificate.subject_alternative_names.into_inner();
|
||||
sans.sort();
|
||||
assert_eq!(
|
||||
sans,
|
||||
vec![
|
||||
"autoconfig.example.org".to_string(),
|
||||
"autodiscover.example.org".to_string(),
|
||||
"imap.example.org".to_string(),
|
||||
"mail.example.org".to_string(),
|
||||
"mta-sts.example.org".to_string(),
|
||||
"mx1.example.org".to_string(),
|
||||
"mx2.example.org".to_string(),
|
||||
"pop3.example.org".to_string(),
|
||||
"smtp.example.org".to_string(),
|
||||
"ua-auto-config.example.org".to_string()
|
||||
]
|
||||
);
|
||||
account.registry_destroy_all(ObjectType::Certificate).await;
|
||||
account.registry_destroy_all(ObjectType::Task).await;
|
||||
|
||||
// Test ACME using DNS-01 challenge
|
||||
let dns_acme_id = account
|
||||
.registry_create_object(AcmeProvider {
|
||||
directory: "https://localhost:14000/dir".to_string(),
|
||||
contact: Map::new(vec!["mailto:hello@dns.org".to_string()]),
|
||||
challenge_type: AcmeChallengeType::Dns01,
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
account
|
||||
.registry_create_object(Domain {
|
||||
name: "dns.org".to_string(),
|
||||
certificate_management: CertificateManagement::Automatic(
|
||||
CertificateManagementProperties {
|
||||
acme_provider_id: dns_acme_id,
|
||||
subject_alternative_names: Default::default(),
|
||||
},
|
||||
),
|
||||
dkim_management: DkimManagement::Manual,
|
||||
dns_management: DnsManagement::Automatic(DnsManagementProperties {
|
||||
dns_server_id: pebble_dns_id,
|
||||
publish_records: Map::new(vec![DnsRecordType::Caa]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
test.wait_for_tasks_skip_not_due().await;
|
||||
let certificate = account
|
||||
.registry_get_all::<Certificate>()
|
||||
.await
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.1;
|
||||
let mut sans = certificate.subject_alternative_names.into_inner();
|
||||
sans.sort();
|
||||
assert_eq!(sans, vec!["*.dns.org".to_string()]);
|
||||
account.registry_destroy_all(ObjectType::Certificate).await;
|
||||
account.registry_destroy_all(ObjectType::Task).await;
|
||||
|
||||
// Test ACME using DNS-01 challenge
|
||||
let dns_acme_id = account
|
||||
.registry_create_object(AcmeProvider {
|
||||
directory: "https://localhost:14000/dir".to_string(),
|
||||
contact: Map::new(vec!["mailto:hello@persist.org".to_string()]),
|
||||
challenge_type: AcmeChallengeType::DnsPersist01,
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
account
|
||||
.registry_create_object(Domain {
|
||||
name: "persist.org".to_string(),
|
||||
certificate_management: CertificateManagement::Automatic(
|
||||
CertificateManagementProperties {
|
||||
acme_provider_id: dns_acme_id,
|
||||
subject_alternative_names: Default::default(),
|
||||
},
|
||||
),
|
||||
dkim_management: DkimManagement::Manual,
|
||||
dns_management: DnsManagement::Automatic(DnsManagementProperties {
|
||||
dns_server_id: pebble_dns_id,
|
||||
publish_records: Map::new(vec![DnsRecordType::Caa]),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
test.wait_for_tasks_skip_not_due().await;
|
||||
let certificate = account
|
||||
.registry_get_all::<Certificate>()
|
||||
.await
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.1;
|
||||
let mut sans = certificate.subject_alternative_names.into_inner();
|
||||
sans.sort();
|
||||
assert_eq!(sans, vec!["*.persist.org".to_string()]);
|
||||
}
|
||||
11
tests/src/automation/dkim.rs
Normal file
11
tests/src/automation/dkim.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::utils::server::TestServer;
|
||||
|
||||
pub async fn test(test: &TestServer) {
|
||||
println!("Running DKIM Management tests...");
|
||||
}
|
||||
11
tests/src/automation/dns.rs
Normal file
11
tests/src/automation/dns.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::utils::server::TestServer;
|
||||
|
||||
pub async fn test(test: &TestServer) {
|
||||
println!("Running DNS Management tests...");
|
||||
}
|
||||
117
tests/src/automation/mod.rs
Normal file
117
tests/src/automation/mod.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod acme;
|
||||
pub mod dkim;
|
||||
pub mod dns;
|
||||
|
||||
use registry::{
|
||||
schema::{
|
||||
enums::{NetworkListenerProtocol, ServiceProtocol},
|
||||
prelude::Property,
|
||||
structs::{MailExchanger, Service, SystemSettings},
|
||||
},
|
||||
types::list::List,
|
||||
};
|
||||
use utils::map::vec_map::VecMap;
|
||||
|
||||
use crate::utils::server::TestServerBuilder;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn automation_tests() {
|
||||
let mut test = TestServerBuilder::new("automation_tests")
|
||||
.await
|
||||
.with_listener(NetworkListenerProtocol::Http, "http", 8898, false)
|
||||
.await
|
||||
.with_default_listeners()
|
||||
.await
|
||||
.build()
|
||||
.await;
|
||||
|
||||
// Create admin account
|
||||
let account = test.create_admin_account("admin@example.org").await;
|
||||
account
|
||||
.registry_update_setting(
|
||||
SystemSettings {
|
||||
mail_exchangers: List::from_iter([
|
||||
MailExchanger {
|
||||
priority: 10u64,
|
||||
hostname: "mx1.example.org".to_string().into(),
|
||||
},
|
||||
MailExchanger {
|
||||
priority: 20u64,
|
||||
hostname: "mx2.example.org".to_string().into(),
|
||||
},
|
||||
]),
|
||||
services: VecMap::from_iter([
|
||||
(
|
||||
ServiceProtocol::Caldav,
|
||||
Service {
|
||||
cleartext: false,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
ServiceProtocol::Carddav,
|
||||
Service {
|
||||
cleartext: false,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
ServiceProtocol::Imap,
|
||||
Service {
|
||||
cleartext: false,
|
||||
hostname: "imap.example.org".to_string().into(),
|
||||
},
|
||||
),
|
||||
(
|
||||
ServiceProtocol::Jmap,
|
||||
Service {
|
||||
cleartext: false,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
ServiceProtocol::Managesieve,
|
||||
Service {
|
||||
cleartext: false,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
ServiceProtocol::Pop3,
|
||||
Service {
|
||||
cleartext: false,
|
||||
hostname: "pop3.example.org".to_string().into(),
|
||||
},
|
||||
),
|
||||
(
|
||||
ServiceProtocol::Smtp,
|
||||
Service {
|
||||
cleartext: false,
|
||||
hostname: "smtp.example.org".to_string().into(),
|
||||
},
|
||||
),
|
||||
(
|
||||
ServiceProtocol::Webdav,
|
||||
Service {
|
||||
cleartext: false,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
]),
|
||||
|
||||
..Default::default()
|
||||
},
|
||||
&[Property::MailExchangers, Property::Services],
|
||||
)
|
||||
.await;
|
||||
account.reload_settings().await;
|
||||
test.insert_account(account);
|
||||
|
||||
acme::test(&test).await;
|
||||
}
|
||||
@@ -18,7 +18,7 @@ use registry::{
|
||||
prelude::{ObjectType, Property, SocketAddr},
|
||||
structs::{
|
||||
ClusterListenerGroup, ClusterListenerGroupProperties, ClusterRole, ClusterTaskGroup,
|
||||
Coordinator, Expression, Http, NatsCoordinator, NetworkListener, RedisStore,
|
||||
Coordinator, NatsCoordinator, NetworkListener, RedisStore,
|
||||
},
|
||||
},
|
||||
types::map::Map,
|
||||
@@ -60,14 +60,6 @@ fn cluster_tests() {
|
||||
|
||||
// Create initial server
|
||||
let test = TestServerBuilder::new("cluster_test_0")
|
||||
.await
|
||||
.with_object(Http {
|
||||
base_url: Expression {
|
||||
else_: "'https://127.0.0.1:' + local_port".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.with_object(coordinator)
|
||||
.await
|
||||
|
||||
@@ -13,6 +13,8 @@ use jemallocator::Jemalloc;
|
||||
#[global_allocator]
|
||||
static GLOBAL: Jemalloc = Jemalloc;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod automation;
|
||||
#[cfg(test)]
|
||||
pub mod cluster;
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -21,8 +21,8 @@ use mail_auth::{
|
||||
};
|
||||
use registry::{
|
||||
schema::structs::{
|
||||
DkimReportSettings, DmarcReportSettings, Domain, Expression, ExpressionMatch, SenderAuth,
|
||||
SpfReportSettings,
|
||||
CertificateManagement, DkimManagement, DkimReportSettings, DmarcReportSettings,
|
||||
DnsManagement, Domain, Expression, ExpressionMatch, SenderAuth, SpfReportSettings,
|
||||
},
|
||||
types::list::List,
|
||||
};
|
||||
@@ -45,6 +45,9 @@ async fn dmarc() {
|
||||
let domain_id = admin
|
||||
.registry_create_object(Domain {
|
||||
name: "localdomain.org".into(),
|
||||
certificate_management: CertificateManagement::Manual,
|
||||
dns_management: DnsManagement::Manual,
|
||||
dkim_management: DkimManagement::Manual,
|
||||
allow_relaying: true,
|
||||
..Default::default()
|
||||
})
|
||||
|
||||
@@ -13,9 +13,9 @@ use crate::{
|
||||
};
|
||||
use core::panic;
|
||||
use registry::schema::structs::{
|
||||
Domain, Expression, LookupStore, MtaStageConnect, MtaStageData, MtaStageEhlo,
|
||||
MtaStageMail, MtaStageRcpt, SieveSystemInterpreter, SieveSystemScript, SqliteStore,
|
||||
StoreLookup,
|
||||
CertificateManagement, DkimManagement, DnsManagement, Domain, Expression, LookupStore,
|
||||
MtaStageConnect, MtaStageData, MtaStageEhlo, MtaStageMail, MtaStageRcpt,
|
||||
SieveSystemInterpreter, SieveSystemScript, SqliteStore, StoreLookup,
|
||||
};
|
||||
use smtp::scripts::{ScriptResult, event_loop::RunScript};
|
||||
use std::{fs, path::PathBuf};
|
||||
@@ -36,6 +36,9 @@ async fn sieve_scripts() {
|
||||
let domain_id = admin
|
||||
.registry_create_object(Domain {
|
||||
name: "foobar.org".into(),
|
||||
certificate_management: CertificateManagement::Manual,
|
||||
dns_management: DnsManagement::Manual,
|
||||
dkim_management: DkimManagement::Manual,
|
||||
allow_relaying: true,
|
||||
..Default::default()
|
||||
})
|
||||
|
||||
@@ -18,7 +18,8 @@ use mail_auth::{
|
||||
use registry::schema::{
|
||||
enums::{DkimCanonicalization, DkimRotationStage},
|
||||
structs::{
|
||||
Dkim1Signature, DkimSignature, Domain, Expression, SecretText, SecretTextValue, SenderAuth,
|
||||
CertificateManagement, Dkim1Signature, DkimManagement, DkimSignature, DnsManagement,
|
||||
Domain, Expression, SecretText, SecretTextValue, SenderAuth,
|
||||
},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -40,6 +41,9 @@ async fn sign_and_seal() {
|
||||
let domain_id = admin
|
||||
.registry_create_object(Domain {
|
||||
name: "example.com".into(),
|
||||
certificate_management: CertificateManagement::Manual,
|
||||
dns_management: DnsManagement::Manual,
|
||||
dkim_management: DkimManagement::Manual,
|
||||
allow_relaying: true,
|
||||
..Default::default()
|
||||
})
|
||||
|
||||
@@ -12,8 +12,9 @@ use registry::{
|
||||
enums::{AccountType, Locale, Permission, StorageQuota},
|
||||
prelude::{Object, ObjectType, Property},
|
||||
structs::{
|
||||
Account, Credential, CredentialPermissions, CredentialPermissionsList, CustomRoles,
|
||||
Domain, EmailAlias, EncryptionAtRest, EncryptionSettings, GroupAccount, MailingList,
|
||||
Account, CertificateManagement, Credential, CredentialPermissions,
|
||||
CredentialPermissionsList, CustomRoles, DkimManagement, DnsManagement, Domain,
|
||||
EmailAlias, EncryptionAtRest, EncryptionSettings, GroupAccount, MailingList,
|
||||
PasswordCredential, Permissions, PermissionsList, PublicKey, Roles,
|
||||
SecondaryCredential, UserAccount, UserRoles,
|
||||
},
|
||||
@@ -114,6 +115,9 @@ pub async fn test(test: &TestServer) {
|
||||
.write(RegistryWrite::insert(
|
||||
&Domain {
|
||||
name: "test.org".into(),
|
||||
certificate_management: CertificateManagement::Manual,
|
||||
dns_management: DnsManagement::Manual,
|
||||
dkim_management: DkimManagement::Manual,
|
||||
is_enabled: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -126,6 +130,9 @@ pub async fn test(test: &TestServer) {
|
||||
.write(RegistryWrite::insert(
|
||||
&Domain {
|
||||
name: "test.net".into(),
|
||||
certificate_management: CertificateManagement::Manual,
|
||||
dns_management: DnsManagement::Manual,
|
||||
dkim_management: DkimManagement::Manual,
|
||||
is_enabled: true,
|
||||
..Default::default()
|
||||
}
|
||||
@@ -243,6 +250,9 @@ pub async fn test(test: &TestServer) {
|
||||
Domain {
|
||||
name: "test.org".into(),
|
||||
is_enabled: true,
|
||||
certificate_management: CertificateManagement::Manual,
|
||||
dns_management: DnsManagement::Manual,
|
||||
dkim_management: DkimManagement::Manual,
|
||||
..Default::default()
|
||||
},
|
||||
RegistryWriteResult::PrimaryKeyConflict {
|
||||
|
||||
@@ -15,8 +15,9 @@ use registry::{
|
||||
enums::{AccountType, StorageQuota},
|
||||
prelude::{ObjectType, Property},
|
||||
structs::{
|
||||
Account, Credential, Domain, EmailAlias, Expression, ExpressionMatch, GroupAccount,
|
||||
MailingList, PasswordCredential, SubAddressing, SubAddressingCustom, UserAccount,
|
||||
Account, CertificateManagement, Credential, DkimManagement, DnsManagement, Domain,
|
||||
EmailAlias, Expression, ExpressionMatch, GroupAccount, MailingList, PasswordCredential,
|
||||
SubAddressing, SubAddressingCustom, UserAccount,
|
||||
},
|
||||
},
|
||||
types::{EnumImpl, list::List, map::Map},
|
||||
@@ -33,6 +34,9 @@ pub async fn test(test: &TestServer) {
|
||||
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("catchy@example.com".to_string()),
|
||||
@@ -60,6 +64,9 @@ pub async fn test(test: &TestServer) {
|
||||
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
|
||||
@@ -371,6 +378,9 @@ pub async fn test(test: &TestServer) {
|
||||
.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(),
|
||||
|
||||
@@ -18,10 +18,10 @@ use registry::{
|
||||
enums::{AccountType, Permission, TenantStorageQuota},
|
||||
prelude::{ObjectType, Property},
|
||||
structs::{
|
||||
Account, Credential, Dkim1Signature, DkimSignature, DnsServer, DnsServerCloudflare,
|
||||
Domain, GroupAccount, MailingList, OAuthClient, PasswordCredential, Permissions,
|
||||
PermissionsList, Role, SecretKey, SecretKeyValue, SecretText, SecretTextValue, Tenant,
|
||||
UserAccount, UserRoles,
|
||||
Account, CertificateManagement, Credential, Dkim1Signature, DkimManagement,
|
||||
DkimSignature, DnsManagement, DnsServer, DnsServerCloudflare, Domain, GroupAccount,
|
||||
MailingList, OAuthClient, PasswordCredential, Permissions, PermissionsList, Role,
|
||||
SecretKey, SecretKeyValue, SecretText, SecretTextValue, Tenant, UserAccount, UserRoles,
|
||||
},
|
||||
},
|
||||
types::{EnumImpl, ObjectImpl, list::List, map::Map},
|
||||
@@ -60,6 +60,9 @@ pub async fn test(test: &mut TestServer) {
|
||||
.registry_create_object(Domain {
|
||||
name: format!("tenant{name}.org"),
|
||||
member_tenant_id: tenant_id.into(),
|
||||
certificate_management: CertificateManagement::Manual,
|
||||
dns_management: DnsManagement::Manual,
|
||||
dkim_management: DkimManagement::Manual,
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
@@ -176,6 +179,9 @@ pub async fn test(test: &mut TestServer) {
|
||||
Domain {
|
||||
name: format!("tenant{tenant_id_pos}.org"),
|
||||
member_tenant_id,
|
||||
certificate_management: CertificateManagement::Manual,
|
||||
dns_management: DnsManagement::Manual,
|
||||
dkim_management: DkimManagement::Manual,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
|
||||
@@ -12,8 +12,9 @@ use registry::{
|
||||
enums::Permission,
|
||||
prelude::{ObjectType, Property},
|
||||
structs::{
|
||||
self, Credential, CustomRoles, Domain, EmailAlias, GroupAccount, PasswordCredential,
|
||||
Permissions, PermissionsList, Roles, UserAccount,
|
||||
self, CertificateManagement, Credential, CustomRoles, DkimManagement, DnsManagement,
|
||||
Domain, EmailAlias, GroupAccount, PasswordCredential, Permissions, PermissionsList,
|
||||
Roles, UserAccount,
|
||||
},
|
||||
},
|
||||
types::{list::List, map::Map},
|
||||
@@ -244,6 +245,9 @@ impl Account {
|
||||
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
|
||||
|
||||
@@ -407,7 +407,14 @@ fn remove_server_set_props(value: &mut serde_json::Value) {
|
||||
.and_then(|v| v.as_str())
|
||||
.is_some_and(|t| ["AppPassword", "ApiKey"].contains(&t));
|
||||
obj.retain(|k, v| {
|
||||
!(["createdAt", "credentialId", "retireAt"].contains(&k.as_str())
|
||||
!([
|
||||
"createdAt",
|
||||
"credentialId",
|
||||
"retireAt",
|
||||
"accountKey",
|
||||
"accountUri",
|
||||
]
|
||||
.contains(&k.as_str())
|
||||
|| (is_app_pass && k == "secret")
|
||||
|| (k == "memberTenantId" && v.is_null()))
|
||||
});
|
||||
|
||||
@@ -42,8 +42,8 @@ use registry::{
|
||||
enums::{DataStoreType, EventPolicy, NetworkListenerProtocol, TracingLevel},
|
||||
prelude::{Object, ObjectType, SocketAddr},
|
||||
structs::{
|
||||
Authentication, Certificate, Expression, Http, NetworkListener, PublicText,
|
||||
SecretKeyFile, SecretText, Tracer, TracerStdout,
|
||||
Authentication, Certificate, NetworkListener, PublicText, SecretKeyFile, SecretText,
|
||||
Tracer, TracerStdout,
|
||||
},
|
||||
},
|
||||
types::{EnumImpl, map::Map},
|
||||
@@ -149,28 +149,12 @@ impl TestServerBuilder {
|
||||
] {
|
||||
this = this.with_listener(protocol, name, port, use_tls).await;
|
||||
}
|
||||
this.with_object(Http {
|
||||
base_url: Expression {
|
||||
else_: "'https://127.0.0.1:8899'".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
this
|
||||
}
|
||||
|
||||
pub async fn with_http_listener(self, port: u16) -> Self {
|
||||
self.with_listener(NetworkListenerProtocol::Http, "jmap", port, true)
|
||||
.await
|
||||
.with_object(Http {
|
||||
base_url: Expression {
|
||||
else_: format!("'https://127.0.0.1:{}'", port),
|
||||
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn with_smtp_listener(self, port: u16) -> Self {
|
||||
@@ -216,7 +200,7 @@ impl TestServerBuilder {
|
||||
}
|
||||
self.insert_object(NetworkListener {
|
||||
bind: Map::new(vec![
|
||||
SocketAddr::from_str(&format!("127.0.0.1:{port}")).unwrap(),
|
||||
SocketAddr::from_str(&format!("0.0.0.0:{port}")).unwrap(),
|
||||
]),
|
||||
name: name.to_string(),
|
||||
protocol,
|
||||
@@ -469,11 +453,15 @@ impl TestServer {
|
||||
}
|
||||
|
||||
pub async fn wait_for_tasks(&self) {
|
||||
wait_for_tasks(&self.server, false).await;
|
||||
wait_for_tasks(&self.server, false, false).await;
|
||||
}
|
||||
|
||||
pub async fn wait_for_tasks_skip_failures(&self) {
|
||||
wait_for_tasks(&self.server, true).await;
|
||||
wait_for_tasks(&self.server, false, true).await;
|
||||
}
|
||||
|
||||
pub async fn wait_for_tasks_skip_not_due(&self) {
|
||||
wait_for_tasks(&self.server, true, false).await;
|
||||
}
|
||||
|
||||
pub async fn blob_expire_all(&self) {
|
||||
|
||||
@@ -21,6 +21,7 @@ use registry::{
|
||||
},
|
||||
types::{EnumImpl, duration::Duration},
|
||||
};
|
||||
use store::write::now;
|
||||
use store::{
|
||||
Deserialize, IterateParams, ValueKey,
|
||||
write::{TaskQueueClass, ValueClass},
|
||||
@@ -153,7 +154,7 @@ fn build_search_store(typ: SearchStoreType, _path: &str) -> SearchStore {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn wait_for_tasks(server: &Server, skip_permanent_failures: bool) {
|
||||
pub async fn wait_for_tasks(server: &Server, skip_not_due: bool, skip_permanent_failures: bool) {
|
||||
let mut count = 0;
|
||||
loop {
|
||||
let mut has_index_tasks = None;
|
||||
@@ -169,7 +170,9 @@ pub async fn wait_for_tasks(server: &Server, skip_permanent_failures: bool) {
|
||||
.ascending(),
|
||||
|_, value| {
|
||||
let task = Task::deserialize(value)?;
|
||||
if skip_permanent_failures && matches!(task.status(), TaskStatus::Failed(_)) {
|
||||
if (skip_permanent_failures && matches!(task.status(), TaskStatus::Failed(_)))
|
||||
|| (skip_not_due && task.due_timestamp() > now())
|
||||
{
|
||||
Ok(true)
|
||||
} else {
|
||||
has_index_tasks = Some(task);
|
||||
@@ -195,7 +198,7 @@ pub async fn wait_for_tasks(server: &Server, skip_permanent_failures: bool) {
|
||||
|
||||
pub async fn assert_is_empty(server: &Server, include_registry: bool) {
|
||||
// Wait for pending index tasks
|
||||
wait_for_tasks(server, false).await;
|
||||
wait_for_tasks(server, false, false).await;
|
||||
|
||||
// Assert is empty
|
||||
store_assert_is_empty(
|
||||
|
||||
@@ -1174,7 +1174,8 @@ fn flatten_xml(xml: &str) -> Vec<(String, String)> {
|
||||
}
|
||||
}
|
||||
Event::GeneralRef(entity) => {
|
||||
let value: Cow<str> = match entity.as_ref() {
|
||||
let entity_slice: &[u8] = entity.as_ref();
|
||||
let value: Cow<str> = match entity_slice {
|
||||
b"lt" => "<".into(),
|
||||
b"gt" => ">".into(),
|
||||
b"amp" => "&".into(),
|
||||
|
||||
Reference in New Issue
Block a user