Skip to content

Commit 6a9aaa2

Browse files
Add crypto shared history sanity test
1 parent e848b92 commit 6a9aaa2

File tree

2 files changed

+241
-6
lines changed

2 files changed

+241
-6
lines changed

matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
4242
import org.matrix.android.sdk.api.session.events.model.toContent
4343
import org.matrix.android.sdk.api.session.room.Room
4444
import org.matrix.android.sdk.api.session.room.model.Membership
45+
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
4546
import org.matrix.android.sdk.api.session.room.model.RoomSummary
4647
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
4748
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
@@ -60,11 +61,14 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
6061
/**
6162
* @return alice session
6263
*/
63-
fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
64+
fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true, roomHistoryVisibility: RoomHistoryVisibility? = null): CryptoTestData {
6465
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
6566

6667
val roomId = testHelper.runBlockingTest {
67-
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
68+
aliceSession.createRoom(CreateRoomParams().apply {
69+
historyVisibility = roomHistoryVisibility
70+
name = "MyRoom"
71+
})
6872
}
6973
if (encryptedRoom) {
7074
testHelper.waitWithLatch { latch ->
@@ -88,8 +92,8 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
8892
/**
8993
* @return alice and bob sessions
9094
*/
91-
fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData {
92-
val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom)
95+
fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true, roomHistoryVisibility: RoomHistoryVisibility? = null): CryptoTestData {
96+
val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom, roomHistoryVisibility)
9397
val aliceSession = cryptoTestData.firstSession
9498
val aliceRoomId = cryptoTestData.roomId
9599

@@ -288,7 +292,8 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
288292
)
289293
)
290294
}
291-
}, it)
295+
}, it
296+
)
292297
}
293298
}
294299

@@ -305,7 +310,8 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
305310
requestID,
306311
roomId,
307312
bob.myUserId,
308-
bob.sessionParams.credentials.deviceId!!)
313+
bob.sessionParams.credentials.deviceId!!
314+
)
309315

