Skip to content

Commit 9efb0de

Browse files
authored
Merge pull request #5819 from matrix-org/rav/cryptostore_withheld_sessions_by_room_id
crypto: add new `CryptoStore` method `get_withheld_sessions_by_room_id`
2 parents 05b40af + 0faf3ee commit 9efb0de

File tree

14 files changed

+391
-58
lines changed

14 files changed

+391
-58
lines changed

crates/matrix-sdk-crypto/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ All notable changes to this project will be documented in this file.
88

99
### Features
1010

11+
- Expose new method `CryptoStore::get_withheld_sessions_by_room_id`.
12+
([#5819](https://github.com/matrix-org/matrix-rust-sdk/pull/5819))
1113
- Use new withheld code in key bundles for sessions not marked as
1214
`shared_history`.
1315
([#5807](https://github.com/matrix-org/matrix-rust-sdk/pull/5807)

crates/matrix-sdk-crypto/src/store/integration_tests.rs

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,55 +1139,59 @@ macro_rules! cryptostore_integration_tests {
11391139
async fn test_withheld_info_storage() {
11401140
let (account, store) = get_loaded_store("withheld_info_storage").await;
11411141

1142-
let mut info_list: BTreeMap<_, BTreeMap<_, RoomKeyWithheldEntry>> = BTreeMap::new();
1143-
11441142
let user_id = account.user_id().to_owned();
11451143
let room_id = room_id!("!DwLygpkclUAfQNnfva:example.com");
11461144
let session_id_1 = "GBnDxGP9i3IkPsz3/ihNr6P7qjIXxSRVWZ1MYmSn09w";
11471145
let session_id_2 = "IDLtnNCH2kIr3xIf1B7JFkGpQmTjyMca2jww+X6zeOE";
11481146

1149-
let content = RoomKeyWithheldContent::MegolmV1AesSha2(
1150-
MegolmV1AesSha2WithheldContent::Unverified(
1151-
CommonWithheldCodeContent::new(
1152-
room_id.to_owned(),
1153-
session_id_1.into(),
1154-
Curve25519PublicKey::from_base64(
1155-
"9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA",
1147+
{
1148+
let mut info_list: BTreeMap<_, BTreeMap<_, RoomKeyWithheldEntry>> = BTreeMap::new();
1149+
1150+
let content = RoomKeyWithheldContent::MegolmV1AesSha2(
1151+
MegolmV1AesSha2WithheldContent::Unverified(
1152+
CommonWithheldCodeContent::new(
1153+
room_id.to_owned(),
1154+
session_id_1.into(),
1155+
Curve25519PublicKey::from_base64(
1156+
"9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA",
1157+
)
1158+
.unwrap(),
1159+
"DEVICEID".into(),
11561160
)
1157-
.unwrap(),
1158-
"DEVICEID".into(),
1159-
)
1160-
.into(),
1161-
),
1162-
);
1163-
let event = ToDeviceEvent::new(user_id.to_owned(), content);
1164-
info_list
1165-
.entry(room_id.to_owned())
1166-
.or_default()
1167-
.insert(session_id_1.to_owned(), event.into());
1168-
1169-
let content = RoomKeyWithheldContent::MegolmV1AesSha2(
1170-
MegolmV1AesSha2WithheldContent::BlackListed(
1171-
CommonWithheldCodeContent::new(
1172-
room_id.to_owned(),
1173-
session_id_2.into(),
1174-
Curve25519PublicKey::from_base64(
1175-
"9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA",
1161+
.into(),
1162+
),
1163+
);
1164+
let event = ToDeviceEvent::new(user_id.to_owned(), content);
1165+
info_list
1166+
.entry(room_id.to_owned())
1167+
.or_default()
1168+
.insert(session_id_1.to_owned(), event.into());
1169+
1170+
let content = RoomKeyWithheldContent::MegolmV1AesSha2(
1171+
MegolmV1AesSha2WithheldContent::BlackListed(
1172+
CommonWithheldCodeContent::new(
1173+
room_id.to_owned(),
1174+
session_id_2.into(),
1175+
Curve25519PublicKey::from_base64(
1176+
"9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA",
1177+
)
1178+
.unwrap(),
1179+
"DEVICEID".into(),
11761180
)
1177-
.unwrap(),
1178-
"DEVICEID".into(),
1179-
)
1180-
.into(),
1181-
),
1182-
);
1183-
let event = ToDeviceEvent::new(user_id.to_owned(), content);
1184-
info_list
1185-
.entry(room_id.to_owned())
1186-
.or_default()
1187-
.insert(session_id_2.to_owned(), event.into());
1181+
.into(),
1182+
),
1183+
);
1184+
let event = ToDeviceEvent::new(user_id.to_owned(), content);
1185+
info_list
1186+
.entry(room_id.to_owned())
1187+
.or_default()
1188+
.insert(session_id_2.to_owned(), event.into());
11881189

1189-
let changes = Changes { withheld_session_info: info_list, ..Default::default() };
1190-
store.save_changes(changes).await.unwrap();
1190+
let changes = Changes { withheld_session_info: info_list, ..Default::default() };
1191+
store.save_changes(changes).await.unwrap();
1192+
}
1193+
1194+
// Test `get_withheld_info`
11911195

11921196
let is_withheld = store.get_withheld_info(room_id, session_id_1).await.unwrap();
11931197

@@ -1211,6 +1215,12 @@ macro_rules! cryptostore_integration_tests {
12111215
store.get_withheld_info(other_room_id, session_id_2).await.unwrap();
12121216

12131217
assert!(is_withheld.is_none());
1218+
1219+
// Test `get_withheld_sessions_by_room_id`
1220+
let withhelds = store.get_withheld_sessions_by_room_id(room_id).await.expect("Error getting withheld sessions by room ID");
1221+
assert_eq!(withhelds.len(), 2);
1222+
let withheld1 = withhelds.iter().find(|entry| entry.content.megolm_session_id() == Some(session_id_1)).expect("Did not find session 1 in withhelds list");
1223+
assert_eq!(withheld1.content.withheld_code(), WithheldCode::Unverified)
12141224
}
12151225

12161226
#[async_test]

crates/matrix-sdk-crypto/src/store/memorystore.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,18 @@ impl CryptoStore for MemoryStore {
428428
.and_then(|e| Some(e.get(session_id)?.to_owned())))
429429
}
430430

431+
async fn get_withheld_sessions_by_room_id(
432+
&self,
433+
room_id: &RoomId,
434+
) -> crate::store::Result<Vec<RoomKeyWithheldEntry>, Self::Error> {
435+
Ok(self
436+
.direct_withheld_info
437+
.read()
438+
.get(room_id)
439+
.map(|e| e.values().cloned().collect())
440+
.unwrap_or_default())
441+
}
442+
431443
async fn get_inbound_group_sessions(&self) -> Result<Vec<InboundGroupSession>> {
432444
let inbounds = self
433445
.inbound_group_sessions
@@ -1375,6 +1387,13 @@ mod integration_tests {
13751387
self.0.get_withheld_info(room_id, session_id).await
13761388
}
13771389

1390+
async fn get_withheld_sessions_by_room_id(
1391+
&self,
1392+
room_id: &RoomId,
1393+
) -> Result<Vec<RoomKeyWithheldEntry>, Self::Error> {
1394+
self.0.get_withheld_sessions_by_room_id(room_id).await
1395+
}
1396+
13781397
async fn get_inbound_group_sessions(
13791398
&self,
13801399
) -> Result<Vec<InboundGroupSession>, Self::Error> {

crates/matrix-sdk-crypto/src/store/traits.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,19 @@ pub trait CryptoStore: AsyncTraitDeps {
118118
session_id: &str,
119119
) -> Result<Option<RoomKeyWithheldEntry>, Self::Error>;
120120

121+
/// Get all the sessions where we have received an `m.room_key.withheld`
122+
/// event (or, post-[MSC4268], where there was a `withheld` entry in the key
123+
/// bundle).
124+
///
125+
/// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
126+
///
127+
/// # Arguments
128+
/// * `room_id` - The ID of the room to return withheld sessions for.
129+
async fn get_withheld_sessions_by_room_id(
130+
&self,
131+
room_id: &RoomId,
132+
) -> Result<Vec<RoomKeyWithheldEntry>, Self::Error>;
133+
121134
/// Get all the inbound group sessions we have stored.
122135
async fn get_inbound_group_sessions(&self) -> Result<Vec<InboundGroupSession>, Self::Error>;
123136

@@ -592,6 +605,13 @@ impl<T: CryptoStore> CryptoStore for EraseCryptoStoreError<T> {
592605
self.0.get_withheld_info(room_id, session_id).await.map_err(Into::into)
593606
}
594607

608+
async fn get_withheld_sessions_by_room_id(
609+
&self,
610+
room_id: &RoomId,
611+
) -> Result<Vec<RoomKeyWithheldEntry>, Self::Error> {
612+
self.0.get_withheld_sessions_by_room_id(room_id).await.map_err(Into::into)
613+
}
614+
595615
async fn get_room_settings(&self, room_id: &RoomId) -> Result<Option<RoomSettings>> {
596616
self.0.get_room_settings(room_id).await.map_err(Into::into)
597617
}

crates/matrix-sdk-crypto/src/types/events/room_key_withheld.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,27 @@ impl RoomKeyWithheldContent {
154154
RoomKeyWithheldContent::Unknown(c) => c.algorithm.to_owned(),
155155
}
156156
}
157+
158+
/// Get the room ID of the withheld session, if known
159+
pub fn room_id(&self) -> Option<&RoomId> {
160+
match &self {
161+
RoomKeyWithheldContent::MegolmV1AesSha2(c) => c.room_id(),
162+
#[cfg(feature = "experimental-algorithms")]
163+
RoomKeyWithheldContent::MegolmV2AesSha2(c) => c.room_id(),
164+
RoomKeyWithheldContent::Unknown(_) => None,
165+
}
166+
}
167+
168+
/// Get the megolm session ID of the withheld session, if it is in fact a
169+
/// megolm session.
170+
pub fn megolm_session_id(&self) -> Option<&str> {
171+
match &self {
172+
RoomKeyWithheldContent::MegolmV1AesSha2(c) => c.session_id(),
173+
#[cfg(feature = "experimental-algorithms")]
174+
RoomKeyWithheldContent::MegolmV2AesSha2(c) => c.session_id(),
175+
RoomKeyWithheldContent::Unknown(_) => None,
176+
}
177+
}
157178
}
158179

159180
impl EventType for RoomKeyWithheldContent {

crates/matrix-sdk-indexeddb/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ All notable changes to this project will be documented in this file.
88

99
### Features
1010

11-
- [**breaking**] `IndexeddbCryptoStore::get_withheld_info` now returns `Result<Option<RoomKeyWithheldEntry>, ...>`
11+
- Implement new method `CryptoStore::get_withheld_sessions_by_room_id`.
12+
([#5819](https://github.com/matrix-org/matrix-rust-sdk/pull/5819))
13+
- [**breaking**] `IndexeddbCryptoStore::get_withheld_info` now returns `Result<Option<RoomKeyWithheldEntry>, ...>`.
1214
([#5737](https://github.com/matrix-org/matrix-rust-sdk/pull/5737))
1315

1416
### Performance

crates/matrix-sdk-indexeddb/src/crypto_store/migrations/mod.rs

Lines changed: 101 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ mod v10_to_v11;
3333
mod v11_to_v12;
3434
mod v12_to_v13;
3535
mod v13_to_v14;
36+
mod v14_to_v101;
3637
mod v5_to_v7;
3738
mod v7;
3839
mod v7_to_v8;
@@ -101,7 +102,7 @@ impl Drop for MigrationDb {
101102
/// as is version 200, but versions 101-199 are all backwards compatible with
102103
/// version 100. In other words, if you divide by 100, you get something
103104
/// approaching semver: version 200 is major version 2, minor version 0.
104-
const MAX_SUPPORTED_SCHEMA_VERSION: u32 = 99;
105+
const MAX_SUPPORTED_SCHEMA_VERSION: u32 = 199;
105106

106107
/// Open the indexeddb with the given name, upgrading it to the latest version
107108
/// of the schema if necessary.
@@ -176,6 +177,15 @@ pub async fn open_and_upgrade_db(
176177
v13_to_v14::schema_bump(name).await?;
177178
}
178179

180+
if old_version < 100 {
181+
v14_to_v101::schema_add(name).await?;
182+
}
183+
184+
if old_version < 101 {
185+
v14_to_v101::data_migrate(name, serializer).await?;
186+
v14_to_v101::schema_delete(name).await?;
187+
}
188+
179189
// If you add more migrations here, you'll need to update
180190
// `tests::EXPECTED_SCHEMA_VERSION`.
181191

@@ -254,16 +264,18 @@ mod tests {
254264
use indexed_db_futures::{
255265
database::VersionChangeEvent, prelude::*, transaction::TransactionMode,
256266
};
257-
use matrix_sdk_common::js_tracing::make_tracing_subscriber;
267+
use matrix_sdk_common::{
268+
deserialized_responses::WithheldCode, js_tracing::make_tracing_subscriber,
269+
};
258270
use matrix_sdk_crypto::{
259271
olm::{InboundGroupSession, SenderData, SessionKey},
260-
store::CryptoStore,
261-
types::EventEncryptionAlgorithm,
272+
store::{types::RoomKeyWithheldEntry, CryptoStore},
273+
types::{events::room_key_withheld::RoomKeyWithheldContent, EventEncryptionAlgorithm},
262274
vodozemac::{Curve25519PublicKey, Curve25519SecretKey, Ed25519PublicKey, Ed25519SecretKey},
263275
};
264276
use matrix_sdk_store_encryption::StoreCipher;
265277
use matrix_sdk_test::async_test;
266-
use ruma::{room_id, OwnedRoomId, RoomId};
278+
use ruma::{device_id, owned_user_id, room_id, OwnedRoomId, RoomId};
267279
use serde::Serialize;
268280
use tracing_subscriber::util::SubscriberInitExt;
269281
use wasm_bindgen::JsValue;
@@ -278,7 +290,7 @@ mod tests {
278290
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
279291

280292
/// The schema version we expect after we open the store.
281-
const EXPECTED_SCHEMA_VERSION: u32 = 14;
293+
const EXPECTED_SCHEMA_VERSION: u32 = 101;
282294

283295
/// Adjust this to test do a more comprehensive perf test
284296
const NUM_RECORDS_FOR_PERF: usize = 2_000;
@@ -786,6 +798,89 @@ mod tests {
786798
assert_eq!(backup_data.backup_version, Some("1".to_owned()));
787799
}
788800

801+
/// Test migrating `withheld_sessions` data from store v14 to latest,
802+
/// on a store with encryption disabled.
803+
#[async_test]
804+
async fn test_v14_v101_migration_unencrypted() {
805+
test_v14_v101_migration_with_cipher("test_v101_migration_unencrypted", None).await
806+
}
807+
808+
/// Test migrating `withheld_sessions` data from store v14 to latest,
809+
/// on a store with encryption enabled.
810+
#[async_test]
811+
async fn test_v14_v101_migration_encrypted() {
812+
let cipher = StoreCipher::new().unwrap();
813+
test_v14_v101_migration_with_cipher(
814+
"test_v101_migration_encrypted",
815+
Some(Arc::new(cipher)),
816+
)
817+
.await;
818+
}
819+
820+
/// Helper function for `test_v14_v101_migration_{un,}encrypted`: test
821+
/// migrating `withheld_sessions` data from store v14 to store v101.
822+
async fn test_v14_v101_migration_with_cipher(
823+
db_prefix: &str,
824+
store_cipher: Option<Arc<StoreCipher>>,
825+
) {
826+
let serializer = SafeEncodeSerializer::new(store_cipher.clone());
827+
828+
let _ = make_tracing_subscriber(None).try_init();
829+
let db_name = format!("{db_prefix:0}::matrix-sdk-crypto");
830+
831+
// delete the db in case it was used in a previous run
832+
let _ = Database::delete_by_name(&db_name).unwrap().await.unwrap();
833+
834+
let room_id = room_id!("!test:example.com");
835+
let session_id = "12345";
836+
837+
// Given a DB with data in it as it was at v5
838+
{
839+
let db = create_v5_db(&db_name).await.unwrap();
840+
841+
let txn = db
842+
.transaction(old_keys::DIRECT_WITHHELD_INFO)
843+
.with_mode(TransactionMode::Readwrite)
844+
.build()
845+
.unwrap();
846+
let store = txn.object_store(old_keys::DIRECT_WITHHELD_INFO).unwrap();
847+
848+
let sender_key =
849+
Curve25519PublicKey::from_base64("9n7mdWKOjr9c4NTlG6zV8dbFtNK79q9vZADoh7nMUwA")
850+
.unwrap();
851+
852+
let withheld_entry = RoomKeyWithheldEntry {
853+
sender: owned_user_id!("@alice:example.com"),
854+
content: RoomKeyWithheldContent::new(
855+
EventEncryptionAlgorithm::MegolmV1AesSha2,
856+
WithheldCode::Blacklisted,
857+
room_id.to_owned(),
858+
session_id.to_owned(),
859+
sender_key,
860+
device_id!("ABC").to_owned(),
861+
),
862+
};
863+
864+
let key = serializer.encode_key(old_keys::DIRECT_WITHHELD_INFO, (room_id, session_id));
865+
let value = serializer.serialize_value(&withheld_entry).unwrap();
866+
store.add(value).with_key(key).build().unwrap();
867+
txn.commit().await.unwrap();
868+
db.close();
869+
}
870+
871+
// When I open a store based on that DB, triggering an upgrade
872+
let store =
873+
IndexeddbCryptoStore::open_with_store_cipher(&db_prefix, store_cipher).await.unwrap();
874+
875+
// Then I can read the withheld session settings
876+
let withheld_entry = store
877+
.get_withheld_info(room_id, session_id)
878+
.await
879+
.unwrap()
880+
.expect("Should find a withheld entry in migrated data");
881+
assert_eq!(withheld_entry.content.withheld_code(), WithheldCode::Blacklisted)
882+
}
883+
789884
async fn create_v5_db(name: &str) -> std::result::Result<Database, OpenDbError> {
790885
v0_to_v5::schema_add(name).await?;
791886
Database::open(name).with_version(5u32).build()?.await

0 commit comments

Comments
 (0)