11/*
2- Copyright 2022 The Matrix.org Foundation C.I.C.
2+ Copyright 2022-2024 The Matrix.org Foundation C.I.C.
33
44Licensed under the Apache License, Version 2.0 (the "License");
55you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@ limitations under the License.
1515*/
1616
1717import type { Page } from "@playwright/test" ;
18+ import type { EmittedEvents , Preset } from "matrix-js-sdk/src/matrix" ;
1819import { expect , test } from "../../element-web-test" ;
1920import {
2021 copyAndContinue ,
@@ -31,6 +32,7 @@ import {
3132import { Bot } from "../../pages/bot" ;
3233import { ElementAppPage } from "../../pages/ElementAppPage" ;
3334import { Client } from "../../pages/client" ;
35+ import { isDendrite } from "../../plugins/homeserver/dendrite" ;
3436
3537const openRoomInfo = async ( page : Page ) => {
3638 await page . getByRole ( "button" , { name : "Room info" } ) . click ( ) ;
@@ -599,5 +601,217 @@ test.describe("Cryptography", function () {
599601 await expect ( tilesAfterVerify [ 1 ] ) . toContainText ( "test2 test2" ) ;
600602 await expect ( tilesAfterVerify [ 1 ] . locator ( ".mx_EventTile_e2eIcon_normal" ) ) . toBeVisible ( ) ;
601603 } ) ;
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+ } ) ;
602816 } ) ;
603817} ) ;
0 commit comments