Everything clients, users and operators meet now carries the fork's name, with no aliases (SPEC.md §2.4, changed here from "protocol identifiers stay"): - JMAP: upstream's registry capability is urn:inbuxa:jmap:registry, beside the fork's own urn:inbuxa:jmap. - WebDAV lock and sync tokens are urn:inbuxa:dav*; clients resync once. - Sieve: vnd.inbuxa.while and vnd.inbuxa.expressions. sieve-rs spells these into its compiler, so it's vendored (vendor/sieve-rs, 0.7.3) and patched in; a unit test fails if Cargo.lock ever moves past the vendored copy. The trusted runtime now names itself too, rather than answering sieve-rs's default. - The web interface's OAuth client is inbuxa-webui. On every start the old stalwart-webui client is removed and any application naming it is moved over. - The spam filter's blobs are INBUXA_SPAM_*; every start moves any left under the old keys, so a trained model survives. - SQL stores and log files default to inbuxa, in the code and in the schema served to the admin (checksum regenerated). - Settings are INBUXA_* only. A STALWART_* variable that's set where its INBUXA_* one isn't stops the server at startup, naming it. - The version-upgrade messages link docs.inbuxa.org's migration page, and the OpenAPI description, smtp crate metadata and web-push test fixtures lose the name. Kept on purpose, allowlisted with reasons: the OAuth key-derivation contexts (renaming them would end every session and invalidate every sealed client id) and the hashed application prefix. Also fixes a latent start-up failure: ensure_client updated an existing first-party client with a revision of 0, which the registry's assertion never matches, so adding a redirect URI or changing the webmail secret failed start-up. And the principal session test now expects legacyProtocols (C-1, added 2026-09-21), which it had missed. Tested: the server builds without warnings; common's 106 unit tests, including the vendoring check; a new integration test for the two start-up migrations; and the webdav, jmap, imap and SMTP Sieve suites.
113 lines
3.9 KiB
Rust
113 lines
3.9 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2026 Coffey Labs
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
//! inbuxa: what an install from before the rename carries over (SPEC.md
|
|
//! §2.4). The web interface's OAuth client is retired rather than kept as an
|
|
//! alias, and a trained spam filter moves to its new keys.
|
|
|
|
use crate::utils::server::TestServerBuilder;
|
|
use common::manager::{SPAM_CLASSIFIER_KEY, SPAM_TRAINER_KEY, first_party::WEB_INTERFACE_CLIENT_ID};
|
|
use registry::{
|
|
schema::{
|
|
enums::CompressionAlgo,
|
|
prelude::{ObjectType, Property},
|
|
structs::{Application, OAuthClient},
|
|
},
|
|
types::map::Map,
|
|
};
|
|
|
|
const OLD_CLIENT_ID: &str = "stalwart-webui";
|
|
const OLD_TRAINER_KEY: &[u8] = b"STALWART_SPAM_TRAIN_DATA.lz4";
|
|
const OLD_CLASSIFIER_KEY: &[u8] = b"STALWART_SPAM_CLASSIFIER_MODEL.lz4";
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
async fn renamed_identifiers() {
|
|
// The web interface registered under upstream's client id, and an
|
|
// application naming it. Disabled, so nothing is fetched for it.
|
|
let test = TestServerBuilder::new("renamed_identifiers")
|
|
.await
|
|
.with_object(OAuthClient {
|
|
client_id: OLD_CLIENT_ID.to_string(),
|
|
redirect_uris: Map::new(vec!["https://127.0.0.1/admin/oauth/callback".to_string()]),
|
|
..Default::default()
|
|
})
|
|
.await
|
|
.with_object(Application {
|
|
enabled: false,
|
|
description: "Web interface".to_string(),
|
|
resource_url: "https://127.0.0.1/webui.zip".to_string(),
|
|
url_prefix: Map::new(vec!["/admin".to_string()]),
|
|
oauth_client_id: Some(OLD_CLIENT_ID.to_string()),
|
|
..Default::default()
|
|
})
|
|
.await
|
|
.disable_services()
|
|
.build()
|
|
.await;
|
|
|
|
println!("Running renamed identifier tests...");
|
|
let registry = test.server.registry();
|
|
assert_eq!(
|
|
registry
|
|
.primary_key(
|
|
ObjectType::OAuthClient.into(),
|
|
Property::ClientId,
|
|
OLD_CLIENT_ID.as_bytes().to_vec(),
|
|
)
|
|
.await
|
|
.unwrap(),
|
|
None,
|
|
"the pre-rename web interface client is retired on start"
|
|
);
|
|
let applications = registry.list::<Application>().await.unwrap();
|
|
assert_eq!(applications.len(), 1);
|
|
assert_eq!(
|
|
applications[0].object.oauth_client_id.as_deref(),
|
|
Some(WEB_INTERFACE_CLIENT_ID),
|
|
"an application naming the old client moves to the new one"
|
|
);
|
|
|
|
// A trained model under the pre-rename keys moves to the new ones.
|
|
let blobs = test.server.blob_store();
|
|
for (key, data) in [
|
|
(OLD_TRAINER_KEY, &b"trainer"[..]),
|
|
(OLD_CLASSIFIER_KEY, &b"classifier"[..]),
|
|
] {
|
|
blobs.put_blob(key, data, CompressionAlgo::None).await.unwrap();
|
|
}
|
|
migration::try_migrate(&test.server).await.unwrap();
|
|
for (old, new, data) in [
|
|
(OLD_TRAINER_KEY, SPAM_TRAINER_KEY, &b"trainer"[..]),
|
|
(OLD_CLASSIFIER_KEY, SPAM_CLASSIFIER_KEY, &b"classifier"[..]),
|
|
] {
|
|
assert_eq!(blobs.get_blob(new, 0..usize::MAX).await.unwrap().as_deref(), Some(data));
|
|
assert_eq!(blobs.get_blob(old, 0..usize::MAX).await.unwrap(), None);
|
|
}
|
|
|
|
// A blob already under the new key wins over a stale old one.
|
|
blobs
|
|
.put_blob(OLD_CLASSIFIER_KEY, b"stale", CompressionAlgo::None)
|
|
.await
|
|
.unwrap();
|
|
migration::try_migrate(&test.server).await.unwrap();
|
|
assert_eq!(
|
|
blobs
|
|
.get_blob(SPAM_CLASSIFIER_KEY, 0..usize::MAX)
|
|
.await
|
|
.unwrap()
|
|
.as_deref(),
|
|
Some(&b"classifier"[..])
|
|
);
|
|
assert_eq!(blobs.get_blob(OLD_CLASSIFIER_KEY, 0..usize::MAX).await.unwrap(), None);
|
|
|
|
// With nothing left to move, starting again changes nothing.
|
|
migration::try_migrate(&test.server).await.unwrap();
|
|
assert_eq!(
|
|
blobs.get_blob(SPAM_TRAINER_KEY, 0..usize::MAX).await.unwrap().as_deref(),
|
|
Some(&b"trainer"[..])
|
|
);
|
|
}
|