310316
// we should reach SHOW SAS on both
311317
var alicePovTx: OutgoingSasVerificationTransaction? = null
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
/*
2+
* Copyright 2022 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.matrix.android.sdk.internal.crypto
18+
19+
import android.util.Log
20+
import androidx.test.filters.LargeTest
21+
import org.amshove.kluent.internal.assertEquals
22+
import org.junit.Assert
23+
import org.junit.FixMethodOrder
24+
import org.junit.Test
25+
import org.junit.runner.RunWith
26+
import org.junit.runners.JUnit4
27+
import org.junit.runners.MethodSorters
28+
import org.matrix.android.sdk.InstrumentedTest
29+
import org.matrix.android.sdk.api.session.Session
30+
import org.matrix.android.sdk.api.session.events.model.EventType
31+
import org.matrix.android.sdk.api.session.events.model.toModel
32+
import org.matrix.android.sdk.api.session.room.Room
33+
import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
34+
import org.matrix.android.sdk.api.session.room.model.Membership
35+
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
36+
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
37+
import org.matrix.android.sdk.api.session.room.send.SendState
38+
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
39+
import org.matrix.android.sdk.common.CommonTestHelper
40+
import org.matrix.android.sdk.common.CryptoTestHelper
41+
import org.matrix.android.sdk.common.SessionTestParams
42+
import org.matrix.android.sdk.common.TestConstants
43+
44+
@RunWith(JUnit4::class)
45+
@FixMethodOrder(MethodSorters.JVM)
46+
@LargeTest
47+
class E2eeShareKeysSanityTest : InstrumentedTest {
48+
49+
private val testHelper = CommonTestHelper(context())
50+
private val cryptoTestHelper = CryptoTestHelper(testHelper)
51+
52+
@Test
53+
fun testShareMessagesHistoryWithRoomWorldReadable() {
54+
testSharedHistoryWithRoomVisibility(RoomHistoryVisibility.WORLD_READABLE)
55+
}
56+
57+
@Test
58+
fun testShareMessagesHistoryWithRoomShared() {
59+
testSharedHistoryWithRoomVisibility(RoomHistoryVisibility.SHARED)
60+
}
61+
62+
@Test
63+
fun testShareMessagesHistoryWithRoomJoined() {
64+
testSharedHistoryWithRoomVisibility(RoomHistoryVisibility.JOINED)
65+
}
66+
67+
@Test
68+
fun testShareMessagesHistoryWithRoomInvited() {
69+
testSharedHistoryWithRoomVisibility(RoomHistoryVisibility.INVITED)
70+
}
71+
72+
/**
73+
* Simple test that creates an e2ee room.
74+
* In this test we create a room and test that new members
75+
* can decrypt history when the room visibility is
76+
* RoomHistoryVisibility.SHARED or RoomHistoryVisibility.WORLD_READABLE
77+
*/
78+
private fun testSharedHistoryWithRoomVisibility(roomHistoryVisibility: RoomHistoryVisibility? = null) {
79+
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true, roomHistoryVisibility)
80+
81+
val e2eRoomID = cryptoTestData.roomId
82+
83+
// Alice
84+
val aliceSession = cryptoTestData.firstSession
85+
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
86+
87+
// Bob
88+
val bobSession = cryptoTestData.secondSession
89+
val bobRoomPOV = bobSession!!.getRoom(e2eRoomID)!!
90+
91+
assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2)
92+
Log.v("#E2E TEST", "Alice and Bob are in roomId: $e2eRoomID")
93+
94+
val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, "Hello Bob, I am Alice!")
95+
Assert.assertTrue("Message should be sent", aliceMessageId != null)
96+
Log.v("#E2E TEST", "Alice sent message to roomId: $e2eRoomID")
97+
98+
// Bob should be able to decrypt the message
99+
testHelper.waitWithLatch { latch ->
100+
testHelper.retryPeriodicallyWithLatch(latch) {
101+
val timelineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!)
102+
(timelineEvent != null &&
103+
timelineEvent.isEncrypted() &&
104+
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
105+
if (it) {
106+
Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
107+
}
108+
}
109+
}
110+
}
111+
112+
// Create a new user
113+
val arisSession = testHelper.createAccount("aris", SessionTestParams(true))
114+
Log.v("#E2E TEST", "Aris user created")
115+
116+
// Alice invites new user to the room
117+
testHelper.runBlockingTest {
118+
Log.v("#E2E TEST", "Alice invites ${arisSession.myUserId}")
119+
aliceRoomPOV.invite(arisSession.myUserId)
120+
}
121+
122+
waitForAndAcceptInviteInRoom(arisSession, e2eRoomID)
123+
124+
ensureMembersHaveJoined(aliceSession, arrayListOf(arisSession), e2eRoomID)
125+
Log.v("#E2E TEST", "Aris has joined roomId: $e2eRoomID")
126+
127+
when (roomHistoryVisibility) {
128+
RoomHistoryVisibility.WORLD_READABLE,
129+
RoomHistoryVisibility.SHARED,
130+
null
131+
-> {
132+
// Aris should be able to decrypt the message
133+
testHelper.waitWithLatch { latch ->
134+
testHelper.retryPeriodicallyWithLatch(latch) {
135+
val timelineEvent = arisSession.getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!)
136+
(timelineEvent != null &&
137+
timelineEvent.isEncrypted() &&
138+
timelineEvent.root.getClearType() == EventType.MESSAGE
139+
).also {
140+
if (it) {
141+
Log.v("#E2E TEST", "Aris can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
142+
}
143+
}
144+
}
145+
}
146+
}
147+
RoomHistoryVisibility.INVITED,
148+
RoomHistoryVisibility.JOINED -> {
149+
// Aris should not even be able to get the message
150+
testHelper.waitWithLatch { latch ->
151+
testHelper.retryPeriodicallyWithLatch(latch) {
152+
val timelineEvent = arisSession.getRoom(e2eRoomID)?.getTimelineEvent(aliceMessageId!!)
153+
timelineEvent == null
154+
}
155+
}
156+
}
157+
}
158+
159+
testHelper.signOutAndClose(arisSession)
160+
cryptoTestData.cleanUp(testHelper)
161+
}
162+
163+
private fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? {
164+
aliceRoomPOV.sendTextMessage(text)
165+
var sentEventId: String? = null
166+
testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch ->
167+
val timeline = aliceRoomPOV.createTimeline(null, TimelineSettings(60))
168+
timeline.start()
169+
170+
testHelper.retryPeriodicallyWithLatch(latch) {
171+
val decryptedMsg = timeline.getSnapshot()
172+
.filter { it.root.getClearType() == EventType.MESSAGE }
173+
.also { list ->
174+
val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" }
175+
Log.v("#E2E TEST", "Timeline snapshot is $message")
176+
}
177+
.filter { it.root.sendState == SendState.SYNCED }
178+
.firstOrNull { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(text) == true }
179+
sentEventId = decryptedMsg?.eventId
180+
decryptedMsg != null
181+
}
182+
183+
timeline.dispose()
184+
}
185+
return sentEventId
186+
}
187+
188+
private fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {
189+
testHelper.waitWithLatch { latch ->
190+
testHelper.retryPeriodicallyWithLatch(latch) {
191+
otherAccounts.map {
192+
aliceSession.getRoomMember(it.myUserId, e2eRoomID)?.membership
193+
}.all {
194+
it == Membership.JOIN
195+
}
196+
}
197+
}
198+
}
199+
200+
private fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String) {
201+
testHelper.waitWithLatch { latch ->
202+
testHelper.retryPeriodicallyWithLatch(latch) {
203+
val roomSummary = otherSession.getRoomSummary(e2eRoomID)
204+
(roomSummary != null && roomSummary.membership == Membership.INVITE).also {
205+
if (it) {
206+
Log.v("#E2E TEST", "${otherSession.myUserId} can see the invite from alice")
207+
}
208+
}
209+
}
210+
}
211+
212+
testHelper.runBlockingTest(60_000) {
213+
Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID")
214+
try {
215+
otherSession.joinRoom(e2eRoomID)
216+
} catch (ex: JoinRoomFailure.JoinedWithTimeout) {
217+
// it's ok we will wait after
218+
}
219+
}
220+
221+
Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...")
222+
testHelper.waitWithLatch {
223+
testHelper.retryPeriodicallyWithLatch(it) {
224+
val roomSummary = otherSession.getRoomSummary(e2eRoomID)
225+
roomSummary != null && roomSummary.membership == Membership.JOIN
226+
}
227+
}
228+
}
229+
}

0 commit comments

Comments
 (0)