From 419673675c5a76fbeec9e174a933d098e0b4f2d6 Mon Sep 17 00:00:00 2001 From: valere Date: Tue, 3 Jan 2023 19:35:15 +0100 Subject: [PATCH] Added Self verification UI test --- .../sdk/internal/crypto/E2eeSanityTests.kt | 8 +- .../sdk/internal/session/SessionModule.kt | 2 + .../im/vector/app/VerificationTestBase.kt | 9 +- .../app/VerifySessionInteractiveTest.kt | 189 ++++++++++-------- 4 files changed, 120 insertions(+), 88 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt index b3cc1f20e8..6e3541556d 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt @@ -194,7 +194,7 @@ class E2eeSanityTests : InstrumentedTest { val megolmBackupCreationInfo = bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null) val version = bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo) - Log.v("#E2E TEST", "... Key backup started and enabled for bob") + Log.v("#E2E TEST", "... Key backup started and enabled for bob: version:$version") // Bob session should now have val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!! @@ -230,6 +230,7 @@ class E2eeSanityTests : InstrumentedTest { fail("All keys should be backedup") } ) { + Log.v("#E2E TEST", "backedUp=${ bobKeysBackupService.getTotalNumbersOfBackedUpKeys()}, known=${bobKeysBackupService.getTotalNumbersOfKeys()}") bobKeysBackupService.getTotalNumbersOfBackedUpKeys() == bobKeysBackupService.getTotalNumbersOfKeys() } Log.v("#E2E TEST", "... Key backup done for Bob") @@ -268,7 +269,7 @@ class E2eeSanityTests : InstrumentedTest { ) // MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) // Let's now import keys from backup - + Log.v("#E2E TEST", "Restore backup for the new session") newBobSession.cryptoService().keysBackupService().let { kbs -> val keyVersionResult = kbs.getVersion(version.version) @@ -284,9 +285,12 @@ class E2eeSanityTests : InstrumentedTest { } // ensure bob can now decrypt + + Log.v("#E2E TEST", "Check that bob can decrypt now") cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText) // Check key trust + Log.v("#E2E TEST", "Check key safety") sentEventIds.forEach { sentEventId -> val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)!! val result = newBobSession.cryptoService().decryptEvent(timelineEvent.root, "") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index 27647ba86a..e386be4b57 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -296,6 +296,8 @@ internal abstract class SessionModule { var uri = sessionParams.homeServerConnectionConfig.homeServerUriBase.toString() if (uri == "http://localhost:8080") { uri = "http://10.0.2.2:8080" + } else if (uri == "http://localhost:8081") { + uri = "http://10.0.2.2:8081" } return retrofitFactory .create(okHttpClient, uri) diff --git a/vector-app/src/androidTest/java/im/vector/app/VerificationTestBase.kt b/vector-app/src/androidTest/java/im/vector/app/VerificationTestBase.kt index 97a2a14da3..bfb9a8401f 100644 --- a/vector-app/src/androidTest/java/im/vector/app/VerificationTestBase.kt +++ b/vector-app/src/androidTest/java/im/vector/app/VerificationTestBase.kt @@ -18,6 +18,7 @@ package im.vector.app import android.net.Uri import androidx.lifecycle.Observer +import im.vector.app.ui.robot.ElementRobot import im.vector.app.ui.robot.OnboardingRobot import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers @@ -41,6 +42,7 @@ abstract class VerificationTestBase { val homeServerUrl: String = "http://10.0.2.2:8080" protected val uiTestBase = OnboardingRobot() + protected val elementRobot = ElementRobot() fun createAccountAndSync( matrix: Matrix, @@ -114,9 +116,10 @@ abstract class VerificationTestBase { private fun syncSession(session: Session) { val lock = CountDownLatch(1) - GlobalScope.launch(Dispatchers.Main) { session.open() } - - session.syncService().startSync(true) + GlobalScope.launch(Dispatchers.Main) { + session.open() + session.syncService().startSync(true) + } val syncLiveData = runBlocking(Dispatchers.Main) { session.syncService().getSyncStateLive() diff --git a/vector-app/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt b/vector-app/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt index 1f5a59676c..773ae489c0 100644 --- a/vector-app/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt +++ b/vector-app/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt @@ -19,10 +19,8 @@ package im.vector.app import android.view.View import androidx.recyclerview.widget.RecyclerView import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.IdlingResource import androidx.test.espresso.action.ViewActions.click -import androidx.test.espresso.action.ViewActions.closeSoftKeyboard import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem import androidx.test.espresso.matcher.ViewMatchers.hasDescendant @@ -33,19 +31,25 @@ import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest -import com.adevinta.android.barista.internal.viewaction.SleepViewAction import im.vector.app.core.utils.getMatrixInstance +import im.vector.app.espresso.tools.waitUntilActivityVisible +import im.vector.app.espresso.tools.waitUntilViewVisible import im.vector.app.features.MainActivity import im.vector.app.features.home.HomeActivity +import im.vector.app.ui.robot.AnalyticsRobot +import im.vector.app.ui.robot.ElementRobot +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import org.amshove.kluent.internal.assertEquals import org.hamcrest.CoreMatchers.not +import org.junit.Assert.assertTrue import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -54,9 +58,11 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest import org.matrix.android.sdk.api.session.crypto.verification.SasTransactionState import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod +import org.matrix.android.sdk.api.session.crypto.verification.getRequest import org.matrix.android.sdk.api.session.crypto.verification.getTransaction import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -64,7 +70,6 @@ import kotlin.random.Random @RunWith(AndroidJUnit4::class) @LargeTest -@Ignore class VerifySessionInteractiveTest : VerificationTestBase() { var existingSession: Session? = null @@ -72,6 +77,8 @@ class VerifySessionInteractiveTest : VerificationTestBase() { @get:Rule val activityRule = ActivityScenarioRule(MainActivity::class.java) + private val testScope = CoroutineScope(SupervisorJob()) + @Before fun createSessionWithCrossSigning() { val matrix = getMatrixInstance() @@ -101,28 +108,23 @@ class VerifySessionInteractiveTest : VerificationTestBase() { uiTestBase.login(userId = userId, password = password, homeServerUrl = homeServerUrl) - // Thread.sleep(6000) - withIdlingResource(activityIdlingResource(HomeActivity::class.java)) { - onView(withId(R.id.roomListContainer)) - .check(matches(isDisplayed())) - .perform(closeSoftKeyboard()) - } + val analyticsRobot = AnalyticsRobot() + analyticsRobot.optOut() + waitUntilActivityVisible { + waitUntilViewVisible(withId(R.id.roomListContainer)) + } val activity = EspressoHelper.getCurrentActivity()!! val uiSession = (activity as HomeActivity).activeSessionHolder.getActiveSession() - withIdlingResource(initialSyncIdlingResource(uiSession)) { - onView(withId(R.id.roomListContainer)) - .check(matches(isDisplayed())) + waitUntilViewVisible(withId(R.id.roomListContainer)) } // THIS IS THE ONLY WAY I FOUND TO CLICK ON ALERTERS... :( // Cannot wait for view because of alerter animation? ... onView(isRoot()) .perform(waitForView(withId(com.tapadoo.alerter.R.id.llAlertBackground))) -// Thread.sleep(1000) -// onView(withId(com.tapadoo.alerter.R.id.llAlertBackground)) -// .perform(click()) + Thread.sleep(1000) val popup = activity.findViewById(com.tapadoo.alerter.R.id.llAlertBackground) activity.runOnUiThread { @@ -131,74 +133,105 @@ class VerifySessionInteractiveTest : VerificationTestBase() { onView(isRoot()) .perform(waitForView(withId(R.id.bottomSheetFragmentContainer))) -// .check() -// onView(withId(R.id.bottomSheetFragmentContainer)) -// .check(matches(isDisplayed())) -// onView(isRoot()).perform(SleepViewAction.sleep(2000)) - - onView(withText(R.string.use_latest_app)) + onView(withText(R.string.verification_verify_identity)) .check(matches(isDisplayed())) // 4S is not setup so passphrase option should be hidden - onView(withId(R.id.bottomSheetFragmentContainer)) + onView(withId(R.id.bottomSheetVerificationRecyclerView)) .check(matches(not(hasDescendant(withText(R.string.verification_cannot_access_other_session))))) - val request = runBlockingTest { - existingSession!!.cryptoService().verificationService().requestSelfKeyVerification( - listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), - ) + onView(withId(R.id.bottomSheetVerificationRecyclerView)) + .check(matches(hasDescendant(withText(R.string.verification_verify_with_another_device)))) + + onView(withId(R.id.bottomSheetVerificationRecyclerView)) + .check(matches(hasDescendant(withText(R.string.bad_passphrase_key_reset_all_action)))) + + val otherRequest = CompletableDeferred() + + testScope.launch { + existingSession!!.cryptoService().verificationService().requestEventFlow().collect { + if (it.getRequest() != null) { + otherRequest.complete(it.getRequest()!!) + return@collect cancel() + } + } } - val transactionId = request.transactionId - val sasReadyIdle = verificationStateIdleResource(transactionId, SasTransactionState.SasShortCodeReady, uiSession) - val otherSessionSasReadyIdle = verificationStateIdleResource(transactionId, SasTransactionState.SasShortCodeReady, existingSession!!) - - onView(isRoot()).perform(SleepViewAction.sleep(1000)) - - // Assert QR code option is there and available - onView(withId(R.id.bottomSheetVerificationRecyclerView)) - .check(matches(hasDescendant(withText(R.string.verification_scan_their_code)))) - - onView(withId(R.id.bottomSheetVerificationRecyclerView)) - .check(matches(hasDescendant(withId(R.id.itemVerificationQrCodeImage)))) - + // Send out a self verification request onView(withId(R.id.bottomSheetVerificationRecyclerView)) .perform( actionOnItem( - hasDescendant(withText(R.string.verification_scan_emoji_title)), + hasDescendant(withText(R.string.verification_verify_with_another_device)), click() ) ) - val firstSessionTr = runBlockingTest { - existingSession!!.cryptoService().verificationService().getExistingTransaction( + onView(withId(R.id.bottomSheetVerificationRecyclerView)) + .check(matches(hasDescendant(withText(R.string.verification_request_was_sent)))) + + val txId = runBlockingTest { + otherRequest.await().transactionId + } + + // accept from other session + runBlockingTest { + existingSession!!.cryptoService().verificationService().readyPendingVerification( + listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), existingSession!!.myUserId, - transactionId - ) as SasVerificationTransaction + txId + ) } - IdlingRegistry.getInstance().register(sasReadyIdle) - IdlingRegistry.getInstance().register(otherSessionSasReadyIdle) - onView(isRoot()).perform(SleepViewAction.sleep(300)) - // will only execute when Idle is ready - val expectedEmojis = firstSessionTr.getEmojiCodeRepresentation() - val targets = listOf(R.id.emoji0, R.id.emoji1, R.id.emoji2, R.id.emoji3, R.id.emoji4, R.id.emoji5, R.id.emoji6) - targets.forEachIndexed { index, res -> - onView(withId(res)) - .check( - matches(hasDescendant(withText(expectedEmojis[index].nameResId))) - ) + onView(withId(R.id.bottomSheetVerificationRecyclerView)) + .perform(waitForView(hasDescendant(withText(R.string.verification_scan_self_notice)))) + onView(withId(R.id.bottomSheetVerificationRecyclerView)) + .perform(waitForView(hasDescendant(withText(R.string.verification_scan_self_emoji_subtitle)))) + + // there should be the QR code also + onView(withId(R.id.bottomSheetVerificationRecyclerView)) + .check(matches(hasDescendant(withId(R.id.itemVerificationQrCodeImage)))) + + // proceed with emoji + onView(withId(R.id.bottomSheetVerificationRecyclerView)) + .perform( + actionOnItem( + hasDescendant(withText(R.string.verification_scan_self_emoji_subtitle)), + click() + ) + ) + + onView(withId(R.id.bottomSheetVerificationRecyclerView)) + .perform(waitForView(hasDescendant(withText(R.string.verification_sas_do_not_match)))) + + onView(withId(R.id.bottomSheetVerificationRecyclerView)) + .perform(waitForView(hasDescendant(withText(R.string.verification_sas_match)))) + + // check that the code matches + val uiCode = runBlockingTest { + (uiSession.cryptoService() + .verificationService() + .getExistingTransaction(uiSession.myUserId, txId) as SasVerificationTransaction) + .getDecimalCodeRepresentation()!! } - IdlingRegistry.getInstance().unregister(sasReadyIdle) - IdlingRegistry.getInstance().unregister(otherSessionSasReadyIdle) + val backgroundCode = runBlockingTest { + (existingSession!!.cryptoService() + .verificationService() + .getExistingTransaction(uiSession.myUserId, txId) as SasVerificationTransaction) + .getDecimalCodeRepresentation()!! + } - val verificationSuccessIdle = - verificationStateIdleResource(transactionId, SasTransactionState.Done(true), uiSession) + assertEquals("SAS code should be equals", backgroundCode, uiCode) - // CLICK ON THEY MATCH + runBlockingTest { + (existingSession!!.cryptoService() + .verificationService() + .getExistingTransaction(uiSession.myUserId, txId) as SasVerificationTransaction) + .userHasVerifiedShortCode() + } + // Do the same on ui onView(withId(R.id.bottomSheetVerificationRecyclerView)) .perform( actionOnItem( @@ -207,23 +240,10 @@ class VerifySessionInteractiveTest : VerificationTestBase() { ) ) - runBlockingTest { - firstSessionTr.userHasVerifiedShortCode() - } + onView(withId(R.id.bottomSheetVerificationRecyclerView)) + .perform(waitForView(hasDescendant(withText(R.string.verification_conclusion_ok_notice)))) - onView(isRoot()).perform(SleepViewAction.sleep(1000)) - - withIdlingResource(verificationSuccessIdle) { - onView(withId(R.id.bottomSheetVerificationRecyclerView)) - .check( - matches(hasDescendant(withText(R.string.verification_conclusion_ok_self_notice))) - ) - } - - // Wait a bit before done (to delay a bit sending of secrets to let other have time - // to mark as verified :/ - Thread.sleep(5_000) - // Click on done + // click on done onView(withId(R.id.bottomSheetVerificationRecyclerView)) .perform( actionOnItem( @@ -232,11 +252,14 @@ class VerifySessionInteractiveTest : VerificationTestBase() { ) ) - // Wait until local secrets are known (gossip) - withIdlingResource(allSecretsKnownIdling(uiSession)) { - onView(withId(R.id.roomListContainer)) - .check(matches(isDisplayed())) - } + Thread.sleep(1_000) + + // check that current session is actually trusted + assertTrue("I should be verified", + runBlockingTest { uiSession.cryptoService().crossSigningService().isCrossSigningVerified() } + ) + + ElementRobot().signout(false) } fun signout() {