mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-04-27 02:31:15 -04:00
Test for recovery
This commit is contained in:
@@ -1,3 +1,104 @@
|
||||
mod backups;
|
||||
mod recovery;
|
||||
mod secret_storage;
|
||||
mod verification;
|
||||
|
||||
async fn mock_secret_store_with_backup_key(
|
||||
user_id: &ruma::UserId,
|
||||
key_id: &str,
|
||||
server: &wiremock::MockServer,
|
||||
) {
|
||||
use serde_json::json;
|
||||
use wiremock::{
|
||||
matchers::{header, method, path},
|
||||
Mock, ResponseTemplate,
|
||||
};
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.secret_storage.default_key"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"key": key_id,
|
||||
})))
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.secret_storage.key.{key_id}"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
|
||||
"iv": "1Sl4os6UhNRkVQcT6ArQ0g",
|
||||
"mac": "UCZlTzqVT7mNvLkwlcCJmuq9nA27oxqpXGdLr9SxD/Y",
|
||||
"name": null,
|
||||
"passphrase": {
|
||||
"algorithm": "m.pbkdf2",
|
||||
"iterations": 1,
|
||||
"salt": "ooLiz7Kz0TeWH2eYcyjP2fCegEB7PH5B"
|
||||
}
|
||||
})))
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!("_matrix/client/r0/user/{user_id}/account_data/m.cross_signing.master")))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.cross_signing.user_signing"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.cross_signing.self_signing"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("POST"))
|
||||
.and(path("_matrix/client/r0/keys/query"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"device_keys": {}
|
||||
})))
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!("_matrix/client/r0/user/{user_id}/account_data/m.megolm_backup.v1")))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"encrypted": {
|
||||
"yJWwBm2Ts8jHygTBslKpABFyykavhhfA": {
|
||||
"ciphertext": "c39B25f6GSvW7gCUZI1OC0V821Ht2WUfxPWB43rvFSsubouHf16ImqLrwQ",
|
||||
"iv": "hpyoGAElX8YRuigbqa7tfA",
|
||||
"mac": "nE/RCVmFQxu+KuqxmYDDzIxf2JUlxz2oTpoJTj5pUxM"
|
||||
}
|
||||
}
|
||||
})))
|
||||
.mount(server)
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -27,9 +27,7 @@ use matrix_sdk::{
|
||||
};
|
||||
use matrix_sdk_base::SessionMeta;
|
||||
use matrix_sdk_test::{async_test, JoinedRoomBuilder, SyncResponseBuilder};
|
||||
use ruma::{
|
||||
device_id, event_id, events::room::message::RoomMessageEvent, room_id, user_id, UserId,
|
||||
};
|
||||
use ruma::{device_id, event_id, events::room::message::RoomMessageEvent, room_id, user_id};
|
||||
use serde_json::json;
|
||||
use tempfile::tempdir;
|
||||
use tokio::spawn;
|
||||
@@ -38,7 +36,10 @@ use wiremock::{
|
||||
Mock, ResponseTemplate,
|
||||
};
|
||||
|
||||
use crate::{mock_sync, no_retry_test_client, test_client_builder};
|
||||
use crate::{
|
||||
encryption::mock_secret_store_with_backup_key, mock_sync, no_retry_test_client,
|
||||
test_client_builder,
|
||||
};
|
||||
|
||||
const ROOM_KEY: &[u8] = b"\
|
||||
-----BEGIN MEGOLM SESSION DATA-----\n\
|
||||
@@ -555,107 +556,6 @@ async fn steady_state_waiting_errors() {
|
||||
task.await.unwrap();
|
||||
}
|
||||
|
||||
async fn mock_secret_store_with_backup_key(
|
||||
user_id: &UserId,
|
||||
key_id: &str,
|
||||
server: &wiremock::MockServer,
|
||||
) {
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.secret_storage.default_key"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"key": key_id,
|
||||
})))
|
||||
.expect(1..)
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.secret_storage.key.{key_id}"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
|
||||
"iv": "1Sl4os6UhNRkVQcT6ArQ0g",
|
||||
"mac": "UCZlTzqVT7mNvLkwlcCJmuq9nA27oxqpXGdLr9SxD/Y",
|
||||
"name": null,
|
||||
"passphrase": {
|
||||
"algorithm": "m.pbkdf2",
|
||||
"iterations": 1,
|
||||
"salt": "ooLiz7Kz0TeWH2eYcyjP2fCegEB7PH5B"
|
||||
}
|
||||
})))
|
||||
.expect(1..)
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!("_matrix/client/r0/user/{user_id}/account_data/m.cross_signing.master")))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.expect(1..)
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.cross_signing.user_signing"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.expect(1..)
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.cross_signing.self_signing"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.expect(1..)
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("POST"))
|
||||
.and(path("_matrix/client/r0/keys/query"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"device_keys": {}
|
||||
})))
|
||||
.expect(1..)
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!("_matrix/client/r0/user/{user_id}/account_data/m.megolm_backup.v1")))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"encrypted": {
|
||||
"yJWwBm2Ts8jHygTBslKpABFyykavhhfA": {
|
||||
"ciphertext": "c39B25f6GSvW7gCUZI1OC0V821Ht2WUfxPWB43rvFSsubouHf16ImqLrwQ",
|
||||
"iv": "hpyoGAElX8YRuigbqa7tfA",
|
||||
"mac": "nE/RCVmFQxu+KuqxmYDDzIxf2JUlxz2oTpoJTj5pUxM"
|
||||
}
|
||||
}
|
||||
})))
|
||||
.expect(1..)
|
||||
.mount(server)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn enable_from_secret_storage() {
|
||||
const SECRET_STORE_KEY: &str = "mypassphrase";
|
||||
|
||||
789
crates/matrix-sdk/tests/integration/encryption/recovery.rs
Normal file
789
crates/matrix-sdk/tests/integration/encryption/recovery.rs
Normal file
@@ -0,0 +1,789 @@
|
||||
// Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use futures_util::StreamExt;
|
||||
use matrix_sdk::{
|
||||
config::RequestConfig,
|
||||
encryption::{
|
||||
backups::BackupState,
|
||||
recovery::{EnableProgress, RecoveryState},
|
||||
},
|
||||
matrix_auth::{MatrixSession, MatrixSessionTokens},
|
||||
Client,
|
||||
};
|
||||
use matrix_sdk_base::SessionMeta;
|
||||
use matrix_sdk_test::async_test;
|
||||
use ruma::{device_id, user_id, UserId};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Value};
|
||||
use tokio::spawn;
|
||||
use wiremock::{
|
||||
matchers::{header, method, path, path_regex},
|
||||
Mock, ResponseTemplate,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
encryption::mock_secret_store_with_backup_key, logged_in_client, no_retry_test_client,
|
||||
test_client_builder,
|
||||
};
|
||||
|
||||
async fn test_client(user_id: &UserId) -> (Client, wiremock::MockServer) {
|
||||
let session = MatrixSession {
|
||||
meta: SessionMeta { user_id: user_id.into(), device_id: device_id!("DEVICEID").to_owned() },
|
||||
tokens: MatrixSessionTokens { access_token: "1234".to_owned(), refresh_token: None },
|
||||
};
|
||||
|
||||
let (builder, server) = test_client_builder().await;
|
||||
let client = builder
|
||||
.request_config(RequestConfig::new().disable_retry())
|
||||
.with_encryption_settings(matrix_sdk::encryption::EncryptionSettings {
|
||||
auto_enable_cross_signing: true,
|
||||
auto_download_from_backup: false,
|
||||
auto_enable_backups: true,
|
||||
})
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let _guard = Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.secret_storage.default_key"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.expect(1)
|
||||
.named("m.secret_storage.default_key account data GET")
|
||||
.mount_as_scoped(&server)
|
||||
.await;
|
||||
|
||||
let _guard = Mock::given(method("POST"))
|
||||
.and(path("_matrix/client/r0/keys/upload"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"one_time_key_counts": {
|
||||
"signed_curve25519": 50
|
||||
}
|
||||
})))
|
||||
.expect(1)
|
||||
.named("/keys/upload POST")
|
||||
.mount_as_scoped(&server)
|
||||
.await;
|
||||
|
||||
let _guard = Mock::given(method("POST"))
|
||||
.and(path("_matrix/client/unstable/keys/device_signing/upload"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
|
||||
.expect(1)
|
||||
.named("/keys/device_signing/upload POST")
|
||||
.mount_as_scoped(&server)
|
||||
.await;
|
||||
|
||||
let _guard = Mock::given(method("POST"))
|
||||
.and(path("_matrix/client/unstable/keys/signatures/upload"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"failures": {},
|
||||
})))
|
||||
.expect(1)
|
||||
.named("/keys/signatures/upload POST")
|
||||
.mount_as_scoped(&server)
|
||||
.await;
|
||||
|
||||
client.restore_session(session).await.unwrap();
|
||||
client.encryption().wait_for_e2ee_initialization_tasks().await;
|
||||
client
|
||||
.encryption()
|
||||
.bootstrap_cross_signing(None)
|
||||
.await
|
||||
.expect("We should be able to bootstrap our cross-signing");
|
||||
assert_eq!(client.encryption().recovery().state(), RecoveryState::Disabled);
|
||||
|
||||
(client, server)
|
||||
}
|
||||
|
||||
async fn mock_put_new_default_secret_storage_key(user_id: &UserId, server: &wiremock::MockServer) {
|
||||
let default_key_content = Arc::new(Mutex::new(None));
|
||||
|
||||
Mock::given(method("PUT"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.secret_storage.default_key"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.and({
|
||||
let default_key_content = default_key_content.clone();
|
||||
move |request: &wiremock::Request| {
|
||||
let content: Value = request.body_json().expect("The body should be a JSON body");
|
||||
*default_key_content.lock().unwrap() = Some(content);
|
||||
|
||||
true
|
||||
}
|
||||
})
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
|
||||
.expect(1..)
|
||||
.named("m.secret_storage.default_key deletion")
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.secret_storage.default_key"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(move |_: &wiremock::Request| {
|
||||
let content = default_key_content.lock().unwrap().take().unwrap();
|
||||
ResponseTemplate::new(200).set_body_json(content)
|
||||
})
|
||||
.named("m.secret_storage.default_key account data GET")
|
||||
.mount(server)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn recovery_status_server_unavailable() {
|
||||
let (client, _) = logged_in_client().await;
|
||||
client.encryption().wait_for_e2ee_initialization_tasks().await;
|
||||
assert_eq!(client.encryption().recovery().state(), RecoveryState::Unknown);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn recovery_status_secret_storage_set_up() {
|
||||
const KEY_ID: &str = "yJWwBm2Ts8jHygTBslKpABFyykavhhfA";
|
||||
let user_id = user_id!("@example:morpheus.localhost");
|
||||
|
||||
let session = MatrixSession {
|
||||
meta: SessionMeta { user_id: user_id.into(), device_id: device_id!("DEVICEID").to_owned() },
|
||||
tokens: MatrixSessionTokens { access_token: "1234".to_owned(), refresh_token: None },
|
||||
};
|
||||
|
||||
let (client, server) = no_retry_test_client().await;
|
||||
|
||||
mock_secret_store_with_backup_key(user_id, KEY_ID, &server).await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.org.matrix.custom.backup_disabled"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.expect(1..)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
client.restore_session(session).await.unwrap();
|
||||
client.encryption().wait_for_e2ee_initialization_tasks().await;
|
||||
|
||||
assert_eq!(client.encryption().recovery().state(), RecoveryState::Incomplete);
|
||||
|
||||
server.verify().await;
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn recovery_status_secret_storage_not_set_up() {
|
||||
let user_id = user_id!("@example:morpheus.localhost");
|
||||
|
||||
let session = MatrixSession {
|
||||
meta: SessionMeta { user_id: user_id.into(), device_id: device_id!("DEVICEID").to_owned() },
|
||||
tokens: MatrixSessionTokens { access_token: "1234".to_owned(), refresh_token: None },
|
||||
};
|
||||
|
||||
let (client, server) = no_retry_test_client().await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.secret_storage.default_key"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.expect(1..)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
client.restore_session(session).await.unwrap();
|
||||
client.encryption().wait_for_e2ee_initialization_tasks().await;
|
||||
|
||||
assert_eq!(client.encryption().recovery().state(), RecoveryState::Disabled);
|
||||
|
||||
server.verify().await;
|
||||
}
|
||||
|
||||
async fn enable(
|
||||
user_id: &UserId,
|
||||
client: &Client,
|
||||
server: &wiremock::MockServer,
|
||||
wait_for_backups_to_upload: bool,
|
||||
) {
|
||||
let recovery = client.encryption().recovery();
|
||||
|
||||
let backup_disabled_content = Arc::new(Mutex::new(None));
|
||||
|
||||
let _quard = Mock::given(method("PUT"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.org.matrix.custom.backup_disabled"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.and({
|
||||
let backup_disabled_content = backup_disabled_content.clone();
|
||||
move |request: &wiremock::Request| {
|
||||
let content: Value = request.body_json().expect("The body should be a JSON body");
|
||||
|
||||
*backup_disabled_content.lock().unwrap() = Some(content);
|
||||
|
||||
true
|
||||
}
|
||||
})
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
|
||||
.expect(1)
|
||||
.mount_as_scoped(server)
|
||||
.await;
|
||||
|
||||
let _guard = Mock::given(method("GET"))
|
||||
.and(path("_matrix/client/r0/room_keys/version"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.expect(1)
|
||||
.mount_as_scoped(server)
|
||||
.await;
|
||||
|
||||
let _guard = Mock::given(method("POST"))
|
||||
.and(path("_matrix/client/unstable/room_keys/version"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "version": "1"})))
|
||||
.expect(1)
|
||||
.mount_as_scoped(server)
|
||||
.await;
|
||||
|
||||
let _guard = Mock::given(method("PUT"))
|
||||
.and(path_regex(format!(
|
||||
r"_matrix/client/r0/user/{user_id}/account_data/m.secret_storage.key.[A-Za-z0-9]"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
|
||||
.mount_as_scoped(server)
|
||||
.await;
|
||||
|
||||
let _guard = Mock::given(method("GET"))
|
||||
.and(path(format!("_matrix/client/r0/user/{user_id}/account_data/m.megolm_backup.v1")))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.expect(1)
|
||||
.mount_as_scoped(server)
|
||||
.await;
|
||||
|
||||
let _guard = Mock::given(method("PUT"))
|
||||
.and(path(format!("_matrix/client/r0/user/{user_id}/account_data/m.megolm_backup.v1")))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
|
||||
.expect(1)
|
||||
.mount_as_scoped(server)
|
||||
.await;
|
||||
|
||||
let default_key_content = Arc::new(Mutex::new(None));
|
||||
|
||||
let _guard = Mock::given(method("PUT"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.secret_storage.default_key"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.and({
|
||||
let default_key_content = default_key_content.clone();
|
||||
move |request: &wiremock::Request| {
|
||||
let content: Value = request.body_json().expect("The body should be a JSON body");
|
||||
|
||||
*default_key_content.lock().unwrap() = Some(content);
|
||||
|
||||
true
|
||||
}
|
||||
})
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
|
||||
.expect(1)
|
||||
.mount_as_scoped(server)
|
||||
.await;
|
||||
|
||||
let _guard = Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.secret_storage.default_key"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(move |_: &wiremock::Request| {
|
||||
let content = default_key_content.lock().unwrap().take().unwrap();
|
||||
ResponseTemplate::new(200).set_body_json(content)
|
||||
})
|
||||
.expect(1)
|
||||
.mount_as_scoped(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!("_matrix/client/r0/user/{user_id}/account_data/m.cross_signing.master")))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.expect(1..)
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.cross_signing.self_signing",
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.expect(1..)
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.cross_signing.user_signing",
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.expect(1..)
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("PUT"))
|
||||
.and(path(format!("_matrix/client/r0/user/{user_id}/account_data/m.cross_signing.master")))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.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.cross_signing.self_signing",
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.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.cross_signing.user_signing",
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
|
||||
.expect(1..)
|
||||
.mount(server)
|
||||
.await;
|
||||
|
||||
let enable = if wait_for_backups_to_upload {
|
||||
recovery.enable().wait_for_backups_to_upload()
|
||||
} else {
|
||||
recovery.enable()
|
||||
};
|
||||
|
||||
let mut progress_stream = enable.subscribe_to_progress();
|
||||
|
||||
let task = spawn(async move {
|
||||
let mut counter = 0;
|
||||
|
||||
while let Some(state) = progress_stream.next().await {
|
||||
let Ok(state) = state else { panic!("Error while waiting for the upload state") };
|
||||
|
||||
match state {
|
||||
EnableProgress::Starting => {
|
||||
assert_eq!(counter, 0, "The first state should be the starting state");
|
||||
counter += 1;
|
||||
}
|
||||
|
||||
EnableProgress::CreatingBackup => {
|
||||
assert_eq!(counter, 1, "The second state should be the creating backup state");
|
||||
counter += 1;
|
||||
}
|
||||
EnableProgress::CreatingRecoveryKey => {
|
||||
assert_eq!(
|
||||
counter, 2,
|
||||
"The third state should be the creating recovery key state"
|
||||
);
|
||||
counter += 1;
|
||||
}
|
||||
EnableProgress::Done { .. } => {
|
||||
assert_eq!(counter, 3, "The fifth state should be the done state");
|
||||
counter += 1;
|
||||
}
|
||||
_ => panic!("No other states should be received"),
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(counter, 4, "We should have gone through 4 states, counter: {counter}");
|
||||
});
|
||||
|
||||
enable.await.expect("We should be able to enable recovery");
|
||||
task.await.unwrap();
|
||||
|
||||
server.verify().await
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn recovery_setup() {
|
||||
let user_id = user_id!("@example:morpheus.localhost");
|
||||
let (client, server) = test_client(user_id).await;
|
||||
|
||||
enable(user_id, &client, &server, true).await;
|
||||
|
||||
assert_eq!(client.encryption().backups().state(), BackupState::Enabled);
|
||||
assert_eq!(client.encryption().recovery().state(), RecoveryState::Enabled);
|
||||
|
||||
server.verify().await
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn recovery_setup_without_wait() {
|
||||
let user_id = user_id!("@example:morpheus.localhost");
|
||||
let (client, server) = test_client(user_id).await;
|
||||
|
||||
enable(user_id, &client, &server, false).await;
|
||||
|
||||
assert_eq!(client.encryption().backups().state(), BackupState::Enabled);
|
||||
assert_eq!(client.encryption().recovery().state(), RecoveryState::Enabled);
|
||||
|
||||
server.verify().await
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn backups_enabling() {
|
||||
let user_id = user_id!("@example:morpheus.localhost");
|
||||
let (client, server) = test_client(user_id).await;
|
||||
|
||||
let recovery = client.encryption().recovery();
|
||||
|
||||
assert_eq!(client.encryption().backups().state(), BackupState::Unknown);
|
||||
assert_eq!(recovery.state(), RecoveryState::Disabled);
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("_matrix/client/r0/room_keys/version"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.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"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.and(|request: &wiremock::Request| {
|
||||
#[derive(Deserialize)]
|
||||
struct Disabled {
|
||||
disabled: bool,
|
||||
}
|
||||
|
||||
let content: Disabled = request.body_json().expect("The body should be a JSON body");
|
||||
|
||||
assert!(!content.disabled, "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("POST"))
|
||||
.and(path("_matrix/client/unstable/room_keys/version"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "version": "1"})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
recovery.enable_backup().await.expect("We should be able to only enable backups");
|
||||
|
||||
assert_eq!(recovery.state(), RecoveryState::Disabled);
|
||||
assert_eq!(client.encryption().backups().state(), BackupState::Enabled);
|
||||
|
||||
server.verify().await
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn backups_enabling_already_enabled() {
|
||||
let user_id = user_id!("@example:morpheus.localhost");
|
||||
let (client, server) = test_client(user_id).await;
|
||||
|
||||
let recovery = client.encryption().recovery();
|
||||
|
||||
assert_eq!(recovery.state(), RecoveryState::Disabled);
|
||||
assert_eq!(client.encryption().backups().state(), BackupState::Unknown);
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("_matrix/client/r0/room_keys/version"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
|
||||
"auth_data": {
|
||||
"public_key": "hdx5rSn94rBuvJI5cwnhKAVmFyZgfJjk7vwEBD6mIHc",
|
||||
"signatures": {}
|
||||
},
|
||||
"count": 1,
|
||||
"etag": "1",
|
||||
"version": "6"
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
recovery
|
||||
.enable_backup()
|
||||
.await
|
||||
.expect_err("We should throw an error if a backup already exists on the server");
|
||||
|
||||
assert_eq!(client.encryption().backups().state(), BackupState::Unknown);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn recovery_disabling() {
|
||||
let user_id = user_id!("@example:morpheus.localhost");
|
||||
let (client, server) = test_client(user_id).await;
|
||||
|
||||
enable(user_id, &client, &server, true).await;
|
||||
|
||||
let recovery = client.encryption().recovery();
|
||||
assert_eq!(recovery.state(), RecoveryState::Enabled);
|
||||
|
||||
Mock::given(method("DELETE"))
|
||||
.and(path("_matrix/client/r0/room_keys/version/1"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let default_key_content = Arc::new(Mutex::new(None));
|
||||
|
||||
Mock::given(method("PUT"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.secret_storage.default_key"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.and({
|
||||
let default_key_content = default_key_content.clone();
|
||||
move |request: &wiremock::Request| {
|
||||
let content: Value = request.body_json().expect("The body should be a JSON body");
|
||||
|
||||
assert_eq!(
|
||||
content,
|
||||
json!({}),
|
||||
"We should have put the default key to an empty JSON content"
|
||||
);
|
||||
*default_key_content.lock().unwrap() = Some(content);
|
||||
|
||||
true
|
||||
}
|
||||
})
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
|
||||
.expect(1)
|
||||
.named("m.secret_storage.default_key deletion")
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("PUT"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.org.matrix.custom.backup_disabled"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.and(|request: &wiremock::Request| {
|
||||
#[derive(Deserialize)]
|
||||
struct Disabled {
|
||||
disabled: bool,
|
||||
}
|
||||
|
||||
let content: Disabled = request.body_json().expect("The body should be a JSON body");
|
||||
|
||||
assert!(content.disabled, "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("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.secret_storage.default_key"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(move |_: &wiremock::Request| {
|
||||
let content = default_key_content.lock().unwrap().take().unwrap();
|
||||
ResponseTemplate::new(200).set_body_json(content)
|
||||
})
|
||||
.expect(1)
|
||||
.named("m.secret_storage.default_key account data GET")
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
recovery.disable().await.expect("We should be able to disable recovery again.");
|
||||
assert_eq!(client.encryption().backups().state(), BackupState::Unknown);
|
||||
assert_eq!(recovery.state(), RecoveryState::Disabled);
|
||||
|
||||
server.verify().await
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn reset_recovery_key() {
|
||||
let user_id = user_id!("@example:morpheus.localhost");
|
||||
let (client, server) = test_client(user_id).await;
|
||||
|
||||
enable(user_id, &client, &server, true).await;
|
||||
|
||||
let recovery = client.encryption().recovery();
|
||||
assert_eq!(recovery.state(), RecoveryState::Enabled);
|
||||
|
||||
Mock::given(method("PUT"))
|
||||
.and(path_regex(format!(
|
||||
r"_matrix/client/r0/user/{user_id}/account_data/m.secret_storage.key.[A-Za-z0-9]"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!("_matrix/client/r0/user/{user_id}/account_data/m.megolm_backup.v1")))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("PUT"))
|
||||
.and(path(format!("_matrix/client/r0/user/{user_id}/account_data/m.megolm_backup.v1")))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
mock_put_new_default_secret_storage_key(user_id, &server).await;
|
||||
|
||||
recovery.reset_key().await.expect("We should be able to reset our recovery key");
|
||||
|
||||
server.verify().await
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn recover_and_reset() {
|
||||
let user_id = user_id!("@example:morpheus.localhost");
|
||||
const SECRET_STORE_KEY: &str = "mypassphrase";
|
||||
const KEY_ID: &str = "yJWwBm2Ts8jHygTBslKpABFyykavhhfA";
|
||||
|
||||
let session = MatrixSession {
|
||||
meta: SessionMeta { user_id: user_id.into(), device_id: device_id!("DEVICEID").to_owned() },
|
||||
tokens: MatrixSessionTokens { access_token: "1234".to_owned(), refresh_token: None },
|
||||
};
|
||||
|
||||
let (client, server) = no_retry_test_client().await;
|
||||
|
||||
mock_secret_store_with_backup_key(user_id, KEY_ID, &server).await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path(format!(
|
||||
"_matrix/client/r0/user/{user_id}/account_data/m.org.matrix.custom.backup_disabled"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Account data not found"
|
||||
})))
|
||||
.expect(1..)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path("_matrix/client/r0/room_keys/version"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
|
||||
"auth_data": {
|
||||
"public_key": "hdx5rSn94rBuvJI5cwnhKAVmFyZgfJjk7vwEBD6mIHc",
|
||||
"signatures": {}
|
||||
},
|
||||
"count": 1,
|
||||
"etag": "1",
|
||||
"version": "6"
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
Mock::given(method("PUT"))
|
||||
.and(path_regex(format!(
|
||||
r"_matrix/client/r0/user/{user_id}/account_data/m.secret_storage.key.[A-Za-z0-9]"
|
||||
)))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.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.megolm_backup.v1")))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
mock_put_new_default_secret_storage_key(user_id, &server).await;
|
||||
|
||||
client.restore_session(session).await.unwrap();
|
||||
client.encryption().wait_for_e2ee_initialization_tasks().await;
|
||||
|
||||
let recovery = client.encryption().recovery();
|
||||
|
||||
assert_eq!(recovery.state(), RecoveryState::Incomplete);
|
||||
|
||||
recovery
|
||||
.recover_and_reset(SECRET_STORE_KEY)
|
||||
.await
|
||||
.expect("We should be able to recover our secrets and reset the secret storage key");
|
||||
|
||||
server.verify().await
|
||||
}
|
||||
Reference in New Issue
Block a user