diff --git a/crates/matrix-sdk/CHANGELOG.md b/crates/matrix-sdk/CHANGELOG.md index 9e52eacdf3f..ae15ab9afdd 100644 --- a/crates/matrix-sdk/CHANGELOG.md +++ b/crates/matrix-sdk/CHANGELOG.md @@ -8,8 +8,11 @@ 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)) - [**breaking**] Added `HomeserverCapabilities` and `Client::homeserver_capabilities()` to get the capabilities - of the homeserver. This replaces `Client::get_capabilities()`. + of the homeserver. This replaces `Client::get_capabilities()`. ([#6371](https://github.com/matrix-org/matrix-rust-sdk/pull/6371)) - [**breaking**] `matrix_sdk::error::Error` has a new variant `Timeout` which occurs when a cross-signing reset does not succeed after some period of time. diff --git a/crates/matrix-sdk/src/encryption/recovery/mod.rs b/crates/matrix-sdk/src/encryption/recovery/mod.rs index 370ef35f150..472fb219688 100644 --- a/crates/matrix-sdk/src/encryption/recovery/mod.rs +++ b/crates/matrix-sdk/src/encryption/recovery/mod.rs @@ -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 { - Ok(self - .client - .account() - .fetch_account_data_static::() - .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::().await? + { + Ok(key_backup_content.deserialize().map(|event| !event.enabled).unwrap_or(false)) + } else { + Ok(self + .client + .account() + .fetch_account_data_static::() + .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(()) diff --git a/crates/matrix-sdk/src/encryption/recovery/types.rs b/crates/matrix-sdk/src/encryption/recovery/types.rs index 7d22137a385..042a5e9632b 100644 --- a/crates/matrix-sdk/src/encryption/recovery/types.rs +++ b/crates/matrix-sdk/src/encryption/recovery/types.rs @@ -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, diff --git a/crates/matrix-sdk/tests/integration/encryption/recovery.rs b/crates/matrix-sdk/tests/integration/encryption/recovery.rs index b921e55b5b2..3745779d203 100644 --- a/crates/matrix-sdk/tests/integration/encryption/recovery.rs +++ b/crates/matrix-sdk/tests/integration/encryption/recovery.rs @@ -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;