Support stable prefix for MSC4287 m.key_backup

This commit is contained in:
Andy Balaam
2026-04-21 13:40:42 +01:00
committed by Andy Balaam
parent 103ff7d3ec
commit fd6cb6647f
4 changed files with 109 additions and 20 deletions
+3
View File
@@ -8,6 +8,9 @@ All notable changes to this project will be documented in this file.
### Features
- Support the stable `m.key_backup` prefix for MSC4287: Sharing key backup
preference between clients.
([#6410](https://github.com/matrix-org/matrix-rust-sdk/pull/6410))
- Add new high-level search helpers in `matrix_sdk_ui::search` to perform searches for messages in
a room or across all rooms.
([#6394](https://github.com/matrix-org/matrix-rust-sdk/pull/6394))
@@ -120,7 +120,7 @@ mod types;
pub use self::types::{EnableProgress, RecoveryError, RecoveryState, Result};
use self::{
futures::{Enable, RecoverAndReset, Reset},
types::{BackupDisabledContent, SecretStorageDisabledContent},
types::{BackupDisabledContent, KeyBackupContent, SecretStorageDisabledContent},
};
use crate::encryption::{AuthData, CrossSigningResetAuthType, CrossSigningResetHandle};
@@ -325,6 +325,8 @@ impl Recovery {
// Now let's "delete" the actual `m.secret.storage.default_key` event.
self.client.account().set_account_data(SecretStorageDisabledContent {}).await?;
// Make sure that we don't re-enable backups automatically.
self.client.account().set_account_data(KeyBackupContent { enabled: false }).await?;
// (Unstable prefix version of KeyBackupContent)
self.client.account().set_account_data(BackupDisabledContent { disabled: true }).await?;
// Finally, "delete" all the known secrets we have in the account data.
self.delete_all_known_secrets().await?;
@@ -651,16 +653,26 @@ impl Recovery {
/// Run a network request to figure whether backups have been disabled at
/// the account level.
async fn are_backups_marked_as_disabled(&self) -> Result<bool> {
Ok(self
.client
.account()
.fetch_account_data_static::<BackupDisabledContent>()
.await?
.map(|event| event.deserialize().map(|event| event.disabled).unwrap_or(false))
.unwrap_or(false))
if let Some(key_backup_content) =
self.client.account().fetch_account_data_static::<KeyBackupContent>().await?
{
Ok(key_backup_content.deserialize().map(|event| !event.enabled).unwrap_or(false))
} else {
Ok(self
.client
.account()
.fetch_account_data_static::<BackupDisabledContent>()
.await?
.map(|event| event.deserialize().map(|event| event.disabled).unwrap_or(false))
.unwrap_or(false))
}
}
async fn mark_backup_as_enabled(&self) -> Result<()> {
self.client.account().set_account_data(KeyBackupContent { enabled: true }).await?;
// Unstable prefix: will be removed when sufficient time has passed for clients
// to use the stable prefix.
self.client.account().set_account_data(BackupDisabledContent { disabled: false }).await?;
Ok(())
@@ -105,13 +105,22 @@ pub enum RecoveryState {
#[ruma_event(type = "m.secret_storage.default_key", kind = GlobalAccountData)]
pub(super) struct SecretStorageDisabledContent {}
/// A custom global account data event which tells us that a new backup should
/// not be automatically created.
/// A global account data event which tells us that a new backup should
/// be automatically created.
///
/// This event is defined in [MSC4287].
///
/// [MSC4287]: https://github.com/matrix-org/matrix-spec-proposals/pull/4287
#[derive(Clone, Debug, Default, Deserialize, Serialize, EventContent)]
#[ruma_event(type = "m.key_backup", kind = GlobalAccountData)]
pub(super) struct KeyBackupContent {
pub enabled: bool,
}
/// Unstable prefix form of [MSC4287].
///
/// [MSC4287]: https://github.com/matrix-org/matrix-spec-proposals/pull/4287
#[derive(Clone, Debug, Default, Deserialize, Serialize, EventContent)]
#[ruma_event(type = "m.org.matrix.custom.backup_disabled", kind = GlobalAccountData)]
pub(super) struct BackupDisabledContent {
pub disabled: bool,
@@ -226,6 +226,26 @@ async fn enable(
) {
let recovery = client.encryption().recovery();
let key_backup_content = Arc::new(Mutex::new(None));
let _quard = Mock::given(method("PUT"))
.and(path(format!("_matrix/client/r0/user/{user_id}/account_data/m.key_backup")))
.and(header("authorization", "Bearer 1234"))
.and({
let key_backup_content = key_backup_content.clone();
move |request: &wiremock::Request| {
let content: Value = request.body_json().expect("The body should be a JSON body");
*key_backup_content.lock().unwrap() = Some(content);
true
}
})
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
.expect(1)
.mount_as_scoped(server)
.await;
let backup_disabled_content = Arc::new(Mutex::new(None));
let _quard = Mock::given(method("PUT"))
@@ -493,6 +513,26 @@ async fn test_backups_enabling() {
.mount(&server)
.await;
Mock::given(method("PUT"))
.and(path(format!("_matrix/client/r0/user/{user_id}/account_data/m.key_backup")))
.and(header("authorization", "Bearer 1234"))
.and(|request: &wiremock::Request| {
#[derive(Deserialize)]
struct Enabled {
enabled: bool,
}
let content: Enabled = request.body_json().expect("The body should be a JSON body");
assert!(content.enabled, "The backup support should be marked as enabled.");
true
})
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
.expect(1)
.mount(&server)
.await;
Mock::given(method("PUT"))
.and(path(format!(
"_matrix/client/r0/user/{user_id}/account_data/m.org.matrix.custom.backup_disabled"
@@ -612,6 +652,26 @@ async fn test_recovery_disabling() {
.mount(&server)
.await;
Mock::given(method("PUT"))
.and(path(format!("_matrix/client/r0/user/{user_id}/account_data/m.key_backup")))
.and(header("authorization", "Bearer 1234"))
.and(|request: &wiremock::Request| {
#[derive(Deserialize)]
struct Enabled {
enabled: bool,
}
let content: Enabled = request.body_json().expect("The body should be a JSON body");
assert!(!content.enabled, "The backup support should be marked as disabled.");
true
})
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
.expect(1)
.mount(&server)
.await;
Mock::given(method("PUT"))
.and(path(format!(
"_matrix/client/r0/user/{user_id}/account_data/m.org.matrix.custom.backup_disabled"
@@ -907,6 +967,15 @@ async fn test_reset_identity() {
.await;
// Re-enable backups
Mock::given(method("PUT"))
.and(path(format!("_matrix/client/r0/user/{user_id}/account_data/m.key_backup")))
.and(header("authorization", "Bearer 1234"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
.expect(1)
.named("m.key_backup PUT")
.mount(&server)
.await;
Mock::given(method("PUT"))
.and(path(format!(
"_matrix/client/r0/user/{user_id}/account_data/m.org.matrix.custom.backup_disabled"
@@ -919,18 +988,14 @@ async fn test_reset_identity() {
.await;
Mock::given(method("GET"))
.and(path(format!(
"_matrix/client/r0/user/{user_id}/account_data/m.org.matrix.custom.backup_disabled"
)))
.and(path(format!("_matrix/client/r0/user/{user_id}/account_data/m.key_backup")))
.and(header("authorization", "Bearer 1234"))
.respond_with(ResponseTemplate::new(200).set_body_json(
json!({"type": "m.org.matrix.custom.backup_disabled",
"content": {
"disabled": false
}}),
))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({"type": "m.key_backup",
"content": {
"enabled": true
}})))
.expect(1)
.named("m.org.matrix.custom.backup_disabled GET")
.named("m.key_backup GET")
.mount(&server)
.await;