17
17
18
18
use std:: collections:: BTreeMap ;
19
19
20
+ use futures_util:: future:: join_all;
20
21
use matrix_sdk_base:: deserialized_responses:: { EncryptionInfo , RawAnySyncOrStrippedState } ;
21
22
use ruma:: {
22
23
api:: client:: {
@@ -197,10 +198,11 @@ impl MatrixDriver {
197
198
async { }
198
199
} ) ;
199
200
let drop_guard_msg_like = self . room . client ( ) . event_handler_drop_guard ( handle_msg_like) ;
200
-
201
+ let _room_id = room_id;
202
+ let _tx = tx;
201
203
// Get only all state events from the state section of the sync.
202
204
let handle_state = self . room . add_event_handler ( move |raw : Raw < AnySyncStateEvent > | {
203
- let _ = tx . send ( attach_room_id ( raw. cast_ref ( ) , & room_id ) ) ;
205
+ let _ = _tx . send ( attach_room_id ( raw. cast_ref ( ) , & _room_id ) ) ;
204
206
async { }
205
207
} ) ;
206
208
let drop_guard_state = self . room . client ( ) . event_handler_drop_guard ( handle_state) ;
@@ -224,8 +226,11 @@ impl MatrixDriver {
224
226
// EncryptionInfo. The widgetAPI expects a boolean `encrypted` to be added
225
227
// (!) to the raw content to know if the to-device message was encrypted or
226
228
// not (as per MSC3819).
227
- move |raw : Raw < AnyToDeviceEvent > , _: Option < EncryptionInfo > | {
228
- let _ = tx. send ( raw) ;
229
+ move |raw : Raw < AnyToDeviceEvent > , encryption_info : Option < EncryptionInfo > | {
230
+ // Only sent encrypted to-device events
231
+ if encryption_info. is_some ( ) {
232
+ let _ = tx. send ( raw) ;
233
+ }
229
234
async { }
230
235
} ,
231
236
) ;
@@ -248,9 +253,36 @@ impl MatrixDriver {
248
253
let client = self . room . client ( ) ;
249
254
250
255
let request = if encrypted {
251
- return Err ( Error :: UnknownError (
252
- "Sending encrypted to-device events is not supported by the widget driver." . into ( ) ,
253
- ) ) ;
256
+ // We first want to get all missing session before we start any to device
257
+ // sending!
258
+ client. claim_one_time_keys ( messages. keys ( ) . map ( |u| u. as_ref ( ) ) ) . await ?;
259
+ let encrypted_content: BTreeMap <
260
+ OwnedUserId ,
261
+ BTreeMap < DeviceIdOrAllDevices , Raw < AnyToDeviceEventContent > > ,
262
+ > = join_all ( messages. into_iter ( ) . map ( |( user_id, device_content_map) | {
263
+ let event_type = event_type. clone ( ) ;
264
+ async move {
265
+ (
266
+ user_id. clone ( ) ,
267
+ to_device_crypto:: encrypted_device_content_map (
268
+ & self . room . client ( ) ,
269
+ & user_id,
270
+ & event_type,
271
+ device_content_map,
272
+ )
273
+ . await ,
274
+ )
275
+ }
276
+ } ) )
277
+ . await
278
+ . into_iter ( )
279
+ . collect ( ) ;
280
+
281
+ RumaToDeviceRequest :: new_raw (
282
+ ToDeviceEventType :: RoomEncrypted ,
283
+ TransactionId :: new ( ) ,
284
+ encrypted_content,
285
+ )
254
286
} else {
255
287
RumaToDeviceRequest :: new_raw ( event_type, TransactionId :: new ( ) , messages)
256
288
} ;
@@ -280,13 +312,153 @@ fn attach_room_id(raw_ev: &Raw<AnySyncTimelineEvent>, room_id: &RoomId) -> Raw<A
280
312
Raw :: new ( & ev_obj) . unwrap ( ) . cast ( )
281
313
}
282
314
315
+ /// Move this into the `matrix_crypto` crate!
316
+ /// This module contains helper functions to encrypt to device events.
317
+ mod to_device_crypto {
318
+ use std:: collections:: BTreeMap ;
319
+
320
+ use futures_util:: future:: join_all;
321
+ use ruma:: {
322
+ events:: { AnyToDeviceEventContent , ToDeviceEventType } ,
323
+ serde:: Raw ,
324
+ to_device:: DeviceIdOrAllDevices ,
325
+ UserId ,
326
+ } ;
327
+ use serde_json:: Value ;
328
+ use tracing:: { info, warn} ;
329
+
330
+ use crate :: { encryption:: identities:: Device , executor:: spawn, Client , Error , Result } ;
331
+
332
+ /// This encrypts to device content for a collection of devices.
333
+ /// It will ignore all devices where errors occurred or where the device
334
+ /// is not verified or where th user has a has_verification_violation.
335
+ async fn encrypted_content_for_devices (
336
+ unencrypted_content : & Raw < AnyToDeviceEventContent > ,
337
+ devices : Vec < Device > ,
338
+ event_type : & ToDeviceEventType ,
339
+ ) -> Result < impl Iterator < Item = ( DeviceIdOrAllDevices , Raw < AnyToDeviceEventContent > ) > > {
340
+ let content: Value = unencrypted_content. deserialize_as ( ) . map_err ( Into :: < Error > :: into) ?;
341
+ let event_type = event_type. clone ( ) ;
342
+ let device_content_tasks = devices. into_iter ( ) . map ( |device| spawn ( {
343
+ let event_type = event_type. clone ( ) ;
344
+ let content = content. clone ( ) ;
345
+
346
+ async move {
347
+ // This is not yet used. It is incompatible with the spa guest mode (the spa will not verify its crypto identity)
348
+ // if !device.is_cross_signed_by_owner() {
349
+ // info!("Device {} is not verified, skipping encryption", device.device_id());
350
+ // return None;
351
+ // }
352
+ match device
353
+ . inner
354
+ . encrypt_event_raw ( & event_type. to_string ( ) , & content)
355
+ . await {
356
+ Ok ( encrypted) => Some ( ( device. device_id ( ) . to_owned ( ) . into ( ) , encrypted. cast ( ) ) ) ,
357
+ Err ( e) =>{ info ! ( "Failed to encrypt to_device event from widget for device: {} because, {}" , device. device_id( ) , e) ; None } ,
358
+ }
359
+ }
360
+ } ) ) ;
361
+ let device_encrypted_content_map =
362
+ join_all ( device_content_tasks) . await . into_iter ( ) . flatten ( ) . flatten ( ) ;
363
+ Ok ( device_encrypted_content_map)
364
+ }
365
+
366
+ /// Convert the device content map for one user into the same content
367
+ /// map with encrypted content This needs to flatten the vectors
368
+ /// we get from `encrypted_content_for_devices`
369
+ /// since one `DeviceIdOrAllDevices` id can be multiple devices.
370
+ pub ( super ) async fn encrypted_device_content_map (
371
+ client : & Client ,
372
+ user_id : & UserId ,
373
+ event_type : & ToDeviceEventType ,
374
+ device_content_map : BTreeMap < DeviceIdOrAllDevices , Raw < AnyToDeviceEventContent > > ,
375
+ ) -> BTreeMap < DeviceIdOrAllDevices , Raw < AnyToDeviceEventContent > > {
376
+ let device_map_futures =
377
+ device_content_map. into_iter ( ) . map ( |( device_or_all_id, content) | spawn ( {
378
+ let client = client. clone ( ) ;
379
+ let user_id = user_id. to_owned ( ) ;
380
+ let event_type = event_type. clone ( ) ;
381
+ async move {
382
+ let Ok ( user_devices) = client. encryption ( ) . get_user_devices ( & user_id) . await else {
383
+ warn ! ( "Failed to get user devices for user: {}" , user_id) ;
384
+ return None ;
385
+ } ;
386
+ // This is not yet used. It is incompatible with the spa guest mode (the spa will not verify its crypto identity)
387
+ // let Ok(user_identity) = client.encryption().get_user_identity(&user_id).await else{
388
+ // warn!("Failed to get user identity for user: {}", user_id);
389
+ // return None;
390
+ // };
391
+ // if user_identity.map(|i|i.has_verification_violation()).unwrap_or(false) {
392
+ // info!("User {} has a verification violation, skipping encryption", user_id);
393
+ // return None;
394
+ // }
395
+ let devices: Vec < Device > = match device_or_all_id {
396
+ DeviceIdOrAllDevices :: DeviceId ( device_id) => {
397
+ vec ! [ user_devices. get( & device_id) ] . into_iter ( ) . flatten ( ) . collect ( )
398
+ }
399
+ DeviceIdOrAllDevices :: AllDevices => user_devices. devices ( ) . collect ( ) ,
400
+ } ;
401
+ encrypted_content_for_devices (
402
+ & content,
403
+ devices,
404
+ & event_type,
405
+ )
406
+ . await
407
+ . map_err ( |e| info ! ( "WidgetDriver: could not encrypt content for to device widget event content: {}. because, {}" , content. json( ) , e) )
408
+ . ok ( )
409
+ } } ) ) ;
410
+ let content_map_iterator = join_all ( device_map_futures) . await . into_iter ( ) ;
411
+
412
+ // The first flatten takes the iterator over Result<Option<impl Iterator<Item =
413
+ // (DeviceIdOrAllDevices, Raw<AnyToDeviceEventContent>)>>, JoinError>>
414
+ // and flattens the Result (drops Err() items)
415
+ // The second takes the iterator over: Option<impl Iterator<Item =
416
+ // (DeviceIdOrAllDevices, Raw<AnyToDeviceEventContent>)>>
417
+ // and flattens the Option (drops None items)
418
+ // The third takes the iterator over iterators: impl Iterator<Item =
419
+ // (DeviceIdOrAllDevices, Raw<AnyToDeviceEventContent>)>
420
+ // and flattens it to just an iterator over (DeviceIdOrAllDevices,
421
+ // Raw<AnyToDeviceEventContent>)
422
+ content_map_iterator. flatten ( ) . flatten ( ) . flatten ( ) . collect ( )
423
+ }
424
+ }
425
+
283
426
#[ cfg( test) ]
284
427
mod tests {
285
428
use insta;
286
- use ruma:: { events:: AnyTimelineEvent , room_id, serde:: Raw } ;
429
+ use ruma:: {
430
+ events:: { AnySyncTimelineEvent , AnyTimelineEvent } ,
431
+ room_id,
432
+ serde:: Raw ,
433
+ } ;
287
434
use serde_json:: { json, Value } ;
288
435
289
436
use super :: attach_room_id;
437
+ #[ test]
438
+ fn test_app_props_to_raw ( ) {
439
+ let raw = Raw :: new ( & json ! ( {
440
+ "encrypted" : true ,
441
+ "type" : "m.room.message" ,
442
+ "content" : {
443
+ "body" : "Hello world"
444
+ }
445
+ } ) )
446
+ . unwrap ( )
447
+ . cast :: < AnySyncTimelineEvent > ( ) ;
448
+ let room_id = room_id ! ( "!my_id:example.org" ) ;
449
+ let new = attach_room_id ( & raw , room_id) ;
450
+ assert_eq ! (
451
+ serde_json:: to_value( new) . unwrap( ) ,
452
+ json!( {
453
+ "encrypted" : true ,
454
+ "room_id" : "!my_id:example.org" ,
455
+ "type" : "m.room.message" ,
456
+ "content" : {
457
+ "body" : "Hello world"
458
+ }
459
+ } )
460
+ ) ;
461
+ }
290
462
291
463
#[ test]
292
464
fn test_add_room_id_to_raw ( ) {
0 commit comments