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