mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-21 17:05:39 +03:00
Instrumentation test coroutines (#7207)
Converting SDK instrumentation tests from CountdownLatch to suspending functions
This commit is contained in:
parent
a422361872
commit
fad02062d8
29 changed files with 1488 additions and 2039 deletions
1
changelog.d/7207.sdk
Normal file
1
changelog.d/7207.sdk
Normal file
|
@ -0,0 +1 @@
|
|||
Ports SDK instrumentation tests to use suspending functions instead of countdown latches
|
|
@ -221,6 +221,8 @@ dependencies {
|
|||
androidTestImplementation libs.mockk.mockkAndroid
|
||||
androidTestImplementation libs.androidx.coreTesting
|
||||
androidTestImplementation libs.jetbrains.coroutinesAndroid
|
||||
androidTestImplementation libs.jetbrains.coroutinesTest
|
||||
|
||||
// Plant Timber tree for test
|
||||
androidTestImplementation libs.tests.timberJunitRule
|
||||
|
||||
|
|
|
@ -43,9 +43,7 @@ class ChangePasswordTest : InstrumentedTest {
|
|||
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false))
|
||||
|
||||
// Change password
|
||||
commonTestHelper.runBlockingTest {
|
||||
session.accountService().changePassword(TestConstants.PASSWORD, NEW_PASSWORD)
|
||||
}
|
||||
session.accountService().changePassword(TestConstants.PASSWORD, NEW_PASSWORD)
|
||||
|
||||
// Try to login with the previous password, it will fail
|
||||
val throwable = commonTestHelper.logAccountWithError(session.myUserId, TestConstants.PASSWORD)
|
||||
|
|
|
@ -44,22 +44,20 @@ class DeactivateAccountTest : InstrumentedTest {
|
|||
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
|
||||
|
||||
// Deactivate the account
|
||||
commonTestHelper.runBlockingTest {
|
||||
session.accountService().deactivateAccount(
|
||||
eraseAllData = false,
|
||||
userInteractiveAuthInterceptor = object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(
|
||||
UserPasswordAuth(
|
||||
user = session.myUserId,
|
||||
password = TestConstants.PASSWORD,
|
||||
session = flowResponse.session
|
||||
)
|
||||
)
|
||||
}
|
||||
session.accountService().deactivateAccount(
|
||||
eraseAllData = false,
|
||||
userInteractiveAuthInterceptor = object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(
|
||||
UserPasswordAuth(
|
||||
user = session.myUserId,
|
||||
password = TestConstants.PASSWORD,
|
||||
session = flowResponse.session
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Try to login on the previous account, it will fail (M_USER_DEACTIVATED)
|
||||
val throwable = commonTestHelper.logAccountWithError(session.myUserId, TestConstants.PASSWORD)
|
||||
|
@ -74,23 +72,19 @@ class DeactivateAccountTest : InstrumentedTest {
|
|||
// Try to create an account with the deactivate account user id, it will fail (M_USER_IN_USE)
|
||||
val hs = commonTestHelper.createHomeServerConfig()
|
||||
|
||||
commonTestHelper.runBlockingTest {
|
||||
commonTestHelper.matrix.authenticationService.getLoginFlow(hs)
|
||||
}
|
||||
commonTestHelper.matrix.authenticationService.getLoginFlow(hs)
|
||||
|
||||
var accountCreationError: Throwable? = null
|
||||
commonTestHelper.runBlockingTest {
|
||||
try {
|
||||
commonTestHelper.matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.createAccount(
|
||||
session.myUserId.substringAfter("@").substringBefore(":"),
|
||||
TestConstants.PASSWORD,
|
||||
null
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
accountCreationError = failure
|
||||
}
|
||||
try {
|
||||
commonTestHelper.matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.createAccount(
|
||||
session.myUserId.substringAfter("@").substringBefore(":"),
|
||||
TestConstants.PASSWORD,
|
||||
null
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
accountCreationError = failure
|
||||
}
|
||||
|
||||
// Test the error
|
||||
|
|
|
@ -19,18 +19,16 @@ package org.matrix.android.sdk.common
|
|||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
|
@ -51,12 +49,12 @@ import org.matrix.android.sdk.api.session.room.send.SendState
|
|||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CancellationException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
/**
|
||||
* This class exposes methods to be used in common cases
|
||||
|
@ -65,32 +63,42 @@ import java.util.concurrent.TimeUnit
|
|||
class CommonTestHelper internal constructor(context: Context) {
|
||||
|
||||
companion object {
|
||||
internal fun runSessionTest(context: Context, autoSignoutOnClose: Boolean = true, block: (CommonTestHelper) -> Unit) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal fun runSessionTest(context: Context, autoSignoutOnClose: Boolean = true, block: suspend CoroutineScope.(CommonTestHelper) -> Unit) {
|
||||
val testHelper = CommonTestHelper(context)
|
||||
return try {
|
||||
block(testHelper)
|
||||
} finally {
|
||||
if (autoSignoutOnClose) {
|
||||
testHelper.cleanUpOpenedSessions()
|
||||
return runTest(dispatchTimeoutMs = TestConstants.timeOutMillis) {
|
||||
try {
|
||||
withContext(Dispatchers.Default) {
|
||||
block(testHelper)
|
||||
}
|
||||
} finally {
|
||||
if (autoSignoutOnClose) {
|
||||
testHelper.cleanUpOpenedSessions()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun runCryptoTest(context: Context, autoSignoutOnClose: Boolean = true, block: (CryptoTestHelper, CommonTestHelper) -> Unit) {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal fun runCryptoTest(context: Context, autoSignoutOnClose: Boolean = true, block: suspend CoroutineScope.(CryptoTestHelper, CommonTestHelper) -> Unit) {
|
||||
val testHelper = CommonTestHelper(context)
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
return try {
|
||||
block(cryptoTestHelper, testHelper)
|
||||
} finally {
|
||||
if (autoSignoutOnClose) {
|
||||
testHelper.cleanUpOpenedSessions()
|
||||
return runTest(dispatchTimeoutMs = TestConstants.timeOutMillis) {
|
||||
try {
|
||||
withContext(Dispatchers.Default) {
|
||||
block(cryptoTestHelper, testHelper)
|
||||
}
|
||||
} finally {
|
||||
if (autoSignoutOnClose) {
|
||||
testHelper.cleanUpOpenedSessions()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val matrix: TestMatrix
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
private var accountNumber = 0
|
||||
|
||||
private val trackedSessions = mutableListOf<Session>()
|
||||
|
@ -112,19 +120,17 @@ class CommonTestHelper internal constructor(context: Context) {
|
|||
matrix = _matrix!!
|
||||
}
|
||||
|
||||
fun createAccount(userNamePrefix: String, testParams: SessionTestParams): Session {
|
||||
suspend fun createAccount(userNamePrefix: String, testParams: SessionTestParams): Session {
|
||||
return createAccount(userNamePrefix, TestConstants.PASSWORD, testParams)
|
||||
}
|
||||
|
||||
fun logIntoAccount(userId: String, testParams: SessionTestParams): Session {
|
||||
suspend fun logIntoAccount(userId: String, testParams: SessionTestParams): Session {
|
||||
return logIntoAccount(userId, TestConstants.PASSWORD, testParams)
|
||||
}
|
||||
|
||||
fun cleanUpOpenedSessions() {
|
||||
suspend fun cleanUpOpenedSessions() {
|
||||
trackedSessions.forEach {
|
||||
runBlockingTest {
|
||||
it.signOutService().signOut(true)
|
||||
}
|
||||
it.signOutService().signOut(true)
|
||||
}
|
||||
trackedSessions.clear()
|
||||
}
|
||||
|
@ -138,27 +144,10 @@ class CommonTestHelper internal constructor(context: Context) {
|
|||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods init the event stream and check for initial sync
|
||||
*
|
||||
* @param session the session to sync
|
||||
*/
|
||||
fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis * 10) {
|
||||
val lock = CountDownLatch(1)
|
||||
coroutineScope.launch {
|
||||
session.syncService().startSync(true)
|
||||
val syncLiveData = session.syncService().getSyncStateLive()
|
||||
val syncObserver = object : Observer<SyncState> {
|
||||
override fun onChanged(t: SyncState?) {
|
||||
if (session.syncService().hasAlreadySynced()) {
|
||||
lock.countDown()
|
||||
syncLiveData.removeObserver(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
syncLiveData.observeForever(syncObserver)
|
||||
}
|
||||
await(lock, timeout)
|
||||
suspend fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis * 10) {
|
||||
session.syncService().startSync(true)
|
||||
val syncLiveData = session.syncService().getSyncStateLive()
|
||||
syncLiveData.first(timeout) { session.syncService().hasAlreadySynced() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -166,22 +155,11 @@ class CommonTestHelper internal constructor(context: Context) {
|
|||
*
|
||||
* @param session the session to sync
|
||||
*/
|
||||
fun clearCacheAndSync(session: Session, timeout: Long = TestConstants.timeOutMillis) {
|
||||
waitWithLatch(timeout) { latch ->
|
||||
session.clearCache()
|
||||
val syncLiveData = session.syncService().getSyncStateLive()
|
||||
val syncObserver = object : Observer<SyncState> {
|
||||
override fun onChanged(t: SyncState?) {
|
||||
if (session.syncService().hasAlreadySynced()) {
|
||||
Timber.v("Clear cache and synced")
|
||||
syncLiveData.removeObserver(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
syncLiveData.observeForever(syncObserver)
|
||||
session.syncService().startSync(true)
|
||||
}
|
||||
suspend fun clearCacheAndSync(session: Session, timeout: Long = TestConstants.timeOutMillis) {
|
||||
session.clearCache()
|
||||
syncSession(session, timeout)
|
||||
session.syncService().getSyncStateLive().first(timeout) { session.syncService().hasAlreadySynced() }
|
||||
Timber.v("Clear cache and synced")
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -191,7 +169,7 @@ class CommonTestHelper internal constructor(context: Context) {
|
|||
* @param message the message to send
|
||||
* @param nbOfMessages the number of time the message will be sent
|
||||
*/
|
||||
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
|
||||
suspend fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
|
||||
val timeline = room.timelineService().createTimeline(null, TimelineSettings(10))
|
||||
timeline.start()
|
||||
val sentEvents = sendTextMessagesBatched(timeline, room, message, nbOfMessages, timeout)
|
||||
|
@ -204,66 +182,72 @@ class CommonTestHelper internal constructor(context: Context) {
|
|||
/**
|
||||
* Will send nb of messages provided by count parameter but waits every 10 messages to avoid gap in sync
|
||||
*/
|
||||
private fun sendTextMessagesBatched(timeline: Timeline, room: Room, message: String, count: Int, timeout: Long, rootThreadEventId: String? = null): List<TimelineEvent> {
|
||||
private suspend fun sendTextMessagesBatched(timeline: Timeline, room: Room, message: String, count: Int, timeout: Long, rootThreadEventId: String? = null): List<TimelineEvent> {
|
||||
val sentEvents = ArrayList<TimelineEvent>(count)
|
||||
(1 until count + 1)
|
||||
.map { "$message #$it" }
|
||||
.chunked(10)
|
||||
.forEach { batchedMessages ->
|
||||
batchedMessages.forEach { formattedMessage ->
|
||||
if (rootThreadEventId != null) {
|
||||
room.relationService().replyInThread(
|
||||
rootThreadEventId = rootThreadEventId,
|
||||
replyInThreadText = formattedMessage
|
||||
)
|
||||
} else {
|
||||
room.sendService().sendTextMessage(formattedMessage)
|
||||
}
|
||||
}
|
||||
waitWithLatch(timeout) { latch ->
|
||||
val timelineListener = object : Timeline.Listener {
|
||||
waitFor(
|
||||
continueWhen = {
|
||||
wrapWithTimeout(timeout) {
|
||||
suspendCoroutine<Unit> { continuation ->
|
||||
val timelineListener = object : Timeline.Listener {
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val allSentMessages = snapshot
|
||||
.filter { it.root.sendState == SendState.SYNCED }
|
||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val allSentMessages = snapshot
|
||||
.filter { it.root.sendState == SendState.SYNCED }
|
||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
|
||||
|
||||
val hasSyncedAllBatchedMessages = allSentMessages
|
||||
.map {
|
||||
it.root.getClearContent().toModel<MessageContent>()?.body
|
||||
val hasSyncedAllBatchedMessages = allSentMessages
|
||||
.map {
|
||||
it.root.getClearContent().toModel<MessageContent>()?.body
|
||||
}
|
||||
.containsAll(batchedMessages)
|
||||
|
||||
if (allSentMessages.size == count) {
|
||||
sentEvents.addAll(allSentMessages)
|
||||
}
|
||||
if (hasSyncedAllBatchedMessages) {
|
||||
timeline.removeListener(this)
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
.containsAll(batchedMessages)
|
||||
|
||||
if (allSentMessages.size == count) {
|
||||
sentEvents.addAll(allSentMessages)
|
||||
timeline.addListener(timelineListener)
|
||||
}
|
||||
}
|
||||
if (hasSyncedAllBatchedMessages) {
|
||||
timeline.removeListener(this)
|
||||
latch.countDown()
|
||||
},
|
||||
action = {
|
||||
batchedMessages.forEach { formattedMessage ->
|
||||
if (rootThreadEventId != null) {
|
||||
room.relationService().replyInThread(
|
||||
rootThreadEventId = rootThreadEventId,
|
||||
replyInThreadText = formattedMessage
|
||||
)
|
||||
} else {
|
||||
room.sendService().sendTextMessage(formattedMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
timeline.addListener(timelineListener)
|
||||
}
|
||||
)
|
||||
}
|
||||
return sentEvents
|
||||
}
|
||||
|
||||
fun waitForAndAcceptInviteInRoom(otherSession: Session, roomID: String) {
|
||||
waitWithLatch { latch ->
|
||||
retryPeriodicallyWithLatch(latch) {
|
||||
val roomSummary = otherSession.getRoomSummary(roomID)
|
||||
(roomSummary != null && roomSummary.membership == Membership.INVITE).also {
|
||||
if (it) {
|
||||
Log.v("# TEST", "${otherSession.myUserId} can see the invite")
|
||||
}
|
||||
suspend fun waitForAndAcceptInviteInRoom(otherSession: Session, roomID: String) {
|
||||
retryPeriodically {
|
||||
val roomSummary = otherSession.getRoomSummary(roomID)
|
||||
(roomSummary != null && roomSummary.membership == Membership.INVITE).also {
|
||||
if (it) {
|
||||
Log.v("# TEST", "${otherSession.myUserId} can see the invite")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// not sure why it's taking so long :/
|
||||
runBlockingTest(90_000) {
|
||||
wrapWithTimeout(90_000) {
|
||||
Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $roomID")
|
||||
try {
|
||||
otherSession.roomService().joinRoom(roomID)
|
||||
|
@ -273,11 +257,9 @@ class CommonTestHelper internal constructor(context: Context) {
|
|||
}
|
||||
|
||||
Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...")
|
||||
waitWithLatch {
|
||||
retryPeriodicallyWithLatch(it) {
|
||||
val roomSummary = otherSession.getRoomSummary(roomID)
|
||||
roomSummary != null && roomSummary.membership == Membership.JOIN
|
||||
}
|
||||
retryPeriodically {
|
||||
val roomSummary = otherSession.getRoomSummary(roomID)
|
||||
roomSummary != null && roomSummary.membership == Membership.JOIN
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -287,7 +269,7 @@ class CommonTestHelper internal constructor(context: Context) {
|
|||
* @param message the message to send
|
||||
* @param numberOfMessages the number of time the message will be sent
|
||||
*/
|
||||
fun replyInThreadMessage(
|
||||
suspend fun replyInThreadMessage(
|
||||
room: Room,
|
||||
message: String,
|
||||
numberOfMessages: Int,
|
||||
|
@ -305,15 +287,7 @@ class CommonTestHelper internal constructor(context: Context) {
|
|||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
/**
|
||||
* Creates a unique account
|
||||
*
|
||||
* @param userNamePrefix the user name prefix
|
||||
* @param password the password
|
||||
* @param testParams test params about the session
|
||||
* @return the session associated with the newly created account
|
||||
*/
|
||||
private fun createAccount(
|
||||
private suspend fun createAccount(
|
||||
userNamePrefix: String,
|
||||
password: String,
|
||||
testParams: SessionTestParams
|
||||
|
@ -331,15 +305,7 @@ class CommonTestHelper internal constructor(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs into an existing account
|
||||
*
|
||||
* @param userId the userId to log in
|
||||
* @param password the password to log in
|
||||
* @param testParams test params about the session
|
||||
* @return the session associated with the existing account
|
||||
*/
|
||||
fun logIntoAccount(
|
||||
suspend fun logIntoAccount(
|
||||
userId: String,
|
||||
password: String,
|
||||
testParams: SessionTestParams
|
||||
|
@ -351,32 +317,25 @@ class CommonTestHelper internal constructor(context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an account and a dedicated session
|
||||
*
|
||||
* @param userName the account username
|
||||
* @param password the password
|
||||
* @param sessionTestParams parameters for the test
|
||||
*/
|
||||
private fun createAccountAndSync(
|
||||
private suspend fun createAccountAndSync(
|
||||
userName: String,
|
||||
password: String,
|
||||
sessionTestParams: SessionTestParams
|
||||
): Session {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
runBlockingTest {
|
||||
wrapWithTimeout(TestConstants.timeOutMillis) {
|
||||
matrix.authenticationService.getLoginFlow(hs)
|
||||
}
|
||||
|
||||
runBlockingTest(timeout = 60_000) {
|
||||
wrapWithTimeout(60_000L) {
|
||||
matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.createAccount(userName, password, null)
|
||||
}
|
||||
|
||||
// Perform dummy step
|
||||
val registrationResult = runBlockingTest(timeout = 60_000) {
|
||||
val registrationResult = wrapWithTimeout(timeout = 60_000) {
|
||||
matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.dummy()
|
||||
|
@ -391,29 +350,14 @@ class CommonTestHelper internal constructor(context: Context) {
|
|||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
* Start an account login
|
||||
*
|
||||
* @param userName the account username
|
||||
* @param password the password
|
||||
* @param sessionTestParams session test params
|
||||
*/
|
||||
private fun logAccountAndSync(
|
||||
userName: String,
|
||||
password: String,
|
||||
sessionTestParams: SessionTestParams
|
||||
): Session {
|
||||
private suspend fun logAccountAndSync(userName: String, password: String, sessionTestParams: SessionTestParams): Session {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
runBlockingTest {
|
||||
matrix.authenticationService.getLoginFlow(hs)
|
||||
}
|
||||
matrix.authenticationService.getLoginFlow(hs)
|
||||
|
||||
val session = runBlockingTest {
|
||||
matrix.authenticationService
|
||||
.getLoginWizard()
|
||||
.login(userName, password, "myDevice")
|
||||
}
|
||||
val session = matrix.authenticationService
|
||||
.getLoginWizard()
|
||||
.login(userName, password, "myDevice")
|
||||
session.open()
|
||||
if (sessionTestParams.withInitialSync) {
|
||||
syncSession(session)
|
||||
|
@ -428,25 +372,21 @@ class CommonTestHelper internal constructor(context: Context) {
|
|||
* @param userName the account username
|
||||
* @param password the password
|
||||
*/
|
||||
fun logAccountWithError(
|
||||
suspend fun logAccountWithError(
|
||||
userName: String,
|
||||
password: String
|
||||
): Throwable {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
runBlockingTest {
|
||||
matrix.authenticationService.getLoginFlow(hs)
|
||||
}
|
||||
matrix.authenticationService.getLoginFlow(hs)
|
||||
|
||||
var requestFailure: Throwable? = null
|
||||
runBlockingTest {
|
||||
try {
|
||||
matrix.authenticationService
|
||||
.getLoginWizard()
|
||||
.login(userName, password, "myDevice")
|
||||
} catch (failure: Throwable) {
|
||||
requestFailure = failure
|
||||
}
|
||||
try {
|
||||
matrix.authenticationService
|
||||
.getLoginWizard()
|
||||
.login(userName, password, "myDevice")
|
||||
} catch (failure: Throwable) {
|
||||
requestFailure = failure
|
||||
}
|
||||
|
||||
assertNotNull(requestFailure)
|
||||
|
@ -482,65 +422,48 @@ class CommonTestHelper internal constructor(context: Context) {
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) {
|
||||
while (true) {
|
||||
try {
|
||||
delay(1000)
|
||||
} catch (ex: CancellationException) {
|
||||
// the job was canceled, just stop
|
||||
return
|
||||
}
|
||||
if (condition()) {
|
||||
latch.countDown()
|
||||
return
|
||||
suspend fun retryPeriodically(timeout: Long = TestConstants.timeOutMillis, predicate: suspend () -> Boolean) {
|
||||
wrapWithTimeout(timeout) {
|
||||
while (!predicate()) {
|
||||
runBlocking { delay(500) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun waitWithLatch(timeout: Long? = TestConstants.timeOutMillis, dispatcher: CoroutineDispatcher = Dispatchers.Main, block: suspend (CountDownLatch) -> Unit) {
|
||||
val latch = CountDownLatch(1)
|
||||
val job = coroutineScope.launch(dispatcher) {
|
||||
block(latch)
|
||||
}
|
||||
await(latch, timeout, job)
|
||||
}
|
||||
|
||||
fun <T> runBlockingTest(timeout: Long = TestConstants.timeOutMillis, block: suspend () -> T): T {
|
||||
return runBlocking {
|
||||
withTimeout(timeout) {
|
||||
block()
|
||||
suspend fun <T> waitForCallback(timeout: Long = TestConstants.timeOutMillis, block: (MatrixCallback<T>) -> Unit): T {
|
||||
return wrapWithTimeout(timeout) {
|
||||
suspendCoroutine { continuation ->
|
||||
val callback = object : MatrixCallback<T> {
|
||||
override fun onSuccess(data: T) {
|
||||
continuation.resume(data)
|
||||
}
|
||||
}
|
||||
block(callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transform a method with a MatrixCallback to a synchronous method
|
||||
inline fun <reified T> doSync(timeout: Long? = TestConstants.timeOutMillis, block: (MatrixCallback<T>) -> Unit): T {
|
||||
val lock = CountDownLatch(1)
|
||||
var result: T? = null
|
||||
|
||||
val callback = object : TestMatrixCallback<T>(lock) {
|
||||
override fun onSuccess(data: T) {
|
||||
result = data
|
||||
super.onSuccess(data)
|
||||
suspend fun <T> waitForCallbackError(timeout: Long = TestConstants.timeOutMillis, block: (MatrixCallback<T>) -> Unit): Throwable {
|
||||
return wrapWithTimeout(timeout) {
|
||||
suspendCoroutine { continuation ->
|
||||
val callback = object : MatrixCallback<T> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
continuation.resume(failure)
|
||||
}
|
||||
}
|
||||
block(callback)
|
||||
}
|
||||
}
|
||||
|
||||
block.invoke(callback)
|
||||
|
||||
await(lock, timeout)
|
||||
|
||||
assertNotNull(result)
|
||||
return result!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all provided sessions
|
||||
*/
|
||||
fun Iterable<Session>.signOutAndClose() = forEach { signOutAndClose(it) }
|
||||
suspend fun Iterable<Session>.signOutAndClose() = forEach { signOutAndClose(it) }
|
||||
|
||||
fun signOutAndClose(session: Session) {
|
||||
suspend fun signOutAndClose(session: Session) {
|
||||
trackedSessions.remove(session)
|
||||
runBlockingTest(timeout = 60_000) {
|
||||
wrapWithTimeout(timeout = 60_000L) {
|
||||
session.signOutService().signOut(true)
|
||||
}
|
||||
// no need signout will close
|
||||
|
|
|
@ -32,7 +32,7 @@ data class CryptoTestData(
|
|||
val thirdSession: Session?
|
||||
get() = sessions.getOrNull(2)
|
||||
|
||||
fun cleanUp(testHelper: CommonTestHelper) {
|
||||
suspend fun cleanUp(testHelper: CommonTestHelper) {
|
||||
sessions.forEach {
|
||||
testHelper.signOutAndClose(it)
|
||||
}
|
||||
|
|
|
@ -16,19 +16,15 @@
|
|||
|
||||
package org.matrix.android.sdk.common
|
||||
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Observer
|
||||
import org.amshove.kluent.fail
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
@ -46,22 +42,16 @@ import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerific
|
|||
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner
|
||||
import org.matrix.android.sdk.api.session.securestorage.KeyRef
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.awaitCallback
|
||||
import org.matrix.android.sdk.api.util.toBase64NoPadding
|
||||
import java.util.UUID
|
||||
import kotlin.coroutines.Continuation
|
||||
|
@ -77,30 +67,19 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
/**
|
||||
* @return alice session
|
||||
*/
|
||||
fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true, roomHistoryVisibility: RoomHistoryVisibility? = null): CryptoTestData {
|
||||
suspend fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true, roomHistoryVisibility: RoomHistoryVisibility? = null): CryptoTestData {
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
|
||||
val roomId = testHelper.runBlockingTest {
|
||||
aliceSession.roomService().createRoom(CreateRoomParams().apply {
|
||||
historyVisibility = roomHistoryVisibility
|
||||
name = "MyRoom"
|
||||
})
|
||||
}
|
||||
val roomId = aliceSession.roomService().createRoom(CreateRoomParams().apply {
|
||||
historyVisibility = roomHistoryVisibility
|
||||
name = "MyRoom"
|
||||
})
|
||||
if (encryptedRoom) {
|
||||
testHelper.waitWithLatch { latch ->
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
room.roomCryptoService().enableEncryption()
|
||||
val roomSummaryLive = room.getRoomSummaryLive()
|
||||
val roomSummaryObserver = object : Observer<Optional<RoomSummary>> {
|
||||
override fun onChanged(roomSummary: Optional<RoomSummary>) {
|
||||
if (roomSummary.getOrNull()?.isEncrypted.orFalse()) {
|
||||
roomSummaryLive.removeObserver(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
roomSummaryLive.observeForever(roomSummaryObserver)
|
||||
}
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
waitFor(
|
||||
continueWhen = { room.onMain { getRoomSummaryLive() }.first { it.getOrNull()?.isEncrypted.orFalse() } },
|
||||
action = { room.roomCryptoService().enableEncryption() }
|
||||
)
|
||||
}
|
||||
return CryptoTestData(roomId, listOf(aliceSession))
|
||||
}
|
||||
|
@ -108,7 +87,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
/**
|
||||
* @return alice and bob sessions
|
||||
*/
|
||||
fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true, roomHistoryVisibility: RoomHistoryVisibility? = null): CryptoTestData {
|
||||
suspend fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true, roomHistoryVisibility: RoomHistoryVisibility? = null): CryptoTestData {
|
||||
val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom, roomHistoryVisibility)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
@ -117,36 +96,23 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
|
||||
val bobSession = testHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
val bobRoomSummariesLive = bobSession.roomService().getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
val newRoomObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
if (t?.isNotEmpty() == true) {
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||
aliceRoom.membershipService().invite(bobSession.myUserId)
|
||||
}
|
||||
waitFor(
|
||||
continueWhen = { bobSession.roomService().onMain { getRoomSummariesLive(roomSummaryQueryParams { }) }.first { it.isNotEmpty() } },
|
||||
action = { aliceRoom.membershipService().invite(bobSession.myUserId) }
|
||||
)
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
val bobRoomSummariesLive = bobSession.roomService().getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
val roomJoinedObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
if (bobSession.getRoom(aliceRoomId)
|
||||
?.membershipService()
|
||||
?.getRoomMember(bobSession.myUserId)
|
||||
?.membership == Membership.JOIN) {
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
latch.countDown()
|
||||
waitFor(
|
||||
continueWhen = {
|
||||
bobSession.roomService().onMain { getRoomSummariesLive(roomSummaryQueryParams { }) }.first {
|
||||
bobSession.getRoom(aliceRoomId)
|
||||
?.membershipService()
|
||||
?.getRoomMember(bobSession.myUserId)
|
||||
?.membership == Membership.JOIN
|
||||
}
|
||||
}
|
||||
}
|
||||
bobRoomSummariesLive.observeForever(roomJoinedObserver)
|
||||
bobSession.roomService().joinRoom(aliceRoomId)
|
||||
}
|
||||
},
|
||||
action = { bobSession.roomService().joinRoom(aliceRoomId) }
|
||||
)
|
||||
|
||||
// Ensure bob can send messages to the room
|
||||
// val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||
// assertNotNull(roomFromBobPOV.powerLevels)
|
||||
|
@ -155,46 +121,10 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
return CryptoTestData(aliceRoomId, listOf(aliceSession, bobSession))
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Alice, Bob and Sam session
|
||||
*/
|
||||
fun doE2ETestWithAliceAndBobAndSamInARoom(): CryptoTestData {
|
||||
val cryptoTestData = doE2ETestWithAliceAndBobInARoom()
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
||||
val room = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
||||
val samSession = createSamAccountAndInviteToTheRoom(room)
|
||||
|
||||
// wait the initial sync
|
||||
SystemClock.sleep(1000)
|
||||
|
||||
return CryptoTestData(aliceRoomId, listOf(aliceSession, cryptoTestData.secondSession!!, samSession))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Sam account and invite him in the room. He will accept the invitation
|
||||
* @Return Sam session
|
||||
*/
|
||||
fun createSamAccountAndInviteToTheRoom(room: Room): Session {
|
||||
val samSession = testHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
room.membershipService().invite(samSession.myUserId, null)
|
||||
}
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
samSession.roomService().joinRoom(room.roomId, null, emptyList())
|
||||
}
|
||||
|
||||
return samSession
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Alice and Bob sessions
|
||||
*/
|
||||
fun doE2ETestWithAliceAndBobInARoomWithEncryptedMessages(): CryptoTestData {
|
||||
suspend fun doE2ETestWithAliceAndBobInARoomWithEncryptedMessages(): CryptoTestData {
|
||||
val cryptoTestData = doE2ETestWithAliceAndBobInARoom()
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
@ -235,49 +165,20 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
return cryptoTestData
|
||||
}
|
||||
|
||||
private fun ensureEventReceived(roomId: String, eventId: String, session: Session, andCanDecrypt: Boolean) {
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timeLineEvent = session.getRoom(roomId)?.timelineService()?.getTimelineEvent(eventId)
|
||||
if (andCanDecrypt) {
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
} else {
|
||||
timeLineEvent != null
|
||||
}
|
||||
private suspend fun ensureEventReceived(roomId: String, eventId: String, session: Session, andCanDecrypt: Boolean) {
|
||||
testHelper.retryPeriodically {
|
||||
val timeLineEvent = session.getRoom(roomId)?.timelineService()?.getTimelineEvent(eventId)
|
||||
if (andCanDecrypt) {
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
} else {
|
||||
timeLineEvent != null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkEncryptedEvent(event: Event, roomId: String, clearMessage: String, senderSession: Session) {
|
||||
assertEquals(EventType.ENCRYPTED, event.type)
|
||||
assertNotNull(event.content)
|
||||
|
||||
val eventWireContent = event.content.toContent()
|
||||
assertNotNull(eventWireContent)
|
||||
|
||||
assertNull(eventWireContent["body"])
|
||||
assertEquals(MXCRYPTO_ALGORITHM_MEGOLM, eventWireContent["algorithm"])
|
||||
|
||||
assertNotNull(eventWireContent["ciphertext"])
|
||||
assertNotNull(eventWireContent["session_id"])
|
||||
assertNotNull(eventWireContent["sender_key"])
|
||||
|
||||
assertEquals(senderSession.sessionParams.deviceId, eventWireContent["device_id"])
|
||||
|
||||
assertNotNull(event.eventId)
|
||||
assertEquals(roomId, event.roomId)
|
||||
assertEquals(EventType.MESSAGE, event.getClearType())
|
||||
// TODO assertTrue(event.getAge() < 10000)
|
||||
|
||||
val eventContent = event.toContent()
|
||||
assertNotNull(eventContent)
|
||||
assertEquals(clearMessage, eventContent["body"])
|
||||
assertEquals(senderSession.myUserId, event.senderId)
|
||||
}
|
||||
|
||||
fun createFakeMegolmBackupAuthData(): MegolmBackupAuthData {
|
||||
private fun createFakeMegolmBackupAuthData(): MegolmBackupAuthData {
|
||||
return MegolmBackupAuthData(
|
||||
publicKey = "abcdefg",
|
||||
signatures = mapOf("something" to mapOf("ed25519:something" to "hijklmnop"))
|
||||
|
@ -292,44 +193,35 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
)
|
||||
}
|
||||
|
||||
fun createDM(alice: Session, bob: Session): String {
|
||||
var roomId: String = ""
|
||||
testHelper.waitWithLatch { latch ->
|
||||
roomId = alice.roomService().createDirectRoom(bob.myUserId)
|
||||
val bobRoomSummariesLive = bob.roomService().getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
val newRoomObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
if (t?.any { it.roomId == roomId }.orFalse()) {
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||
}
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
val bobRoomSummariesLive = bob.roomService().getRoomSummariesLive(roomSummaryQueryParams { })
|
||||
val newRoomObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(t: List<RoomSummary>?) {
|
||||
if (bob.getRoom(roomId)
|
||||
?.membershipService()
|
||||
?.getRoomMember(bob.myUserId)
|
||||
?.membership == Membership.JOIN) {
|
||||
bobRoomSummariesLive.removeObserver(this)
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
bobRoomSummariesLive.observeForever(newRoomObserver)
|
||||
bob.roomService().joinRoom(roomId)
|
||||
}
|
||||
suspend fun createDM(alice: Session, bob: Session): String {
|
||||
var roomId = ""
|
||||
waitFor(
|
||||
continueWhen = {
|
||||
bob.roomService()
|
||||
.onMain { getRoomSummariesLive(roomSummaryQueryParams { }) }
|
||||
.first { it.any { it.roomId == roomId }.orFalse() }
|
||||
},
|
||||
action = { roomId = alice.roomService().createDirectRoom(bob.myUserId) }
|
||||
)
|
||||
|
||||
waitFor(
|
||||
continueWhen = {
|
||||
bob.roomService()
|
||||
.onMain { getRoomSummariesLive(roomSummaryQueryParams { }) }
|
||||
.first {
|
||||
bob.getRoom(roomId)
|
||||
?.membershipService()
|
||||
?.getRoomMember(bob.myUserId)
|
||||
?.membership == Membership.JOIN
|
||||
}
|
||||
},
|
||||
action = { bob.roomService().joinRoom(roomId) }
|
||||
)
|
||||
return roomId
|
||||
}
|
||||
|
||||
fun initializeCrossSigning(session: Session) {
|
||||
testHelper.doSync<Unit> {
|
||||
suspend fun initializeCrossSigning(session: Session) {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
session.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
|
@ -350,57 +242,55 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
/**
|
||||
* Initialize cross-signing, set up megolm backup and save all in 4S
|
||||
*/
|
||||
fun bootstrapSecurity(session: Session) {
|
||||
suspend fun bootstrapSecurity(session: Session) {
|
||||
initializeCrossSigning(session)
|
||||
val ssssService = session.sharedSecretStorageService()
|
||||
testHelper.runBlockingTest {
|
||||
val keyInfo = ssssService.generateKey(
|
||||
UUID.randomUUID().toString(),
|
||||
null,
|
||||
"ssss_key",
|
||||
EmptyKeySigner()
|
||||
)
|
||||
ssssService.setDefaultKey(keyInfo.keyId)
|
||||
val keyInfo = ssssService.generateKey(
|
||||
UUID.randomUUID().toString(),
|
||||
null,
|
||||
"ssss_key",
|
||||
EmptyKeySigner()
|
||||
)
|
||||
ssssService.setDefaultKey(keyInfo.keyId)
|
||||
|
||||
ssssService.storeSecret(
|
||||
MASTER_KEY_SSSS_NAME,
|
||||
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master!!,
|
||||
listOf(KeyRef(keyInfo.keyId, keyInfo.keySpec))
|
||||
)
|
||||
|
||||
ssssService.storeSecret(
|
||||
SELF_SIGNING_KEY_SSSS_NAME,
|
||||
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned!!,
|
||||
listOf(KeyRef(keyInfo.keyId, keyInfo.keySpec))
|
||||
)
|
||||
|
||||
ssssService.storeSecret(
|
||||
USER_SIGNING_KEY_SSSS_NAME,
|
||||
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user!!,
|
||||
listOf(KeyRef(keyInfo.keyId, keyInfo.keySpec))
|
||||
)
|
||||
|
||||
// set up megolm backup
|
||||
val creationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> {
|
||||
session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
|
||||
}
|
||||
val version = testHelper.waitForCallback<KeysVersion> {
|
||||
session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
|
||||
}
|
||||
// Save it for gossiping
|
||||
session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
|
||||
|
||||
extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret ->
|
||||
ssssService.storeSecret(
|
||||
MASTER_KEY_SSSS_NAME,
|
||||
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master!!,
|
||||
KEYBACKUP_SECRET_SSSS_NAME,
|
||||
secret,
|
||||
listOf(KeyRef(keyInfo.keyId, keyInfo.keySpec))
|
||||
)
|
||||
|
||||
ssssService.storeSecret(
|
||||
SELF_SIGNING_KEY_SSSS_NAME,
|
||||
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned!!,
|
||||
listOf(KeyRef(keyInfo.keyId, keyInfo.keySpec))
|
||||
)
|
||||
|
||||
ssssService.storeSecret(
|
||||
USER_SIGNING_KEY_SSSS_NAME,
|
||||
session.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user!!,
|
||||
listOf(KeyRef(keyInfo.keyId, keyInfo.keySpec))
|
||||
)
|
||||
|
||||
// set up megolm backup
|
||||
val creationInfo = awaitCallback<MegolmBackupCreationInfo> {
|
||||
session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
|
||||
}
|
||||
val version = awaitCallback<KeysVersion> {
|
||||
session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
|
||||
}
|
||||
// Save it for gossiping
|
||||
session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
|
||||
|
||||
extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret ->
|
||||
ssssService.storeSecret(
|
||||
KEYBACKUP_SECRET_SSSS_NAME,
|
||||
secret,
|
||||
listOf(KeyRef(keyInfo.keyId, keyInfo.keySpec))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun verifySASCrossSign(alice: Session, bob: Session, roomId: String) {
|
||||
suspend fun verifySASCrossSign(alice: Session, bob: Session, roomId: String) {
|
||||
assertTrue(alice.cryptoService().crossSigningService().canCrossSign())
|
||||
assertTrue(bob.cryptoService().crossSigningService().canCrossSign())
|
||||
|
||||
|
@ -415,12 +305,10 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
roomId = roomId
|
||||
).transactionId
|
||||
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
bobVerificationService.getExistingVerificationRequests(alice.myUserId).firstOrNull {
|
||||
it.requestInfo?.fromDevice == alice.sessionParams.deviceId
|
||||
} != null
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
bobVerificationService.getExistingVerificationRequests(alice.myUserId).firstOrNull {
|
||||
it.requestInfo?.fromDevice == alice.sessionParams.deviceId
|
||||
} != null
|
||||
}
|
||||
val incomingRequest = bobVerificationService.getExistingVerificationRequests(alice.myUserId).first {
|
||||
it.requestInfo?.fromDevice == alice.sessionParams.deviceId
|
||||
|
@ -429,16 +317,14 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
|
||||
var requestID: String? = null
|
||||
// wait for it to be readied
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
val outgoingRequest = aliceVerificationService.getExistingVerificationRequests(bob.myUserId)
|
||||
.firstOrNull { it.localId == localId }
|
||||
if (outgoingRequest?.isReady == true) {
|
||||
requestID = outgoingRequest.transactionId!!
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val outgoingRequest = aliceVerificationService.getExistingVerificationRequests(bob.myUserId)
|
||||
.firstOrNull { it.localId == localId }
|
||||
if (outgoingRequest?.isReady == true) {
|
||||
requestID = outgoingRequest.transactionId!!
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -454,23 +340,19 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
var alicePovTx: OutgoingSasVerificationTransaction? = null
|
||||
var bobPovTx: IncomingSasVerificationTransaction? = null
|
||||
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID!!) as? OutgoingSasVerificationTransaction
|
||||
Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}")
|
||||
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID!!) as? OutgoingSasVerificationTransaction
|
||||
Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}")
|
||||
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
// wait for alice to get the ready
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID!!) as? IncomingSasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
|
||||
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
||||
bobPovTx?.performAccept()
|
||||
}
|
||||
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
||||
testHelper.retryPeriodically {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID!!) as? IncomingSasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
|
||||
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
||||
bobPovTx?.performAccept()
|
||||
}
|
||||
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
|
||||
assertEquals("SAS code do not match", alicePovTx!!.getDecimalCodeRepresentation(), bobPovTx!!.getDecimalCodeRepresentation())
|
||||
|
@ -478,38 +360,30 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
bobPovTx!!.userHasVerifiedShortCode()
|
||||
alicePovTx!!.userHasVerifiedShortCode()
|
||||
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
|
||||
}
|
||||
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
|
||||
}
|
||||
}
|
||||
|
||||
fun doE2ETestWithManyMembers(numberOfMembers: Int): CryptoTestData {
|
||||
suspend fun doE2ETestWithManyMembers(numberOfMembers: Int): CryptoTestData {
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomId = testHelper.runBlockingTest {
|
||||
aliceSession.roomService().createRoom(CreateRoomParams().apply { name = "MyRoom" })
|
||||
}
|
||||
val roomId = aliceSession.roomService().createRoom(CreateRoomParams().apply { name = "MyRoom" })
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
room.roomCryptoService().enableEncryption()
|
||||
}
|
||||
room.roomCryptoService().enableEncryption()
|
||||
|
||||
val sessions = mutableListOf(aliceSession)
|
||||
for (index in 1 until numberOfMembers) {
|
||||
val session = testHelper.createAccount("User_$index", defaultSessionParams)
|
||||
testHelper.runBlockingTest(timeout = 600_000) { room.membershipService().invite(session.myUserId, null) }
|
||||
room.membershipService().invite(session.myUserId, null)
|
||||
println("TEST -> " + session.myUserId + " invited")
|
||||
testHelper.runBlockingTest { session.roomService().joinRoom(room.roomId, null, emptyList()) }
|
||||
session.roomService().joinRoom(room.roomId, null, emptyList())
|
||||
println("TEST -> " + session.myUserId + " joined")
|
||||
sessions.add(session)
|
||||
}
|
||||
|
@ -517,47 +391,41 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
return CryptoTestData(roomId, sessions)
|
||||
}
|
||||
|
||||
fun ensureCanDecrypt(sentEventIds: List<String>, session: Session, e2eRoomID: String, messagesText: List<String>) {
|
||||
suspend fun ensureCanDecrypt(sentEventIds: List<String>, session: Session, e2eRoomID: String, messagesText: List<String>) {
|
||||
sentEventIds.forEachIndexed { index, sentEventId ->
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val event = session.getRoom(e2eRoomID)!!.timelineService().getTimelineEvent(sentEventId)!!.root
|
||||
testHelper.runBlockingTest {
|
||||
try {
|
||||
session.cryptoService().decryptEvent(event, "").let { result ->
|
||||
event.mxDecryptionResult = OlmDecryptionResult(
|
||||
payload = result.clearEvent,
|
||||
senderKey = result.senderCurve25519Key,
|
||||
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
||||
)
|
||||
}
|
||||
} catch (error: MXCryptoError) {
|
||||
// nop
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val event = session.getRoom(e2eRoomID)!!.timelineService().getTimelineEvent(sentEventId)!!.root
|
||||
try {
|
||||
session.cryptoService().decryptEvent(event, "").let { result ->
|
||||
event.mxDecryptionResult = OlmDecryptionResult(
|
||||
payload = result.clearEvent,
|
||||
senderKey = result.senderCurve25519Key,
|
||||
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
||||
)
|
||||
}
|
||||
Log.v("TEST", "ensureCanDecrypt ${event.getClearType()} is ${event.getClearContent()}")
|
||||
event.getClearType() == EventType.MESSAGE &&
|
||||
messagesText[index] == event.getClearContent()?.toModel<MessageContent>()?.body
|
||||
} catch (error: MXCryptoError) {
|
||||
// nop
|
||||
}
|
||||
Log.v("TEST", "ensureCanDecrypt ${event.getClearType()} is ${event.getClearContent()}")
|
||||
event.getClearType() == EventType.MESSAGE &&
|
||||
messagesText[index] == event.getClearContent()?.toModel<MessageContent>()?.body
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ensureCannotDecrypt(sentEventIds: List<String>, session: Session, e2eRoomID: String, expectedError: MXCryptoError.ErrorType? = null) {
|
||||
suspend fun ensureCannotDecrypt(sentEventIds: List<String>, session: Session, e2eRoomID: String, expectedError: MXCryptoError.ErrorType? = null) {
|
||||
sentEventIds.forEach { sentEventId ->
|
||||
val event = session.getRoom(e2eRoomID)!!.timelineService().getTimelineEvent(sentEventId)!!.root
|
||||
testHelper.runBlockingTest {
|
||||
try {
|
||||
session.cryptoService().decryptEvent(event, "")
|
||||
fail("Should not be able to decrypt event")
|
||||
} catch (error: MXCryptoError) {
|
||||
val errorType = (error as? MXCryptoError.Base)?.errorType
|
||||
if (expectedError == null) {
|
||||
assertNotNull(errorType)
|
||||
} else {
|
||||
assertEquals("Unexpected reason", expectedError, errorType)
|
||||
}
|
||||
try {
|
||||
session.cryptoService().decryptEvent(event, "")
|
||||
fail("Should not be able to decrypt event")
|
||||
} catch (error: MXCryptoError) {
|
||||
val errorType = (error as? MXCryptoError.Base)?.errorType
|
||||
if (expectedError == null) {
|
||||
assertNotNull(errorType)
|
||||
} else {
|
||||
assertEquals("Unexpected reason", expectedError, errorType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.common
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
suspend fun <T, R> T.onMain(block: T.() -> R): R {
|
||||
return withContext(Dispatchers.Main) {
|
||||
block(this@onMain)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> LiveData<T>.first(timeout: Long = TestConstants.timeOutMillis, predicate: (T) -> Boolean): T {
|
||||
return wrapWithTimeout(timeout) {
|
||||
withContext(Dispatchers.Main) {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
val observer = object : Observer<T> {
|
||||
override fun onChanged(data: T) {
|
||||
if (predicate(data)) {
|
||||
removeObserver(this)
|
||||
continuation.resume(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
observeForever(observer)
|
||||
continuation.invokeOnCancellation { removeObserver(observer) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> waitFor(continueWhen: suspend () -> T, action: suspend () -> Unit) {
|
||||
coroutineScope {
|
||||
val deferred = async { continueWhen() }
|
||||
action()
|
||||
deferred.await()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> wrapWithTimeout(timeout: Long = TestConstants.timeOutMillis, block: suspend () -> T): T {
|
||||
val deferred = coroutineScope {
|
||||
async { block() }
|
||||
}
|
||||
return withTimeout(timeout) { deferred.await() }
|
||||
}
|
|
@ -46,30 +46,26 @@ class DecryptRedactedEventTest : InstrumentedTest {
|
|||
roomALicePOV.sendService().redactEvent(timelineEvent.root, redactionReason)
|
||||
|
||||
// get the event from bob
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)?.root?.isRedacted() == true
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)?.root?.isRedacted() == true
|
||||
}
|
||||
|
||||
val eventBobPov = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)!!
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
try {
|
||||
val result = bobSession.cryptoService().decryptEvent(eventBobPov.root, "")
|
||||
Assert.assertEquals(
|
||||
"Unexpected redacted reason",
|
||||
redactionReason,
|
||||
result.clearEvent.toModel<Event>()?.unsignedData?.redactedEvent?.content?.get("reason")
|
||||
)
|
||||
Assert.assertEquals(
|
||||
"Unexpected Redacted event id",
|
||||
timelineEvent.eventId,
|
||||
result.clearEvent.toModel<Event>()?.unsignedData?.redactedEvent?.redacts
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
Assert.fail("Should not throw when decrypting a redacted event")
|
||||
}
|
||||
try {
|
||||
val result = bobSession.cryptoService().decryptEvent(eventBobPov.root, "")
|
||||
Assert.assertEquals(
|
||||
"Unexpected redacted reason",
|
||||
redactionReason,
|
||||
result.clearEvent.toModel<Event>()?.unsignedData?.redactedEvent?.content?.get("reason")
|
||||
)
|
||||
Assert.assertEquals(
|
||||
"Unexpected Redacted event id",
|
||||
timelineEvent.eventId,
|
||||
result.clearEvent.toModel<Event>()?.unsignedData?.redactedEvent?.redacts
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
Assert.fail("Should not throw when decrypting a redacted event")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,6 @@ import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
|||
import org.matrix.android.sdk.common.CryptoTestData
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import org.matrix.android.sdk.common.TestMatrixCallback
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
|
@ -57,18 +56,14 @@ class E2EShareKeysConfigTest : InstrumentedTest {
|
|||
fun ensureKeysAreNotSharedIfOptionDisabled() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
|
||||
aliceSession.cryptoService().enableShareKeyOnInvite(false)
|
||||
val roomId = commonTestHelper.runBlockingTest {
|
||||
aliceSession.roomService().createRoom(CreateRoomParams().apply {
|
||||
historyVisibility = RoomHistoryVisibility.SHARED
|
||||
name = "MyRoom"
|
||||
enableEncryption()
|
||||
})
|
||||
}
|
||||
val roomId = aliceSession.roomService().createRoom(CreateRoomParams().apply {
|
||||
historyVisibility = RoomHistoryVisibility.SHARED
|
||||
name = "MyRoom"
|
||||
enableEncryption()
|
||||
})
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession.roomService().getRoomSummary(roomId)?.isEncrypted == true
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
aliceSession.roomService().getRoomSummary(roomId)?.isEncrypted == true
|
||||
}
|
||||
val roomAlice = aliceSession.roomService().getRoom(roomId)!!
|
||||
|
||||
|
@ -81,9 +76,7 @@ class E2EShareKeysConfigTest : InstrumentedTest {
|
|||
val bobSession = commonTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(withInitialSync = true))
|
||||
|
||||
// Let alice invite bob
|
||||
commonTestHelper.runBlockingTest {
|
||||
roomAlice.membershipService().invite(bobSession.myUserId)
|
||||
}
|
||||
roomAlice.membershipService().invite(bobSession.myUserId)
|
||||
|
||||
commonTestHelper.waitForAndAcceptInviteInRoom(bobSession, roomId)
|
||||
|
||||
|
@ -114,9 +107,7 @@ class E2EShareKeysConfigTest : InstrumentedTest {
|
|||
val samSession = commonTestHelper.createAccount(TestConstants.USER_SAM, SessionTestParams(withInitialSync = true))
|
||||
|
||||
// Let alice invite sam
|
||||
commonTestHelper.runBlockingTest {
|
||||
roomAlice.membershipService().invite(samSession.myUserId)
|
||||
}
|
||||
roomAlice.membershipService().invite(samSession.myUserId)
|
||||
|
||||
commonTestHelper.waitForAndAcceptInviteInRoom(samSession, roomId)
|
||||
|
||||
|
@ -135,7 +126,7 @@ class E2EShareKeysConfigTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun ifSharingDisabledOnAliceSideBobShouldNotShareAliceHistoty() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
fun ifSharingDisabledOnAliceSideBobShouldNotShareAliceHistory() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(roomHistoryVisibility = RoomHistoryVisibility.SHARED)
|
||||
val aliceSession = testData.firstSession.also {
|
||||
it.cryptoService().enableShareKeyOnInvite(false)
|
||||
|
@ -162,7 +153,7 @@ class E2EShareKeysConfigTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun ifSharingEnabledOnAliceSideBobShouldShareAliceHistoty() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
fun ifSharingEnabledOnAliceSideBobShouldShareAliceHistory() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(roomHistoryVisibility = RoomHistoryVisibility.SHARED)
|
||||
val aliceSession = testData.firstSession.also {
|
||||
it.cryptoService().enableShareKeyOnInvite(true)
|
||||
|
@ -186,7 +177,7 @@ class E2EShareKeysConfigTest : InstrumentedTest {
|
|||
fromBobSharable.map { it.root.getClearContent()?.get("body") as String })
|
||||
}
|
||||
|
||||
private fun commonAliceAndBobSendMessages(commonTestHelper: CommonTestHelper, aliceSession: Session, testData: CryptoTestData, bobSession: Session): Triple<List<TimelineEvent>, List<TimelineEvent>, Session> {
|
||||
private suspend fun commonAliceAndBobSendMessages(commonTestHelper: CommonTestHelper, aliceSession: Session, testData: CryptoTestData, bobSession: Session): Triple<List<TimelineEvent>, List<TimelineEvent>, Session> {
|
||||
val fromAliceNotSharable = commonTestHelper.sendTextMessage(aliceSession.getRoom(testData.roomId)!!, "Hello from alice", 1)
|
||||
val fromBobSharable = commonTestHelper.sendTextMessage(bobSession.getRoom(testData.roomId)!!, "Hello from bob", 1)
|
||||
|
||||
|
@ -195,9 +186,7 @@ class E2EShareKeysConfigTest : InstrumentedTest {
|
|||
val samSession = commonTestHelper.createAccount(TestConstants.USER_SAM, SessionTestParams(withInitialSync = true))
|
||||
|
||||
// Let bob invite sam
|
||||
commonTestHelper.runBlockingTest {
|
||||
bobSession.getRoom(testData.roomId)!!.membershipService().invite(samSession.myUserId)
|
||||
}
|
||||
bobSession.getRoom(testData.roomId)!!.membershipService().invite(samSession.myUserId)
|
||||
|
||||
commonTestHelper.waitForAndAcceptInviteInRoom(samSession, testData.roomId)
|
||||
return Triple(fromAliceNotSharable, fromBobSharable, samSession)
|
||||
|
@ -209,18 +198,14 @@ class E2EShareKeysConfigTest : InstrumentedTest {
|
|||
fun testBackupFlagIsCorrect() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
|
||||
aliceSession.cryptoService().enableShareKeyOnInvite(false)
|
||||
val roomId = commonTestHelper.runBlockingTest {
|
||||
aliceSession.roomService().createRoom(CreateRoomParams().apply {
|
||||
historyVisibility = RoomHistoryVisibility.SHARED
|
||||
name = "MyRoom"
|
||||
enableEncryption()
|
||||
})
|
||||
}
|
||||
val roomId = aliceSession.roomService().createRoom(CreateRoomParams().apply {
|
||||
historyVisibility = RoomHistoryVisibility.SHARED
|
||||
name = "MyRoom"
|
||||
enableEncryption()
|
||||
})
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession.roomService().getRoomSummary(roomId)?.isEncrypted == true
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
aliceSession.roomService().getRoomSummary(roomId)?.isEncrypted == true
|
||||
}
|
||||
val roomAlice = aliceSession.roomService().getRoom(roomId)!!
|
||||
|
||||
|
@ -232,18 +217,15 @@ class E2EShareKeysConfigTest : InstrumentedTest {
|
|||
Log.v("#E2E TEST", "Create and start key backup for bob ...")
|
||||
val keysBackupService = aliceSession.cryptoService().keysBackupService()
|
||||
val keyBackupPassword = "FooBarBaz"
|
||||
val megolmBackupCreationInfo = commonTestHelper.doSync<MegolmBackupCreationInfo> {
|
||||
val megolmBackupCreationInfo = commonTestHelper.waitForCallback<MegolmBackupCreationInfo> {
|
||||
keysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it)
|
||||
}
|
||||
val version = commonTestHelper.doSync<KeysVersion> {
|
||||
val version = commonTestHelper.waitForCallback<KeysVersion> {
|
||||
keysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
keysBackupService.backupAllGroupSessions(
|
||||
null,
|
||||
TestMatrixCallback(latch, true)
|
||||
)
|
||||
commonTestHelper.waitForCallback<Unit> {
|
||||
keysBackupService.backupAllGroupSessions(null, it)
|
||||
}
|
||||
|
||||
// signout
|
||||
|
@ -253,11 +235,11 @@ class E2EShareKeysConfigTest : InstrumentedTest {
|
|||
newAliceSession.cryptoService().enableShareKeyOnInvite(true)
|
||||
|
||||
newAliceSession.cryptoService().keysBackupService().let { kbs ->
|
||||
val keyVersionResult = commonTestHelper.doSync<KeysVersionResult?> {
|
||||
val keyVersionResult = commonTestHelper.waitForCallback<KeysVersionResult?> {
|
||||
kbs.getVersion(version.version, it)
|
||||
}
|
||||
|
||||
val importedResult = commonTestHelper.doSync<ImportRoomKeysResult> {
|
||||
val importedResult = commonTestHelper.waitForCallback<ImportRoomKeysResult> {
|
||||
kbs.restoreKeyBackupWithPassword(
|
||||
keyVersionResult!!,
|
||||
keyBackupPassword,
|
||||
|
@ -276,9 +258,7 @@ class E2EShareKeysConfigTest : InstrumentedTest {
|
|||
val samSession = commonTestHelper.createAccount(TestConstants.USER_SAM, SessionTestParams(withInitialSync = true))
|
||||
|
||||
// Let alice invite sam
|
||||
commonTestHelper.runBlockingTest {
|
||||
newAliceSession.getRoom(roomId)!!.membershipService().invite(samSession.myUserId)
|
||||
}
|
||||
newAliceSession.getRoom(roomId)!!.membershipService().invite(samSession.myUserId)
|
||||
|
||||
commonTestHelper.waitForAndAcceptInviteInRoom(samSession, roomId)
|
||||
|
||||
|
|
|
@ -18,12 +18,16 @@ package org.matrix.android.sdk.internal.crypto
|
|||
|
||||
import android.util.Log
|
||||
import androidx.test.filters.LargeTest
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.amshove.kluent.fail
|
||||
import org.amshove.kluent.internal.assertEquals
|
||||
import org.junit.Assert
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
|
@ -57,12 +61,9 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
|||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
||||
import org.matrix.android.sdk.common.RetryTestRule
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import org.matrix.android.sdk.common.TestMatrixCallback
|
||||
import org.matrix.android.sdk.mustFail
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
// @Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.")
|
||||
@RunWith(JUnit4::class)
|
||||
|
@ -70,8 +71,6 @@ import java.util.concurrent.CountDownLatch
|
|||
@LargeTest
|
||||
class E2eeSanityTests : InstrumentedTest {
|
||||
|
||||
@get:Rule val rule = RetryTestRule(3)
|
||||
|
||||
/**
|
||||
* Simple test that create an e2ee room.
|
||||
* Some new members are added, and a message is sent.
|
||||
|
@ -104,10 +103,8 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
Log.v("#E2E TEST", "All accounts created")
|
||||
// we want to invite them in the room
|
||||
otherAccounts.forEach {
|
||||
testHelper.runBlockingTest {
|
||||
Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
|
||||
aliceRoomPOV.membershipService().invite(it.myUserId)
|
||||
}
|
||||
Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
|
||||
aliceRoomPOV.membershipService().invite(it.myUserId)
|
||||
}
|
||||
|
||||
// All user should accept invite
|
||||
|
@ -129,13 +126,11 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
|
||||
// All should be able to decrypt
|
||||
otherAccounts.forEach { otherSession ->
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timeLineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val timeLineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,10 +141,8 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
}
|
||||
|
||||
newAccount.forEach {
|
||||
testHelper.runBlockingTest {
|
||||
Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
|
||||
aliceRoomPOV.membershipService().invite(it.myUserId)
|
||||
}
|
||||
Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
|
||||
aliceRoomPOV.membershipService().invite(it.myUserId)
|
||||
}
|
||||
|
||||
newAccount.forEach {
|
||||
|
@ -159,21 +152,17 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
ensureMembersHaveJoined(testHelper, aliceSession, newAccount, e2eRoomID)
|
||||
|
||||
// wait a bit
|
||||
testHelper.runBlockingTest {
|
||||
delay(3_000)
|
||||
}
|
||||
delay(3_000)
|
||||
|
||||
// check that messages are encrypted (uisi)
|
||||
newAccount.forEach { otherSession ->
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also {
|
||||
Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
|
||||
}
|
||||
timelineEvent != null &&
|
||||
timelineEvent.root.getClearType() == EventType.ENCRYPTED &&
|
||||
timelineEvent.root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID
|
||||
testHelper.retryPeriodically {
|
||||
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also {
|
||||
Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
|
||||
}
|
||||
timelineEvent != null &&
|
||||
timelineEvent.root.getClearType() == EventType.ENCRYPTED &&
|
||||
timelineEvent.root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,15 +174,13 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
|
||||
// new members should be able to decrypt it
|
||||
newAccount.forEach { otherSession ->
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(secondSentEventId!!).also {
|
||||
Log.v("#E2E TEST", "Second Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
|
||||
}
|
||||
timelineEvent != null &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE &&
|
||||
secondMessage == timelineEvent.root.getClearContent().toModel<MessageContent>()?.body
|
||||
testHelper.retryPeriodically {
|
||||
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(secondSentEventId!!).also {
|
||||
Log.v("#E2E TEST", "Second Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
|
||||
}
|
||||
timelineEvent != null &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE &&
|
||||
secondMessage == timelineEvent.root.getClearContent().toModel<MessageContent>()?.body
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -229,10 +216,10 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
Log.v("#E2E TEST", "Create and start key backup for bob ...")
|
||||
val bobKeysBackupService = bobSession.cryptoService().keysBackupService()
|
||||
val keyBackupPassword = "FooBarBaz"
|
||||
val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> {
|
||||
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> {
|
||||
bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it)
|
||||
}
|
||||
val version = testHelper.doSync<KeysVersion> {
|
||||
val version = testHelper.waitForCallback<KeysVersion> {
|
||||
bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
Log.v("#E2E TEST", "... Key backup started and enabled for bob")
|
||||
|
@ -248,32 +235,21 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
sentEventIds.add(it)
|
||||
}
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
// we want more so let's discard the session
|
||||
aliceSession.cryptoService().discardOutboundSession(e2eRoomID)
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
delay(1_000)
|
||||
}
|
||||
}
|
||||
Log.v("#E2E TEST", "Bob received all and can decrypt")
|
||||
|
||||
// Let's wait a bit to be sure that bob has backed up the session
|
||||
|
||||
Log.v("#E2E TEST", "Force key backup for Bob...")
|
||||
testHelper.waitWithLatch { latch ->
|
||||
bobKeysBackupService.backupAllGroupSessions(
|
||||
null,
|
||||
TestMatrixCallback(latch, true)
|
||||
)
|
||||
}
|
||||
testHelper.waitForCallback<Unit> { bobKeysBackupService.backupAllGroupSessions(null, it) }
|
||||
Log.v("#E2E TEST", "... Key backup done for Bob")
|
||||
|
||||
// Now lets logout both alice and bob to ensure that we won't have any gossiping
|
||||
|
@ -284,9 +260,7 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
testHelper.signOutAndClose(bobSession)
|
||||
Log.v("#E2E TEST", "..Logout alice and bob...")
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
delay(1_000)
|
||||
}
|
||||
delay(1_000)
|
||||
|
||||
// Create a new session for bob
|
||||
Log.v("#E2E TEST", "Create a new session for Bob")
|
||||
|
@ -295,14 +269,11 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
// check that bob can't currently decrypt
|
||||
Log.v("#E2E TEST", "check that bob can't currently decrypt")
|
||||
sentEventIds.forEach { sentEventId ->
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)?.also {
|
||||
Log.v("#E2E TEST", "Event seen by new user ${it.root.getClearType()}|${it.root.mCryptoError}")
|
||||
}
|
||||
timelineEvent != null &&
|
||||
timelineEvent.root.getClearType() == EventType.ENCRYPTED
|
||||
testHelper.retryPeriodically {
|
||||
val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)?.also {
|
||||
Log.v("#E2E TEST", "Event seen by new user ${it.root.getClearType()}|${it.root.mCryptoError}")
|
||||
}
|
||||
timelineEvent != null && timelineEvent.root.getClearType() == EventType.ENCRYPTED
|
||||
}
|
||||
}
|
||||
// after initial sync events are not decrypted, so we have to try manually
|
||||
|
@ -311,11 +282,11 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
// Let's now import keys from backup
|
||||
|
||||
newBobSession.cryptoService().keysBackupService().let { kbs ->
|
||||
val keyVersionResult = testHelper.doSync<KeysVersionResult?> {
|
||||
val keyVersionResult = testHelper.waitForCallback<KeysVersionResult?> {
|
||||
kbs.getVersion(version.version, it)
|
||||
}
|
||||
|
||||
val importedResult = testHelper.doSync<ImportRoomKeysResult> {
|
||||
val importedResult = testHelper.waitForCallback<ImportRoomKeysResult> {
|
||||
kbs.restoreKeyBackupWithPassword(
|
||||
keyVersionResult!!,
|
||||
keyBackupPassword,
|
||||
|
@ -357,13 +328,11 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
sentEventIds.add(it)
|
||||
}
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -399,22 +368,20 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
val megolmSessionId = newBobSession.getRoom(e2eRoomID)!!
|
||||
.getTimelineEvent(sentEventId)!!
|
||||
.root.content.toModel<EncryptedEventContent>()!!.sessionId
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val aliceReply = newBobSession.cryptoService().getOutgoingRoomKeyRequests()
|
||||
.first {
|
||||
it.sessionId == megolmSessionId &&
|
||||
it.roomId == e2eRoomID
|
||||
}
|
||||
.results.also {
|
||||
Log.w("##TEST", "result list is $it")
|
||||
}
|
||||
.firstOrNull { it.userId == aliceSession.myUserId }
|
||||
?.result
|
||||
aliceReply != null &&
|
||||
aliceReply is RequestResult.Failure &&
|
||||
WithHeldCode.UNAUTHORISED == aliceReply.code
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val aliceReply = newBobSession.cryptoService().getOutgoingRoomKeyRequests()
|
||||
.first {
|
||||
it.sessionId == megolmSessionId &&
|
||||
it.roomId == e2eRoomID
|
||||
}
|
||||
.results.also {
|
||||
Log.w("##TEST", "result list is $it")
|
||||
}
|
||||
.firstOrNull { it.userId == aliceSession.myUserId }
|
||||
?.result
|
||||
aliceReply != null &&
|
||||
aliceReply is RequestResult.Failure &&
|
||||
WithHeldCode.UNAUTHORISED == aliceReply.code
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -455,13 +422,11 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
firstMessage.let { text ->
|
||||
firstEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -483,13 +448,11 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
secondMessage.let { text ->
|
||||
secondEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -503,18 +466,14 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
Assert.assertTrue("Should be the same session id", firstSessionId == secondSessionId)
|
||||
|
||||
// Confirm we can decrypt one but not the other
|
||||
testHelper.runBlockingTest {
|
||||
mustFail(message = "Should not be able to decrypt event") {
|
||||
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
|
||||
}
|
||||
mustFail(message = "Should not be able to decrypt event") {
|
||||
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
|
||||
}
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
try {
|
||||
newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "")
|
||||
} catch (error: MXCryptoError) {
|
||||
fail("Should be able to decrypt event")
|
||||
}
|
||||
try {
|
||||
newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "")
|
||||
} catch (error: MXCryptoError) {
|
||||
fail("Should be able to decrypt event")
|
||||
}
|
||||
|
||||
// Now let's verify bobs session, and re-request keys
|
||||
|
@ -533,50 +492,42 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
|
||||
// old session should have shared the key at earliest known index now
|
||||
// we should be able to decrypt both
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
val canDecryptFirst = try {
|
||||
testHelper.runBlockingTest {
|
||||
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
|
||||
}
|
||||
true
|
||||
} catch (error: MXCryptoError) {
|
||||
false
|
||||
}
|
||||
val canDecryptSecond = try {
|
||||
testHelper.runBlockingTest {
|
||||
newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "")
|
||||
}
|
||||
true
|
||||
} catch (error: MXCryptoError) {
|
||||
false
|
||||
}
|
||||
canDecryptFirst && canDecryptSecond
|
||||
testHelper.retryPeriodically {
|
||||
val canDecryptFirst = try {
|
||||
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
|
||||
true
|
||||
} catch (error: MXCryptoError) {
|
||||
false
|
||||
}
|
||||
val canDecryptSecond = try {
|
||||
newBobSession.cryptoService().decryptEvent(secondEventNewBobPov.root, "")
|
||||
true
|
||||
} catch (error: MXCryptoError) {
|
||||
false
|
||||
}
|
||||
canDecryptFirst && canDecryptSecond
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendMessageInRoom(testHelper: CommonTestHelper, aliceRoomPOV: Room, text: String): String? {
|
||||
aliceRoomPOV.sendService().sendTextMessage(text)
|
||||
private suspend fun sendMessageInRoom(testHelper: CommonTestHelper, aliceRoomPOV: Room, text: String): String? {
|
||||
var sentEventId: String? = null
|
||||
testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch ->
|
||||
val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
|
||||
timeline.start()
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val decryptedMsg = timeline.getSnapshot()
|
||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||
.also { list ->
|
||||
val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" }
|
||||
Log.v("#E2E TEST", "Timeline snapshot is $message")
|
||||
}
|
||||
.filter { it.root.sendState == SendState.SYNCED }
|
||||
.firstOrNull { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(text) == true }
|
||||
sentEventId = decryptedMsg?.eventId
|
||||
decryptedMsg != null
|
||||
}
|
||||
aliceRoomPOV.sendService().sendTextMessage(text)
|
||||
|
||||
timeline.dispose()
|
||||
val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
|
||||
timeline.start()
|
||||
testHelper.retryPeriodically {
|
||||
val decryptedMsg = timeline.getSnapshot()
|
||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||
.also { list ->
|
||||
val message = list.joinToString(",", "[", "]") { "${it.root.type}|${it.root.sendState}" }
|
||||
Log.v("#E2E TEST", "Timeline snapshot is $message")
|
||||
}
|
||||
.filter { it.root.sendState == SendState.SYNCED }
|
||||
.firstOrNull { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(text) == true }
|
||||
sentEventId = decryptedMsg?.eventId
|
||||
decryptedMsg != null
|
||||
}
|
||||
timeline.dispose()
|
||||
return sentEventId
|
||||
}
|
||||
|
||||
|
@ -593,106 +544,35 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
|
||||
val aliceNewSession = testHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
|
||||
|
||||
val oldCompleteLatch = CountDownLatch(1)
|
||||
lateinit var oldCode: String
|
||||
aliceSession.cryptoService().verificationService().addListener(object : VerificationService.Listener {
|
||||
|
||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||
val readyInfo = pr.readyInfo
|
||||
if (readyInfo != null) {
|
||||
aliceSession.cryptoService().verificationService().beginKeyVerification(
|
||||
VerificationMethod.SAS,
|
||||
aliceSession.myUserId,
|
||||
readyInfo.fromDevice,
|
||||
readyInfo.transactionId
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.d("##TEST", "exitsingPov: $tx")
|
||||
val sasTx = tx as OutgoingSasVerificationTransaction
|
||||
when (sasTx.uxState) {
|
||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
// for the test we just accept?
|
||||
oldCode = sasTx.getDecimalCodeRepresentation()
|
||||
sasTx.userHasVerifiedShortCode()
|
||||
}
|
||||
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
// we can release this latch?
|
||||
oldCompleteLatch.countDown()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
val newCompleteLatch = CountDownLatch(1)
|
||||
lateinit var newCode: String
|
||||
aliceNewSession.cryptoService().verificationService().addListener(object : VerificationService.Listener {
|
||||
|
||||
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||
// let's ready
|
||||
aliceNewSession.cryptoService().verificationService().readyPendingVerification(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
aliceSession.myUserId,
|
||||
pr.transactionId!!
|
||||
)
|
||||
}
|
||||
|
||||
var matchOnce = true
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.d("##TEST", "newPov: $tx")
|
||||
|
||||
val sasTx = tx as IncomingSasVerificationTransaction
|
||||
when (sasTx.uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
// no need to accept as there was a request first it will auto accept
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
if (matchOnce) {
|
||||
sasTx.userHasVerifiedShortCode()
|
||||
newCode = sasTx.getDecimalCodeRepresentation()
|
||||
matchOnce = false
|
||||
}
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
newCompleteLatch.countDown()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
val deferredOldCode = aliceSession.cryptoService().verificationService().readOldVerificationCodeAsync(this, aliceSession.myUserId)
|
||||
val deferredNewCode = aliceNewSession.cryptoService().verificationService().readNewVerificationCodeAsync(this, aliceSession.myUserId)
|
||||
// initiate self verification
|
||||
aliceSession.cryptoService().verificationService().requestKeyVerification(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
aliceNewSession.myUserId,
|
||||
listOf(aliceNewSession.sessionParams.deviceId!!)
|
||||
)
|
||||
testHelper.await(oldCompleteLatch)
|
||||
testHelper.await(newCompleteLatch)
|
||||
|
||||
val (oldCode, newCode) = awaitAll(deferredOldCode, deferredNewCode)
|
||||
|
||||
assertEquals("Decimal code should have matched", oldCode, newCode)
|
||||
|
||||
// Assert that devices are verified
|
||||
val newDeviceFromOldPov: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId)
|
||||
val oldDeviceFromNewPov: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
|
||||
val newDeviceFromOldPov: CryptoDeviceInfo? =
|
||||
aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId)
|
||||
val oldDeviceFromNewPov: CryptoDeviceInfo? =
|
||||
aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
|
||||
|
||||
Assert.assertTrue("new device should be verified from old point of view", newDeviceFromOldPov!!.isVerified)
|
||||
Assert.assertTrue("old device should be verified from new point of view", oldDeviceFromNewPov!!.isVerified)
|
||||
|
||||
// wait for secret gossiping to happen
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceNewSession.cryptoService().crossSigningService().allPrivateKeysKnown()
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
aliceNewSession.cryptoService().crossSigningService().allPrivateKeysKnown()
|
||||
}
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
|
@ -725,27 +605,112 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
)
|
||||
}
|
||||
|
||||
private fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
otherAccounts.map {
|
||||
aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership
|
||||
}.all {
|
||||
it == Membership.JOIN
|
||||
private suspend fun VerificationService.readOldVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
|
||||
return scope.async {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
var oldCode: String? = null
|
||||
val listener = object : VerificationService.Listener {
|
||||
|
||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||
val readyInfo = pr.readyInfo
|
||||
if (readyInfo != null) {
|
||||
beginKeyVerification(
|
||||
VerificationMethod.SAS,
|
||||
userId,
|
||||
readyInfo.fromDevice,
|
||||
readyInfo.transactionId
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.d("##TEST", "exitsingPov: $tx")
|
||||
val sasTx = tx as OutgoingSasVerificationTransaction
|
||||
when (sasTx.uxState) {
|
||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
// for the test we just accept?
|
||||
oldCode = sasTx.getDecimalCodeRepresentation()
|
||||
sasTx.userHasVerifiedShortCode()
|
||||
}
|
||||
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
removeListener(this)
|
||||
// we can release this latch?
|
||||
continuation.resume(oldCode!!)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
addListener(listener)
|
||||
continuation.invokeOnCancellation { removeListener(listener) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ensureIsDecrypted(testHelper: CommonTestHelper, sentEventIds: List<String>, session: Session, e2eRoomID: String) {
|
||||
testHelper.waitWithLatch { latch ->
|
||||
sentEventIds.forEach { sentEventId ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timeLineEvent = session.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
private suspend fun VerificationService.readNewVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
|
||||
return scope.async {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
var newCode: String? = null
|
||||
|
||||
val listener = object : VerificationService.Listener {
|
||||
|
||||
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||
// let's ready
|
||||
readyPendingVerification(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
userId,
|
||||
pr.transactionId!!
|
||||
)
|
||||
}
|
||||
|
||||
var matchOnce = true
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.d("##TEST", "newPov: $tx")
|
||||
|
||||
val sasTx = tx as IncomingSasVerificationTransaction
|
||||
when (sasTx.uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
// no need to accept as there was a request first it will auto accept
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
if (matchOnce) {
|
||||
sasTx.userHasVerifiedShortCode()
|
||||
newCode = sasTx.getDecimalCodeRepresentation()
|
||||
matchOnce = false
|
||||
}
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
removeListener(this)
|
||||
continuation.resume(newCode!!)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
addListener(listener)
|
||||
continuation.invokeOnCancellation { removeListener(listener) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {
|
||||
testHelper.retryPeriodically {
|
||||
otherAccounts.map {
|
||||
aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership
|
||||
}.all {
|
||||
it == Membership.JOIN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun ensureIsDecrypted(testHelper: CommonTestHelper, sentEventIds: List<String>, session: Session, e2eRoomID: String) {
|
||||
sentEventIds.forEach { sentEventId ->
|
||||
testHelper.retryPeriodically {
|
||||
val timeLineEvent = session.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,8 +41,8 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityConten
|
|||
import org.matrix.android.sdk.api.session.room.model.shouldShareHistory
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
import org.matrix.android.sdk.common.wrapWithTimeout
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
|
@ -101,15 +101,13 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||
Log.v("#E2E TEST", "Alice sent message to roomId: $e2eRoomID")
|
||||
|
||||
// Bob should be able to decrypt the message
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
|
||||
(timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
||||
if (it) {
|
||||
Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
|
||||
(timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
||||
if (it) {
|
||||
Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,10 +119,8 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||
Log.v("#E2E TEST", "Aris user created")
|
||||
|
||||
// Alice invites new user to the room
|
||||
testHelper.runBlockingTest {
|
||||
Log.v("#E2E TEST", "Alice invites ${arisSession.myUserId}")
|
||||
aliceRoomPOV.membershipService().invite(arisSession.myUserId)
|
||||
}
|
||||
Log.v("#E2E TEST", "Alice invites ${arisSession.myUserId}")
|
||||
aliceRoomPOV.membershipService().invite(arisSession.myUserId)
|
||||
|
||||
waitForAndAcceptInviteInRoom(arisSession, e2eRoomID, testHelper)
|
||||
|
||||
|
@ -137,30 +133,26 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||
null
|
||||
-> {
|
||||
// Aris should be able to decrypt the message
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
|
||||
(timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE
|
||||
).also {
|
||||
if (it) {
|
||||
Log.v("#E2E TEST", "Aris can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
|
||||
(timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE
|
||||
).also {
|
||||
if (it) {
|
||||
Log.v("#E2E TEST", "Aris can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RoomHistoryVisibility.INVITED,
|
||||
RoomHistoryVisibility.JOINED -> {
|
||||
// Aris should not even be able to get the message
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)
|
||||
?.timelineService()
|
||||
?.getTimelineEvent(aliceMessageId!!)
|
||||
timelineEvent == null
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)
|
||||
?.timelineService()
|
||||
?.getTimelineEvent(aliceMessageId!!)
|
||||
timelineEvent == null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -238,10 +230,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||
private fun testRotationDueToVisibilityChange(
|
||||
initRoomHistoryVisibility: RoomHistoryVisibility,
|
||||
nextRoomHistoryVisibility: RoomHistoryVisibilityContent
|
||||
) {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
) = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true, initRoomHistoryVisibility)
|
||||
val e2eRoomID = cryptoTestData.roomId
|
||||
|
||||
|
@ -267,21 +256,19 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||
// Bob should be able to decrypt the message
|
||||
var firstAliceMessageMegolmSessionId: String? = null
|
||||
val bobRoomPov = bobSession.roomService().getRoom(e2eRoomID)
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = bobRoomPov
|
||||
?.timelineService()
|
||||
?.getTimelineEvent(aliceMessageId!!)
|
||||
(timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
||||
if (it) {
|
||||
firstAliceMessageMegolmSessionId = timelineEvent?.root?.content?.get("session_id") as? String
|
||||
Log.v(
|
||||
"#E2E TEST",
|
||||
"Bob can decrypt the message (sid:$firstAliceMessageMegolmSessionId): ${timelineEvent?.root?.getDecryptedTextSummary()}"
|
||||
)
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val timelineEvent = bobRoomPov
|
||||
?.timelineService()
|
||||
?.getTimelineEvent(aliceMessageId!!)
|
||||
(timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
||||
if (it) {
|
||||
firstAliceMessageMegolmSessionId = timelineEvent?.root?.content?.get("session_id") as? String
|
||||
Log.v(
|
||||
"#E2E TEST",
|
||||
"Bob can decrypt the message (sid:$firstAliceMessageMegolmSessionId): ${timelineEvent?.root?.getDecryptedTextSummary()}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -290,21 +277,19 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||
|
||||
var secondAliceMessageSessionId: String? = null
|
||||
sendMessageInRoom(aliceRoomPOV, "Other msg", testHelper)?.let { secondMessage ->
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = bobRoomPov
|
||||
?.timelineService()
|
||||
?.getTimelineEvent(secondMessage)
|
||||
(timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
||||
if (it) {
|
||||
secondAliceMessageSessionId = timelineEvent?.root?.content?.get("session_id") as? String
|
||||
Log.v(
|
||||
"#E2E TEST",
|
||||
"Bob can decrypt the message (sid:$secondAliceMessageSessionId): ${timelineEvent?.root?.getDecryptedTextSummary()}"
|
||||
)
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val timelineEvent = bobRoomPov
|
||||
?.timelineService()
|
||||
?.getTimelineEvent(secondMessage)
|
||||
(timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
||||
if (it) {
|
||||
secondAliceMessageSessionId = timelineEvent?.root?.content?.get("session_id") as? String
|
||||
Log.v(
|
||||
"#E2E TEST",
|
||||
"Bob can decrypt the message (sid:$secondAliceMessageSessionId): ${timelineEvent?.root?.getDecryptedTextSummary()}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -313,50 +298,42 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||
Log.v("#E2E TEST ROTATION", "No rotation needed yet")
|
||||
|
||||
// Let's change the room history visibility
|
||||
testHelper.runBlockingTest {
|
||||
aliceRoomPOV.stateService()
|
||||
.sendStateEvent(
|
||||
eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||
stateKey = "",
|
||||
body = RoomHistoryVisibilityContent(
|
||||
historyVisibilityStr = nextRoomHistoryVisibility.historyVisibilityStr
|
||||
).toContent()
|
||||
)
|
||||
}
|
||||
aliceRoomPOV.stateService()
|
||||
.sendStateEvent(
|
||||
eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||
stateKey = "",
|
||||
body = RoomHistoryVisibilityContent(
|
||||
historyVisibilityStr = nextRoomHistoryVisibility.historyVisibilityStr
|
||||
).toContent()
|
||||
)
|
||||
|
||||
// ensure that the state did synced down
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceRoomPOV.stateService().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)?.content
|
||||
?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility == nextRoomHistoryVisibility.historyVisibility
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
aliceRoomPOV.stateService().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)?.content
|
||||
?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility == nextRoomHistoryVisibility.historyVisibility
|
||||
}
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val roomVisibility = aliceSession.getRoom(e2eRoomID)!!
|
||||
.stateService()
|
||||
.getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)
|
||||
?.content
|
||||
?.toModel<RoomHistoryVisibilityContent>()
|
||||
Log.v("#E2E TEST ROTATION", "Room visibility changed from: ${initRoomHistoryVisibility.name} to: ${roomVisibility?.historyVisibility?.name}")
|
||||
roomVisibility?.historyVisibility == nextRoomHistoryVisibility.historyVisibility
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val roomVisibility = aliceSession.getRoom(e2eRoomID)!!
|
||||
.stateService()
|
||||
.getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)
|
||||
?.content
|
||||
?.toModel<RoomHistoryVisibilityContent>()
|
||||
Log.v("#E2E TEST ROTATION", "Room visibility changed from: ${initRoomHistoryVisibility.name} to: ${roomVisibility?.historyVisibility?.name}")
|
||||
roomVisibility?.historyVisibility == nextRoomHistoryVisibility.historyVisibility
|
||||
}
|
||||
|
||||
var aliceThirdMessageSessionId: String? = null
|
||||
sendMessageInRoom(aliceRoomPOV, "Message after visibility change", testHelper)?.let { thirdMessage ->
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timelineEvent = bobRoomPov
|
||||
?.timelineService()
|
||||
?.getTimelineEvent(thirdMessage)
|
||||
(timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
||||
if (it) {
|
||||
aliceThirdMessageSessionId = timelineEvent?.root?.content?.get("session_id") as? String
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val timelineEvent = bobRoomPov
|
||||
?.timelineService()
|
||||
?.getTimelineEvent(thirdMessage)
|
||||
(timelineEvent != null &&
|
||||
timelineEvent.isEncrypted() &&
|
||||
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
||||
if (it) {
|
||||
aliceThirdMessageSessionId = timelineEvent?.root?.content?.get("session_id") as? String
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -376,35 +353,31 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
private fun sendMessageInRoom(aliceRoomPOV: Room, text: String, testHelper: CommonTestHelper): String? {
|
||||
private suspend fun sendMessageInRoom(aliceRoomPOV: Room, text: String, testHelper: CommonTestHelper): String? {
|
||||
return testHelper.sendTextMessage(aliceRoomPOV, text, 1).firstOrNull()?.eventId
|
||||
}
|
||||
|
||||
private fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String, testHelper: CommonTestHelper) {
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
otherAccounts.map {
|
||||
aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership
|
||||
}.all {
|
||||
it == Membership.JOIN
|
||||
}
|
||||
private suspend fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String, testHelper: CommonTestHelper) {
|
||||
testHelper.retryPeriodically {
|
||||
otherAccounts.map {
|
||||
aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership
|
||||
}.all {
|
||||
it == Membership.JOIN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String, testHelper: CommonTestHelper) {
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID)
|
||||
(roomSummary != null && roomSummary.membership == Membership.INVITE).also {
|
||||
if (it) {
|
||||
Log.v("#E2E TEST", "${otherSession.myUserId} can see the invite from alice")
|
||||
}
|
||||
private suspend fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String, testHelper: CommonTestHelper) {
|
||||
testHelper.retryPeriodically {
|
||||
val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID)
|
||||
(roomSummary != null && roomSummary.membership == Membership.INVITE).also {
|
||||
if (it) {
|
||||
Log.v("#E2E TEST", "${otherSession.myUserId} can see the invite from alice")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testHelper.runBlockingTest(60_000) {
|
||||
wrapWithTimeout(60_000) {
|
||||
Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID")
|
||||
try {
|
||||
otherSession.roomService().joinRoom(e2eRoomID)
|
||||
|
@ -414,11 +387,9 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...")
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID)
|
||||
roomSummary != null && roomSummary.membership == Membership.JOIN
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID)
|
||||
roomSummary != null && roomSummary.membership == Membership.JOIN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,15 +52,13 @@ class PreShareKeysTest : InstrumentedTest {
|
|||
Log.d("#Test", "Room Key Received from alice $preShareCount")
|
||||
|
||||
// Force presharing of new outbound key
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID, it)
|
||||
}
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val newKeysCount = bobSession.cryptoService().keysBackupService().getTotalNumbersOfKeys()
|
||||
newKeysCount > preShareCount
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val newKeysCount = bobSession.cryptoService().keysBackupService().getTotalNumbersOfKeys()
|
||||
newKeysCount > preShareCount
|
||||
}
|
||||
|
||||
val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
|
||||
|
@ -85,10 +83,8 @@ class PreShareKeysTest : InstrumentedTest {
|
|||
val sentEvent = testHelper.sendTextMessage(aliceSession.getRoom(e2eRoomID)!!, "Allo", 1).first()
|
||||
|
||||
assertEquals("Unexpected megolm session", megolmSessionId, sentEvent.root.content.toModel<EncryptedEventContent>()?.sessionId)
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
package org.matrix.android.sdk.internal.crypto
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.amshove.kluent.shouldBe
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.FixMethodOrder
|
||||
|
@ -45,7 +46,6 @@ import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
|
|||
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
|
||||
import org.matrix.olm.OlmSession
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
|
@ -98,69 +98,37 @@ class UnwedgingTest : InstrumentedTest {
|
|||
val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(20))
|
||||
bobTimeline.start()
|
||||
|
||||
val bobFinalLatch = CountDownLatch(1)
|
||||
val bobHasThreeDecryptedEventsListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||
// noop
|
||||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
val decryptedEventReceivedByBob = snapshot.filter { it.root.type == EventType.ENCRYPTED }
|
||||
Timber.d("Bob can now decrypt ${decryptedEventReceivedByBob.size} messages")
|
||||
if (decryptedEventReceivedByBob.size == 3) {
|
||||
if (decryptedEventReceivedByBob[0].root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
||||
bobFinalLatch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bobTimeline.addListener(bobHasThreeDecryptedEventsListener)
|
||||
|
||||
var latch = CountDownLatch(1)
|
||||
var bobEventsListener = createEventListener(latch, 1)
|
||||
bobTimeline.addListener(bobEventsListener)
|
||||
messagesReceivedByBob = emptyList()
|
||||
|
||||
// - Alice sends a 1st message with a 1st megolm session
|
||||
roomFromAlicePOV.sendService().sendTextMessage("First message")
|
||||
|
||||
// Wait for the message to be received by Bob
|
||||
testHelper.await(latch)
|
||||
bobTimeline.removeListener(bobEventsListener)
|
||||
messagesReceivedByBob = bobTimeline.waitForMessages(expectedCount = 1)
|
||||
|
||||
messagesReceivedByBob.size shouldBe 1
|
||||
messagesReceivedByBob.size shouldBeEqualTo 1
|
||||
val firstMessageSession = messagesReceivedByBob[0].root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
|
||||
// - Store the olm session between A&B devices
|
||||
// Let us pickle our session with bob here so we can later unpickle it
|
||||
// and wedge our session.
|
||||
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(bobSession.cryptoService().getMyDevice().identityKey()!!)
|
||||
sessionIdsForBob!!.size shouldBe 1
|
||||
sessionIdsForBob!!.size shouldBeEqualTo 1
|
||||
val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), bobSession.cryptoService().getMyDevice().identityKey()!!)!!
|
||||
|
||||
val oldSession = serializeForRealm(olmSession.olmSession)
|
||||
|
||||
aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId)
|
||||
Thread.sleep(6_000)
|
||||
|
||||
latch = CountDownLatch(1)
|
||||
bobEventsListener = createEventListener(latch, 2)
|
||||
bobTimeline.addListener(bobEventsListener)
|
||||
messagesReceivedByBob = emptyList()
|
||||
|
||||
Timber.i("## CRYPTO | testUnwedging: Alice sends a 2nd message with a 2nd megolm session")
|
||||
// - Alice sends a 2nd message with a 2nd megolm session
|
||||
roomFromAlicePOV.sendService().sendTextMessage("Second message")
|
||||
|
||||
// Wait for the message to be received by Bob
|
||||
testHelper.await(latch)
|
||||
bobTimeline.removeListener(bobEventsListener)
|
||||
messagesReceivedByBob = bobTimeline.waitForMessages(expectedCount = 2)
|
||||
|
||||
messagesReceivedByBob.size shouldBe 2
|
||||
messagesReceivedByBob.size shouldBeEqualTo 2
|
||||
// Session should have changed
|
||||
val secondMessageSession = messagesReceivedByBob[0].root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
Assert.assertNotEquals(firstMessageSession, secondMessageSession)
|
||||
|
@ -173,25 +141,18 @@ class UnwedgingTest : InstrumentedTest {
|
|||
bobSession.cryptoService().getMyDevice().identityKey()!!
|
||||
)
|
||||
olmDevice.clearOlmSessionCache()
|
||||
Thread.sleep(6_000)
|
||||
|
||||
// Force new session, and key share
|
||||
aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId)
|
||||
|
||||
Timber.i("## CRYPTO | testUnwedging: Alice sends a 3rd message with a 3rd megolm session but a wedged olm session")
|
||||
// - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session
|
||||
roomFromAlicePOV.sendService().sendTextMessage("Third message")
|
||||
// Bob should not be able to decrypt, because the session key could not be sent
|
||||
// Wait for the message to be received by Bob
|
||||
testHelper.waitWithLatch {
|
||||
bobEventsListener = createEventListener(it, 3)
|
||||
bobTimeline.addListener(bobEventsListener)
|
||||
messagesReceivedByBob = emptyList()
|
||||
messagesReceivedByBob = bobTimeline.waitForMessages(expectedCount = 3)
|
||||
|
||||
Timber.i("## CRYPTO | testUnwedging: Alice sends a 3rd message with a 3rd megolm session but a wedged olm session")
|
||||
// - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session
|
||||
roomFromAlicePOV.sendService().sendTextMessage("Third message")
|
||||
// Bob should not be able to decrypt, because the session key could not be sent
|
||||
}
|
||||
bobTimeline.removeListener(bobEventsListener)
|
||||
|
||||
messagesReceivedByBob.size shouldBe 3
|
||||
messagesReceivedByBob.size shouldBeEqualTo 3
|
||||
|
||||
val thirdMessageSession = messagesReceivedByBob[0].root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
Timber.i("## CRYPTO | testUnwedging: third message session ID $thirdMessageSession")
|
||||
|
@ -201,11 +162,11 @@ class UnwedgingTest : InstrumentedTest {
|
|||
Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[1].root.getClearType())
|
||||
Assert.assertEquals(EventType.MESSAGE, messagesReceivedByBob[2].root.getClearType())
|
||||
// Bob Should not be able to decrypt last message, because session could not be sent as the olm channel was wedged
|
||||
testHelper.await(bobFinalLatch)
|
||||
bobTimeline.removeListener(bobHasThreeDecryptedEventsListener)
|
||||
|
||||
Assert.assertTrue(messagesReceivedByBob[0].root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
|
||||
|
||||
// It's a trick to force key request on fail to decrypt
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
bobSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
|
@ -223,24 +184,22 @@ class UnwedgingTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
// Wait until we received back the key
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
// we should get back the key and be able to decrypt
|
||||
val result = testHelper.runBlockingTest {
|
||||
tryOrNull {
|
||||
bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
|
||||
}
|
||||
}
|
||||
Timber.i("## CRYPTO | testUnwedging: decrypt result ${result?.clearEvent}")
|
||||
result != null
|
||||
testHelper.retryPeriodically {
|
||||
// we should get back the key and be able to decrypt
|
||||
val result = tryOrNull {
|
||||
bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
|
||||
}
|
||||
Timber.i("## CRYPTO | testUnwedging: decrypt result ${result?.clearEvent}")
|
||||
result != null
|
||||
}
|
||||
|
||||
bobTimeline.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createEventListener(latch: CountDownLatch, expectedNumberOfMessages: Int): Timeline.Listener {
|
||||
return object : Timeline.Listener {
|
||||
private suspend fun Timeline.waitForMessages(expectedCount: Int): List<TimelineEvent> {
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
val listener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
// noop
|
||||
}
|
||||
|
@ -250,12 +209,16 @@ class UnwedgingTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||
messagesReceivedByBob = snapshot.filter { it.root.type == EventType.ENCRYPTED }
|
||||
val messagesReceived = snapshot.filter { it.root.type == EventType.ENCRYPTED }
|
||||
|
||||
if (messagesReceivedByBob.size == expectedNumberOfMessages) {
|
||||
latch.countDown()
|
||||
if (messagesReceived.size == expectedCount) {
|
||||
removeListener(this)
|
||||
continuation.resume(messagesReceived)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addListener(listener)
|
||||
continuation.invokeOnCancellation { removeListener(listener) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ class XSigningTest : InstrumentedTest {
|
|||
fun test_InitializeAndStoreKeys() = runSessionTest(context()) { testHelper ->
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
aliceSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
|
@ -101,14 +101,14 @@ class XSigningTest : InstrumentedTest {
|
|||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
|
@ -117,7 +117,7 @@ class XSigningTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
// Check that alice can see bob keys
|
||||
testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) }
|
||||
testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) }
|
||||
|
||||
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
|
||||
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
|
||||
|
@ -154,14 +154,14 @@ class XSigningTest : InstrumentedTest {
|
|||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
|
@ -171,12 +171,12 @@ class XSigningTest : InstrumentedTest {
|
|||
|
||||
// Check that alice can see bob keys
|
||||
val bobUserId = bobSession.myUserId
|
||||
testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) }
|
||||
testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) }
|
||||
|
||||
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
|
||||
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
|
||||
|
||||
testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) }
|
||||
testHelper.waitForCallback<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) }
|
||||
|
||||
// Now bobs logs in on a new device and verifies it
|
||||
// We will want to test that in alice POV, this new device would be trusted by cross signing
|
||||
|
@ -185,7 +185,7 @@ class XSigningTest : InstrumentedTest {
|
|||
val bobSecondDeviceId = bobSession2.sessionParams.deviceId!!
|
||||
|
||||
// Check that bob first session sees the new login
|
||||
val data = testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
val data = testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
|
||||
}
|
||||
|
||||
|
@ -197,12 +197,12 @@ class XSigningTest : InstrumentedTest {
|
|||
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
|
||||
|
||||
// Manually mark it as trusted from first session
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId, it)
|
||||
}
|
||||
|
||||
// Now alice should cross trust bob's second device
|
||||
val data2 = testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
val data2 = testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
package org.matrix.android.sdk.internal.crypto.encryption
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.amshove.kluent.shouldBe
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
|
@ -34,54 +34,59 @@ import org.matrix.android.sdk.api.session.room.send.SendState
|
|||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import org.matrix.android.sdk.common.waitFor
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class EncryptionTest : InstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun test_EncryptionEvent() {
|
||||
runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
performTest(cryptoTestHelper, testHelper, roomShouldBeEncrypted = false) { room ->
|
||||
// Send an encryption Event as an Event (and not as a state event)
|
||||
room.sendService().sendEvent(
|
||||
eventType = EventType.STATE_ROOM_ENCRYPTION,
|
||||
content = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
|
||||
)
|
||||
}
|
||||
fun test_EncryptionEvent() = runCryptoTest(context()) { cryptoTestHelper, _ ->
|
||||
performTest(cryptoTestHelper, roomShouldBeEncrypted = false) { room ->
|
||||
// Send an encryption Event as an Event (and not as a state event)
|
||||
room.sendService().sendEvent(
|
||||
eventType = EventType.STATE_ROOM_ENCRYPTION,
|
||||
content = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_EncryptionStateEvent() {
|
||||
runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
performTest(cryptoTestHelper, testHelper, roomShouldBeEncrypted = true) { room ->
|
||||
runBlocking {
|
||||
// Send an encryption Event as a State Event
|
||||
room.stateService().sendStateEvent(
|
||||
eventType = EventType.STATE_ROOM_ENCRYPTION,
|
||||
stateKey = "",
|
||||
body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
|
||||
)
|
||||
}
|
||||
}
|
||||
fun test_EncryptionStateEvent() = runCryptoTest(context()) { cryptoTestHelper, _ ->
|
||||
performTest(cryptoTestHelper, roomShouldBeEncrypted = true) { room ->
|
||||
// Send an encryption Event as a State Event
|
||||
room.stateService().sendStateEvent(
|
||||
eventType = EventType.STATE_ROOM_ENCRYPTION,
|
||||
stateKey = "",
|
||||
body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun performTest(cryptoTestHelper: CryptoTestHelper, testHelper: CommonTestHelper, roomShouldBeEncrypted: Boolean, action: (Room) -> Unit) {
|
||||
private suspend fun performTest(cryptoTestHelper: CryptoTestHelper, roomShouldBeEncrypted: Boolean, action: suspend (Room) -> Unit) {
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(encryptedRoom = false)
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val room = aliceSession.getRoom(cryptoTestData.roomId)!!
|
||||
|
||||
room.roomCryptoService().isEncrypted() shouldBe false
|
||||
|
||||
val timeline = room.timelineService().createTimeline(null, TimelineSettings(10))
|
||||
val latch = CountDownLatch(1)
|
||||
timeline.start()
|
||||
waitFor(
|
||||
continueWhen = { timeline.waitForEncryptedMessages() },
|
||||
action = { action.invoke(room) }
|
||||
)
|
||||
timeline.dispose()
|
||||
|
||||
room.roomCryptoService().isEncrypted() shouldBe roomShouldBeEncrypted
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun Timeline.waitForEncryptedMessages() {
|
||||
suspendCancellableCoroutine<Unit> { continuation ->
|
||||
val timelineListener = object : Timeline.Listener {
|
||||
override fun onTimelineFailure(throwable: Throwable) {
|
||||
}
|
||||
|
@ -96,20 +101,12 @@ class EncryptionTest : InstrumentedTest {
|
|||
.filter { it.root.getClearType() == EventType.STATE_ROOM_ENCRYPTION }
|
||||
|
||||
if (newMessages.isNotEmpty()) {
|
||||
timeline.removeListener(this)
|
||||
latch.countDown()
|
||||
removeListener(this)
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
timeline.start()
|
||||
timeline.addListener(timelineListener)
|
||||
|
||||
action.invoke(room)
|
||||
testHelper.await(latch)
|
||||
timeline.dispose()
|
||||
testHelper.waitWithLatch {
|
||||
room.roomCryptoService().isEncrypted() shouldBe roomShouldBeEncrypted
|
||||
it.countDown()
|
||||
}
|
||||
addListener(timelineListener)
|
||||
continuation.invokeOnCancellation { removeListener(timelineListener) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,14 +63,12 @@ class KeyShareTests : InstrumentedTest {
|
|||
Log.v("TEST", "=======> AliceSession 1 is ${aliceSession.sessionParams.deviceId}")
|
||||
|
||||
// Create an encrypted room and add a message
|
||||
val roomId = commonTestHelper.runBlockingTest {
|
||||
aliceSession.roomService().createRoom(
|
||||
CreateRoomParams().apply {
|
||||
visibility = RoomDirectoryVisibility.PRIVATE
|
||||
enableEncryption()
|
||||
}
|
||||
)
|
||||
}
|
||||
val roomId = aliceSession.roomService().createRoom(
|
||||
CreateRoomParams().apply {
|
||||
visibility = RoomDirectoryVisibility.PRIVATE
|
||||
enableEncryption()
|
||||
}
|
||||
)
|
||||
val room = aliceSession.getRoom(roomId)
|
||||
assertNotNull(room)
|
||||
Thread.sleep(4_000)
|
||||
|
@ -94,10 +92,8 @@ class KeyShareTests : InstrumentedTest {
|
|||
assertNotNull(receivedEvent)
|
||||
assert(receivedEvent!!.isEncrypted())
|
||||
|
||||
commonTestHelper.runBlockingTest {
|
||||
mustFail {
|
||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||
}
|
||||
mustFail {
|
||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||
}
|
||||
|
||||
val outgoingRequestsBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||
|
@ -111,15 +107,13 @@ class KeyShareTests : InstrumentedTest {
|
|||
|
||||
var outGoingRequestId: String? = null
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||
.let {
|
||||
val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId }
|
||||
outGoingRequestId = outgoing?.requestId
|
||||
outgoing != null
|
||||
}
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||
.let {
|
||||
val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId }
|
||||
outGoingRequestId = outgoing?.requestId
|
||||
outgoing != null
|
||||
}
|
||||
}
|
||||
Log.v("TEST", "=======> Outgoing requet Id is $outGoingRequestId")
|
||||
|
||||
|
@ -131,9 +125,8 @@ class KeyShareTests : InstrumentedTest {
|
|||
|
||||
// The first session should see an incoming request
|
||||
// the request should be refused, because the device is not trusted
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
// DEBUG LOGS
|
||||
commonTestHelper.retryPeriodically {
|
||||
// DEBUG LOGS
|
||||
// aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
|
||||
// Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
|
||||
// Log.v("TEST", "=========================")
|
||||
|
@ -143,32 +136,27 @@ class KeyShareTests : InstrumentedTest {
|
|||
// Log.v("TEST", "=========================")
|
||||
// }
|
||||
|
||||
val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
|
||||
incoming != null
|
||||
}
|
||||
val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
|
||||
incoming != null
|
||||
}
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
// DEBUG LOGS
|
||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequests().forEach { keyRequest ->
|
||||
Log.v("TEST", "=========================")
|
||||
Log.v("TEST", "requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}")
|
||||
Log.v("TEST", "replies -> ${keyRequest.results.joinToString { it.toString() }}")
|
||||
Log.v("TEST", "=========================")
|
||||
}
|
||||
|
||||
val outgoing = aliceSession2.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
|
||||
val reply = outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
val resultCode = (reply?.result as? RequestResult.Failure)?.code
|
||||
resultCode == WithHeldCode.UNVERIFIED
|
||||
commonTestHelper.retryPeriodically {
|
||||
// DEBUG LOGS
|
||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequests().forEach { keyRequest ->
|
||||
Log.v("TEST", "=========================")
|
||||
Log.v("TEST", "requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}")
|
||||
Log.v("TEST", "replies -> ${keyRequest.results.joinToString { it.toString() }}")
|
||||
Log.v("TEST", "=========================")
|
||||
}
|
||||
|
||||
val outgoing = aliceSession2.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
|
||||
val reply = outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
val resultCode = (reply?.result as? RequestResult.Failure)?.code
|
||||
resultCode == WithHeldCode.UNVERIFIED
|
||||
}
|
||||
|
||||
commonTestHelper.runBlockingTest {
|
||||
mustFail {
|
||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||
}
|
||||
mustFail {
|
||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||
}
|
||||
|
||||
// Mark the device as trusted
|
||||
|
@ -210,12 +198,10 @@ class KeyShareTests : InstrumentedTest {
|
|||
// As it was share previously alice should accept to reshare
|
||||
bobSession.cryptoService().reRequestRoomKeyForEvent(sentEvent.root)
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val outgoing = bobSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val aliceReply = outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
aliceReply != null && aliceReply.result is RequestResult.Success
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
val outgoing = bobSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val aliceReply = outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
aliceReply != null && aliceReply.result is RequestResult.Success
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,12 +219,10 @@ class KeyShareTests : InstrumentedTest {
|
|||
val aliceNewSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
|
||||
|
||||
// we wait for alice first session to be aware of that session?
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val newSession = aliceSession.cryptoService().getUserDevices(aliceSession.myUserId)
|
||||
.firstOrNull { it.deviceId == aliceNewSession.sessionParams.deviceId }
|
||||
newSession != null
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
val newSession = aliceSession.cryptoService().getUserDevices(aliceSession.myUserId)
|
||||
.firstOrNull { it.deviceId == aliceNewSession.sessionParams.deviceId }
|
||||
newSession != null
|
||||
}
|
||||
val sentEvent = commonTestHelper.sendTextMessage(roomFromAlice, "Hello", 1).first()
|
||||
val sentEventMegolmSession = sentEvent.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
|
@ -247,13 +231,11 @@ class KeyShareTests : InstrumentedTest {
|
|||
// As it was share previously alice should accept to reshare
|
||||
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvent.root)
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val ownDeviceReply =
|
||||
outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
ownDeviceReply != null && ownDeviceReply.result is RequestResult.Success
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val ownDeviceReply =
|
||||
outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
ownDeviceReply != null && ownDeviceReply.result is RequestResult.Success
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -277,12 +259,10 @@ class KeyShareTests : InstrumentedTest {
|
|||
commonTestHelper.syncSession(aliceNewSession)
|
||||
|
||||
// we wait bob first session to be aware of that session?
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val newSession = bobSession.cryptoService().getUserDevices(aliceSession.myUserId)
|
||||
.firstOrNull { it.deviceId == aliceNewSession.sessionParams.deviceId }
|
||||
newSession != null
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
val newSession = bobSession.cryptoService().getUserDevices(aliceSession.myUserId)
|
||||
.firstOrNull { it.deviceId == aliceNewSession.sessionParams.deviceId }
|
||||
newSession != null
|
||||
}
|
||||
|
||||
val newEvent = commonTestHelper.sendTextMessage(roomFromBob, "The New", 1).first()
|
||||
|
@ -304,26 +284,22 @@ class KeyShareTests : InstrumentedTest {
|
|||
aliceNewSession.cryptoService().enableKeyGossiping(true)
|
||||
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(newEvent.root)
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val ownDeviceReply = outgoing?.results
|
||||
?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
val result = ownDeviceReply?.result
|
||||
Log.v("TEST", "own device result is $result")
|
||||
result != null && result is RequestResult.Failure && result.code == WithHeldCode.UNVERIFIED
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val ownDeviceReply = outgoing?.results
|
||||
?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
val result = ownDeviceReply?.result
|
||||
Log.v("TEST", "own device result is $result")
|
||||
result != null && result is RequestResult.Failure && result.code == WithHeldCode.UNVERIFIED
|
||||
}
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val bobDeviceReply = outgoing?.results
|
||||
?.firstOrNull { it.userId == bobSession.myUserId && it.fromDevice == bobSession.sessionParams.deviceId }
|
||||
val result = bobDeviceReply?.result
|
||||
Log.v("TEST", "bob device result is $result")
|
||||
result != null && result is RequestResult.Success && result.chainIndex > 0
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val bobDeviceReply = outgoing?.results
|
||||
?.firstOrNull { it.userId == bobSession.myUserId && it.fromDevice == bobSession.sessionParams.deviceId }
|
||||
val result = bobDeviceReply?.result
|
||||
Log.v("TEST", "bob device result is $result")
|
||||
result != null && result is RequestResult.Success && result.chainIndex > 0
|
||||
}
|
||||
|
||||
// it's a success but still can't decrypt first message
|
||||
|
@ -337,21 +313,19 @@ class KeyShareTests : InstrumentedTest {
|
|||
// Let's now try to request
|
||||
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvents.first().root)
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
// DEBUG LOGS
|
||||
aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().forEach { keyRequest ->
|
||||
Log.v("TEST", "=========================")
|
||||
Log.v("TEST", "requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}")
|
||||
Log.v("TEST", "replies -> ${keyRequest.results.joinToString { it.toString() }}")
|
||||
Log.v("TEST", "=========================")
|
||||
}
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val ownDeviceReply =
|
||||
outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
val result = ownDeviceReply?.result
|
||||
result != null && result is RequestResult.Success && result.chainIndex == 0
|
||||
commonTestHelper.retryPeriodically {
|
||||
// DEBUG LOGS
|
||||
aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().forEach { keyRequest ->
|
||||
Log.v("TEST", "=========================")
|
||||
Log.v("TEST", "requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId}")
|
||||
Log.v("TEST", "replies -> ${keyRequest.results.joinToString { it.toString() }}")
|
||||
Log.v("TEST", "=========================")
|
||||
}
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val ownDeviceReply =
|
||||
outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
val result = ownDeviceReply?.result
|
||||
result != null && result is RequestResult.Success && result.chainIndex == 0
|
||||
}
|
||||
|
||||
// now the new session should be able to decrypt all!
|
||||
|
@ -363,13 +337,11 @@ class KeyShareTests : InstrumentedTest {
|
|||
)
|
||||
|
||||
// Additional test, can we check that bob replied successfully but with a ratcheted key
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val bobReply = outgoing?.results?.firstOrNull { it.userId == bobSession.myUserId }
|
||||
val result = bobReply?.result
|
||||
result != null && result is RequestResult.Success && result.chainIndex == 3
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val bobReply = outgoing?.results?.firstOrNull { it.userId == bobSession.myUserId }
|
||||
val result = bobReply?.result
|
||||
result != null && result is RequestResult.Success && result.chainIndex == 3
|
||||
}
|
||||
|
||||
commonTestHelper.signOutAndClose(aliceNewSession)
|
||||
|
@ -394,12 +366,10 @@ class KeyShareTests : InstrumentedTest {
|
|||
val aliceNewSession = commonTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
|
||||
|
||||
// we wait bob first session to be aware of that session?
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val newSession = bobSession.cryptoService().getUserDevices(aliceSession.myUserId)
|
||||
.firstOrNull { it.deviceId == aliceNewSession.sessionParams.deviceId }
|
||||
newSession != null
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
val newSession = bobSession.cryptoService().getUserDevices(aliceSession.myUserId)
|
||||
.firstOrNull { it.deviceId == aliceNewSession.sessionParams.deviceId }
|
||||
newSession != null
|
||||
}
|
||||
|
||||
val newEvent = commonTestHelper.sendTextMessage(roomFromBob, "The New", 1).first()
|
||||
|
@ -430,14 +400,12 @@ class KeyShareTests : InstrumentedTest {
|
|||
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvents.first().root)
|
||||
|
||||
// Should get a reply from bob and not from alice
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
// Log.d("#TEST", "outgoing key requests :${aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().joinToString { it.sessionId ?: "?" }}")
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val bobReply = outgoing?.results?.firstOrNull { it.userId == bobSession.myUserId }
|
||||
val result = bobReply?.result
|
||||
result != null && result is RequestResult.Success && result.chainIndex == 3
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
// Log.d("#TEST", "outgoing key requests :${aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().joinToString { it.sessionId ?: "?" }}")
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val bobReply = outgoing?.results?.firstOrNull { it.userId == bobSession.myUserId }
|
||||
val result = bobReply?.result
|
||||
result != null && result is RequestResult.Success && result.chainIndex == 3
|
||||
}
|
||||
|
||||
val outgoingReq = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
|
@ -450,14 +418,12 @@ class KeyShareTests : InstrumentedTest {
|
|||
aliceSession.syncService().startSync(true)
|
||||
|
||||
// We should now get a reply from first session
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val ownDeviceReply =
|
||||
outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
val result = ownDeviceReply?.result
|
||||
result != null && result is RequestResult.Success && result.chainIndex == 0
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
val outgoing = aliceNewSession.cryptoService().getOutgoingRoomKeyRequests().firstOrNull { it.sessionId == sentEventMegolmSession }
|
||||
val ownDeviceReply =
|
||||
outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
val result = ownDeviceReply?.result
|
||||
result != null && result is RequestResult.Success && result.chainIndex == 0
|
||||
}
|
||||
|
||||
// It should be in sent then cancel
|
||||
|
|
|
@ -80,10 +80,8 @@ class WithHeldTests : InstrumentedTest {
|
|||
val timelineEvent = testHelper.sendTextMessage(roomAlicePOV, "Hello Bob", 1).first()
|
||||
|
||||
// await for bob unverified session to get the message
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(timelineEvent.eventId) != null
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(timelineEvent.eventId) != null
|
||||
}
|
||||
|
||||
val eventBobPOV = bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(timelineEvent.eventId)!!
|
||||
|
@ -95,60 +93,52 @@ class WithHeldTests : InstrumentedTest {
|
|||
|
||||
// Bob should not be able to decrypt because the keys is withheld
|
||||
// .. might need to wait a bit for stability?
|
||||
testHelper.runBlockingTest {
|
||||
mustFail(
|
||||
message = "This session should not be able to decrypt",
|
||||
failureBlock = { failure ->
|
||||
val type = (failure as MXCryptoError.Base).errorType
|
||||
val technicalMessage = failure.technicalMessage
|
||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
|
||||
}
|
||||
) {
|
||||
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
||||
}
|
||||
mustFail(
|
||||
message = "This session should not be able to decrypt",
|
||||
failureBlock = { failure ->
|
||||
val type = (failure as MXCryptoError.Base).errorType
|
||||
val technicalMessage = failure.technicalMessage
|
||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
|
||||
}
|
||||
) {
|
||||
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
||||
}
|
||||
|
||||
// Let's see if the reply we got from bob first session is unverified
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
bobUnverifiedSession.cryptoService().getOutgoingRoomKeyRequests()
|
||||
.firstOrNull { it.sessionId == megolmSessionId }
|
||||
?.results
|
||||
?.firstOrNull { it.fromDevice == bobSession.sessionParams.deviceId }
|
||||
?.result
|
||||
?.let {
|
||||
it as? RequestResult.Failure
|
||||
}
|
||||
?.code == WithHeldCode.UNVERIFIED
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
bobUnverifiedSession.cryptoService().getOutgoingRoomKeyRequests()
|
||||
.firstOrNull { it.sessionId == megolmSessionId }
|
||||
?.results
|
||||
?.firstOrNull { it.fromDevice == bobSession.sessionParams.deviceId }
|
||||
?.result
|
||||
?.let {
|
||||
it as? RequestResult.Failure
|
||||
}
|
||||
?.code == WithHeldCode.UNVERIFIED
|
||||
}
|
||||
// enable back sending to unverified
|
||||
aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(false)
|
||||
|
||||
val secondEvent = testHelper.sendTextMessage(roomAlicePOV, "Verify your device!!", 1).first()
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val ev = bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(secondEvent.eventId)
|
||||
// wait until it's decrypted
|
||||
ev?.root?.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val ev = bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(secondEvent.eventId)
|
||||
// wait until it's decrypted
|
||||
ev?.root?.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
|
||||
// Previous message should still be undecryptable (partially withheld session)
|
||||
// .. might need to wait a bit for stability?
|
||||
testHelper.runBlockingTest {
|
||||
mustFail(
|
||||
message = "This session should not be able to decrypt",
|
||||
failureBlock = { failure ->
|
||||
val type = (failure as MXCryptoError.Base).errorType
|
||||
val technicalMessage = failure.technicalMessage
|
||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
|
||||
}) {
|
||||
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
||||
}
|
||||
mustFail(
|
||||
message = "This session should not be able to decrypt",
|
||||
failureBlock = { failure ->
|
||||
val type = (failure as MXCryptoError.Base).errorType
|
||||
val technicalMessage = failure.technicalMessage
|
||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
|
||||
}) {
|
||||
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,26 +167,22 @@ class WithHeldTests : InstrumentedTest {
|
|||
val eventId = testHelper.sendTextMessage(roomAlicePov, "first message", 1).first().eventId
|
||||
|
||||
// await for bob session to get the message
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId) != null
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId) != null
|
||||
}
|
||||
|
||||
// Previous message should still be undecryptable (partially withheld session)
|
||||
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)
|
||||
// .. might need to wait a bit for stability?
|
||||
testHelper.runBlockingTest {
|
||||
mustFail(
|
||||
message = "This session should not be able to decrypt",
|
||||
failureBlock = { failure ->
|
||||
val type = (failure as MXCryptoError.Base).errorType
|
||||
val technicalMessage = failure.technicalMessage
|
||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.NO_OLM.value, technicalMessage)
|
||||
}) {
|
||||
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
|
||||
}
|
||||
mustFail(
|
||||
message = "This session should not be able to decrypt",
|
||||
failureBlock = { failure ->
|
||||
val type = (failure as MXCryptoError.Base).errorType
|
||||
val technicalMessage = failure.technicalMessage
|
||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.NO_OLM.value, technicalMessage)
|
||||
}) {
|
||||
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
|
||||
}
|
||||
|
||||
// Ensure that alice has marked the session to be shared with bob
|
||||
|
@ -216,10 +202,8 @@ class WithHeldTests : InstrumentedTest {
|
|||
|
||||
// Check that the
|
||||
// await for bob SecondSession session to get the message
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(secondMessageId) != null
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(secondMessageId) != null
|
||||
}
|
||||
|
||||
val chainIndex2 = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(
|
||||
|
@ -258,27 +242,21 @@ class WithHeldTests : InstrumentedTest {
|
|||
var sessionId: String? = null
|
||||
// Check that the
|
||||
// await for bob SecondSession session to get the message
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)?.also {
|
||||
// try to decrypt and force key request
|
||||
tryOrNull {
|
||||
testHelper.runBlockingTest {
|
||||
bobSecondSession.cryptoService().decryptEvent(it.root, "")
|
||||
}
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)?.also {
|
||||
// try to decrypt and force key request
|
||||
tryOrNull {
|
||||
bobSecondSession.cryptoService().decryptEvent(it.root, "")
|
||||
}
|
||||
sessionId = timeLineEvent?.root?.content?.toModel<EncryptedEventContent>()?.sessionId
|
||||
timeLineEvent != null
|
||||
}
|
||||
sessionId = timeLineEvent?.root?.content?.toModel<EncryptedEventContent>()?.sessionId
|
||||
timeLineEvent != null
|
||||
}
|
||||
|
||||
// Check that bob second session requested the key
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val wc = bobSecondSession.cryptoService().getWithHeldMegolmSession(roomAlicePov.roomId, sessionId!!)
|
||||
wc?.code == WithHeldCode.UNAUTHORISED
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val wc = bobSecondSession.cryptoService().getWithHeldMegolmSession(roomAlicePov.roomId, sessionId!!)
|
||||
wc?.code == WithHeldCode.UNAUTHORISED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ internal data class KeysBackupScenarioData(
|
|||
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
|
||||
val aliceSession2: Session
|
||||
) {
|
||||
fun cleanUp(testHelper: CommonTestHelper) {
|
||||
suspend fun cleanUp(testHelper: CommonTestHelper) {
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
testHelper.signOutAndClose(aliceSession2)
|
||||
}
|
||||
|
|
|
@ -18,10 +18,10 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
|
|||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.LargeTest
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Rule
|
||||
|
@ -48,9 +48,11 @@ import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
|||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
||||
import org.matrix.android.sdk.common.RetryTestRule
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import org.matrix.android.sdk.common.TestMatrixCallback
|
||||
import org.matrix.android.sdk.common.waitFor
|
||||
import java.security.InvalidParameterException
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
|
@ -116,7 +118,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
assertFalse(keysBackup.isEnabled())
|
||||
|
||||
val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> {
|
||||
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> {
|
||||
keysBackup.prepareKeysBackupVersion(null, null, it)
|
||||
}
|
||||
|
||||
|
@ -133,7 +135,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
*/
|
||||
@Test
|
||||
fun createKeysBackupVersionTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
|
||||
val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
|
||||
cryptoTestHelper.initializeCrossSigning(bobSession)
|
||||
|
||||
|
@ -143,14 +144,14 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
assertFalse(keysBackup.isEnabled())
|
||||
|
||||
val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> {
|
||||
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> {
|
||||
keysBackup.prepareKeysBackupVersion(null, null, it)
|
||||
}
|
||||
|
||||
assertFalse(keysBackup.isEnabled())
|
||||
|
||||
// Create the version
|
||||
val version = testHelper.doSync<KeysVersion> {
|
||||
val version = testHelper.waitForCallback<KeysVersion> {
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
|
||||
|
@ -158,10 +159,10 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertTrue(keysBackup.isEnabled())
|
||||
|
||||
// Check that it's signed with MSK
|
||||
val versionResult = testHelper.doSync<KeysVersionResult?> {
|
||||
val versionResult = testHelper.waitForCallback<KeysVersionResult?> {
|
||||
keysBackup.getVersion(version.version, it)
|
||||
}
|
||||
val trust = testHelper.doSync<KeysBackupVersionTrust> {
|
||||
val trust = testHelper.waitForCallback<KeysBackupVersionTrust> {
|
||||
keysBackup.getKeysBackupTrust(versionResult!!, it)
|
||||
}
|
||||
|
||||
|
@ -257,7 +258,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
var lastBackedUpKeysProgress = 0
|
||||
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
assertEquals(nbOfKeys, total)
|
||||
|
@ -299,7 +300,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
val keyBackupCreationInfo = keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup).megolmBackupCreationInfo
|
||||
|
||||
// - Check encryptGroupSession() returns stg
|
||||
val keyBackupData = testHelper.runBlockingTest { keysBackup.encryptGroupSession(session) }
|
||||
val keyBackupData = keysBackup.encryptGroupSession(session)
|
||||
assertNotNull(keyBackupData)
|
||||
assertNotNull(keyBackupData!!.sessionData)
|
||||
|
||||
|
@ -334,7 +335,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
// - Restore the e2e backup from the homeserver
|
||||
val importRoomKeysResult = testHelper.doSync<ImportRoomKeysResult> {
|
||||
val importRoomKeysResult = testHelper.waitForCallback<ImportRoomKeysResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
|
@ -379,7 +380,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
// assertTrue(unsentRequest != null || sentRequest != null)
|
||||
//
|
||||
// // - Restore the e2e backup from the homeserver
|
||||
// val importRoomKeysResult = mTestHelper.doSync<ImportRoomKeysResult> {
|
||||
// val importRoomKeysResult = mTestHelper.doSyncSuspending<> { }<ImportRoomKeysResult> {
|
||||
// testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
// testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
// null,
|
||||
|
@ -429,7 +430,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
||||
|
||||
// - Trust the backup from the new device
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
true,
|
||||
|
@ -445,14 +446,14 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
|
||||
|
||||
// - Retrieve the last version from the server
|
||||
val keysVersionResult = testHelper.doSync<KeysBackupLastVersionResult> {
|
||||
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
|
||||
}.toKeysVersionResult()
|
||||
|
||||
// - It must be the same
|
||||
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
|
||||
|
||||
val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
|
||||
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
|
||||
}
|
||||
|
||||
|
@ -489,7 +490,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
||||
|
||||
// - Trust the backup from the new device with the recovery key
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
|
@ -505,14 +506,14 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
|
||||
|
||||
// - Retrieve the last version from the server
|
||||
val keysVersionResult = testHelper.doSync<KeysBackupLastVersionResult> {
|
||||
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
|
||||
}.toKeysVersionResult()
|
||||
|
||||
// - It must be the same
|
||||
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
|
||||
|
||||
val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
|
||||
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
|
||||
}
|
||||
|
||||
|
@ -547,13 +548,13 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
||||
|
||||
// - Try to trust the backup from the new device with a wrong recovery key
|
||||
val latch = CountDownLatch(1)
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
"Bad recovery key",
|
||||
TestMatrixCallback(latch, false)
|
||||
)
|
||||
testHelper.await(latch)
|
||||
testHelper.waitForCallbackError<Unit> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
"Bad recovery key",
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
// - The new device must still see the previous backup as not trusted
|
||||
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
|
||||
|
@ -591,7 +592,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
||||
|
||||
// - Trust the backup from the new device with the password
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
password,
|
||||
|
@ -607,14 +608,14 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
|
||||
|
||||
// - Retrieve the last version from the server
|
||||
val keysVersionResult = testHelper.doSync<KeysBackupLastVersionResult> {
|
||||
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
|
||||
}.toKeysVersionResult()
|
||||
|
||||
// - It must be the same
|
||||
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
|
||||
|
||||
val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
|
||||
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
|
||||
}
|
||||
|
||||
|
@ -652,13 +653,13 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
||||
|
||||
// - Try to trust the backup from the new device with a wrong password
|
||||
val latch = CountDownLatch(1)
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
badPassword,
|
||||
TestMatrixCallback(latch, false)
|
||||
)
|
||||
testHelper.await(latch)
|
||||
testHelper.waitForCallbackError<Unit> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
badPassword,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
// - The new device must still see the previous backup as not trusted
|
||||
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
|
||||
|
@ -679,26 +680,21 @@ class KeysBackupTest : InstrumentedTest {
|
|||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
|
||||
|
||||
// - Try to restore the e2e backup with a wrong recovery key
|
||||
val latch2 = CountDownLatch(1)
|
||||
var importRoomKeysResult: ImportRoomKeysResult? = null
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
object : TestMatrixCallback<ImportRoomKeysResult>(latch2, false) {
|
||||
override fun onSuccess(data: ImportRoomKeysResult) {
|
||||
importRoomKeysResult = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
}
|
||||
)
|
||||
testHelper.await(latch2)
|
||||
val importRoomKeysResult = testHelper.waitForCallbackError<ImportRoomKeysResult> {
|
||||
keysBackupService.restoreKeysWithRecoveryKey(
|
||||
keysBackupService.keysBackupVersion!!,
|
||||
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
// onSuccess may not have been called
|
||||
assertNull(importRoomKeysResult)
|
||||
assertTrue(importRoomKeysResult is InvalidParameterException)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -718,7 +714,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
// - Restore the e2e backup with the password
|
||||
val steps = ArrayList<StepProgressListener.Step>()
|
||||
|
||||
val importRoomKeysResult = testHelper.doSync<ImportRoomKeysResult> {
|
||||
val importRoomKeysResult = testHelper.waitForCallback<ImportRoomKeysResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
password,
|
||||
|
@ -771,26 +767,21 @@ class KeysBackupTest : InstrumentedTest {
|
|||
val wrongPassword = "passw0rd"
|
||||
|
||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
|
||||
|
||||
// - Try to restore the e2e backup with a wrong password
|
||||
val latch2 = CountDownLatch(1)
|
||||
var importRoomKeysResult: ImportRoomKeysResult? = null
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
wrongPassword,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
object : TestMatrixCallback<ImportRoomKeysResult>(latch2, false) {
|
||||
override fun onSuccess(data: ImportRoomKeysResult) {
|
||||
importRoomKeysResult = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
}
|
||||
)
|
||||
testHelper.await(latch2)
|
||||
val importRoomKeysResult = testHelper.waitForCallbackError<ImportRoomKeysResult> {
|
||||
keysBackupService.restoreKeyBackupWithPassword(
|
||||
keysBackupService.keysBackupVersion!!,
|
||||
wrongPassword,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
// onSuccess may not have been called
|
||||
assertNull(importRoomKeysResult)
|
||||
assertTrue(importRoomKeysResult is InvalidParameterException)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -808,7 +799,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||
|
||||
// - Restore the e2e backup with the recovery key.
|
||||
val importRoomKeysResult = testHelper.doSync<ImportRoomKeysResult> {
|
||||
val importRoomKeysResult = testHelper.waitForCallback<ImportRoomKeysResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
|
@ -833,26 +824,21 @@ class KeysBackupTest : InstrumentedTest {
|
|||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
|
||||
|
||||
// - Try to restore the e2e backup with a password
|
||||
val latch2 = CountDownLatch(1)
|
||||
var importRoomKeysResult: ImportRoomKeysResult? = null
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
"password",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
object : TestMatrixCallback<ImportRoomKeysResult>(latch2, false) {
|
||||
override fun onSuccess(data: ImportRoomKeysResult) {
|
||||
importRoomKeysResult = data
|
||||
super.onSuccess(data)
|
||||
}
|
||||
}
|
||||
)
|
||||
testHelper.await(latch2)
|
||||
val importRoomKeysResult = testHelper.waitForCallbackError<ImportRoomKeysResult> {
|
||||
keysBackupService.restoreKeyBackupWithPassword(
|
||||
keysBackupService.keysBackupVersion!!,
|
||||
"password",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
// onSuccess may not have been called
|
||||
assertNull(importRoomKeysResult)
|
||||
assertTrue(importRoomKeysResult is IllegalStateException)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -874,12 +860,12 @@ class KeysBackupTest : InstrumentedTest {
|
|||
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
// Get key backup version from the homeserver
|
||||
val keysVersionResult = testHelper.doSync<KeysBackupLastVersionResult> {
|
||||
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> {
|
||||
keysBackup.getCurrentVersion(it)
|
||||
}.toKeysVersionResult()
|
||||
|
||||
// - Check the returned KeyBackupVersion is trusted
|
||||
val keysBackupVersionTrust = testHelper.doSync<KeysBackupVersionTrust> {
|
||||
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> {
|
||||
keysBackup.getKeysBackupTrust(keysVersionResult!!, it)
|
||||
}
|
||||
|
||||
|
@ -918,34 +904,39 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertFalse(keysBackup.isEnabled())
|
||||
|
||||
// Wait for keys backup to be finished
|
||||
val latch0 = CountDownLatch(1)
|
||||
var count = 0
|
||||
keysBackup.addListener(object : KeysBackupStateListener {
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
// Check the backup completes
|
||||
if (newState == KeysBackupState.ReadyToBackUp) {
|
||||
count++
|
||||
waitFor(
|
||||
continueWhen = {
|
||||
suspendCancellableCoroutine<Unit> { continuation ->
|
||||
val listener = object : KeysBackupStateListener {
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
// Check the backup completes
|
||||
if (newState == KeysBackupState.ReadyToBackUp) {
|
||||
count++
|
||||
|
||||
if (count == 2) {
|
||||
// Remove itself from the list of listeners
|
||||
keysBackup.removeListener(this)
|
||||
|
||||
latch0.countDown()
|
||||
if (count == 2) {
|
||||
// Remove itself from the list of listeners
|
||||
keysBackup.removeListener(this)
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
keysBackup.addListener(listener)
|
||||
continuation.invokeOnCancellation { keysBackup.removeListener(listener) }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// - Make alice back up her keys to her homeserver
|
||||
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
},
|
||||
action = {
|
||||
// - Make alice back up her keys to her homeserver
|
||||
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
},
|
||||
)
|
||||
|
||||
assertTrue(keysBackup.isEnabled())
|
||||
|
||||
testHelper.await(latch0)
|
||||
|
||||
// - Create a new backup with fake data on the homeserver, directly using the rest client
|
||||
val megolmBackupCreationInfo = cryptoTestHelper.createFakeMegolmBackupCreationInfo()
|
||||
testHelper.doSync<KeysVersion> {
|
||||
testHelper.waitForCallback<KeysVersion> {
|
||||
(keysBackup as DefaultKeysBackupService).createFakeKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
|
||||
|
@ -953,9 +944,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
(cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store.resetBackupMarkers()
|
||||
|
||||
// - Make alice back up all her keys again
|
||||
val latch2 = CountDownLatch(1)
|
||||
keysBackup.backupAllGroupSessions(null, TestMatrixCallback(latch2, false))
|
||||
testHelper.await(latch2)
|
||||
testHelper.waitForCallbackError<Unit> { keysBackup.backupAllGroupSessions(null, it) }
|
||||
|
||||
// -> That must fail and her backup state must be WrongBackUpVersion
|
||||
assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.getState())
|
||||
|
@ -991,7 +980,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
// Wait for keys backup to finish by asking again to backup keys.
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
keysBackup.backupAllGroupSessions(null, it)
|
||||
}
|
||||
|
||||
|
@ -1016,19 +1005,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
val stateObserver2 = StateObserver(keysBackup2)
|
||||
|
||||
var isSuccessful = false
|
||||
val latch2 = CountDownLatch(1)
|
||||
keysBackup2.backupAllGroupSessions(
|
||||
null,
|
||||
object : TestMatrixCallback<Unit>(latch2, false) {
|
||||
override fun onSuccess(data: Unit) {
|
||||
isSuccessful = true
|
||||
super.onSuccess(data)
|
||||
}
|
||||
})
|
||||
testHelper.await(latch2)
|
||||
|
||||
assertFalse(isSuccessful)
|
||||
testHelper.waitForCallbackError<Unit> { keysBackup2.backupAllGroupSessions(null, it) }
|
||||
|
||||
// Backup state must be NotTrusted
|
||||
assertEquals("Backup state must be NotTrusted", KeysBackupState.NotTrusted, keysBackup2.getState())
|
||||
|
@ -1042,24 +1019,25 @@ class KeysBackupTest : InstrumentedTest {
|
|||
)
|
||||
|
||||
// -> Backup should automatically enable on the new device
|
||||
val latch4 = CountDownLatch(1)
|
||||
keysBackup2.addListener(object : KeysBackupStateListener {
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
// Check the backup completes
|
||||
if (keysBackup2.getState() == KeysBackupState.ReadyToBackUp) {
|
||||
// Remove itself from the list of listeners
|
||||
keysBackup2.removeListener(this)
|
||||
|
||||
latch4.countDown()
|
||||
suspendCancellableCoroutine<Unit> { continuation ->
|
||||
val listener = object : KeysBackupStateListener {
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
// Check the backup completes
|
||||
if (keysBackup2.getState() == KeysBackupState.ReadyToBackUp) {
|
||||
// Remove itself from the list of listeners
|
||||
keysBackup2.removeListener(this)
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
testHelper.await(latch4)
|
||||
keysBackup2.addListener(listener)
|
||||
continuation.invokeOnCancellation { keysBackup2.removeListener(listener) }
|
||||
}
|
||||
|
||||
// -> It must use the same backup version
|
||||
assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion)
|
||||
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it)
|
||||
}
|
||||
|
||||
|
@ -1092,7 +1070,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertTrue(keysBackup.isEnabled())
|
||||
|
||||
// Delete the backup
|
||||
testHelper.doSync<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) }
|
||||
testHelper.waitForCallback<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) }
|
||||
|
||||
// Backup is now disabled
|
||||
assertFalse(keysBackup.isEnabled())
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.crypto.keysbackup
|
||||
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.junit.Assert
|
||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
@ -29,7 +30,7 @@ import org.matrix.android.sdk.common.CryptoTestHelper
|
|||
import org.matrix.android.sdk.common.assertDictEquals
|
||||
import org.matrix.android.sdk.common.assertListEquals
|
||||
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
internal class KeysBackupTestHelper(
|
||||
private val testHelper: CommonTestHelper,
|
||||
|
@ -47,7 +48,7 @@ internal class KeysBackupTestHelper(
|
|||
*
|
||||
* @param password optional password
|
||||
*/
|
||||
fun createKeysBackupScenarioWithPassword(password: String?): KeysBackupScenarioData {
|
||||
suspend fun createKeysBackupScenarioWithPassword(password: String?): KeysBackupScenarioData {
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
waitForKeybackUpBatching()
|
||||
|
@ -64,7 +65,7 @@ internal class KeysBackupTestHelper(
|
|||
|
||||
var lastProgress = 0
|
||||
var lastTotal = 0
|
||||
testHelper.doSync<Unit> {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
lastProgress = progress
|
||||
|
@ -97,13 +98,13 @@ internal class KeysBackupTestHelper(
|
|||
)
|
||||
}
|
||||
|
||||
fun prepareAndCreateKeysBackupData(
|
||||
suspend fun prepareAndCreateKeysBackupData(
|
||||
keysBackup: KeysBackupService,
|
||||
password: String? = null
|
||||
): PrepareKeysBackupDataResult {
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
val megolmBackupCreationInfo = testHelper.doSync<MegolmBackupCreationInfo> {
|
||||
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> {
|
||||
keysBackup.prepareKeysBackupVersion(password, null, it)
|
||||
}
|
||||
|
||||
|
@ -112,7 +113,7 @@ internal class KeysBackupTestHelper(
|
|||
Assert.assertFalse("Key backup should not be enabled before creation", keysBackup.isEnabled())
|
||||
|
||||
// Create the version
|
||||
val keysVersion = testHelper.doSync<KeysVersion> {
|
||||
val keysVersion = testHelper.waitForCallback<KeysVersion> {
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
|
||||
|
@ -129,25 +130,26 @@ internal class KeysBackupTestHelper(
|
|||
* As KeysBackup is doing asynchronous call to update its internal state, this method help to wait for the
|
||||
* KeysBackup object to be in the specified state
|
||||
*/
|
||||
fun waitForKeysBackupToBeInState(session: Session, state: KeysBackupState) {
|
||||
suspend fun waitForKeysBackupToBeInState(session: Session, state: KeysBackupState) {
|
||||
// If already in the wanted state, return
|
||||
if (session.cryptoService().keysBackupService().getState() == state) {
|
||||
val keysBackupService = session.cryptoService().keysBackupService()
|
||||
if (keysBackupService.getState() == state) {
|
||||
return
|
||||
}
|
||||
|
||||
// Else observe state changes
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
session.cryptoService().keysBackupService().addListener(object : KeysBackupStateListener {
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
if (newState == state) {
|
||||
session.cryptoService().keysBackupService().removeListener(this)
|
||||
latch.countDown()
|
||||
suspendCancellableCoroutine<Unit> { continuation ->
|
||||
val listener = object : KeysBackupStateListener {
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
if (newState == state) {
|
||||
keysBackupService.removeListener(this)
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
testHelper.await(latch)
|
||||
keysBackupService.addListener(listener)
|
||||
continuation.invokeOnCancellation { keysBackupService.removeListener(listener) }
|
||||
}
|
||||
}
|
||||
|
||||
fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) {
|
||||
|
|
|
@ -58,18 +58,16 @@ class ReplayAttackTest : InstrumentedTest {
|
|||
val fakeEventWithTheSameIndex =
|
||||
sentEvents[0].copy(eventId = fakeEventId, root = sentEvents[0].root.copy(eventId = fakeEventId))
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
// Lets assume we are from the main timelineId
|
||||
val timelineId = "timelineId"
|
||||
// Lets decrypt the original event
|
||||
aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId)
|
||||
// Lets decrypt the fake event that will have the same message index
|
||||
val exception = assertFailsWith<MXCryptoError.Base> {
|
||||
// An exception should be thrown while the same index would have been used for the previous decryption
|
||||
aliceSession.cryptoService().decryptEvent(fakeEventWithTheSameIndex.root, timelineId)
|
||||
}
|
||||
assertEquals(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, exception.errorType)
|
||||
// Lets assume we are from the main timelineId
|
||||
val timelineId = "timelineId"
|
||||
// Lets decrypt the original event
|
||||
aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId)
|
||||
// Lets decrypt the fake event that will have the same message index
|
||||
val exception = assertFailsWith<MXCryptoError.Base> {
|
||||
// An exception should be thrown while the same index would have been used for the previous decryption
|
||||
aliceSession.cryptoService().decryptEvent(fakeEventWithTheSameIndex.root, timelineId)
|
||||
}
|
||||
assertEquals(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, exception.errorType)
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
|
@ -93,17 +91,15 @@ class ReplayAttackTest : InstrumentedTest {
|
|||
Assert.assertTrue("Message should be sent", sentEvents.size == 1)
|
||||
assertEquals(sentEvents.size, 1)
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
// Lets assume we are from the main timelineId
|
||||
val timelineId = "timelineId"
|
||||
// Lets decrypt the original event
|
||||
// Lets assume we are from the main timelineId
|
||||
val timelineId = "timelineId"
|
||||
// Lets decrypt the original event
|
||||
aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId)
|
||||
try {
|
||||
// Lets try to decrypt the same event
|
||||
aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId)
|
||||
try {
|
||||
// Lets try to decrypt the same event
|
||||
aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId)
|
||||
} catch (ex: Throwable) {
|
||||
fail("Shouldn't throw a decryption error for same event")
|
||||
}
|
||||
} catch (ex: Throwable) {
|
||||
fail("Shouldn't throw a decryption error for same event")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.crypto.ssss
|
||||
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
|
@ -37,12 +36,12 @@ import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
|
|||
import org.matrix.android.sdk.api.session.securestorage.SecretStorageKeyContent
|
||||
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageError
|
||||
import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toBase64NoPadding
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import org.matrix.android.sdk.common.first
|
||||
import org.matrix.android.sdk.common.onMain
|
||||
import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
|
@ -64,22 +63,14 @@ class QuadSTests : InstrumentedTest {
|
|||
|
||||
val TEST_KEY_ID = "my.test.Key"
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner)
|
||||
}
|
||||
quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner)
|
||||
|
||||
var accountData: UserAccountDataEvent? = null
|
||||
// Assert Account data is updated
|
||||
testHelper.waitWithLatch {
|
||||
val liveAccountData = aliceSession.accountDataService().getLiveUserAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID")
|
||||
val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
|
||||
if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") {
|
||||
accountData = t.getOrNull()
|
||||
}
|
||||
it.countDown()
|
||||
}
|
||||
liveAccountData.observeForever(accountDataObserver)
|
||||
}
|
||||
val accountData = aliceSession.accountDataService()
|
||||
.onMain { getLiveUserAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") }
|
||||
.first { it.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID" }
|
||||
.getOrNull()
|
||||
|
||||
assertNotNull("Key should be stored in account data", accountData)
|
||||
val parsed = SecretStorageKeyContent.fromJson(accountData!!.content)
|
||||
assertNotNull("Key Content cannot be parsed", parsed)
|
||||
|
@ -87,20 +78,13 @@ class QuadSTests : InstrumentedTest {
|
|||
assertEquals("Unexpected key name", "Test Key", parsed.name)
|
||||
assertNull("Key was not generated from passphrase", parsed.passphrase)
|
||||
|
||||
var defaultKeyAccountData: UserAccountDataEvent? = null
|
||||
quadS.setDefaultKey(TEST_KEY_ID)
|
||||
val defaultKeyAccountData = aliceSession.accountDataService()
|
||||
.onMain { getLiveUserAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID) }
|
||||
.first { it.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID }
|
||||
.getOrNull()
|
||||
|
||||
// Set as default key
|
||||
testHelper.waitWithLatch { latch ->
|
||||
quadS.setDefaultKey(TEST_KEY_ID)
|
||||
val liveDefAccountData =
|
||||
aliceSession.accountDataService().getLiveUserAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
|
||||
val accountDefDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
|
||||
if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) {
|
||||
defaultKeyAccountData = t.getOrNull()!!
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
liveDefAccountData.observeForever(accountDefDataObserver)
|
||||
}
|
||||
assertNotNull(defaultKeyAccountData?.content)
|
||||
assertEquals("Unexpected default key ${defaultKeyAccountData?.content}", TEST_KEY_ID, defaultKeyAccountData?.content?.get("key"))
|
||||
|
||||
|
@ -112,21 +96,19 @@ class QuadSTests : InstrumentedTest {
|
|||
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
val keyId = "My.Key"
|
||||
val info = generatedSecret(testHelper, aliceSession, keyId, true)
|
||||
val info = generatedSecret(aliceSession, keyId, true)
|
||||
|
||||
val keySpec = RawBytesKeySpec.fromRecoveryKey(info.recoveryKey)
|
||||
|
||||
// Store a secret
|
||||
val clearSecret = "42".toByteArray().toBase64NoPadding()
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.sharedSecretStorageService().storeSecret(
|
||||
"secret.of.life",
|
||||
clearSecret,
|
||||
listOf(KeyRef(null, keySpec)) // default key
|
||||
)
|
||||
}
|
||||
aliceSession.sharedSecretStorageService().storeSecret(
|
||||
"secret.of.life",
|
||||
clearSecret,
|
||||
listOf(KeyRef(null, keySpec)) // default key
|
||||
)
|
||||
|
||||
val secretAccountData = assertAccountData(testHelper, aliceSession, "secret.of.life")
|
||||
val secretAccountData = assertAccountData(aliceSession, "secret.of.life")
|
||||
|
||||
val encryptedContent = secretAccountData.content["encrypted"] as? Map<*, *>
|
||||
assertNotNull("Element should be encrypted", encryptedContent)
|
||||
|
@ -139,13 +121,11 @@ class QuadSTests : InstrumentedTest {
|
|||
|
||||
// Try to decrypt??
|
||||
|
||||
val decryptedSecret = testHelper.runBlockingTest {
|
||||
aliceSession.sharedSecretStorageService().getSecret(
|
||||
"secret.of.life",
|
||||
null, // default key
|
||||
keySpec!!
|
||||
)
|
||||
}
|
||||
val decryptedSecret = aliceSession.sharedSecretStorageService().getSecret(
|
||||
"secret.of.life",
|
||||
null, // default key
|
||||
keySpec!!
|
||||
)
|
||||
|
||||
assertEquals("Secret mismatch", clearSecret, decryptedSecret)
|
||||
}
|
||||
|
@ -159,14 +139,10 @@ class QuadSTests : InstrumentedTest {
|
|||
|
||||
val TEST_KEY_ID = "my.test.Key"
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner)
|
||||
}
|
||||
quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner)
|
||||
|
||||
// Test that we don't need to wait for an account data sync to access directly the keyid from DB
|
||||
testHelper.runBlockingTest {
|
||||
quadS.setDefaultKey(TEST_KEY_ID)
|
||||
}
|
||||
quadS.setDefaultKey(TEST_KEY_ID)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -174,22 +150,20 @@ class QuadSTests : InstrumentedTest {
|
|||
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
val keyId1 = "Key.1"
|
||||
val key1Info = generatedSecret(testHelper, aliceSession, keyId1, true)
|
||||
val key1Info = generatedSecret(aliceSession, keyId1, true)
|
||||
val keyId2 = "Key2"
|
||||
val key2Info = generatedSecret(testHelper, aliceSession, keyId2, true)
|
||||
val key2Info = generatedSecret(aliceSession, keyId2, true)
|
||||
|
||||
val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.sharedSecretStorageService().storeSecret(
|
||||
"my.secret",
|
||||
mySecretText.toByteArray().toBase64NoPadding(),
|
||||
listOf(
|
||||
KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)),
|
||||
KeyRef(keyId2, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey))
|
||||
)
|
||||
)
|
||||
}
|
||||
aliceSession.sharedSecretStorageService().storeSecret(
|
||||
"my.secret",
|
||||
mySecretText.toByteArray().toBase64NoPadding(),
|
||||
listOf(
|
||||
KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)),
|
||||
KeyRef(keyId2, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey))
|
||||
)
|
||||
)
|
||||
|
||||
val accountDataEvent = aliceSession.accountDataService().getUserAccountDataEvent("my.secret")
|
||||
val encryptedContent = accountDataEvent?.content?.get("encrypted") as? Map<*, *>
|
||||
|
@ -200,21 +174,17 @@ class QuadSTests : InstrumentedTest {
|
|||
assertNotNull(encryptedContent?.get(keyId2))
|
||||
|
||||
// Assert that can decrypt with both keys
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.sharedSecretStorageService().getSecret(
|
||||
"my.secret",
|
||||
keyId1,
|
||||
RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!
|
||||
)
|
||||
}
|
||||
aliceSession.sharedSecretStorageService().getSecret(
|
||||
"my.secret",
|
||||
keyId1,
|
||||
RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!
|
||||
)
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.sharedSecretStorageService().getSecret(
|
||||
"my.secret",
|
||||
keyId2,
|
||||
RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!
|
||||
)
|
||||
}
|
||||
aliceSession.sharedSecretStorageService().getSecret(
|
||||
"my.secret",
|
||||
keyId2,
|
||||
RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -224,104 +194,84 @@ class QuadSTests : InstrumentedTest {
|
|||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
val keyId1 = "Key.1"
|
||||
val passphrase = "The good pass phrase"
|
||||
val key1Info = generatedSecretFromPassphrase(testHelper, aliceSession, passphrase, keyId1, true)
|
||||
val key1Info = generatedSecretFromPassphrase(aliceSession, passphrase, keyId1, true)
|
||||
|
||||
val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
aliceSession.sharedSecretStorageService().storeSecret(
|
||||
"my.secret",
|
||||
mySecretText.toByteArray().toBase64NoPadding(),
|
||||
listOf(KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)))
|
||||
)
|
||||
}
|
||||
aliceSession.sharedSecretStorageService().storeSecret(
|
||||
"my.secret",
|
||||
mySecretText.toByteArray().toBase64NoPadding(),
|
||||
listOf(KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)))
|
||||
)
|
||||
|
||||
testHelper.runBlockingTest {
|
||||
try {
|
||||
aliceSession.sharedSecretStorageService().getSecret(
|
||||
"my.secret",
|
||||
keyId1,
|
||||
RawBytesKeySpec.fromPassphrase(
|
||||
"A bad passphrase",
|
||||
key1Info.content?.passphrase?.salt ?: "",
|
||||
key1Info.content?.passphrase?.iterations ?: 0,
|
||||
null
|
||||
)
|
||||
)
|
||||
} catch (throwable: Throwable) {
|
||||
assert(throwable is SharedSecretStorageError.BadMac)
|
||||
}
|
||||
}
|
||||
|
||||
// Now try with correct key
|
||||
testHelper.runBlockingTest {
|
||||
try {
|
||||
aliceSession.sharedSecretStorageService().getSecret(
|
||||
"my.secret",
|
||||
keyId1,
|
||||
RawBytesKeySpec.fromPassphrase(
|
||||
passphrase,
|
||||
"A bad passphrase",
|
||||
key1Info.content?.passphrase?.salt ?: "",
|
||||
key1Info.content?.passphrase?.iterations ?: 0,
|
||||
null
|
||||
)
|
||||
)
|
||||
} catch (throwable: Throwable) {
|
||||
assert(throwable is SharedSecretStorageError.BadMac)
|
||||
}
|
||||
|
||||
// Now try with correct key
|
||||
aliceSession.sharedSecretStorageService().getSecret(
|
||||
"my.secret",
|
||||
keyId1,
|
||||
RawBytesKeySpec.fromPassphrase(
|
||||
passphrase,
|
||||
key1Info.content?.passphrase?.salt ?: "",
|
||||
key1Info.content?.passphrase?.iterations ?: 0,
|
||||
null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun assertAccountData(testHelper: CommonTestHelper, session: Session, type: String): UserAccountDataEvent {
|
||||
var accountData: UserAccountDataEvent? = null
|
||||
testHelper.waitWithLatch {
|
||||
val liveAccountData = session.accountDataService().getLiveUserAccountDataEvent(type)
|
||||
val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t ->
|
||||
if (t?.getOrNull()?.type == type) {
|
||||
accountData = t.getOrNull()
|
||||
it.countDown()
|
||||
}
|
||||
}
|
||||
liveAccountData.observeForever(accountDataObserver)
|
||||
}
|
||||
private suspend fun assertAccountData(session: Session, type: String): UserAccountDataEvent {
|
||||
val accountData = session.accountDataService()
|
||||
.onMain { getLiveUserAccountDataEvent(type) }
|
||||
.first { it.getOrNull()?.type == type }
|
||||
.getOrNull()
|
||||
|
||||
assertNotNull("Account Data type:$type should be found", accountData)
|
||||
return accountData!!
|
||||
}
|
||||
|
||||
private fun generatedSecret(testHelper: CommonTestHelper, session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
|
||||
private suspend fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
|
||||
val quadS = session.sharedSecretStorageService()
|
||||
|
||||
val creationInfo = testHelper.runBlockingTest {
|
||||
quadS.generateKey(keyId, null, keyId, emptyKeySigner)
|
||||
}
|
||||
val creationInfo = quadS.generateKey(keyId, null, keyId, emptyKeySigner)
|
||||
|
||||
assertAccountData(testHelper, session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
|
||||
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
|
||||
|
||||
if (asDefault) {
|
||||
testHelper.runBlockingTest {
|
||||
quadS.setDefaultKey(keyId)
|
||||
}
|
||||
assertAccountData(testHelper, session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
|
||||
quadS.setDefaultKey(keyId)
|
||||
assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
|
||||
}
|
||||
|
||||
return creationInfo
|
||||
}
|
||||
|
||||
private fun generatedSecretFromPassphrase(testHelper: CommonTestHelper, session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
|
||||
private suspend fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
|
||||
val quadS = session.sharedSecretStorageService()
|
||||
|
||||
val creationInfo = testHelper.runBlockingTest {
|
||||
quadS.generateKeyWithPassphrase(
|
||||
keyId,
|
||||
keyId,
|
||||
passphrase,
|
||||
emptyKeySigner,
|
||||
null
|
||||
)
|
||||
}
|
||||
val creationInfo = quadS.generateKeyWithPassphrase(
|
||||
keyId,
|
||||
keyId,
|
||||
passphrase,
|
||||
emptyKeySigner,
|
||||
null
|
||||
)
|
||||
|
||||
assertAccountData(testHelper, session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
|
||||
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
|
||||
if (asDefault) {
|
||||
testHelper.runBlockingTest {
|
||||
quadS.setDefaultKey(keyId)
|
||||
}
|
||||
assertAccountData(testHelper, session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
|
||||
quadS.setDefaultKey(keyId)
|
||||
assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
|
||||
}
|
||||
|
||||
return creationInfo
|
||||
|
|
|
@ -547,23 +547,19 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
var requestID: String? = null
|
||||
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
|
||||
requestID = prAlicePOV?.transactionId
|
||||
Log.v("TEST", "== alicePOV is $prAlicePOV")
|
||||
prAlicePOV?.transactionId != null && prAlicePOV.localId == req.localId
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
|
||||
requestID = prAlicePOV?.transactionId
|
||||
Log.v("TEST", "== alicePOV is $prAlicePOV")
|
||||
prAlicePOV?.transactionId != null && prAlicePOV.localId == req.localId
|
||||
}
|
||||
|
||||
Log.v("TEST", "== requestID is $requestID")
|
||||
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
val prBobPOV = bobVerificationService.getExistingVerificationRequests(aliceSession.myUserId).firstOrNull()
|
||||
Log.v("TEST", "== prBobPOV is $prBobPOV")
|
||||
prBobPOV?.transactionId == requestID
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val prBobPOV = bobVerificationService.getExistingVerificationRequests(aliceSession.myUserId).firstOrNull()
|
||||
Log.v("TEST", "== prBobPOV is $prBobPOV")
|
||||
prBobPOV?.transactionId == requestID
|
||||
}
|
||||
|
||||
bobVerificationService.readyPendingVerification(
|
||||
|
@ -573,12 +569,10 @@ class SASTest : InstrumentedTest {
|
|||
)
|
||||
|
||||
// wait for alice to get the ready
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
|
||||
Log.v("TEST", "== prAlicePOV is $prAlicePOV")
|
||||
prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
|
||||
Log.v("TEST", "== prAlicePOV is $prAlicePOV")
|
||||
prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null
|
||||
}
|
||||
|
||||
// Start concurrent!
|
||||
|
@ -602,20 +596,16 @@ class SASTest : InstrumentedTest {
|
|||
var alicePovTx: SasVerificationTransaction?
|
||||
var bobPovTx: SasVerificationTransaction?
|
||||
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID!!) as? SasVerificationTransaction
|
||||
Log.v("TEST", "== alicePovTx is $alicePovTx")
|
||||
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID!!) as? SasVerificationTransaction
|
||||
Log.v("TEST", "== alicePovTx is $alicePovTx")
|
||||
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
// wait for alice to get the ready
|
||||
testHelper.waitWithLatch {
|
||||
testHelper.retryPeriodicallyWithLatch(it) {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID!!) as? SasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is $bobPovTx")
|
||||
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID!!) as? SasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is $bobPovTx")
|
||||
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -164,7 +164,7 @@ class VerificationTest : InstrumentedTest {
|
|||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
testHelper.doSync<Unit> { callback ->
|
||||
testHelper.waitForCallback<Unit> { callback ->
|
||||
aliceSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
|
@ -181,7 +181,7 @@ class VerificationTest : InstrumentedTest {
|
|||
)
|
||||
}
|
||||
|
||||
testHelper.doSync<Unit> { callback ->
|
||||
testHelper.waitForCallback<Unit> { callback ->
|
||||
bobSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
|
@ -261,7 +261,11 @@ class VerificationTest : InstrumentedTest {
|
|||
|
||||
val aliceSessionToVerify = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
val aliceSessionThatVerifies = testHelper.logIntoAccount(aliceSessionToVerify.myUserId, TestConstants.PASSWORD, defaultSessionParams)
|
||||
val aliceSessionThatReceivesCanceledEvent = testHelper.logIntoAccount(aliceSessionToVerify.myUserId, TestConstants.PASSWORD, defaultSessionParams)
|
||||
val aliceSessionThatReceivesCanceledEvent = testHelper.logIntoAccount(
|
||||
aliceSessionToVerify.myUserId,
|
||||
TestConstants.PASSWORD,
|
||||
defaultSessionParams
|
||||
)
|
||||
|
||||
val verificationMethods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW)
|
||||
|
||||
|
@ -286,11 +290,9 @@ class VerificationTest : InstrumentedTest {
|
|||
otherDevices = listOfNotNull(aliceSessionThatVerifies.sessionParams.deviceId, aliceSessionThatReceivesCanceledEvent.sessionParams.deviceId),
|
||||
)
|
||||
|
||||
testHelper.waitWithLatch { latch ->
|
||||
testHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val requests = serviceOfUserWhoReceivesCancellation.getExistingVerificationRequests(aliceSessionToVerify.myUserId)
|
||||
requests.any { it.cancelConclusion == CancelCode.AcceptedByAnotherDevice }
|
||||
}
|
||||
testHelper.retryPeriodically {
|
||||
val requests = serviceOfUserWhoReceivesCancellation.getExistingVerificationRequests(aliceSessionToVerify.myUserId)
|
||||
requests.any { it.cancelConclusion == CancelCode.AcceptedByAnotherDevice }
|
||||
}
|
||||
|
||||
testHelper.signOutAndClose(aliceSessionToVerify)
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
package org.matrix.android.sdk.session.room.timeline
|
||||
|
||||
import androidx.test.filters.LargeTest
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.amshove.kluent.internal.assertEquals
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Ignore
|
||||
|
@ -35,6 +35,9 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
|||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import org.matrix.android.sdk.common.waitFor
|
||||
import org.matrix.android.sdk.common.wrapWithTimeout
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
|
@ -69,30 +72,36 @@ class TimelineSimpleBackPaginationTest : InstrumentedTest {
|
|||
val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(30))
|
||||
bobTimeline.start()
|
||||
|
||||
commonTestHelper.waitWithLatch(timeout = TestConstants.timeOutMillis * 10) {
|
||||
val listener = object : Timeline.Listener {
|
||||
waitFor(
|
||||
continueWhen = {
|
||||
wrapWithTimeout(timeout = TestConstants.timeOutMillis * 10) {
|
||||
suspendCancellableCoroutine<Unit> { continuation ->
|
||||
val listener = object : Timeline.Listener {
|
||||
|
||||
override fun onStateUpdated(direction: Timeline.Direction, state: Timeline.PaginationState) {
|
||||
if (direction == Timeline.Direction.FORWARDS) {
|
||||
return
|
||||
override fun onStateUpdated(direction: Timeline.Direction, state: Timeline.PaginationState) {
|
||||
if (direction == Timeline.Direction.FORWARDS) {
|
||||
return
|
||||
}
|
||||
if (state.hasMoreToLoad && !state.loading) {
|
||||
bobTimeline.paginate(Timeline.Direction.BACKWARDS, 30)
|
||||
} else if (!state.hasMoreToLoad) {
|
||||
bobTimeline.removeListener(this)
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
bobTimeline.addListener(listener)
|
||||
continuation.invokeOnCancellation { bobTimeline.removeListener(listener) }
|
||||
}
|
||||
}
|
||||
if (state.hasMoreToLoad && !state.loading) {
|
||||
bobTimeline.paginate(Timeline.Direction.BACKWARDS, 30)
|
||||
} else if (!state.hasMoreToLoad) {
|
||||
bobTimeline.removeListener(this)
|
||||
it.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
bobTimeline.addListener(listener)
|
||||
bobTimeline.paginate(Timeline.Direction.BACKWARDS, 30)
|
||||
}
|
||||
},
|
||||
action = { bobTimeline.paginate(Timeline.Direction.BACKWARDS, 30) }
|
||||
)
|
||||
|
||||
assertEquals(false, bobTimeline.hasMoreToLoad(Timeline.Direction.FORWARDS))
|
||||
assertEquals(false, bobTimeline.hasMoreToLoad(Timeline.Direction.BACKWARDS))
|
||||
|
||||
val onlySentEvents = runBlocking {
|
||||
bobTimeline.getSnapshot()
|
||||
}
|
||||
val onlySentEvents = bobTimeline.getSnapshot()
|
||||
.filter {
|
||||
it.root.isTextMessage()
|
||||
}.filter {
|
||||
|
|
|
@ -85,9 +85,7 @@ class SearchMessagesTest : InstrumentedTest {
|
|||
2
|
||||
)
|
||||
|
||||
val data = commonTestHelper.runBlockingTest {
|
||||
block.invoke(cryptoTestData)
|
||||
}
|
||||
val data = block.invoke(cryptoTestData)
|
||||
|
||||
assertTrue(data.results?.size == 2)
|
||||
assertTrue(
|
||||
|
|
|
@ -55,15 +55,11 @@ class SpaceCreationTest : InstrumentedTest {
|
|||
val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true))
|
||||
val roomName = "My Space"
|
||||
val topic = "A public space for test"
|
||||
var spaceId: String = ""
|
||||
commonTestHelper.runBlockingTest {
|
||||
spaceId = session.spaceService().createSpace(roomName, topic, null, true)
|
||||
}
|
||||
val spaceId = session.spaceService().createSpace(roomName, topic, null, true)
|
||||
|
||||
commonTestHelper.waitWithLatch {
|
||||
commonTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
session.spaceService().getSpace(spaceId)?.asRoom()?.roomSummary()?.name != null
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
val roomSummary = session.spaceService().getSpace(spaceId)?.asRoom()?.roomSummary()
|
||||
roomSummary?.name == roomName && roomSummary.topic == topic
|
||||
}
|
||||
|
||||
val syncedSpace = session.spaceService().getSpace(spaceId)
|
||||
|
@ -79,14 +75,12 @@ class SpaceCreationTest : InstrumentedTest {
|
|||
assertEquals("Room type should be space", RoomType.SPACE, createContent?.type)
|
||||
|
||||
var powerLevelsContent: PowerLevelsContent? = null
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
powerLevelsContent = syncedSpace.asRoom()
|
||||
.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)
|
||||
?.content
|
||||
?.toModel<PowerLevelsContent>()
|
||||
powerLevelsContent != null
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
powerLevelsContent = syncedSpace.asRoom()
|
||||
.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)
|
||||
?.content
|
||||
?.toModel<PowerLevelsContent>()
|
||||
powerLevelsContent != null
|
||||
}
|
||||
assertEquals("Space-rooms should be created with a power level for events_default of 100", 100, powerLevelsContent?.eventsDefault)
|
||||
|
||||
|
@ -116,19 +110,13 @@ class SpaceCreationTest : InstrumentedTest {
|
|||
|
||||
val roomName = "My Space"
|
||||
val topic = "A public space for test"
|
||||
val spaceId: String
|
||||
runBlocking {
|
||||
spaceId = aliceSession.spaceService().createSpace(roomName, topic, null, true)
|
||||
// wait a bit to let the summary update it self :/
|
||||
delay(400)
|
||||
}
|
||||
val spaceId = aliceSession.spaceService().createSpace(roomName, topic, null, true)
|
||||
// wait a bit to let the summary update it self :/
|
||||
delay(400)
|
||||
|
||||
// Try to join from bob, it's a public space no need to invite
|
||||
|
||||
val joinResult: JoinSpaceResult
|
||||
runBlocking {
|
||||
joinResult = bobSession.spaceService().joinSpace(spaceId)
|
||||
}
|
||||
val joinResult = bobSession.spaceService().joinSpace(spaceId)
|
||||
|
||||
assertEquals(JoinSpaceResult.Success, joinResult)
|
||||
|
||||
|
@ -152,43 +140,24 @@ class SpaceCreationTest : InstrumentedTest {
|
|||
val syncedSpace = aliceSession.spaceService().getSpace(spaceId)
|
||||
|
||||
// create a room
|
||||
var firstChild: String? = null
|
||||
commonTestHelper.waitWithLatch {
|
||||
firstChild = aliceSession.roomService().createRoom(CreateRoomParams().apply {
|
||||
this.name = "FirstRoom"
|
||||
this.topic = "Description of first room"
|
||||
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
|
||||
})
|
||||
it.countDown()
|
||||
}
|
||||
val firstChild: String = aliceSession.roomService().createRoom(CreateRoomParams().apply {
|
||||
this.name = "FirstRoom"
|
||||
this.topic = "Description of first room"
|
||||
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
|
||||
})
|
||||
|
||||
commonTestHelper.waitWithLatch {
|
||||
syncedSpace?.addChildren(firstChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", suggested = true)
|
||||
it.countDown()
|
||||
}
|
||||
syncedSpace?.addChildren(firstChild, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", suggested = true)
|
||||
|
||||
var secondChild: String? = null
|
||||
commonTestHelper.waitWithLatch {
|
||||
secondChild = aliceSession.roomService().createRoom(CreateRoomParams().apply {
|
||||
this.name = "SecondRoom"
|
||||
this.topic = "Description of second room"
|
||||
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
|
||||
})
|
||||
it.countDown()
|
||||
}
|
||||
val secondChild = aliceSession.roomService().createRoom(CreateRoomParams().apply {
|
||||
this.name = "SecondRoom"
|
||||
this.topic = "Description of second room"
|
||||
this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
|
||||
})
|
||||
|
||||
commonTestHelper.waitWithLatch {
|
||||
syncedSpace?.addChildren(secondChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", suggested = true)
|
||||
it.countDown()
|
||||
}
|
||||
syncedSpace?.addChildren(secondChild, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", suggested = true)
|
||||
|
||||
// Try to join from bob, it's a public space no need to invite
|
||||
var joinResult: JoinSpaceResult? = null
|
||||
commonTestHelper.waitWithLatch {
|
||||
joinResult = bobSession.spaceService().joinSpace(spaceId)
|
||||
// wait a bit to let the summary update it self :/
|
||||
it.countDown()
|
||||
}
|
||||
val joinResult = bobSession.spaceService().joinSpace(spaceId)
|
||||
|
||||
assertEquals(JoinSpaceResult.Success, joinResult)
|
||||
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
package org.matrix.android.sdk.session.space
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Observer
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.FixMethodOrder
|
||||
|
@ -39,16 +37,17 @@ import org.matrix.android.sdk.api.session.getRoomSummary
|
|||
import org.matrix.android.sdk.api.session.room.getStateEvent
|
||||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset
|
||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||
import org.matrix.android.sdk.api.session.room.powerlevels.Role
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
import org.matrix.android.sdk.common.first
|
||||
import org.matrix.android.sdk.common.onMain
|
||||
import org.matrix.android.sdk.common.waitFor
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
|
@ -60,40 +59,28 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||
val spaceName = "My Space"
|
||||
val topic = "A public space for test"
|
||||
var spaceId = ""
|
||||
commonTestHelper.runBlockingTest {
|
||||
spaceId = session.spaceService().createSpace(spaceName, topic, null, true)
|
||||
}
|
||||
val spaceId = session.spaceService().createSpace(spaceName, topic, null, true)
|
||||
|
||||
val syncedSpace = session.spaceService().getSpace(spaceId)
|
||||
|
||||
var roomId = ""
|
||||
commonTestHelper.runBlockingTest {
|
||||
roomId = session.roomService().createRoom(CreateRoomParams().apply { name = "General" })
|
||||
}
|
||||
val roomId = session.roomService().createRoom(CreateRoomParams().apply { name = "General" })
|
||||
|
||||
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||
|
||||
commonTestHelper.runBlockingTest {
|
||||
syncedSpace!!.addChildren(roomId, viaServers, null, true)
|
||||
}
|
||||
syncedSpace!!.addChildren(roomId, viaServers, null, true)
|
||||
|
||||
commonTestHelper.runBlockingTest {
|
||||
session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers)
|
||||
}
|
||||
session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers)
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val parents = session.getRoom(roomId)?.roomSummary()?.spaceParents
|
||||
val canonicalParents = session.getRoom(roomId)?.roomSummary()?.spaceParents?.filter { it.canonical == true }
|
||||
parents?.forEach {
|
||||
Log.d("## TEST", "parent : $it")
|
||||
}
|
||||
parents?.size == 1 &&
|
||||
parents.first().roomSummary?.name == spaceName &&
|
||||
canonicalParents?.size == 1 &&
|
||||
canonicalParents.first().roomSummary?.name == spaceName
|
||||
commonTestHelper.retryPeriodically {
|
||||
val parents = session.getRoom(roomId)?.roomSummary()?.spaceParents
|
||||
val canonicalParents = session.getRoom(roomId)?.roomSummary()?.spaceParents?.filter { it.canonical == true }
|
||||
parents?.forEach {
|
||||
Log.d("## TEST", "parent : $it")
|
||||
}
|
||||
parents?.size == 1 &&
|
||||
parents.first().roomSummary?.name == spaceName &&
|
||||
canonicalParents?.size == 1 &&
|
||||
canonicalParents.first().roomSummary?.name == spaceName
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,7 +156,6 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||
|
||||
val spaceAInfo = createPublicSpace(
|
||||
commonTestHelper,
|
||||
session, "SpaceA",
|
||||
listOf(
|
||||
Triple("A1", true /*auto-join*/, true/*canonical*/),
|
||||
|
@ -178,7 +164,6 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
)
|
||||
|
||||
/* val spaceBInfo = */ createPublicSpace(
|
||||
commonTestHelper,
|
||||
session, "SpaceB",
|
||||
listOf(
|
||||
Triple("B1", true /*auto-join*/, true/*canonical*/),
|
||||
|
@ -188,7 +173,6 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
)
|
||||
|
||||
val spaceCInfo = createPublicSpace(
|
||||
commonTestHelper,
|
||||
session, "SpaceC",
|
||||
listOf(
|
||||
Triple("C1", true /*auto-join*/, true/*canonical*/),
|
||||
|
@ -199,22 +183,12 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
// add C as a subspace of A
|
||||
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
|
||||
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||
commonTestHelper.runBlockingTest {
|
||||
spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
|
||||
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
|
||||
}
|
||||
spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
|
||||
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
|
||||
|
||||
// Create orphan rooms
|
||||
|
||||
var orphan1 = ""
|
||||
commonTestHelper.runBlockingTest {
|
||||
orphan1 = session.roomService().createRoom(CreateRoomParams().apply { name = "O1" })
|
||||
}
|
||||
|
||||
var orphan2 = ""
|
||||
commonTestHelper.runBlockingTest {
|
||||
orphan2 = session.roomService().createRoom(CreateRoomParams().apply { name = "O2" })
|
||||
}
|
||||
val orphan1 = session.roomService().createRoom(CreateRoomParams().apply { name = "O1" })
|
||||
val orphan2 = session.roomService().createRoom(CreateRoomParams().apply { name = "O2" })
|
||||
|
||||
val allRooms = session.roomService().getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) })
|
||||
|
||||
|
@ -235,15 +209,15 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
assertTrue("A1 should be a grand child of A", aChildren.any { it.name == "C2" })
|
||||
|
||||
// Add a non canonical child and check that it does not appear as orphan
|
||||
commonTestHelper.runBlockingTest {
|
||||
val a3 = session.roomService().createRoom(CreateRoomParams().apply { name = "A3" })
|
||||
spaceA!!.addChildren(a3, viaServers, null, false)
|
||||
}
|
||||
val a3 = session.roomService().createRoom(CreateRoomParams().apply { name = "A3" })
|
||||
spaceA.addChildren(a3, viaServers, null, false)
|
||||
|
||||
val orphansUpdate = session.roomService().onMain {
|
||||
getRoomSummariesLive(roomSummaryQueryParams {
|
||||
spaceFilter = SpaceFilter.OrphanRooms
|
||||
})
|
||||
}.first { it.size == 2 }
|
||||
|
||||
Thread.sleep(6_000)
|
||||
val orphansUpdate = session.roomService().getRoomSummaries(roomSummaryQueryParams {
|
||||
spaceFilter = SpaceFilter.OrphanRooms
|
||||
})
|
||||
assertEquals("Unexpected number of orphan rooms ${orphansUpdate.map { it.name }}", 2, orphansUpdate.size)
|
||||
}
|
||||
|
||||
|
@ -253,7 +227,6 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||
|
||||
val spaceAInfo = createPublicSpace(
|
||||
commonTestHelper,
|
||||
session, "SpaceA",
|
||||
listOf(
|
||||
Triple("A1", true /*auto-join*/, true/*canonical*/),
|
||||
|
@ -262,7 +235,6 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
)
|
||||
|
||||
val spaceCInfo = createPublicSpace(
|
||||
commonTestHelper,
|
||||
session, "SpaceC",
|
||||
listOf(
|
||||
Triple("C1", true /*auto-join*/, true/*canonical*/),
|
||||
|
@ -273,16 +245,12 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
// add C as a subspace of A
|
||||
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
|
||||
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||
commonTestHelper.runBlockingTest {
|
||||
spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
|
||||
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
|
||||
}
|
||||
spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
|
||||
session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
|
||||
|
||||
// add back A as subspace of C
|
||||
commonTestHelper.runBlockingTest {
|
||||
val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId)
|
||||
spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true)
|
||||
}
|
||||
val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId)
|
||||
spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true)
|
||||
|
||||
// A -> C -> A
|
||||
|
||||
|
@ -300,7 +268,6 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||
|
||||
val spaceAInfo = createPublicSpace(
|
||||
commonTestHelper,
|
||||
session,
|
||||
"SpaceA",
|
||||
listOf(
|
||||
|
@ -310,7 +277,6 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
)
|
||||
|
||||
val spaceBInfo = createPublicSpace(
|
||||
commonTestHelper,
|
||||
session,
|
||||
"SpaceB",
|
||||
listOf(
|
||||
|
@ -323,13 +289,10 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
// add B as a subspace of A
|
||||
val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
|
||||
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||
commonTestHelper.runBlockingTest {
|
||||
spaceA!!.addChildren(spaceBInfo.spaceId, viaServers, null, true)
|
||||
session.spaceService().setSpaceParent(spaceBInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
|
||||
}
|
||||
spaceA!!.addChildren(spaceBInfo.spaceId, viaServers, null, true)
|
||||
session.spaceService().setSpaceParent(spaceBInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
|
||||
|
||||
val spaceCInfo = createPublicSpace(
|
||||
commonTestHelper,
|
||||
session,
|
||||
"SpaceC",
|
||||
listOf(
|
||||
|
@ -338,52 +301,39 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
|
||||
val flatAChildren = session.roomService().getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
|
||||
val childObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(children: List<RoomSummary>?) {
|
||||
// Log.d("## TEST", "Space A flat children update : ${children?.map { it.name }}")
|
||||
System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}")
|
||||
if (children?.any { it.name == "C1" } == true && children.any { it.name == "C2" }) {
|
||||
// B1 has been added live!
|
||||
latch.countDown()
|
||||
flatAChildren.removeObserver(this)
|
||||
val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
|
||||
waitFor(
|
||||
continueWhen = {
|
||||
session.roomService().onMain { getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId) }.first { children ->
|
||||
println("## TEST | Space A flat children update : ${children.map { it.name }}")
|
||||
children.any { it.name == "C1" } && children.any { it.name == "C2" }
|
||||
}
|
||||
},
|
||||
action = {
|
||||
// add C as subspace of B
|
||||
spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
|
||||
}
|
||||
}
|
||||
|
||||
flatAChildren.observeForever(childObserver)
|
||||
|
||||
// add C as subspace of B
|
||||
val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
|
||||
spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
|
||||
|
||||
// C1 and C2 should be in flatten child of A now
|
||||
}
|
||||
)
|
||||
// C1 and C2 should be in flatten child of A now
|
||||
|
||||
// Test part one of the rooms
|
||||
|
||||
val bRoomId = spaceBInfo.roomIds.first()
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
val flatAChildren = session.roomService().getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
|
||||
val childObserver = object : Observer<List<RoomSummary>> {
|
||||
override fun onChanged(children: List<RoomSummary>?) {
|
||||
System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}")
|
||||
if (children?.any { it.roomId == bRoomId } == false) {
|
||||
// B1 has been added live!
|
||||
latch.countDown()
|
||||
flatAChildren.removeObserver(this)
|
||||
waitFor(
|
||||
continueWhen = {
|
||||
// The room should have disappear from flat children
|
||||
session.roomService().onMain { getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId) }.first { children ->
|
||||
println("## TEST | Space A flat children update : ${children.map { it.name }}")
|
||||
!children.any { it.roomId == bRoomId }
|
||||
}
|
||||
},
|
||||
action = {
|
||||
// part from b room
|
||||
session.roomService().leaveRoom(bRoomId)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// The room should have disapear from flat children
|
||||
flatAChildren.observeForever(childObserver)
|
||||
// part from b room
|
||||
session.roomService().leaveRoom(bRoomId)
|
||||
}
|
||||
commonTestHelper.signOutAndClose(session)
|
||||
}
|
||||
|
||||
|
@ -392,68 +342,57 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
val roomIds: List<String>
|
||||
)
|
||||
|
||||
private fun createPublicSpace(
|
||||
commonTestHelper: CommonTestHelper,
|
||||
private suspend fun createPublicSpace(
|
||||
session: Session,
|
||||
spaceName: String,
|
||||
childInfo: List<Triple<String, Boolean, Boolean?>>
|
||||
/** Name, auto-join, canonical*/
|
||||
): TestSpaceCreationResult {
|
||||
var spaceId = ""
|
||||
var roomIds: List<String> = emptyList()
|
||||
commonTestHelper.runBlockingTest {
|
||||
spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true)
|
||||
val syncedSpace = session.spaceService().getSpace(spaceId)
|
||||
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||
val spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true)
|
||||
val syncedSpace = session.spaceService().getSpace(spaceId)
|
||||
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||
|
||||
roomIds = childInfo.map { entry ->
|
||||
session.roomService().createRoom(CreateRoomParams().apply { name = entry.first })
|
||||
}
|
||||
roomIds.forEachIndexed { index, roomId ->
|
||||
syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
|
||||
val canonical = childInfo[index].third
|
||||
if (canonical != null) {
|
||||
session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
|
||||
}
|
||||
val roomIds = childInfo.map { entry ->
|
||||
session.roomService().createRoom(CreateRoomParams().apply { name = entry.first })
|
||||
}
|
||||
roomIds.forEachIndexed { index, roomId ->
|
||||
syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
|
||||
val canonical = childInfo[index].third
|
||||
if (canonical != null) {
|
||||
session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
|
||||
}
|
||||
}
|
||||
return TestSpaceCreationResult(spaceId, roomIds)
|
||||
}
|
||||
|
||||
private fun createPrivateSpace(
|
||||
commonTestHelper: CommonTestHelper,
|
||||
private suspend fun createPrivateSpace(
|
||||
session: Session,
|
||||
spaceName: String,
|
||||
childInfo: List<Triple<String, Boolean, Boolean?>>
|
||||
/** Name, auto-join, canonical*/
|
||||
): TestSpaceCreationResult {
|
||||
var spaceId = ""
|
||||
var roomIds: List<String> = emptyList()
|
||||
commonTestHelper.runBlockingTest {
|
||||
spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false)
|
||||
val syncedSpace = session.spaceService().getSpace(spaceId)
|
||||
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||
roomIds =
|
||||
childInfo.map { entry ->
|
||||
val homeServerCapabilities = session
|
||||
.homeServerCapabilitiesService()
|
||||
.getHomeServerCapabilities()
|
||||
session.roomService().createRoom(CreateRoomParams().apply {
|
||||
name = entry.first
|
||||
this.featurePreset = RestrictedRoomPreset(
|
||||
homeServerCapabilities,
|
||||
listOf(
|
||||
RoomJoinRulesAllowEntry.restrictedToRoom(spaceId)
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
roomIds.forEachIndexed { index, roomId ->
|
||||
syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
|
||||
val canonical = childInfo[index].third
|
||||
if (canonical != null) {
|
||||
session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
|
||||
}
|
||||
val spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false)
|
||||
val syncedSpace = session.spaceService().getSpace(spaceId)
|
||||
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||
val roomIds = childInfo.map { entry ->
|
||||
val homeServerCapabilities = session
|
||||
.homeServerCapabilitiesService()
|
||||
.getHomeServerCapabilities()
|
||||
session.roomService().createRoom(CreateRoomParams().apply {
|
||||
name = entry.first
|
||||
this.featurePreset = RestrictedRoomPreset(
|
||||
homeServerCapabilities,
|
||||
listOf(
|
||||
RoomJoinRulesAllowEntry.restrictedToRoom(spaceId)
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
roomIds.forEachIndexed { index, roomId ->
|
||||
syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
|
||||
val canonical = childInfo[index].third
|
||||
if (canonical != null) {
|
||||
session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
|
||||
}
|
||||
}
|
||||
return TestSpaceCreationResult(spaceId, roomIds)
|
||||
|
@ -464,7 +403,6 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||
|
||||
/* val spaceAInfo = */ createPublicSpace(
|
||||
commonTestHelper,
|
||||
session, "SpaceA",
|
||||
listOf(
|
||||
Triple("A1", true /*auto-join*/, true/*canonical*/),
|
||||
|
@ -473,7 +411,6 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
)
|
||||
|
||||
val spaceBInfo = createPublicSpace(
|
||||
commonTestHelper,
|
||||
session, "SpaceB",
|
||||
listOf(
|
||||
Triple("B1", true /*auto-join*/, true/*canonical*/),
|
||||
|
@ -483,7 +420,6 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
)
|
||||
|
||||
val spaceCInfo = createPublicSpace(
|
||||
commonTestHelper,
|
||||
session, "SpaceC",
|
||||
listOf(
|
||||
Triple("C1", true /*auto-join*/, true/*canonical*/),
|
||||
|
@ -494,10 +430,8 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
|
||||
|
||||
// add C as subspace of B
|
||||
runBlocking {
|
||||
val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
|
||||
spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
|
||||
}
|
||||
val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
|
||||
spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
|
||||
|
||||
// Thread.sleep(4_000)
|
||||
// + A
|
||||
|
@ -507,11 +441,9 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
// + C
|
||||
// + c1, c2
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val rootSpaces = commonTestHelper.runBlockingTest { session.spaceService().getRootSpaceSummaries() }
|
||||
rootSpaces.size == 2
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
val rootSpaces = session.spaceService().getRootSpaceSummaries()
|
||||
rootSpaces.size == 2
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -521,7 +453,6 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
val bobSession = commonTestHelper.createAccount("Bib", SessionTestParams(true))
|
||||
|
||||
val spaceAInfo = createPrivateSpace(
|
||||
commonTestHelper,
|
||||
aliceSession, "Private Space A",
|
||||
listOf(
|
||||
Triple("General", true /*suggested*/, true/*canonical*/),
|
||||
|
@ -529,85 +460,58 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
|
||||
commonTestHelper.runBlockingTest {
|
||||
aliceSession.getRoom(spaceAInfo.spaceId)!!.membershipService().invite(bobSession.myUserId, null)
|
||||
aliceSession.getRoom(spaceAInfo.spaceId)!!.membershipService().invite(bobSession.myUserId, null)
|
||||
|
||||
bobSession.roomService().joinRoom(spaceAInfo.spaceId, null, emptyList())
|
||||
|
||||
val bobRoomId = bobSession.roomService().createRoom(CreateRoomParams().apply { name = "A Bob Room" })
|
||||
bobSession.getRoom(bobRoomId)!!.membershipService().invite(aliceSession.myUserId)
|
||||
|
||||
aliceSession.roomService().joinRoom(bobRoomId)
|
||||
|
||||
commonTestHelper.retryPeriodically {
|
||||
aliceSession.getRoomSummary(bobRoomId)?.membership?.isActive() == true
|
||||
}
|
||||
|
||||
commonTestHelper.runBlockingTest {
|
||||
bobSession.roomService().joinRoom(spaceAInfo.spaceId, null, emptyList())
|
||||
}
|
||||
bobSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
|
||||
|
||||
var bobRoomId = ""
|
||||
commonTestHelper.runBlockingTest {
|
||||
bobRoomId = bobSession.roomService().createRoom(CreateRoomParams().apply { name = "A Bob Room" })
|
||||
bobSession.getRoom(bobRoomId)!!.membershipService().invite(aliceSession.myUserId)
|
||||
}
|
||||
|
||||
commonTestHelper.runBlockingTest {
|
||||
aliceSession.roomService().joinRoom(bobRoomId)
|
||||
}
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
aliceSession.getRoomSummary(bobRoomId)?.membership?.isActive() == true
|
||||
}
|
||||
}
|
||||
|
||||
commonTestHelper.runBlockingTest {
|
||||
bobSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
|
||||
}
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val stateEvent = aliceSession.getRoom(bobRoomId)!!.getStateEvent(EventType.STATE_SPACE_PARENT, QueryStringValue.Equals(spaceAInfo.spaceId))
|
||||
stateEvent != null
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
val stateEvent = aliceSession.getRoom(bobRoomId)!!.getStateEvent(EventType.STATE_SPACE_PARENT, QueryStringValue.Equals(spaceAInfo.spaceId))
|
||||
stateEvent != null
|
||||
}
|
||||
|
||||
// This should be an invalid space parent relation, because no opposite child and bob is not admin of the space
|
||||
commonTestHelper.runBlockingTest {
|
||||
// we can see the state event
|
||||
// but it is not valid and room is not in hierarchy
|
||||
assertTrue("Bob Room should not be listed as a child of the space", aliceSession.getRoomSummary(bobRoomId)?.flattenParentIds?.isEmpty() == true)
|
||||
}
|
||||
// we can see the state event
|
||||
// but it is not valid and room is not in hierarchy
|
||||
assertTrue("Bob Room should not be listed as a child of the space", aliceSession.getRoomSummary(bobRoomId)?.flattenParentIds?.isEmpty() == true)
|
||||
|
||||
// Let's now try to make alice admin of the room
|
||||
|
||||
commonTestHelper.waitWithLatch {
|
||||
val room = bobSession.getRoom(bobRoomId)!!
|
||||
val currentPLContent = room
|
||||
val room = bobSession.getRoom(bobRoomId)!!
|
||||
val currentPLContent = room
|
||||
.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)
|
||||
?.content
|
||||
.toModel<PowerLevelsContent>()
|
||||
|
||||
val newPowerLevelsContent = currentPLContent
|
||||
?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value)
|
||||
?.toContent()
|
||||
|
||||
room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!)
|
||||
|
||||
commonTestHelper.retryPeriodically {
|
||||
val powerLevelsHelper = aliceSession.getRoom(bobRoomId)!!
|
||||
.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)
|
||||
?.content
|
||||
.toModel<PowerLevelsContent>()
|
||||
|
||||
val newPowerLevelsContent = currentPLContent
|
||||
?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value)
|
||||
?.toContent()
|
||||
|
||||
room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!)
|
||||
it.countDown()
|
||||
?.toModel<PowerLevelsContent>()
|
||||
?.let { PowerLevelsHelper(it) }
|
||||
powerLevelsHelper!!.isUserAllowedToSend(aliceSession.myUserId, true, EventType.STATE_SPACE_PARENT)
|
||||
}
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val powerLevelsHelper = aliceSession.getRoom(bobRoomId)!!
|
||||
.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty)
|
||||
?.content
|
||||
?.toModel<PowerLevelsContent>()
|
||||
?.let { PowerLevelsHelper(it) }
|
||||
powerLevelsHelper!!.isUserAllowedToSend(aliceSession.myUserId, true, EventType.STATE_SPACE_PARENT)
|
||||
}
|
||||
}
|
||||
aliceSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
|
||||
|
||||
commonTestHelper.waitWithLatch {
|
||||
aliceSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
|
||||
it.countDown()
|
||||
}
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
bobSession.getRoomSummary(bobRoomId)?.flattenParentIds?.contains(spaceAInfo.spaceId) == true
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
bobSession.getRoomSummary(bobRoomId)?.flattenParentIds?.contains(spaceAInfo.spaceId) == true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -616,7 +520,6 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
val aliceSession = commonTestHelper.createAccount("Alice", SessionTestParams(true))
|
||||
|
||||
val spaceAInfo = createPublicSpace(
|
||||
commonTestHelper,
|
||||
aliceSession, "SpaceA",
|
||||
listOf(
|
||||
Triple("A1", true /*auto-join*/, true/*canonical*/),
|
||||
|
@ -625,7 +528,6 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
)
|
||||
|
||||
val spaceBInfo = createPublicSpace(
|
||||
commonTestHelper,
|
||||
aliceSession, "SpaceB",
|
||||
listOf(
|
||||
Triple("B1", true /*auto-join*/, true/*canonical*/),
|
||||
|
@ -641,51 +543,39 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
|
||||
val spaceA = aliceSession.spaceService().getSpace(spaceAInfo.spaceId)
|
||||
val spaceB = aliceSession.spaceService().getSpace(spaceBInfo.spaceId)
|
||||
commonTestHelper.runBlockingTest {
|
||||
spaceA!!.addChildren(B1roomId, viaServers, null, true)
|
||||
spaceA!!.addChildren(B1roomId, viaServers, null, true)
|
||||
|
||||
commonTestHelper.retryPeriodically {
|
||||
val roomSummary = aliceSession.getRoomSummary(B1roomId)
|
||||
roomSummary != null &&
|
||||
roomSummary.directParentNames.size == 2 &&
|
||||
roomSummary.directParentNames.contains(spaceA.spaceSummary()!!.name) &&
|
||||
roomSummary.directParentNames.contains(spaceB!!.spaceSummary()!!.name)
|
||||
}
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val roomSummary = aliceSession.getRoomSummary(B1roomId)
|
||||
roomSummary != null &&
|
||||
roomSummary.directParentNames.size == 2 &&
|
||||
roomSummary.directParentNames.contains(spaceA!!.spaceSummary()!!.name) &&
|
||||
roomSummary.directParentNames.contains(spaceB!!.spaceSummary()!!.name)
|
||||
}
|
||||
}
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val roomSummary = aliceSession.getRoomSummary(spaceAInfo.roomIds.first())
|
||||
roomSummary != null &&
|
||||
roomSummary.directParentNames.size == 1 &&
|
||||
roomSummary.directParentNames.contains(spaceA!!.spaceSummary()!!.name)
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
val roomSummary = aliceSession.getRoomSummary(spaceAInfo.roomIds.first())
|
||||
roomSummary != null &&
|
||||
roomSummary.directParentNames.size == 1 &&
|
||||
roomSummary.directParentNames.contains(spaceA.spaceSummary()!!.name)
|
||||
}
|
||||
|
||||
val newAName = "FooBar"
|
||||
commonTestHelper.runBlockingTest {
|
||||
spaceA!!.asRoom().stateService().updateName(newAName)
|
||||
spaceA.asRoom().stateService().updateName(newAName)
|
||||
|
||||
commonTestHelper.retryPeriodically {
|
||||
val roomSummary = aliceSession.getRoomSummary(B1roomId)
|
||||
roomSummary != null &&
|
||||
roomSummary.directParentNames.size == 2 &&
|
||||
roomSummary.directParentNames.contains(newAName) &&
|
||||
roomSummary.directParentNames.contains(spaceB!!.spaceSummary()!!.name)
|
||||
}
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val roomSummary = aliceSession.getRoomSummary(B1roomId)
|
||||
roomSummary != null &&
|
||||
roomSummary.directParentNames.size == 2 &&
|
||||
roomSummary.directParentNames.contains(newAName) &&
|
||||
roomSummary.directParentNames.contains(spaceB!!.spaceSummary()!!.name)
|
||||
}
|
||||
}
|
||||
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
commonTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val roomSummary = aliceSession.getRoomSummary(spaceAInfo.roomIds.first())
|
||||
roomSummary != null &&
|
||||
roomSummary.directParentNames.size == 1 &&
|
||||
roomSummary.directParentNames.contains(newAName)
|
||||
}
|
||||
commonTestHelper.retryPeriodically {
|
||||
val roomSummary = aliceSession.getRoomSummary(spaceAInfo.roomIds.first())
|
||||
roomSummary != null &&
|
||||
roomSummary.directParentNames.size == 1 &&
|
||||
roomSummary.directParentNames.contains(newAName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue