| 1 | use std::time::Duration; |
| 2 | |
| 3 | use futures_util::StreamExt; |
| 4 | use matrix_sdk::{ |
| 5 | deserialized_responses::TimelineEventKind, event_cache::RedecryptorReport, |
| 6 | test_utils::mocks::MatrixMockServer, |
| 7 | }; |
| 8 | use matrix_sdk_test::{JoinedRoomBuilder, event_factory::EventFactory}; |
| 9 | use ruma::{RoomVersionId, device_id, event_id, room_id, user_id}; |
| 10 | use serde_json::json; |
| 11 | |
| 12 | #[tokio::test] |
| 13 | async fn late_key_exposes_a_mention_without_a_ui_timeline() { |
| 14 | let server = MatrixMockServer::new().await; |
| 15 | server.mock_crypto_endpoints_preset().await; |
| 16 | let alice_id = user_id!("@alice:localhost"); |
| 17 | let bob_id = user_id!("@bob:localhost"); |
| 18 | let room_id = room_id!("!mentions-encrypted:localhost"); |
| 19 | let event_id = event_id!("$late-mention"); |
| 20 | let alice = server |
| 21 | .client_builder_for_crypto_end_to_end(alice_id, device_id!("ALICEDEVICE")) |
| 22 | .build() |
| 23 | .await; |
| 24 | let bob = server |
| 25 | .client_builder_for_crypto_end_to_end(bob_id, device_id!("BOBDEVICE")) |
| 26 | .build() |
| 27 | .await; |
| 28 | bob.event_cache().subscribe().unwrap(); |
| 29 | server.exchange_e2ee_identities(&alice, &bob).await; |
| 30 | |
| 31 | let factory = EventFactory::new().room(room_id).sender(alice_id); |
| 32 | let joined = JoinedRoomBuilder::new(room_id) |
| 33 | .add_state_event(factory.create(alice_id, RoomVersionId::V1)) |
| 34 | .add_state_event(factory.room_encryption()) |
| 35 | .add_state_event(factory.member(alice_id)) |
| 36 | .add_state_event(factory.member(bob_id)); |
| 37 | server.sync_room(&alice, joined.clone()).await; |
| 38 | server.sync_room(&bob, joined).await; |
| 39 | |
| 40 | let room = alice.get_room(room_id).unwrap(); |
| 41 | let (encrypted_event, send) = server.mock_room_send().ok_with_capture(event_id, alice_id); |
| 42 | let _send = send.mock_once().mount_as_scoped().await; |
| 43 | let (_keys, room_key) = server.mock_capture_put_to_device(alice_id).await; |
| 44 | server |
| 45 | .mock_get_members() |
| 46 | .ok(vec![ |
| 47 | factory.member(alice_id).into_raw(), |
| 48 | factory.member(bob_id).into_raw(), |
| 49 | ]) |
| 50 | .mock_once() |
| 51 | .mount() |
| 52 | .await; |
| 53 | room.send_raw( |
| 54 | "m.room.message", |
| 55 | json!({ |
| 56 | "msgtype": "m.text", |
| 57 | "body": "a private mention", |
| 58 | "m.mentions": { "user_ids": [bob_id] } |
| 59 | }), |
| 60 | ) |
| 61 | .await |
| 62 | .unwrap(); |
| 63 | let encrypted_event = encrypted_event.await.unwrap(); |
| 64 | let room_key = room_key.await; |
| 65 | |
| 66 | let mut sync_updates = bob.subscribe_to_all_room_updates(); |
| 67 | let mut cache_updates = bob.event_cache().subscribe_to_room_generic_updates(); |
| 68 | let mut reports = bob.event_cache().subscribe_to_decryption_reports(); |
| 69 | server |
| 70 | .sync_room( |
| 71 | &bob, |
| 72 | JoinedRoomBuilder::new(room_id).add_timeline_event(encrypted_event), |
| 73 | ) |
| 74 | .await; |
| 75 | let update = sync_updates.recv().await.unwrap(); |
| 76 | let event = &update.joined[room_id].timeline.events[0]; |
| 77 | assert!(matches!( |
| 78 | event.kind, |
| 79 | TimelineEventKind::UnableToDecrypt { .. } |
| 80 | )); |
| 81 | tokio::time::timeout(Duration::from_secs(5), async { |
| 82 | while cache_updates.recv().await.unwrap().room_id != room_id {} |
| 83 | }) |
| 84 | .await |
| 85 | .unwrap(); |
| 86 | |
| 87 | server |
| 88 | .mock_sync() |
| 89 | .ok_and_run(&bob, |sync| { |
| 90 | sync.add_to_device_event(room_key.deserialize_as().unwrap()); |
| 91 | }) |
| 92 | .await; |
| 93 | tokio::time::timeout(Duration::from_secs(5), async { |
| 94 | while let Some(report) = reports.next().await { |
| 95 | if let Ok(RedecryptorReport::ResolvedUtds { |
| 96 | room_id: resolved, |
| 97 | events, |
| 98 | }) = report |
| 99 | { |
| 100 | if resolved == room_id && events.contains(event_id) { |
| 101 | return; |
| 102 | } |
| 103 | } |
| 104 | } |
| 105 | panic!("decryption reports ended before the mention was decrypted"); |
| 106 | }) |
| 107 | .await |
| 108 | .unwrap(); |
| 109 | |
| 110 | let room = bob.get_room(room_id).unwrap(); |
| 111 | let (cache, _cache_handles) = room.event_cache().await.unwrap(); |
| 112 | let decrypted = cache.find_event(event_id).await.unwrap().unwrap(); |
| 113 | assert!(matches!(decrypted.kind, TimelineEventKind::Decrypted(_))); |
| 114 | let content = decrypted |
| 115 | .raw() |
| 116 | .get_field::<serde_json::Value>("content") |
| 117 | .unwrap() |
| 118 | .unwrap(); |
| 119 | assert_eq!(content["body"], "a private mention"); |
| 120 | assert_eq!(content["m.mentions"]["user_ids"], json!([bob_id])); |
| 121 | let push_actions = room |
| 122 | .event_push_actions(decrypted.raw()) |
| 123 | .await |
| 124 | .unwrap() |
| 125 | .unwrap(); |
| 126 | assert!(push_actions.iter().any(|action| action.should_notify())); |
| 127 | assert_eq!(cache.events().await.unwrap().len(), 1); |
| 128 | } |