1
1
/*
2
- Copyright 2022 The Matrix.org Foundation C.I.C.
2
+ Copyright 2022-2024 The Matrix.org Foundation C.I.C.
3
3
4
4
Licensed under the Apache License, Version 2.0 (the "License");
5
5
you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@ limitations under the License.
15
15
*/
16
16
17
17
import type { Page } from "@playwright/test" ;
18
+ import type { EmittedEvents , Preset } from "matrix-js-sdk/src/matrix" ;
18
19
import { expect , test } from "../../element-web-test" ;
19
20
import {
20
21
copyAndContinue ,
@@ -31,6 +32,7 @@ import {
31
32
import { Bot } from "../../pages/bot" ;
32
33
import { ElementAppPage } from "../../pages/ElementAppPage" ;
33
34
import { Client } from "../../pages/client" ;
35
+ import { isDendrite } from "../../plugins/homeserver/dendrite" ;
34
36
35
37
const openRoomInfo = async ( page : Page ) => {
36
38
await page . getByRole ( "button" , { name : "Room info" } ) . click ( ) ;
@@ -599,5 +601,217 @@ test.describe("Cryptography", function () {
599
601
await expect ( tilesAfterVerify [ 1 ] ) . toContainText ( "test2 test2" ) ;
600
602
await expect ( tilesAfterVerify [ 1 ] . locator ( ".mx_EventTile_e2eIcon_normal" ) ) . toBeVisible ( ) ;
601
603
} ) ;
604
+
605
+ test . describe ( "non-joined historical messages" , ( ) => {
606
+ test . skip ( isDendrite , "does not yet support membership on events" ) ;
607
+
608
+ test ( "should display undecryptable non-joined historical messages with a different message" , async ( {
609
+ homeserver,
610
+ page,
611
+ app,
612
+ credentials : aliceCredentials ,
613
+ user : alice ,
614
+ cryptoBackend,
615
+ bot : bob ,
616
+ } ) => {
617
+ test . skip ( cryptoBackend === "legacy" , "Not implemented for legacy crypto" ) ;
618
+
619
+ // Bob creates an encrypted room and sends a message to it. He then invites Alice
620
+ const roomId = await bob . evaluate (
621
+ async ( client , { alice } ) => {
622
+ const encryptionStatePromise = new Promise < void > ( ( resolve ) => {
623
+ client . on ( "RoomState.events" as EmittedEvents , ( event , _state , _lastStateEvent ) => {
624
+ if ( event . getType ( ) === "m.room.encryption" ) {
625
+ resolve ( ) ;
626
+ }
627
+ } ) ;
628
+ } ) ;
629
+
630
+ const { room_id : roomId } = await client . createRoom ( {
631
+ initial_state : [
632
+ {
633
+ type : "m.room.encryption" ,
634
+ content : {
635
+ algorithm : "m.megolm.v1.aes-sha2" ,
636
+ } ,
637
+ } ,
638
+ ] ,
639
+ name : "Test room" ,
640
+ preset : "private_chat" as Preset ,
641
+ } ) ;
642
+
643
+ // wait for m.room.encryption event, so that when we send a
644
+ // message, it will be encrypted
645
+ await encryptionStatePromise ;
646
+
647
+ await client . sendTextMessage ( roomId , "This should be undecryptable" ) ;
648
+
649
+ await client . invite ( roomId , alice . userId ) ;
650
+
651
+ return roomId ;
652
+ } ,
653
+ { alice } ,
654
+ ) ;
655
+
656
+ // Alice accepts the invite
657
+ await expect (
658
+ page . getByRole ( "group" , { name : "Invites" } ) . locator ( ".mx_RoomSublist_tiles" ) . getByRole ( "treeitem" ) ,
659
+ ) . toHaveCount ( 1 ) ;
660
+ await page . getByRole ( "treeitem" , { name : "Test room" } ) . click ( ) ;
661
+ await page . locator ( ".mx_RoomView" ) . getByRole ( "button" , { name : "Accept" } ) . click ( ) ;
662
+
663
+ // Bob sends an encrypted event and an undecryptable event
664
+ await bob . evaluate (
665
+ async ( client , { roomId } ) => {
666
+ await client . sendTextMessage ( roomId , "This should be decryptable" ) ;
667
+ await client . sendEvent (
668
+ roomId ,
669
+ "m.room.encrypted" as any ,
670
+ {
671
+ algorithm : "m.megolm.v1.aes-sha2" ,
672
+ ciphertext : "this+message+will+be+undecryptable" ,
673
+ device_id : client . getDeviceId ( ) ! ,
674
+ sender_key : ( await client . getCrypto ( ) ! . getOwnDeviceKeys ( ) ) . ed25519 ,
675
+ session_id : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ,
676
+ } as any ,
677
+ ) ;
678
+ } ,
679
+ { roomId } ,
680
+ ) ;
681
+
682
+ // We wait for the event tiles that we expect from the messages that
683
+ // Bob sent, in sequence.
684
+ await expect (
685
+ page . locator ( `.mx_EventTile` ) . getByText ( "You don't have access to this message" ) ,
686
+ ) . toBeVisible ( ) ;
687
+ await expect ( page . locator ( `.mx_EventTile` ) . getByText ( "This should be decryptable" ) ) . toBeVisible ( ) ;
688
+ await expect ( page . locator ( `.mx_EventTile` ) . getByText ( "Unable to decrypt message" ) ) . toBeVisible ( ) ;
689
+
690
+ // And then we ensure that they are where we expect them to be
691
+ // Alice should see these event tiles:
692
+ // - first message sent by Bob (undecryptable)
693
+ // - Bob invited Alice
694
+ // - Alice joined the room
695
+ // - second message sent by Bob (decryptable)
696
+ // - third message sent by Bob (undecryptable)
697
+ const tiles = await page . locator ( ".mx_EventTile" ) . all ( ) ;
698
+ expect ( tiles . length ) . toBeGreaterThanOrEqual ( 5 ) ;
699
+
700
+ // The first message from Bob was sent before Alice was in the room, so should
701
+ // be different from the standard UTD message
702
+ await expect ( tiles [ tiles . length - 5 ] ) . toContainText ( "You don't have access to this message" ) ;
703
+ await expect ( tiles [ tiles . length - 5 ] . locator ( ".mx_EventTile_e2eIcon_decryption_failure" ) ) . toBeVisible ( ) ;
704
+
705
+ // The second message from Bob should be decryptable
706
+ await expect ( tiles [ tiles . length - 2 ] ) . toContainText ( "This should be decryptable" ) ;
707
+ // this tile won't have an e2e icon since we got the key from the sender
708
+
709
+ // The third message from Bob is undecryptable, but was sent while Alice was
710
+ // in the room and is expected to be decryptable, so this should have the
711
+ // standard UTD message
712
+ await expect ( tiles [ tiles . length - 1 ] ) . toContainText ( "Unable to decrypt message" ) ;
713
+ await expect ( tiles [ tiles . length - 1 ] . locator ( ".mx_EventTile_e2eIcon_decryption_failure" ) ) . toBeVisible ( ) ;
714
+ } ) ;
715
+
716
+ test ( "should be able to jump to a message sent before our last join event" , async ( {
717
+ homeserver,
718
+ page,
719
+ app,
720
+ credentials : aliceCredentials ,
721
+ user : alice ,
722
+ cryptoBackend,
723
+ bot : bob ,
724
+ } ) => {
725
+ // The old pre-join UTD hiding code would hide events sent
726
+ // before our latest join event, even if the event that we're
727
+ // jumping to was decryptable. We test that this no longer happens.
728
+
729
+ test . skip ( cryptoBackend === "legacy" , "Not implemented for legacy crypto" ) ;
730
+
731
+ // Bob:
732
+ // - creates an encrypted room,
733
+ // - invites Alice,
734
+ // - sends a message to it,
735
+ // - kicks Alice,
736
+ // - sends a bunch more events
737
+ // - invites Alice again
738
+ // In this way, there will be an event that Alice can decrypt,
739
+ // followed by a bunch of undecryptable events which Alice shouldn't
740
+ // expect to be able to decrypt. The old code would have hidden all
741
+ // the events, even the decryptable event (which it wouldn't have
742
+ // even tried to fetch, if it was far enough back).
743
+ const { roomId, eventId } = await bob . evaluate (
744
+ async ( client , { alice } ) => {
745
+ const { room_id : roomId } = await client . createRoom ( {
746
+ initial_state : [
747
+ {
748
+ type : "m.room.encryption" ,
749
+ content : {
750
+ algorithm : "m.megolm.v1.aes-sha2" ,
751
+ } ,
752
+ } ,
753
+ ] ,
754
+ name : "Test room" ,
755
+ preset : "private_chat" as Preset ,
756
+ } ) ;
757
+
758
+ // invite Alice
759
+ const inviteAlicePromise = new Promise < void > ( ( resolve ) => {
760
+ client . on ( "RoomMember.membership" as EmittedEvents , ( _event , member , _oldMembership ?) => {
761
+ if ( member . userId === alice . userId && member . membership === "invite" ) {
762
+ resolve ( ) ;
763
+ }
764
+ } ) ;
765
+ } ) ;
766
+ await client . invite ( roomId , alice . userId ) ;
767
+ // wait for the invite to come back so that we encrypt to Alice
768
+ await inviteAlicePromise ;
769
+
770
+ // send a message that Alice should be able to decrypt
771
+ const { event_id : eventId } = await client . sendTextMessage (
772
+ roomId ,
773
+ "This should be decryptable" ,
774
+ ) ;
775
+
776
+ // kick Alice
777
+ const kickAlicePromise = new Promise < void > ( ( resolve ) => {
778
+ client . on ( "RoomMember.membership" as EmittedEvents , ( _event , member , _oldMembership ?) => {
779
+ if ( member . userId === alice . userId && member . membership === "leave" ) {
780
+ resolve ( ) ;
781
+ }
782
+ } ) ;
783
+ } ) ;
784
+ await client . kick ( roomId , alice . userId ) ;
785
+ await kickAlicePromise ;
786
+
787
+ // send a bunch of messages that Alice won't be able to decrypt
788
+ for ( let i = 0 ; i < 20 ; i ++ ) {
789
+ await client . sendTextMessage ( roomId , `${ i } ` ) ;
790
+ }
791
+
792
+ // invite Alice again
793
+ await client . invite ( roomId , alice . userId ) ;
794
+
795
+ return { roomId, eventId } ;
796
+ } ,
797
+ { alice } ,
798
+ ) ;
799
+
800
+ // Alice accepts the invite
801
+ await expect (
802
+ page . getByRole ( "group" , { name : "Invites" } ) . locator ( ".mx_RoomSublist_tiles" ) . getByRole ( "treeitem" ) ,
803
+ ) . toHaveCount ( 1 ) ;
804
+ await page . getByRole ( "treeitem" , { name : "Test room" } ) . click ( ) ;
805
+ await page . locator ( ".mx_RoomView" ) . getByRole ( "button" , { name : "Accept" } ) . click ( ) ;
806
+
807
+ // wait until we're joined and see the timeline
808
+ await expect ( page . locator ( `.mx_EventTile` ) . getByText ( "Alice joined the room" ) ) . toBeVisible ( ) ;
809
+
810
+ // we should be able to jump to the decryptable message that Bob sent
811
+ await page . goto ( `#/room/${ roomId } /${ eventId } ` ) ;
812
+
813
+ await expect ( page . locator ( `.mx_EventTile` ) . getByText ( "This should be decryptable" ) ) . toBeVisible ( ) ;
814
+ } ) ;
815
+ } ) ;
602
816
} ) ;
603
817
} ) ;
0 commit comments