|
1 |
| -use std::ops::Deref; |
| 1 | +use std::{ops::Deref, time::Duration}; |
2 | 2 |
|
3 | 3 | use anyhow::Result;
|
4 |
| -use assert_matches2::assert_let; |
| 4 | +use assert_matches2::{assert_let, assert_matches}; |
5 | 5 | use assign::assign;
|
6 |
| -use futures::{FutureExt, StreamExt, pin_mut}; |
| 6 | +use futures::{FutureExt, StreamExt, future, pin_mut}; |
7 | 7 | use matrix_sdk::{
|
8 |
| - assert_decrypted_message_eq, |
| 8 | + assert_decrypted_message_eq, assert_next_with_timeout, |
| 9 | + deserialized_responses::TimelineEventKind, |
9 | 10 | encryption::EncryptionSettings,
|
10 | 11 | ruma::{
|
11 |
| - api::client::room::create_room::v3::{Request as CreateRoomRequest, RoomPreset}, |
| 12 | + api::client::{ |
| 13 | + room::create_room::v3::{Request as CreateRoomRequest, RoomPreset}, |
| 14 | + uiaa::Password, |
| 15 | + }, |
12 | 16 | events::room::message::RoomMessageEventContent,
|
13 | 17 | },
|
| 18 | + timeout::timeout, |
14 | 19 | };
|
15 | 20 | use matrix_sdk_common::deserialized_responses::ProcessedToDeviceEvent;
|
16 | 21 | use matrix_sdk_ui::sync_service::SyncService;
|
17 | 22 | use similar_asserts::assert_eq;
|
18 | 23 | use tracing::{Instrument, info};
|
19 | 24 |
|
20 |
| -use crate::helpers::{SyncTokenAwareClient, TestClientBuilder}; |
| 25 | +use crate::helpers::{SyncTokenAwareClient, TestClientBuilder, wait_for_room}; |
21 | 26 |
|
22 | 27 | /// When we invite another user to a room with "joined" history visibility, we
|
23 | 28 | /// share the encryption history.
|
@@ -128,3 +133,200 @@ async fn test_history_share_on_invite() -> Result<()> {
|
128 | 133 |
|
129 | 134 | Ok(())
|
130 | 135 | }
|
| 136 | + |
| 137 | +/// When we invite another user to a room with "joined" history visibility, we |
| 138 | +/// share the encryption history. |
| 139 | +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] |
| 140 | +async fn test_history_share_on_invite_pin_violation() -> Result<()> { |
| 141 | + let alice_span = tracing::info_span!("alice"); |
| 142 | + let bob_span = tracing::info_span!("bob"); |
| 143 | + |
| 144 | + let encryption_settings = |
| 145 | + EncryptionSettings { auto_enable_cross_signing: true, ..Default::default() }; |
| 146 | + |
| 147 | + let alice = TestClientBuilder::new("alice") |
| 148 | + .use_sqlite() |
| 149 | + .encryption_settings(encryption_settings) |
| 150 | + .enable_share_history_on_invite(true) |
| 151 | + .build() |
| 152 | + .await?; |
| 153 | + |
| 154 | + let alice_user_id = alice.user_id().expect("We should have access to Alice's user id"); |
| 155 | + |
| 156 | + let sync_service_span = tracing::info_span!(parent: &alice_span, "sync_service"); |
| 157 | + let alice_sync_service = SyncService::builder(alice.clone()) |
| 158 | + .with_parent_span(sync_service_span) |
| 159 | + .build() |
| 160 | + .await |
| 161 | + .expect("Could not build alice sync service"); |
| 162 | + |
| 163 | + alice.encryption().wait_for_e2ee_initialization_tasks().await; |
| 164 | + alice_sync_service.start().await; |
| 165 | + |
| 166 | + // Bob first creates a room so we can get hold of Alice's identity and verify |
| 167 | + // it. |
| 168 | + |
| 169 | + let bob = TestClientBuilder::new("bob") |
| 170 | + .encryption_settings(encryption_settings) |
| 171 | + .enable_share_history_on_invite(true) |
| 172 | + .build() |
| 173 | + .await?; |
| 174 | + |
| 175 | + let bob_sync_service_span = tracing::info_span!(parent: &bob_span, "sync_service"); |
| 176 | + let bob_sync_service = SyncService::builder(bob.clone()) |
| 177 | + .with_parent_span(bob_sync_service_span) |
| 178 | + .build() |
| 179 | + .await |
| 180 | + .expect("Could not build alice sync service"); |
| 181 | + |
| 182 | + bob_sync_service.start().await; |
| 183 | + |
| 184 | + let bob_room = bob |
| 185 | + .create_room(assign!(CreateRoomRequest::new(), { |
| 186 | + preset: Some(RoomPreset::PublicChat), |
| 187 | + })) |
| 188 | + .await?; |
| 189 | + bob_room.enable_encryption().await?; |
| 190 | + |
| 191 | + // We invite Alice and try to send a message. This will force a /keys/query to |
| 192 | + // fetch the identity. |
| 193 | + bob_room.invite_user_by_id(alice_user_id).await?; |
| 194 | + bob_room.send(RoomMessageEventContent::text_plain("Hello Alice")).await?; |
| 195 | + |
| 196 | + let alice_identity = bob |
| 197 | + .encryption() |
| 198 | + .get_user_identity(alice.user_id().unwrap()) |
| 199 | + .await? |
| 200 | + .expect("Bob should have access to Alice's identity"); |
| 201 | + |
| 202 | + info!("Bob is verifying Alice"); |
| 203 | + |
| 204 | + alice_identity.verify().await?; |
| 205 | + |
| 206 | + alice |
| 207 | + .get_room(bob_room.room_id()) |
| 208 | + .expect("Alice should have access to bob's room") |
| 209 | + .join() |
| 210 | + .instrument(alice_span.clone()) |
| 211 | + .await?; |
| 212 | + |
| 213 | + // Alice creates a room ... |
| 214 | + let alice_room = alice |
| 215 | + .create_room(assign!(CreateRoomRequest::new(), { |
| 216 | + preset: Some(RoomPreset::PublicChat), |
| 217 | + })) |
| 218 | + .await?; |
| 219 | + alice_room.enable_encryption().await?; |
| 220 | + |
| 221 | + info!(room_id = ?alice_room.room_id(), "Alice has created and enabled encryption in the room"); |
| 222 | + |
| 223 | + // ... and sends a message |
| 224 | + let event_id = alice_room |
| 225 | + .send(RoomMessageEventContent::text_plain("Hello Bob")) |
| 226 | + .await |
| 227 | + .expect("We should be able to send a message to the room") |
| 228 | + .event_id; |
| 229 | + |
| 230 | + // Let us create some streams to get notified about a received bundle and a |
| 231 | + // changed identity. We need to create now, before any action happens, so we |
| 232 | + // don't miss the updates. |
| 233 | + let bundle_stream = bob |
| 234 | + .encryption() |
| 235 | + .historic_room_key_stream() |
| 236 | + .await |
| 237 | + .expect("We should be able to get the bundle stream"); |
| 238 | + |
| 239 | + let identity_stream = bob |
| 240 | + .encryption() |
| 241 | + .user_identities_stream() |
| 242 | + .await |
| 243 | + .expect("We should be able to get the identity stream"); |
| 244 | + |
| 245 | + // Alice will reset her identity, once Bob sees that a reset happened, Bob will |
| 246 | + // mark the identity to be in a pin violation. |
| 247 | + info!("Alice is resetting the identity"); |
| 248 | + |
| 249 | + let reset_future = async { |
| 250 | + if let Some(handle) = alice |
| 251 | + .encryption() |
| 252 | + .recovery() |
| 253 | + .reset_identity() |
| 254 | + .instrument(alice_span.clone()) |
| 255 | + .await |
| 256 | + .expect("Resetting the identity should work") |
| 257 | + { |
| 258 | + handle |
| 259 | + .reset(Some(matrix_sdk::ruma::api::client::uiaa::AuthData::Password( |
| 260 | + Password::new( |
| 261 | + matrix_sdk::ruma::api::client::uiaa::UserIdentifier::UserIdOrLocalpart( |
| 262 | + alice_user_id.localpart().to_owned(), |
| 263 | + ), |
| 264 | + alice_user_id.localpart().to_owned(), |
| 265 | + ), |
| 266 | + ))) |
| 267 | + .instrument(alice_span.clone()) |
| 268 | + .await |
| 269 | + .expect("Providing the password to finalize the identity reset should work"); |
| 270 | + } |
| 271 | + }; |
| 272 | + |
| 273 | + timeout(reset_future, Duration::from_secs(2)) |
| 274 | + .await |
| 275 | + .expect("We should be able to reset our identity"); |
| 276 | + |
| 277 | + info!("Alice is inviting Bob"); |
| 278 | + |
| 279 | + // Alice invites Bob to the room |
| 280 | + alice_room.invite_user_by_id(bob.user_id().unwrap()).instrument(alice_span.clone()).await?; |
| 281 | + |
| 282 | + // Alice is done. Bob has been invited and the room key bundle should have been |
| 283 | + // sent out. Let's stop syncing so the logs contain less noise. |
| 284 | + alice_sync_service.stop().await; |
| 285 | + |
| 286 | + // Let's wait for the bundle to arrive. |
| 287 | + pin_mut!(bundle_stream); |
| 288 | + assert_next_with_timeout!(bundle_stream, 3000); |
| 289 | + |
| 290 | + // Let us now wait till Alice's identity gets updated. |
| 291 | + info!("Bob is checking if alice's identity has changed"); |
| 292 | + pin_mut!(identity_stream); |
| 293 | + let mut identity_stream = identity_stream |
| 294 | + .filter(|updates| future::ready(updates.changed.contains_key(alice_user_id))); |
| 295 | + assert_next_with_timeout!(identity_stream, 2000); |
| 296 | + |
| 297 | + // Let's make sure that the pin violation was recorded. |
| 298 | + let alice_identity = bob |
| 299 | + .encryption() |
| 300 | + .get_user_identity(alice.user_id().unwrap()) |
| 301 | + .await? |
| 302 | + .expect("Bob should have access to Alice's identity"); |
| 303 | + |
| 304 | + assert!( |
| 305 | + alice_identity.has_verification_violation(), |
| 306 | + "Alice should be in a pin violation scenario" |
| 307 | + ); |
| 308 | + |
| 309 | + // Now Bob can accept the invitation. |
| 310 | + let room = wait_for_room(&bob, alice_room.room_id()).await; |
| 311 | + room.join() |
| 312 | + .instrument(bob_span.clone()) |
| 313 | + .await |
| 314 | + .expect("Bob should be able to accept the invitation from Alice"); |
| 315 | + |
| 316 | + let event = room |
| 317 | + .event(&event_id, None) |
| 318 | + .instrument(bob_span.clone()) |
| 319 | + .await |
| 320 | + .expect("Bob should be able to fetch the historic event"); |
| 321 | + |
| 322 | + // Even though we received the bundle and got notified by the stream that we |
| 323 | + // received it, the event should not be decryptable as we should not have |
| 324 | + // accepted the bundle. |
| 325 | + assert_matches!( |
| 326 | + event.kind, |
| 327 | + TimelineEventKind::UnableToDecrypt { .. }, |
| 328 | + "The event should not have been decrypted, we should not receive the historic room key bundle" |
| 329 | + ); |
| 330 | + |
| 331 | + Ok(()) |
| 332 | +} |
0 commit comments