Merge branch 'develop' of github.com:vector-im/element-android into michaelk/sonarqube_fixes

This commit is contained in:
Adam Brown 2022-05-30 09:55:37 +01:00
commit d18e7ad001
65 changed files with 1382 additions and 729 deletions

1
changelog.d/5283.wip Normal file
View file

@ -0,0 +1 @@
FTUE - Adds the redesigned Sign In screen

1
changelog.d/6073.feature Normal file
View file

@ -0,0 +1 @@
Adds up navigation in spaces

1
changelog.d/6077.sdk Normal file
View file

@ -0,0 +1 @@
Improve replay attacks and reduce duplicate message index errors

1
changelog.d/6148.bugfix Normal file
View file

@ -0,0 +1 @@
Fix decrypting redacted event from sending errors

View file

@ -199,7 +199,7 @@ dependencies {
implementation libs.apache.commonsImaging implementation libs.apache.commonsImaging
// Phone number https://github.com/google/libphonenumber // Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.48' implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.49'
testImplementation libs.tests.junit testImplementation libs.tests.junit
testImplementation 'org.robolectric:robolectric:4.7.3' testImplementation 'org.robolectric:robolectric:4.7.3'

View file

@ -24,8 +24,8 @@ import org.junit.runner.RunWith
import org.junit.runners.JUnit4 import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.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
@ -34,32 +34,22 @@ import org.matrix.android.sdk.common.TestConstants
@LargeTest @LargeTest
class AccountCreationTest : InstrumentedTest { class AccountCreationTest : InstrumentedTest {
private val commonTestHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
@Test @Test
fun createAccountTest() { fun createAccountTest() = runSessionTest(context()) { commonTestHelper ->
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
commonTestHelper.signOutAndClose(session)
} }
@Test @Test
@Ignore("This test will be ignored until it is fixed") @Ignore("This test will be ignored until it is fixed")
fun createAccountAndLoginAgainTest() { fun createAccountAndLoginAgainTest() = runSessionTest(context()) { commonTestHelper ->
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
// Log again to the same account // Log again to the same account
val session2 = commonTestHelper.logIntoAccount(session.myUserId, SessionTestParams(withInitialSync = true)) commonTestHelper.logIntoAccount(session.myUserId, SessionTestParams(withInitialSync = true))
commonTestHelper.signOutAndClose(session)
commonTestHelper.signOutAndClose(session2)
} }
@Test @Test
fun simpleE2eTest() { fun simpleE2eTest() = runCryptoTest(context()) { cryptoTestHelper, _ ->
val res = cryptoTestHelper.doE2ETestWithAliceInARoom() cryptoTestHelper.doE2ETestWithAliceInARoom()
res.cleanUp(commonTestHelper)
} }
} }

View file

@ -25,7 +25,7 @@ import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidPassword
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.SessionTestParams
import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.common.TestConstants
@ -34,14 +34,12 @@ import org.matrix.android.sdk.common.TestConstants
@Ignore("This test will be ignored until it is fixed") @Ignore("This test will be ignored until it is fixed")
class ChangePasswordTest : InstrumentedTest { class ChangePasswordTest : InstrumentedTest {
private val commonTestHelper = CommonTestHelper(context())
companion object { companion object {
private const val NEW_PASSWORD = "this is a new password" private const val NEW_PASSWORD = "this is a new password"
} }
@Test @Test
fun changePasswordTest() { fun changePasswordTest() = runSessionTest(context()) { commonTestHelper ->
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false)) val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false))
// Change password // Change password
@ -54,9 +52,6 @@ class ChangePasswordTest : InstrumentedTest {
throwable.isInvalidPassword().shouldBeTrue() throwable.isInvalidPassword().shouldBeTrue()
// Try to login with the new password, should work // Try to login with the new password, should work
val session2 = commonTestHelper.logIntoAccount(session.myUserId, NEW_PASSWORD, SessionTestParams(withInitialSync = false)) commonTestHelper.logIntoAccount(session.myUserId, NEW_PASSWORD, SessionTestParams(withInitialSync = false))
commonTestHelper.signOutAndClose(session)
commonTestHelper.signOutAndClose(session2)
} }
} }

View file

@ -29,7 +29,7 @@ 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.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.SessionTestParams
import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.common.TestConstants
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
@ -39,10 +39,8 @@ import kotlin.coroutines.resume
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
class DeactivateAccountTest : InstrumentedTest { class DeactivateAccountTest : InstrumentedTest {
private val commonTestHelper = CommonTestHelper(context())
@Test @Test
fun deactivateAccountTest() { fun deactivateAccountTest() = runSessionTest(context(), false /* session will be deactivated */) { commonTestHelper ->
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

View file

@ -23,7 +23,7 @@ import org.junit.runner.RunWith
import org.junit.runners.JUnit4 import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.SessionTestParams
import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.common.TestConstants
import timber.log.Timber import timber.log.Timber
@ -32,10 +32,8 @@ import timber.log.Timber
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
class ApiInterceptorTest : InstrumentedTest { class ApiInterceptorTest : InstrumentedTest {
private val commonTestHelper = CommonTestHelper(context())
@Test @Test
fun apiInterceptorTest() { fun apiInterceptorTest() = runSessionTest(context()) { commonTestHelper ->
val responses = mutableListOf<String>() val responses = mutableListOf<String>()
val listener = object : ApiInterceptorListener { val listener = object : ApiInterceptorListener {

View file

@ -54,12 +54,39 @@ import java.util.concurrent.TimeUnit
* This class exposes methods to be used in common cases * This class exposes methods to be used in common cases
* Registration, login, Sync, Sending messages... * Registration, login, Sync, Sending messages...
*/ */
class CommonTestHelper(context: Context) { class CommonTestHelper private constructor(context: Context) {
companion object {
internal fun runSessionTest(context: Context, autoSignoutOnClose: Boolean = true, block: (CommonTestHelper) -> Unit) {
val testHelper = CommonTestHelper(context)
return try {
block(testHelper)
} finally {
if (autoSignoutOnClose) {
testHelper.cleanUpOpenedSessions()
}
}
}
internal fun runCryptoTest(context: Context, autoSignoutOnClose: Boolean = true, block: (CryptoTestHelper, CommonTestHelper) -> Unit) {
val testHelper = CommonTestHelper(context)
val cryptoTestHelper = CryptoTestHelper(testHelper)
return try {
block(cryptoTestHelper, testHelper)
} finally {
if (autoSignoutOnClose) {
testHelper.cleanUpOpenedSessions()
}
}
}
}
internal val matrix: TestMatrix internal val matrix: TestMatrix
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private var accountNumber = 0 private var accountNumber = 0
private val trackedSessions = mutableListOf<Session>()
fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
init { init {
@ -84,6 +111,15 @@ class CommonTestHelper(context: Context) {
return logIntoAccount(userId, TestConstants.PASSWORD, testParams) return logIntoAccount(userId, TestConstants.PASSWORD, testParams)
} }
fun cleanUpOpenedSessions() {
trackedSessions.forEach {
runBlockingTest {
it.signOutService().signOut(true)
}
}
trackedSessions.clear()
}
/** /**
* Create a homeserver configuration, with Http connection allowed for test * Create a homeserver configuration, with Http connection allowed for test
*/ */
@ -245,7 +281,9 @@ class CommonTestHelper(context: Context) {
testParams testParams
) )
assertNotNull(session) assertNotNull(session)
return session return session.also {
trackedSessions.add(session)
}
} }
/** /**
@ -261,7 +299,9 @@ class CommonTestHelper(context: Context) {
testParams: SessionTestParams): Session { testParams: SessionTestParams): Session {
val session = logAccountAndSync(userId, password, testParams) val session = logAccountAndSync(userId, password, testParams)
assertNotNull(session) assertNotNull(session)
return session return session.also {
trackedSessions.add(session)
}
} }
/** /**
@ -436,6 +476,7 @@ class CommonTestHelper(context: Context) {
fun Iterable<Session>.signOutAndClose() = forEach { signOutAndClose(it) } fun Iterable<Session>.signOutAndClose() = forEach { signOutAndClose(it) }
fun signOutAndClose(session: Session) { fun signOutAndClose(session: Session) {
trackedSessions.remove(session)
runBlockingTest(timeout = 60_000) { runBlockingTest(timeout = 60_000) {
session.signOutService().signOut(true) session.signOutService().signOut(true)
} }

View file

@ -66,7 +66,7 @@ import java.util.UUID
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
class CryptoTestHelper(private val testHelper: CommonTestHelper) { class CryptoTestHelper(val testHelper: CommonTestHelper) {
private val messagesFromAlice: List<String> = listOf("0 - Hello I'm Alice!", "4 - Go!") private val messagesFromAlice: List<String> = listOf("0 - Hello I'm Alice!", "4 - Go!")
private val messagesFromBob: List<String> = listOf("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.") private val messagesFromBob: List<String> = listOf("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.")

View file

@ -0,0 +1,75 @@
/*
* 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.internal.crypto
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class DecryptRedactedEventTest : InstrumentedTest {
@Test
fun doNotFailToDecryptRedactedEvent() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val e2eRoomID = testData.roomId
val aliceSession = testData.firstSession
val bobSession = testData.secondSession!!
val roomALicePOV = aliceSession.getRoom(e2eRoomID)!!
val timelineEvent = testHelper.sendTextMessage(roomALicePOV, "Hello", 1).first()
val redactionReason = "Wrong Room"
roomALicePOV.sendService().redactEvent(timelineEvent.root, redactionReason)
// get the event from bob
testHelper.waitWithLatch {
testHelper.retryPeriodicallyWithLatch(it) {
bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)?.root?.isRedacted() == true
}
}
val eventBobPov = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)!!
testHelper.runBlockingTest {
try {
val result = bobSession.cryptoService().decryptEvent(eventBobPov.root, "")
Assert.assertEquals(
"Unexpected redacted reason",
redactionReason,
result.clearEvent.toModel<Event>()?.unsignedData?.redactedEvent?.content?.get("reason")
)
Assert.assertEquals(
"Unexpected Redacted event id",
timelineEvent.eventId,
result.clearEvent.toModel<Event>()?.unsignedData?.redactedEvent?.redacts
)
} catch (failure: Throwable) {
Assert.fail("Should not throw when decrypting a redacted event")
}
}
}
}

View file

@ -58,7 +58,8 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
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
import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.common.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.TestConstants
@ -84,9 +85,7 @@ class E2eeSanityTests : InstrumentedTest {
* Alice sends a new message, then check that the new one can be decrypted * Alice sends a new message, then check that the new one can be decrypted
*/ */
@Test @Test
fun testSendingE2EEMessages() { fun testSendingE2EEMessages() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -200,21 +199,12 @@ class E2eeSanityTests : InstrumentedTest {
} }
} }
} }
otherAccounts.forEach {
testHelper.signOutAndClose(it)
}
newAccount.forEach { testHelper.signOutAndClose(it) }
cryptoTestData.cleanUp(testHelper)
} }
@Test @Test
fun testKeyGossipingIsEnabledByDefault() { fun testKeyGossipingIsEnabledByDefault() = runSessionTest(context()) { testHelper ->
val testHelper = CommonTestHelper(context())
val session = testHelper.createAccount("alice", SessionTestParams(true)) val session = testHelper.createAccount("alice", SessionTestParams(true))
Assert.assertTrue("Key gossiping should be enabled by default", session.cryptoService().isKeyGossipingEnabled()) Assert.assertTrue("Key gossiping should be enabled by default", session.cryptoService().isKeyGossipingEnabled())
testHelper.signOutAndClose(session)
} }
/** /**
@ -232,9 +222,7 @@ class E2eeSanityTests : InstrumentedTest {
* 9. Check that new session can decrypt * 9. Check that new session can decrypt
*/ */
@Test @Test
fun testBasicBackupImport() { fun testBasicBackupImport() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -346,8 +334,6 @@ class E2eeSanityTests : InstrumentedTest {
// ensure bob can now decrypt // ensure bob can now decrypt
cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText) cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
testHelper.signOutAndClose(newBobSession)
} }
/** /**
@ -355,9 +341,7 @@ class E2eeSanityTests : InstrumentedTest {
* get them from an older one. * get them from an older one.
*/ */
@Test @Test
fun testSimpleGossip() { fun testSimpleGossip() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -451,18 +435,13 @@ class E2eeSanityTests : InstrumentedTest {
} }
cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText) cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
cryptoTestData.cleanUp(testHelper)
testHelper.signOutAndClose(newBobSession)
} }
/** /**
* Test that if a better key is forwarded (lower index, it is then used) * Test that if a better key is forwarded (lower index, it is then used)
*/ */
@Test @Test
fun testForwardBetterKey() { fun testForwardBetterKey() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -578,10 +557,6 @@ class E2eeSanityTests : InstrumentedTest {
canDecryptFirst && canDecryptSecond canDecryptFirst && canDecryptSecond
} }
} }
testHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(bobSessionWithBetterKey)
testHelper.signOutAndClose(newBobSession)
} }
private fun sendMessageInRoom(testHelper: CommonTestHelper, aliceRoomPOV: Room, text: String): String? { private fun sendMessageInRoom(testHelper: CommonTestHelper, aliceRoomPOV: Room, text: String): String? {
@ -612,9 +587,7 @@ class E2eeSanityTests : InstrumentedTest {
* Test that if a better key is forwared (lower index, it is then used) * Test that if a better key is forwared (lower index, it is then used)
*/ */
@Test @Test
fun testSelfInteractiveVerificationAndGossip() { fun testASelfInteractiveVerificationAndGossip() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val aliceSession = testHelper.createAccount("alice", SessionTestParams(true)) val aliceSession = testHelper.createAccount("alice", SessionTestParams(true))
cryptoTestHelper.bootstrapSecurity(aliceSession) cryptoTestHelper.bootstrapSecurity(aliceSession)
@ -753,9 +726,6 @@ class E2eeSanityTests : InstrumentedTest {
aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version, aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version,
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version
) )
testHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(aliceNewSession)
} }
private fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) { private fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {

View file

@ -30,18 +30,14 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon
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.getTimelineEvent import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CryptoTestHelper
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
class PreShareKeysTest : InstrumentedTest { class PreShareKeysTest : InstrumentedTest {
private val testHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(testHelper)
@Test @Test
fun ensure_outbound_session_happy_path() { fun ensure_outbound_session_happy_path() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val e2eRoomID = testData.roomId val e2eRoomID = testData.roomId
val aliceSession = testData.firstSession val aliceSession = testData.firstSession
@ -94,7 +90,5 @@ class PreShareKeysTest : InstrumentedTest {
bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE
} }
} }
testData.cleanUp(testHelper)
} }
} }

View file

@ -39,8 +39,7 @@ import org.matrix.android.sdk.api.session.getRoom
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.CryptoTestHelper
import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.common.TestConstants
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
@ -65,8 +64,6 @@ import kotlin.coroutines.resume
class UnwedgingTest : InstrumentedTest { class UnwedgingTest : InstrumentedTest {
private lateinit var messagesReceivedByBob: List<TimelineEvent> private lateinit var messagesReceivedByBob: List<TimelineEvent>
private val testHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(testHelper)
@Before @Before
fun init() { fun init() {
@ -87,7 +84,7 @@ class UnwedgingTest : InstrumentedTest {
* -> This is automatically fixed after SDKs restarted the olm session * -> This is automatically fixed after SDKs restarted the olm session
*/ */
@Test @Test
fun testUnwedging() { fun testUnwedging() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -242,8 +239,6 @@ class UnwedgingTest : InstrumentedTest {
} }
bobTimeline.dispose() bobTimeline.dispose()
cryptoTestData.cleanUp(testHelper)
} }
private fun createEventListener(latch: CountDownLatch, expectedNumberOfMessages: Int): Timeline.Listener { private fun createEventListener(latch: CountDownLatch, expectedNumberOfMessages: Int): Timeline.Listener {

View file

@ -38,8 +38,8 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerif
import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.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 kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
@ -51,11 +51,8 @@ import kotlin.coroutines.resume
@Ignore @Ignore
class XSigningTest : InstrumentedTest { class XSigningTest : InstrumentedTest {
private val testHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(testHelper)
@Test @Test
fun test_InitializeAndStoreKeys() { 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.doSync<Unit> {
@ -89,7 +86,7 @@ class XSigningTest : InstrumentedTest {
} }
@Test @Test
fun test_CrossSigningCheckBobSeesTheKeys() { fun test_CrossSigningCheckBobSeesTheKeys() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -139,12 +136,10 @@ class XSigningTest : InstrumentedTest {
) )
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted()) assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
cryptoTestData.cleanUp(testHelper)
} }
@Test @Test
fun test_CrossSigningTestAliceTrustBobNewDevice() { fun test_CrossSigningTestAliceTrustBobNewDevice() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -218,9 +213,5 @@ class XSigningTest : InstrumentedTest {
val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null) val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified()) assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
testHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(bobSession)
testHelper.signOutAndClose(bobSession2)
} }
} }

View file

@ -35,6 +35,7 @@ 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
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 java.util.concurrent.CountDownLatch
@ -42,12 +43,10 @@ import java.util.concurrent.CountDownLatch
@FixMethodOrder(MethodSorters.NAME_ASCENDING) @FixMethodOrder(MethodSorters.NAME_ASCENDING)
class EncryptionTest : InstrumentedTest { class EncryptionTest : InstrumentedTest {
private val testHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(testHelper)
@Test @Test
fun test_EncryptionEvent() { fun test_EncryptionEvent() {
performTest(roomShouldBeEncrypted = false) { room -> runCryptoTest(context()) { cryptoTestHelper, testHelper ->
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,
@ -55,10 +54,12 @@ class EncryptionTest : InstrumentedTest {
) )
} }
} }
}
@Test @Test
fun test_EncryptionStateEvent() { fun test_EncryptionStateEvent() {
performTest(roomShouldBeEncrypted = true) { room -> runCryptoTest(context()) { cryptoTestHelper, testHelper ->
performTest(cryptoTestHelper, testHelper, roomShouldBeEncrypted = true) { room ->
runBlocking { runBlocking {
// Send an encryption Event as a State Event // Send an encryption Event as a State Event
room.stateService().sendStateEvent( room.stateService().sendStateEvent(
@ -69,8 +70,9 @@ class EncryptionTest : InstrumentedTest {
} }
} }
} }
}
private fun performTest(roomShouldBeEncrypted: Boolean, action: (Room) -> Unit) { private fun performTest(cryptoTestHelper: CryptoTestHelper, testHelper: CommonTestHelper, roomShouldBeEncrypted: Boolean, action: (Room) -> Unit) {
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(encryptedRoom = false) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(encryptedRoom = false)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -109,6 +111,5 @@ class EncryptionTest : InstrumentedTest {
room.roomCryptoService().isEncrypted() shouldBe roomShouldBeEncrypted room.roomCryptoService().isEncrypted() shouldBe roomShouldBeEncrypted
it.countDown() it.countDown()
} }
cryptoTestData.cleanUp(testHelper)
} }
} }

View file

@ -42,8 +42,7 @@ import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
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.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.common.RetryTestRule 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.TestConstants
@ -58,9 +57,7 @@ class KeyShareTests : InstrumentedTest {
@get:Rule val rule = RetryTestRule(3) @get:Rule val rule = RetryTestRule(3)
@Test @Test
fun test_DoNotSelfShareIfNotTrusted() { fun test_DoNotSelfShareIfNotTrusted() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
Log.v("TEST", "=======> AliceSession 1 is ${aliceSession.sessionParams.deviceId}") Log.v("TEST", "=======> AliceSession 1 is ${aliceSession.sessionParams.deviceId}")
@ -196,9 +193,7 @@ class KeyShareTests : InstrumentedTest {
* if the key was originally shared with him * if the key was originally shared with him
*/ */
@Test @Test
fun test_reShareIfWasIntendedToBeShared() { fun test_reShareIfWasIntendedToBeShared() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = testData.firstSession val aliceSession = testData.firstSession
@ -229,9 +224,7 @@ class KeyShareTests : InstrumentedTest {
* if the key was originally shared with him * if the key was originally shared with him
*/ */
@Test @Test
fun test_reShareToUnverifiedIfWasIntendedToBeShared() { fun test_reShareToUnverifiedIfWasIntendedToBeShared() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val testData = cryptoTestHelper.doE2ETestWithAliceInARoom(true) val testData = cryptoTestHelper.doE2ETestWithAliceInARoom(true)
val aliceSession = testData.firstSession val aliceSession = testData.firstSession
@ -268,9 +261,7 @@ class KeyShareTests : InstrumentedTest {
* Tests that keys reshared with own verified session are done from the earliest known index * Tests that keys reshared with own verified session are done from the earliest known index
*/ */
@Test @Test
fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() { fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = testData.firstSession val aliceSession = testData.firstSession
@ -390,10 +381,7 @@ class KeyShareTests : InstrumentedTest {
* Tests that we don't cancel a request to early on first forward if the index is not good enough * Tests that we don't cancel a request to early on first forward if the index is not good enough
*/ */
@Test @Test
fun test_dontCancelToEarly() { fun test_dontCancelToEarly() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = testData.firstSession val aliceSession = testData.firstSession
val bobSession = testData.secondSession!! val bobSession = testData.secondSession!!

View file

@ -37,8 +37,7 @@ import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
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.getTimelineEvent import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.common.MockOkHttpInterceptor import org.matrix.android.sdk.common.MockOkHttpInterceptor
import org.matrix.android.sdk.common.RetryTestRule import org.matrix.android.sdk.common.RetryTestRule
import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.SessionTestParams
@ -54,9 +53,7 @@ class WithHeldTests : InstrumentedTest {
@get:Rule val rule = RetryTestRule(3) @get:Rule val rule = RetryTestRule(3)
@Test @Test
fun test_WithHeldUnverifiedReason() { fun test_WithHeldUnverifiedReason() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
// ============================= // =============================
// ARRANGE // ARRANGE
@ -155,16 +152,10 @@ class WithHeldTests : InstrumentedTest {
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "") bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
} }
} }
testHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(bobSession)
testHelper.signOutAndClose(bobUnverifiedSession)
} }
@Test @Test
fun test_WithHeldNoOlm() { fun test_WithHeldNoOlm() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = testData.firstSession val aliceSession = testData.firstSession
@ -241,14 +232,10 @@ class WithHeldTests : InstrumentedTest {
Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2) Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2)
aliceInterceptor.clearRules() aliceInterceptor.clearRules()
testData.cleanUp(testHelper)
testHelper.signOutAndClose(bobSecondSession)
} }
@Test @Test
fun test_WithHeldKeyRequest() { fun test_WithHeldKeyRequest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = testData.firstSession val aliceSession = testData.firstSession
@ -295,8 +282,5 @@ class WithHeldTests : InstrumentedTest {
wc?.code == WithHeldCode.UNAUTHORISED wc?.code == WithHeldCode.UNAUTHORISED
} }
} }
testHelper.signOutAndClose(aliceSession)
testHelper.signOutAndClose(bobSecondSession)
} }
} }

View file

@ -45,8 +45,8 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreation
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.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.TestMatrixCallback
@ -67,9 +67,7 @@ class KeysBackupTest : InstrumentedTest {
* - Reset keys backup markers * - Reset keys backup markers
*/ */
@Test @Test
fun roomKeysTest_testBackupStore_ok() { fun roomKeysTest_testBackupStore_ok() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
@ -108,8 +106,7 @@ class KeysBackupTest : InstrumentedTest {
* Check that prepareKeysBackupVersionWithPassword returns valid data * Check that prepareKeysBackupVersionWithPassword returns valid data
*/ */
@Test @Test
fun prepareKeysBackupVersionTest() { fun prepareKeysBackupVersionTest() = runSessionTest(context()) { testHelper ->
val testHelper = CommonTestHelper(context())
val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams) val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
@ -131,16 +128,13 @@ class KeysBackupTest : InstrumentedTest {
assertNotNull(megolmBackupCreationInfo.recoveryKey) assertNotNull(megolmBackupCreationInfo.recoveryKey)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
testHelper.signOutAndClose(bobSession)
} }
/** /**
* Test creating a keys backup version and check that createKeysBackupVersion() returns valid data * Test creating a keys backup version and check that createKeysBackupVersion() returns valid data
*/ */
@Test @Test
fun createKeysBackupVersionTest() { fun createKeysBackupVersionTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = 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)
@ -199,7 +193,6 @@ class KeysBackupTest : InstrumentedTest {
} }
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
testHelper.signOutAndClose(bobSession)
} }
/** /**
@ -207,9 +200,7 @@ class KeysBackupTest : InstrumentedTest {
* - Check the backup completes * - Check the backup completes
*/ */
@Test @Test
fun backupAfterCreateKeysBackupVersionTest() { fun backupAfterCreateKeysBackupVersionTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
@ -244,16 +235,13 @@ class KeysBackupTest : InstrumentedTest {
KeysBackupState.ReadyToBackUp KeysBackupState.ReadyToBackUp
) )
) )
cryptoTestData.cleanUp(testHelper)
} }
/** /**
* Check that backupAllGroupSessions() returns valid data * Check that backupAllGroupSessions() returns valid data
*/ */
@Test @Test
fun backupAllGroupSessionsTest() { fun backupAllGroupSessionsTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
@ -287,7 +275,6 @@ class KeysBackupTest : InstrumentedTest {
assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys) assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(testHelper)
} }
/** /**
@ -299,9 +286,7 @@ class KeysBackupTest : InstrumentedTest {
* - Compare the decrypted megolm key with the original one * - Compare the decrypted megolm key with the original one
*/ */
@Test @Test
fun testEncryptAndDecryptKeysBackupData() { fun testEncryptAndDecryptKeysBackupData() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
@ -336,7 +321,6 @@ class KeysBackupTest : InstrumentedTest {
keysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData) keysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(testHelper)
} }
/** /**
@ -346,9 +330,7 @@ class KeysBackupTest : InstrumentedTest {
* - Restore must be successful * - Restore must be successful
*/ */
@Test @Test
fun restoreKeysBackupTest() { fun restoreKeysBackupTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
@ -434,9 +416,7 @@ class KeysBackupTest : InstrumentedTest {
* - It must be trusted and must have with 2 signatures now * - It must be trusted and must have with 2 signatures now
*/ */
@Test @Test
fun trustKeyBackupVersionTest() { fun trustKeyBackupVersionTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
// - Do an e2e backup to the homeserver with a recovery key // - Do an e2e backup to the homeserver with a recovery key
@ -483,7 +463,6 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(2, keysBackupVersionTrust.signatures.size) assertEquals(2, keysBackupVersionTrust.signatures.size)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
testData.cleanUp(testHelper)
} }
/** /**
@ -497,9 +476,7 @@ class KeysBackupTest : InstrumentedTest {
* - It must be trusted and must have with 2 signatures now * - It must be trusted and must have with 2 signatures now
*/ */
@Test @Test
fun trustKeyBackupVersionWithRecoveryKeyTest() { fun trustKeyBackupVersionWithRecoveryKeyTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
// - Do an e2e backup to the homeserver with a recovery key // - Do an e2e backup to the homeserver with a recovery key
@ -546,7 +523,6 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(2, keysBackupVersionTrust.signatures.size) assertEquals(2, keysBackupVersionTrust.signatures.size)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
testData.cleanUp(testHelper)
} }
/** /**
@ -558,9 +534,7 @@ class KeysBackupTest : InstrumentedTest {
* - The backup must still be untrusted and disabled * - The backup must still be untrusted and disabled
*/ */
@Test @Test
fun trustKeyBackupVersionWithWrongRecoveryKeyTest() { fun trustKeyBackupVersionWithWrongRecoveryKeyTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
// - Do an e2e backup to the homeserver with a recovery key // - Do an e2e backup to the homeserver with a recovery key
@ -589,7 +563,6 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
testData.cleanUp(testHelper)
} }
/** /**
@ -603,9 +576,7 @@ class KeysBackupTest : InstrumentedTest {
* - It must be trusted and must have with 2 signatures now * - It must be trusted and must have with 2 signatures now
*/ */
@Test @Test
fun trustKeyBackupVersionWithPasswordTest() { fun trustKeyBackupVersionWithPasswordTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
val password = "Password" val password = "Password"
@ -654,7 +625,6 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(2, keysBackupVersionTrust.signatures.size) assertEquals(2, keysBackupVersionTrust.signatures.size)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
testData.cleanUp(testHelper)
} }
/** /**
@ -666,9 +636,7 @@ class KeysBackupTest : InstrumentedTest {
* - The backup must still be untrusted and disabled * - The backup must still be untrusted and disabled
*/ */
@Test @Test
fun trustKeyBackupVersionWithWrongPasswordTest() { fun trustKeyBackupVersionWithWrongPasswordTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
val password = "Password" val password = "Password"
@ -700,7 +668,6 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
testData.cleanUp(testHelper)
} }
/** /**
@ -710,9 +677,7 @@ class KeysBackupTest : InstrumentedTest {
* - It must fail * - It must fail
*/ */
@Test @Test
fun restoreKeysBackupWithAWrongRecoveryKeyTest() { fun restoreKeysBackupWithAWrongRecoveryKeyTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
@ -736,8 +701,6 @@ class KeysBackupTest : InstrumentedTest {
// onSuccess may not have been called // onSuccess may not have been called
assertNull(importRoomKeysResult) assertNull(importRoomKeysResult)
testData.cleanUp(testHelper)
} }
/** /**
@ -747,9 +710,7 @@ class KeysBackupTest : InstrumentedTest {
* - Restore must be successful * - Restore must be successful
*/ */
@Test @Test
fun testBackupWithPassword() { fun testBackupWithPassword() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
val password = "password" val password = "password"
@ -796,8 +757,6 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(100, (steps[104] as StepProgressListener.Step.ImportingKey).progress) assertEquals(100, (steps[104] as StepProgressListener.Step.ImportingKey).progress)
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
testData.cleanUp(testHelper)
} }
/** /**
@ -807,9 +766,7 @@ class KeysBackupTest : InstrumentedTest {
* - It must fail * - It must fail
*/ */
@Test @Test
fun restoreKeysBackupWithAWrongPasswordTest() { fun restoreKeysBackupWithAWrongPasswordTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
val password = "password" val password = "password"
@ -836,8 +793,6 @@ class KeysBackupTest : InstrumentedTest {
// onSuccess may not have been called // onSuccess may not have been called
assertNull(importRoomKeysResult) assertNull(importRoomKeysResult)
testData.cleanUp(testHelper)
} }
/** /**
@ -847,9 +802,7 @@ class KeysBackupTest : InstrumentedTest {
* - Restore must be successful * - Restore must be successful
*/ */
@Test @Test
fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() { fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
val password = "password" val password = "password"
@ -869,8 +822,6 @@ class KeysBackupTest : InstrumentedTest {
} }
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
testData.cleanUp(testHelper)
} }
/** /**
@ -880,9 +831,7 @@ class KeysBackupTest : InstrumentedTest {
* - It must fail * - It must fail
*/ */
@Test @Test
fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() { fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
@ -906,8 +855,6 @@ class KeysBackupTest : InstrumentedTest {
// onSuccess may not have been called // onSuccess may not have been called
assertNull(importRoomKeysResult) assertNull(importRoomKeysResult)
testData.cleanUp(testHelper)
} }
/** /**
@ -915,9 +862,7 @@ class KeysBackupTest : InstrumentedTest {
* - Check the returned KeysVersionResult is trusted * - Check the returned KeysVersionResult is trusted
*/ */
@Test @Test
fun testIsKeysBackupTrusted() { fun testIsKeysBackupTrusted() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
// - Create a backup version // - Create a backup version
@ -951,7 +896,6 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId) assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(testHelper)
} }
/** /**
@ -963,9 +907,7 @@ class KeysBackupTest : InstrumentedTest {
* -> That must fail and her backup state must be WrongBackUpVersion * -> That must fail and her backup state must be WrongBackUpVersion
*/ */
@Test @Test
fun testBackupWhenAnotherBackupWasCreated() { fun testBackupWhenAnotherBackupWasCreated() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
// - Create a backup version // - Create a backup version
@ -1022,7 +964,6 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled) assertFalse(keysBackup.isEnabled)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(testHelper)
} }
/** /**
@ -1038,9 +979,7 @@ class KeysBackupTest : InstrumentedTest {
* -> It must success * -> It must success
*/ */
@Test @Test
fun testBackupAfterVerifyingADevice() { fun testBackupAfterVerifyingADevice() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
// - Create a backup version // - Create a backup version
@ -1131,8 +1070,6 @@ class KeysBackupTest : InstrumentedTest {
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
stateObserver2.stopAndCheckStates(null) stateObserver2.stopAndCheckStates(null)
testHelper.signOutAndClose(aliceSession2)
cryptoTestData.cleanUp(testHelper)
} }
/** /**
@ -1140,9 +1077,7 @@ class KeysBackupTest : InstrumentedTest {
* - Delete the backup * - Delete the backup
*/ */
@Test @Test
fun deleteKeysBackupTest() { fun deleteKeysBackupTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper) val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
// - Create a backup version // - Create a backup version
@ -1165,6 +1100,5 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled) assertFalse(keysBackup.isEnabled)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
cryptoTestData.cleanUp(testHelper)
} }
} }

View file

@ -0,0 +1,109 @@
/*
* 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.internal.crypto.replayattack
import androidx.test.filters.LargeTest
import org.amshove.kluent.internal.assertFailsWith
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.fail
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
@RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@LargeTest
class ReplayAttackTest : InstrumentedTest {
@Test
fun replayAttackAlreadyDecryptedEventTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val e2eRoomID = cryptoTestData.roomId
// Alice
val aliceSession = cryptoTestData.firstSession
val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!!
// Bob
val bobSession = cryptoTestData.secondSession
val bobRoomPOV = bobSession!!.roomService().getRoom(e2eRoomID)!!
assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2)
// Alice will send a message
val sentEvents = testHelper.sendTextMessage(aliceRoomPOV, "Hello I will be decrypted twice", 1)
assertEquals(1, sentEvents.size)
val fakeEventId = sentEvents[0].eventId + "_fake"
val fakeEventWithTheSameIndex =
sentEvents[0].copy(eventId = fakeEventId, root = sentEvents[0].root.copy(eventId = fakeEventId))
testHelper.runBlockingTest {
// Lets assume we are from the main timelineId
val timelineId = "timelineId"
// Lets decrypt the original event
aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId)
// Lets decrypt the fake event that will have the same message index
val exception = assertFailsWith<MXCryptoError.Base> {
// An exception should be thrown while the same index would have been used for the previous decryption
aliceSession.cryptoService().decryptEvent(fakeEventWithTheSameIndex.root, timelineId)
}
assertEquals(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, exception.errorType)
}
cryptoTestData.cleanUp(testHelper)
}
@Test
fun replayAttackSameEventTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val e2eRoomID = cryptoTestData.roomId
// Alice
val aliceSession = cryptoTestData.firstSession
val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!!
// Bob
val bobSession = cryptoTestData.secondSession
val bobRoomPOV = bobSession!!.roomService().getRoom(e2eRoomID)!!
assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2)
// Alice will send a message
val sentEvents = testHelper.sendTextMessage(aliceRoomPOV, "Hello I will be decrypted twice", 1)
Assert.assertTrue("Message should be sent", sentEvents.size == 1)
assertEquals(sentEvents.size, 1)
testHelper.runBlockingTest {
// Lets assume we are from the main timelineId
val timelineId = "timelineId"
// Lets decrypt the original event
aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId)
try {
// Lets try to decrypt the same event
aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId)
} catch (ex: Throwable) {
fail("Shouldn't throw a decryption error for same event")
}
}
}
}

View file

@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
import org.matrix.android.sdk.api.util.Optional 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
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.internal.crypto.secrets.DefaultSharedSecretStorageService import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
@ -55,8 +56,7 @@ class QuadSTests : InstrumentedTest {
} }
@Test @Test
fun test_Generate4SKey() { fun test_Generate4SKey() = runSessionTest(context()) { testHelper ->
val testHelper = CommonTestHelper(context())
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
@ -108,12 +108,11 @@ class QuadSTests : InstrumentedTest {
} }
@Test @Test
fun test_StoreSecret() { fun test_StoreSecret() = runSessionTest(context()) { testHelper ->
val testHelper = CommonTestHelper(context())
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(aliceSession, keyId, true) val info = generatedSecret(testHelper, aliceSession, keyId, true)
val keySpec = RawBytesKeySpec.fromRecoveryKey(info.recoveryKey) val keySpec = RawBytesKeySpec.fromRecoveryKey(info.recoveryKey)
@ -127,7 +126,7 @@ class QuadSTests : InstrumentedTest {
) )
} }
val secretAccountData = assertAccountData(aliceSession, "secret.of.life") val secretAccountData = assertAccountData(testHelper, 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)
@ -149,12 +148,10 @@ class QuadSTests : InstrumentedTest {
} }
assertEquals("Secret mismatch", clearSecret, decryptedSecret) assertEquals("Secret mismatch", clearSecret, decryptedSecret)
testHelper.signOutAndClose(aliceSession)
} }
@Test @Test
fun test_SetDefaultLocalEcho() { fun test_SetDefaultLocalEcho() = runSessionTest(context()) { testHelper ->
val testHelper = CommonTestHelper(context())
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
@ -170,19 +167,16 @@ class QuadSTests : InstrumentedTest {
testHelper.runBlockingTest { testHelper.runBlockingTest {
quadS.setDefaultKey(TEST_KEY_ID) quadS.setDefaultKey(TEST_KEY_ID)
} }
testHelper.signOutAndClose(aliceSession)
} }
@Test @Test
fun test_StoreSecretWithMultipleKey() { fun test_StoreSecretWithMultipleKey() = runSessionTest(context()) { testHelper ->
val testHelper = CommonTestHelper(context())
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(aliceSession, keyId1, true) val key1Info = generatedSecret(testHelper, aliceSession, keyId1, true)
val keyId2 = "Key2" val keyId2 = "Key2"
val key2Info = generatedSecret(aliceSession, keyId2, true) val key2Info = generatedSecret(testHelper, aliceSession, keyId2, true)
val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
@ -221,19 +215,16 @@ class QuadSTests : InstrumentedTest {
RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!! RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!
) )
} }
testHelper.signOutAndClose(aliceSession)
} }
@Test @Test
@Ignore("Test is working locally, not in GitHub actions") @Ignore("Test is working locally, not in GitHub actions")
fun test_GetSecretWithBadPassphrase() { fun test_GetSecretWithBadPassphrase() = runSessionTest(context()) { testHelper ->
val testHelper = CommonTestHelper(context())
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(aliceSession, passphrase, keyId1, true) val key1Info = generatedSecretFromPassphrase(testHelper, aliceSession, passphrase, keyId1, true)
val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
@ -275,13 +266,9 @@ class QuadSTests : InstrumentedTest {
) )
) )
} }
testHelper.signOutAndClose(aliceSession)
} }
private fun assertAccountData(session: Session, type: String): UserAccountDataEvent { private fun assertAccountData(testHelper: CommonTestHelper, session: Session, type: String): UserAccountDataEvent {
val testHelper = CommonTestHelper(context())
var accountData: UserAccountDataEvent? = null var accountData: UserAccountDataEvent? = null
testHelper.waitWithLatch { testHelper.waitWithLatch {
val liveAccountData = session.accountDataService().getLiveUserAccountDataEvent(type) val liveAccountData = session.accountDataService().getLiveUserAccountDataEvent(type)
@ -297,29 +284,27 @@ class QuadSTests : InstrumentedTest {
return accountData!! return accountData!!
} }
private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { private fun generatedSecret(testHelper: CommonTestHelper, session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
val quadS = session.sharedSecretStorageService() val quadS = session.sharedSecretStorageService()
val testHelper = CommonTestHelper(context())
val creationInfo = testHelper.runBlockingTest { val creationInfo = testHelper.runBlockingTest {
quadS.generateKey(keyId, null, keyId, emptyKeySigner) quadS.generateKey(keyId, null, keyId, emptyKeySigner)
} }
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") assertAccountData(testHelper, session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
if (asDefault) { if (asDefault) {
testHelper.runBlockingTest { 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(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { private fun generatedSecretFromPassphrase(testHelper: CommonTestHelper, session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
val quadS = session.sharedSecretStorageService() val quadS = session.sharedSecretStorageService()
val testHelper = CommonTestHelper(context())
val creationInfo = testHelper.runBlockingTest { val creationInfo = testHelper.runBlockingTest {
quadS.generateKeyWithPassphrase( quadS.generateKeyWithPassphrase(
@ -331,12 +316,12 @@ class QuadSTests : InstrumentedTest {
) )
} }
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") assertAccountData(testHelper, session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
if (asDefault) { if (asDefault) {
testHelper.runBlockingTest { 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

View file

@ -44,8 +44,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransa
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.Event
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.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart
import org.matrix.android.sdk.internal.crypto.model.rest.toValue import org.matrix.android.sdk.internal.crypto.model.rest.toValue
@ -57,9 +56,7 @@ import java.util.concurrent.CountDownLatch
class SASTest : InstrumentedTest { class SASTest : InstrumentedTest {
@Test @Test
fun test_aliceStartThenAliceCancel() { fun test_aliceStartThenAliceCancel() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -136,15 +133,11 @@ class SASTest : InstrumentedTest {
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)) assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID)) assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
cryptoTestData.cleanUp(testHelper)
} }
@Test @Test
@Ignore("This test will be ignored until it is fixed") @Ignore("This test will be ignored until it is fixed")
fun test_key_agreement_protocols_must_include_curve25519() { fun test_key_agreement_protocols_must_include_curve25519() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
fail("Not passing for the moment") fail("Not passing for the moment")
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
@ -196,15 +189,11 @@ class SASTest : InstrumentedTest {
testHelper.await(cancelLatch) testHelper.await(cancelLatch)
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason) assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
cryptoTestData.cleanUp(testHelper)
} }
@Test @Test
@Ignore("This test will be ignored until it is fixed") @Ignore("This test will be ignored until it is fixed")
fun test_key_agreement_macs_Must_include_hmac_sha256() { fun test_key_agreement_macs_Must_include_hmac_sha256() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
fail("Not passing for the moment") fail("Not passing for the moment")
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
@ -237,15 +226,11 @@ class SASTest : InstrumentedTest {
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!! val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
cryptoTestData.cleanUp(testHelper)
} }
@Test @Test
@Ignore("This test will be ignored until it is fixed") @Ignore("This test will be ignored until it is fixed")
fun test_key_agreement_short_code_include_decimal() { fun test_key_agreement_short_code_include_decimal() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
fail("Not passing for the moment") fail("Not passing for the moment")
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
@ -278,8 +263,6 @@ class SASTest : InstrumentedTest {
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!! val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
cryptoTestData.cleanUp(testHelper)
} }
private fun fakeBobStart(bobSession: Session, private fun fakeBobStart(bobSession: Session,
@ -315,9 +298,7 @@ class SASTest : InstrumentedTest {
// any two devices may only have at most one key verification in flight at a time. // any two devices may only have at most one key verification in flight at a time.
// If a device has two verifications in progress with the same device, then it should cancel both verifications. // If a device has two verifications in progress with the same device, then it should cancel both verifications.
@Test @Test
fun test_aliceStartTwoRequests() { fun test_aliceStartTwoRequests() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -358,9 +339,7 @@ class SASTest : InstrumentedTest {
*/ */
@Test @Test
@Ignore("This test will be ignored until it is fixed") @Ignore("This test will be ignored until it is fixed")
fun test_aliceAndBobAgreement() { fun test_aliceAndBobAgreement() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -414,14 +393,10 @@ class SASTest : InstrumentedTest {
accepted!!.shortAuthenticationStrings.forEach { accepted!!.shortAuthenticationStrings.forEach {
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it)) assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
} }
cryptoTestData.cleanUp(testHelper)
} }
@Test @Test
fun test_aliceAndBobSASCode() { fun test_aliceAndBobSASCode() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -474,14 +449,10 @@ class SASTest : InstrumentedTest {
"Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL), "Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
bobTx.getShortCodeRepresentation(SasMode.DECIMAL) bobTx.getShortCodeRepresentation(SasMode.DECIMAL)
) )
cryptoTestData.cleanUp(testHelper)
} }
@Test @Test
fun test_happyPath() { fun test_happyPath() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -554,13 +525,10 @@ class SASTest : InstrumentedTest {
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified) assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified) assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
cryptoTestData.cleanUp(testHelper)
} }
@Test @Test
fun test_ConcurrentStart() { fun test_ConcurrentStart() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -647,7 +615,5 @@ class SASTest : InstrumentedTest {
bobPovTx?.state == VerificationTxState.ShortCodeReady bobPovTx?.state == VerificationTxState.ShortCodeReady
} }
} }
cryptoTestData.cleanUp(testHelper)
} }
} }

View file

@ -32,8 +32,8 @@ import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
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.VerificationService import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.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 java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@ -156,9 +156,7 @@ class VerificationTest : InstrumentedTest {
private fun doTest(aliceSupportedMethods: List<VerificationMethod>, private fun doTest(aliceSupportedMethods: List<VerificationMethod>,
bobSupportedMethods: List<VerificationMethod>, bobSupportedMethods: List<VerificationMethod>,
expectedResultForAlice: ExpectedResult, expectedResultForAlice: ExpectedResult,
expectedResultForBob: ExpectedResult) { expectedResultForBob: ExpectedResult) = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val testHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(testHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -253,14 +251,11 @@ class VerificationTest : InstrumentedTest {
pr.otherCanShowQrCode() shouldBe expectedResultForBob.otherCanShowQrCode pr.otherCanShowQrCode() shouldBe expectedResultForBob.otherCanShowQrCode
pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode
} }
cryptoTestData.cleanUp(testHelper)
} }
@Test @Test
fun test_selfVerificationAcceptedCancelsItForOtherSessions() { fun test_selfVerificationAcceptedCancelsItForOtherSessions() = runSessionTest(context()) { testHelper ->
val defaultSessionParams = SessionTestParams(true) val defaultSessionParams = SessionTestParams(true)
val testHelper = CommonTestHelper(context())
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)

View file

@ -34,8 +34,7 @@ import org.matrix.android.sdk.api.session.events.model.isThread
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
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.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.CryptoTestHelper
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@RunWith(JUnit4::class) @RunWith(JUnit4::class)
@ -44,9 +43,7 @@ import java.util.concurrent.CountDownLatch
class ThreadMessagingTest : InstrumentedTest { class ThreadMessagingTest : InstrumentedTest {
@Test @Test
fun reply_in_thread_should_create_a_thread() { fun reply_in_thread_should_create_a_thread() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -104,9 +101,7 @@ class ThreadMessagingTest : InstrumentedTest {
} }
@Test @Test
fun reply_in_thread_should_create_a_thread_from_other_user() { fun reply_in_thread_should_create_a_thread_from_other_user() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -179,9 +174,7 @@ class ThreadMessagingTest : InstrumentedTest {
} }
@Test @Test
fun reply_in_thread_to_timeline_message_multiple_times() { fun reply_in_thread_to_timeline_message_multiple_times() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -244,9 +237,7 @@ class ThreadMessagingTest : InstrumentedTest {
} }
@Test @Test
fun thread_summary_advanced_validation_after_multiple_messages_in_multiple_threads() { fun thread_summary_advanced_validation_after_multiple_messages_in_multiple_threads() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession

View file

@ -38,8 +38,7 @@ import org.matrix.android.sdk.api.session.room.model.message.PollType
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.CryptoTestHelper
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@RunWith(JUnit4::class) @RunWith(JUnit4::class)
@ -47,9 +46,7 @@ import java.util.concurrent.CountDownLatch
class PollAggregationTest : InstrumentedTest { class PollAggregationTest : InstrumentedTest {
@Test @Test
fun testAllPollUseCases() { fun testAllPollUseCases() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -138,7 +135,6 @@ class PollAggregationTest : InstrumentedTest {
aliceSession.stopSync() aliceSession.stopSync()
aliceTimeline.dispose() aliceTimeline.dispose()
cryptoTestData.cleanUp(commonTestHelper)
} }
private fun testInitialPollConditions(pollContent: MessagePollContent, pollSummary: PollResponseAggregatedSummary?) { private fun testInitialPollConditions(pollContent: MessagePollContent, pollSummary: PollResponseAggregatedSummary?) {

View file

@ -34,8 +34,7 @@ import org.matrix.android.sdk.api.session.getRoom
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.timeline.Timeline 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 import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.common.checkSendOrder import org.matrix.android.sdk.common.checkSendOrder
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@ -53,9 +52,7 @@ class TimelineForwardPaginationTest : InstrumentedTest {
* This test ensure that if we click to permalink, we will be able to go back to the live * This test ensure that if we click to permalink, we will be able to go back to the live
*/ */
@Test @Test
fun forwardPaginationTest() { fun forwardPaginationTest() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val numberOfMessagesToSend = 90 val numberOfMessagesToSend = 90
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
@ -177,7 +174,5 @@ class TimelineForwardPaginationTest : InstrumentedTest {
} }
aliceTimeline.dispose() aliceTimeline.dispose()
cryptoTestData.cleanUp(commonTestHelper)
} }
} }

View file

@ -33,7 +33,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
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.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
import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.common.checkSendOrder import org.matrix.android.sdk.common.checkSendOrder
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@ -48,9 +47,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
*/ */
@Test @Test
fun previousLastForwardTest() { fun previousLastForwardTest() = CommonTestHelper.runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -242,7 +239,5 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
} }
bobTimeline.dispose() bobTimeline.dispose()
cryptoTestData.cleanUp(commonTestHelper)
} }
} }

View file

@ -33,8 +33,7 @@ import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
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.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.CryptoTestHelper
import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.common.TestConstants
@RunWith(JUnit4::class) @RunWith(JUnit4::class)
@ -44,9 +43,7 @@ import org.matrix.android.sdk.common.TestConstants
class TimelineSimpleBackPaginationTest : InstrumentedTest { class TimelineSimpleBackPaginationTest : InstrumentedTest {
@Test @Test
fun timeline_backPaginate_shouldReachEndOfTimeline() { fun timeline_backPaginate_shouldReachEndOfTimeline() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val numberOfMessagesToSent = 200 val numberOfMessagesToSent = 200
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
@ -104,6 +101,5 @@ class TimelineSimpleBackPaginationTest : InstrumentedTest {
assertEquals(numberOfMessagesToSent, onlySentEvents.size) assertEquals(numberOfMessagesToSent, onlySentEvents.size)
bobTimeline.dispose() bobTimeline.dispose()
cryptoTestData.cleanUp(commonTestHelper)
} }
} }

View file

@ -30,8 +30,7 @@ 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.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
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.CryptoTestHelper
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
/** !! Not working with the new timeline /** !! Not working with the new timeline
@ -47,15 +46,12 @@ class TimelineWithManyMembersTest : InstrumentedTest {
private const val NUMBER_OF_MEMBERS = 6 private const val NUMBER_OF_MEMBERS = 6
} }
private val commonTestHelper = CommonTestHelper(context())
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
/** /**
* Ensures when someone sends a message to a crowded room, everyone can decrypt the message. * Ensures when someone sends a message to a crowded room, everyone can decrypt the message.
*/ */
@Test @Test
fun everyone_should_decrypt_message_in_a_crowded_room() { fun everyone_should_decrypt_message_in_a_crowded_room() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithManyMembers(NUMBER_OF_MEMBERS) val cryptoTestData = cryptoTestHelper.doE2ETestWithManyMembers(NUMBER_OF_MEMBERS)
val sessionForFirstMember = cryptoTestData.firstSession val sessionForFirstMember = cryptoTestData.firstSession

View file

@ -26,9 +26,8 @@ import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.api.session.search.SearchResult
import org.matrix.android.sdk.common.CommonTestHelper 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.CryptoTestHelper
@RunWith(JUnit4::class) @RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
@ -74,9 +73,7 @@ class SearchMessagesTest : InstrumentedTest {
} }
} }
private fun doTest(block: suspend (CryptoTestData) -> SearchResult) { private fun doTest(block: suspend (CryptoTestData) -> SearchResult) = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val aliceRoomId = cryptoTestData.roomId val aliceRoomId = cryptoTestData.roomId
@ -99,7 +96,5 @@ class SearchMessagesTest : InstrumentedTest {
(it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse() (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse()
}.orFalse() }.orFalse()
) )
cryptoTestData.cleanUp(commonTestHelper)
} }
} }

View file

@ -41,7 +41,7 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.space.JoinSpaceResult import org.matrix.android.sdk.api.session.space.JoinSpaceResult
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.SessionTestParams
@RunWith(JUnit4::class) @RunWith(JUnit4::class)
@ -50,8 +50,7 @@ import org.matrix.android.sdk.common.SessionTestParams
class SpaceCreationTest : InstrumentedTest { class SpaceCreationTest : InstrumentedTest {
@Test @Test
fun createSimplePublicSpace() { fun createSimplePublicSpace() = runSessionTest(context()) { commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
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"
@ -97,13 +96,11 @@ class SpaceCreationTest : InstrumentedTest {
?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility ?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility
assertEquals("Public space room should be world readable", RoomHistoryVisibility.WORLD_READABLE, historyVisibility) assertEquals("Public space room should be world readable", RoomHistoryVisibility.WORLD_READABLE, historyVisibility)
commonTestHelper.signOutAndClose(session)
} }
@Test @Test
fun testJoinSimplePublicSpace() { @Ignore
val commonTestHelper = CommonTestHelper(context()) fun testJoinSimplePublicSpace() = runSessionTest(context()) { commonTestHelper ->
val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true)) val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true)) val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true))
@ -135,9 +132,7 @@ class SpaceCreationTest : InstrumentedTest {
} }
@Test @Test
@Ignore fun testSimplePublicSpaceWithChildren() = runSessionTest(context()) { commonTestHelper ->
fun testSimplePublicSpaceWithChildren() {
val commonTestHelper = CommonTestHelper(context())
val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true)) val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true)) val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true))
@ -206,8 +201,5 @@ class SpaceCreationTest : InstrumentedTest {
// ).size // ).size
// //
// assertEquals("Unexpected number of joined children", 1, childCount) // assertEquals("Unexpected number of joined children", 1, childCount)
commonTestHelper.signOutAndClose(aliceSession)
commonTestHelper.signOutAndClose(bobSession)
} }
} }

View file

@ -48,6 +48,7 @@ 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
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.SessionTestParams
@RunWith(JUnit4::class) @RunWith(JUnit4::class)
@ -55,8 +56,7 @@ import org.matrix.android.sdk.common.SessionTestParams
class SpaceHierarchyTest : InstrumentedTest { class SpaceHierarchyTest : InstrumentedTest {
@Test @Test
fun createCanonicalChildRelation() { fun createCanonicalChildRelation() = runSessionTest(context()) { commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val session = commonTestHelper.createAccount("John", SessionTestParams(true))
val spaceName = "My Space" val spaceName = "My Space"
@ -173,8 +173,7 @@ class SpaceHierarchyTest : InstrumentedTest {
// } // }
@Test @Test
fun testFilteringBySpace() { fun testFilteringBySpace() = CommonTestHelper.runSessionTest(context()) { commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val session = commonTestHelper.createAccount("John", SessionTestParams(true))
val spaceAInfo = createPublicSpace( val spaceAInfo = createPublicSpace(
@ -256,8 +255,7 @@ class SpaceHierarchyTest : InstrumentedTest {
@Test @Test
@Ignore("This test will be ignored until it is fixed") @Ignore("This test will be ignored until it is fixed")
fun testBreakCycle() { fun testBreakCycle() = CommonTestHelper.runSessionTest(context()) { commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val session = commonTestHelper.createAccount("John", SessionTestParams(true))
val spaceAInfo = createPublicSpace( val spaceAInfo = createPublicSpace(
@ -302,8 +300,7 @@ class SpaceHierarchyTest : InstrumentedTest {
} }
@Test @Test
fun testLiveFlatChildren() { fun testLiveFlatChildren() = CommonTestHelper.runSessionTest(context()) { commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val session = commonTestHelper.createAccount("John", SessionTestParams(true))
val spaceAInfo = createPublicSpace( val spaceAInfo = createPublicSpace(
@ -395,9 +392,9 @@ class SpaceHierarchyTest : InstrumentedTest {
childInfo: List<Triple<String, Boolean, Boolean?>> childInfo: List<Triple<String, Boolean, Boolean?>>
/** Name, auto-join, canonical*/ /** Name, auto-join, canonical*/
): TestSpaceCreationResult { ): TestSpaceCreationResult {
val commonTestHelper = CommonTestHelper(context())
var spaceId = "" var spaceId = ""
var roomIds: List<String> = emptyList() var roomIds: List<String> = emptyList()
runSessionTest(context()) { commonTestHelper ->
commonTestHelper.waitWithLatch { latch -> commonTestHelper.waitWithLatch { latch ->
spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true) spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true)
val syncedSpace = session.spaceService().getSpace(spaceId) val syncedSpace = session.spaceService().getSpace(spaceId)
@ -415,6 +412,7 @@ class SpaceHierarchyTest : InstrumentedTest {
} }
latch.countDown() latch.countDown()
} }
}
return TestSpaceCreationResult(spaceId, roomIds) return TestSpaceCreationResult(spaceId, roomIds)
} }
@ -423,9 +421,9 @@ class SpaceHierarchyTest : InstrumentedTest {
childInfo: List<Triple<String, Boolean, Boolean?>> childInfo: List<Triple<String, Boolean, Boolean?>>
/** Name, auto-join, canonical*/ /** Name, auto-join, canonical*/
): TestSpaceCreationResult { ): TestSpaceCreationResult {
val commonTestHelper = CommonTestHelper(context())
var spaceId = "" var spaceId = ""
var roomIds: List<String> = emptyList() var roomIds: List<String> = emptyList()
runSessionTest(context()) { commonTestHelper ->
commonTestHelper.waitWithLatch { latch -> commonTestHelper.waitWithLatch { latch ->
spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false) spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false)
val syncedSpace = session.spaceService().getSpace(spaceId) val syncedSpace = session.spaceService().getSpace(spaceId)
@ -454,12 +452,12 @@ class SpaceHierarchyTest : InstrumentedTest {
} }
latch.countDown() latch.countDown()
} }
}
return TestSpaceCreationResult(spaceId, roomIds) return TestSpaceCreationResult(spaceId, roomIds)
} }
@Test @Test
fun testRootSpaces() { fun testRootSpaces() = runSessionTest(context()) { commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val session = commonTestHelper.createAccount("John", SessionTestParams(true))
/* val spaceAInfo = */ createPublicSpace( /* val spaceAInfo = */ createPublicSpace(
@ -506,13 +504,10 @@ class SpaceHierarchyTest : InstrumentedTest {
} }
assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size) assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size)
commonTestHelper.signOutAndClose(session)
} }
@Test @Test
fun testParentRelation() { fun testParentRelation() = runSessionTest(context()) { commonTestHelper ->
val commonTestHelper = CommonTestHelper(context())
val aliceSession = commonTestHelper.createAccount("Alice", SessionTestParams(true)) val aliceSession = commonTestHelper.createAccount("Alice", SessionTestParams(true))
val bobSession = commonTestHelper.createAccount("Bib", SessionTestParams(true)) val bobSession = commonTestHelper.createAccount("Bib", SessionTestParams(true))
@ -604,8 +599,5 @@ class SpaceHierarchyTest : InstrumentedTest {
bobSession.getRoomSummary(bobRoomId)?.flattenParentIds?.contains(spaceAInfo.spaceId) == true bobSession.getRoomSummary(bobRoomId)?.flattenParentIds?.contains(spaceAInfo.spaceId) == true
} }
} }
commonTestHelper.signOutAndClose(aliceSession)
commonTestHelper.signOutAndClose(bobSession)
} }
} }

View file

@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.api.session.events.model.Event 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.content.OlmEventContent import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent
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.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
@ -42,7 +43,7 @@ import javax.inject.Inject
private const val SEND_TO_DEVICE_RETRY_COUNT = 3 private const val SEND_TO_DEVICE_RETRY_COUNT = 3
private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO) private val loggerTag = LoggerTag("EventDecryptor", LoggerTag.CRYPTO)
@SessionScope @SessionScope
internal class EventDecryptor @Inject constructor( internal class EventDecryptor @Inject constructor(
@ -110,6 +111,16 @@ internal class EventDecryptor @Inject constructor(
if (eventContent == null) { if (eventContent == null) {
Timber.tag(loggerTag.value).e("decryptEvent : empty event content") Timber.tag(loggerTag.value).e("decryptEvent : empty event content")
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON) throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
} else if (event.isRedacted()) {
// we shouldn't attempt to decrypt a redacted event because the content is cleared and decryption will fail because of null algorithm
return MXEventDecryptionResult(
clearEvent = mapOf(
"room_id" to event.roomId.orEmpty(),
"type" to EventType.MESSAGE,
"content" to emptyMap<String, Any>(),
"unsigned" to event.unsignedData.toContent()
)
)
} else { } else {
val algorithm = eventContent["algorithm"]?.toString() val algorithm = eventContent["algorithm"]?.toString()
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm) val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)

View file

@ -96,8 +96,9 @@ internal class MXOlmDevice @Inject constructor(
// So, store these message indexes per timeline id. // So, store these message indexes per timeline id.
// //
// The first level keys are timeline ids. // The first level keys are timeline ids.
// The second level keys are strings of form "<senderKey>|<session_id>|<message_index>" // The second level values is a Map that represents:
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableSet<String>> = HashMap() // "<senderKey>|<session_id>|<roomId>|<message_index>" --> eventId
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, String>> = HashMap()
init { init {
// Retrieve the account from the store // Retrieve the account from the store
@ -755,23 +756,29 @@ internal class MXOlmDevice @Inject constructor(
* @param body the base64-encoded body of the encrypted message. * @param body the base64-encoded body of the encrypted message.
* @param roomId the room in which the message was received. * @param roomId the room in which the message was received.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @param eventId the eventId of the message that will be decrypted
* @param sessionId the session identifier. * @param sessionId the session identifier.
* @param senderKey the base64-encoded curve25519 key of the sender. * @param senderKey the base64-encoded curve25519 key of the sender.
* @return the decrypting result. Nil if the sessionId is unknown. * @return the decrypting result. Null if the sessionId is unknown.
*/ */
@Throws(MXCryptoError::class) @Throws(MXCryptoError::class)
suspend fun decryptGroupMessage(body: String, suspend fun decryptGroupMessage(body: String,
roomId: String, roomId: String,
timeline: String?, timeline: String?,
eventId: String,
sessionId: String, sessionId: String,
senderKey: String): OlmDecryptionResult { senderKey: String): OlmDecryptionResult {
val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId) val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId)
val wrapper = sessionHolder.wrapper val wrapper = sessionHolder.wrapper
val inboundGroupSession = wrapper.olmInboundGroupSession val inboundGroupSession = wrapper.olmInboundGroupSession
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, "Session is null") ?: throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, "Session is null")
if (roomId != wrapper.roomId) {
// Check that the room id matches the original one for the session. This stops // Check that the room id matches the original one for the session. This stops
// the HS pretending a message was targeting a different room. // the HS pretending a message was targeting a different room.
if (roomId == wrapper.roomId) { val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, wrapper.roomId)
Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason)
}
val decryptResult = try { val decryptResult = try {
sessionHolder.mutex.withLock { sessionHolder.mutex.withLock {
inboundGroupSession.decryptMessage(body) inboundGroupSession.decryptMessage(body)
@ -781,20 +788,24 @@ internal class MXOlmDevice @Inject constructor(
throw MXCryptoError.OlmError(e) throw MXCryptoError.OlmError(e)
} }
val messageIndexKey = senderKey + "|" + sessionId + "|" + roomId + "|" + decryptResult.mIndex
Timber.tag(loggerTag.value).v("##########################################################")
Timber.tag(loggerTag.value).v("## decryptGroupMessage() timeline: $timeline")
Timber.tag(loggerTag.value).v("## decryptGroupMessage() senderKey: $senderKey")
Timber.tag(loggerTag.value).v("## decryptGroupMessage() sessionId: $sessionId")
Timber.tag(loggerTag.value).v("## decryptGroupMessage() roomId: $roomId")
Timber.tag(loggerTag.value).v("## decryptGroupMessage() eventId: $eventId")
Timber.tag(loggerTag.value).v("## decryptGroupMessage() mIndex: ${decryptResult.mIndex}")
if (timeline?.isNotBlank() == true) { if (timeline?.isNotBlank() == true) {
val timelineSet = inboundGroupSessionMessageIndexes.getOrPut(timeline) { mutableSetOf() } val replayAttackMap = inboundGroupSessionMessageIndexes.getOrPut(timeline) { mutableMapOf() }
if (replayAttackMap.contains(messageIndexKey) && replayAttackMap[messageIndexKey] != eventId) {
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
if (timelineSet.contains(messageIndexKey)) {
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex) val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
Timber.tag(loggerTag.value).e("## decryptGroupMessage() timelineId=$timeline: $reason") Timber.tag(loggerTag.value).e("## decryptGroupMessage() timelineId=$timeline: $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason) throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)
} }
replayAttackMap[messageIndexKey] = eventId
timelineSet.add(messageIndexKey)
} }
inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey) inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey)
val payload = try { val payload = try {
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE) val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
@ -811,11 +822,6 @@ internal class MXOlmDevice @Inject constructor(
senderKey, senderKey,
wrapper.forwardingCurve25519KeyChain wrapper.forwardingCurve25519KeyChain
) )
} else {
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, wrapper.roomId)
Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason)
}
} }
/** /**

View file

@ -78,6 +78,7 @@ internal class MXMegolmDecryption(
encryptedEventContent.ciphertext, encryptedEventContent.ciphertext,
event.roomId, event.roomId,
timeline, timeline,
eventId = event.eventId.orEmpty(),
encryptedEventContent.sessionId, encryptedEventContent.sessionId,
encryptedEventContent.senderKey encryptedEventContent.senderKey
) )

View file

@ -520,9 +520,10 @@ internal class RoomSyncHandler @Inject constructor(
private fun decryptIfNeeded(event: Event, roomId: String) { private fun decryptIfNeeded(event: Event, roomId: String) {
try { try {
val timelineId = generateTimelineId(roomId)
// Event from sync does not have roomId, so add it to the event first // Event from sync does not have roomId, so add it to the event first
// note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching // note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), "") } val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), timelineId) }
event.mxDecryptionResult = OlmDecryptionResult( event.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent, payload = result.clearEvent,
senderKey = result.senderCurve25519Key, senderKey = result.senderCurve25519Key,
@ -537,6 +538,10 @@ internal class RoomSyncHandler @Inject constructor(
} }
} }
private fun generateTimelineId(roomId: String): String {
return "RoomSyncHandler$roomId"
}
data class EphemeralResult( data class EphemeralResult(
val typingUserIds: List<String> = emptyList() val typingUserIds: List<String> = emptyList()
) )

View file

@ -382,7 +382,7 @@ dependencies {
implementation 'com.facebook.stetho:stetho:1.6.0' implementation 'com.facebook.stetho:stetho:1.6.0'
// Phone number https://github.com/google/libphonenumber // Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.48' implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.49'
// FlowBinding // FlowBinding
implementation libs.github.flowBinding implementation libs.github.flowBinding

View file

@ -60,6 +60,11 @@ class DebugFeaturesStateFactory @Inject constructor(
key = DebugFeatureKeys.onboardingCombinedRegister, key = DebugFeatureKeys.onboardingCombinedRegister,
factory = VectorFeatures::isOnboardingCombinedRegisterEnabled factory = VectorFeatures::isOnboardingCombinedRegisterEnabled
), ),
createBooleanFeature(
label = "FTUE Combined login",
key = DebugFeatureKeys.onboardingCombinedLogin,
factory = VectorFeatures::isOnboardingCombinedLoginEnabled
),
) )
) )
} }

View file

@ -57,6 +57,9 @@ class DebugVectorFeatures(
override fun isOnboardingCombinedRegisterEnabled(): Boolean = read(DebugFeatureKeys.onboardingCombinedRegister) override fun isOnboardingCombinedRegisterEnabled(): Boolean = read(DebugFeatureKeys.onboardingCombinedRegister)
?: vectorFeatures.isOnboardingCombinedRegisterEnabled() ?: vectorFeatures.isOnboardingCombinedRegisterEnabled()
override fun isOnboardingCombinedLoginEnabled(): Boolean = read(DebugFeatureKeys.onboardingCombinedLogin)
?: vectorFeatures.isOnboardingCombinedLoginEnabled()
override fun isScreenSharingEnabled(): Boolean = read(DebugFeatureKeys.screenSharing) override fun isScreenSharingEnabled(): Boolean = read(DebugFeatureKeys.screenSharing)
?: vectorFeatures.isScreenSharingEnabled() ?: vectorFeatures.isScreenSharingEnabled()
@ -113,6 +116,7 @@ object DebugFeatureKeys {
val onboardingUseCase = booleanPreferencesKey("onboarding-splash-carousel") val onboardingUseCase = booleanPreferencesKey("onboarding-splash-carousel")
val onboardingPersonalize = booleanPreferencesKey("onboarding-personalize") val onboardingPersonalize = booleanPreferencesKey("onboarding-personalize")
val onboardingCombinedRegister = booleanPreferencesKey("onboarding-combined-register") val onboardingCombinedRegister = booleanPreferencesKey("onboarding-combined-register")
val onboardingCombinedLogin = booleanPreferencesKey("onboarding-combined-login")
val liveLocationSharing = booleanPreferencesKey("live-location-sharing") val liveLocationSharing = booleanPreferencesKey("live-location-sharing")
val screenSharing = booleanPreferencesKey("screen-sharing") val screenSharing = booleanPreferencesKey("screen-sharing")
} }

View file

@ -72,6 +72,8 @@ class AppStateHandler @Inject constructor(
val selectedRoomGroupingFlow = selectedSpaceDataSource.stream() val selectedRoomGroupingFlow = selectedSpaceDataSource.stream()
private val spaceBackstack = ArrayDeque<String?>()
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? { fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? {
// XXX we should somehow make it live :/ just a work around // XXX we should somehow make it live :/ just a work around
// For example just after creating a space and switching to it the // For example just after creating a space and switching to it the
@ -87,12 +89,16 @@ class AppStateHandler @Inject constructor(
} }
} }
fun setCurrentSpace(spaceId: String?, session: Session? = null, persistNow: Boolean = false) { fun setCurrentSpace(spaceId: String?, session: Session? = null, persistNow: Boolean = false, isForwardNavigation: Boolean = true) {
val currentSpace = (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.BySpace)?.space()
val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
if (selectedSpaceDataSource.currentValue?.orNull() is RoomGroupingMethod.BySpace && if (currentSpace != null && spaceId == currentSpace.roomId) return
spaceId == selectedSpaceDataSource.currentValue?.orNull()?.space()?.roomId) return
val spaceSum = spaceId?.let { uSession.getRoomSummary(spaceId) } val spaceSum = spaceId?.let { uSession.getRoomSummary(spaceId) }
if (isForwardNavigation) {
spaceBackstack.addLast(currentSpace?.roomId)
}
if (persistNow) { if (persistNow) {
uiStateRepository.storeGroupingMethod(true, uSession.sessionId) uiStateRepository.storeGroupingMethod(true, uSession.sessionId)
uiStateRepository.storeSelectedSpace(spaceSum?.roomId, uSession.sessionId) uiStateRepository.storeSelectedSpace(spaceSum?.roomId, uSession.sessionId)
@ -151,6 +157,8 @@ class AppStateHandler @Inject constructor(
}.launchIn(session.coroutineScope) }.launchIn(session.coroutineScope)
} }
fun getSpaceBackstack() = spaceBackstack
fun safeActiveSpaceId(): String? { fun safeActiveSpaceId(): String? {
return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.BySpace)?.spaceSummary?.roomId return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.BySpace)?.spaceSummary?.roomId
} }

View file

@ -101,6 +101,9 @@ import im.vector.app.features.onboarding.ftueauth.FtueAuthAccountCreatedFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthCaptchaFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthCaptchaFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseDisplayNameFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseDisplayNameFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseProfilePictureFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseProfilePictureFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthCombinedLoginFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthCombinedRegisterFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthCombinedServerSelectionFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthEmailEntryFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthEmailEntryFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthGenericTextInputFormFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthGenericTextInputFormFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthLegacyStyleCaptchaFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthLegacyStyleCaptchaFragment
@ -521,6 +524,21 @@ interface FragmentModule {
@FragmentKey(FtueAuthPersonalizationCompleteFragment::class) @FragmentKey(FtueAuthPersonalizationCompleteFragment::class)
fun bindFtueAuthPersonalizationCompleteFragment(fragment: FtueAuthPersonalizationCompleteFragment): Fragment fun bindFtueAuthPersonalizationCompleteFragment(fragment: FtueAuthPersonalizationCompleteFragment): Fragment
@Binds
@IntoMap
@FragmentKey(FtueAuthCombinedLoginFragment::class)
fun bindFtueAuthCombinedLoginFragment(fragment: FtueAuthCombinedLoginFragment): Fragment
@Binds
@IntoMap
@FragmentKey(FtueAuthCombinedRegisterFragment::class)
fun bindFtueAuthCombinedRegisterFragment(fragment: FtueAuthCombinedRegisterFragment): Fragment
@Binds
@IntoMap
@FragmentKey(FtueAuthCombinedServerSelectionFragment::class)
fun bindFtueAuthCombinedServerSelectionFragment(fragment: FtueAuthCombinedServerSelectionFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(UserListFragment::class) @FragmentKey(UserListFragment::class)

View file

@ -26,6 +26,7 @@ interface VectorFeatures {
fun isOnboardingUseCaseEnabled(): Boolean fun isOnboardingUseCaseEnabled(): Boolean
fun isOnboardingPersonalizeEnabled(): Boolean fun isOnboardingPersonalizeEnabled(): Boolean
fun isOnboardingCombinedRegisterEnabled(): Boolean fun isOnboardingCombinedRegisterEnabled(): Boolean
fun isOnboardingCombinedLoginEnabled(): Boolean
fun isScreenSharingEnabled(): Boolean fun isScreenSharingEnabled(): Boolean
enum class OnboardingVariant { enum class OnboardingVariant {
@ -42,5 +43,6 @@ class DefaultVectorFeatures : VectorFeatures {
override fun isOnboardingUseCaseEnabled() = true override fun isOnboardingUseCaseEnabled() = true
override fun isOnboardingPersonalizeEnabled() = false override fun isOnboardingPersonalizeEnabled() = false
override fun isOnboardingCombinedRegisterEnabled() = false override fun isOnboardingCombinedRegisterEnabled() = false
override fun isOnboardingCombinedLoginEnabled() = false
override fun isScreenSharingEnabled(): Boolean = true override fun isScreenSharingEnabled(): Boolean = true
} }

View file

@ -199,43 +199,13 @@ class HomeActivity :
when (sharedAction) { when (sharedAction) {
is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START) is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START)
is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START) is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START)
is HomeActivitySharedAction.OpenGroup -> { is HomeActivitySharedAction.OpenGroup -> openGroup(sharedAction.shouldClearFragment)
views.drawerLayout.closeDrawer(GravityCompat.START) is HomeActivitySharedAction.OpenSpacePreview -> startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId))
is HomeActivitySharedAction.AddSpace -> createSpaceResultLauncher.launch(SpaceCreationActivity.newIntent(this))
// Temporary is HomeActivitySharedAction.ShowSpaceSettings -> showSpaceSettings(sharedAction.spaceId)
// When switching from space to group or group to space, we need to reload the fragment is HomeActivitySharedAction.OpenSpaceInvite -> openSpaceInvite(sharedAction.spaceId)
// To be removed when dropping legacy groups HomeActivitySharedAction.SendSpaceFeedBack -> bugReporter.openBugReportScreen(this, ReportType.SPACE_BETA_FEEDBACK)
if (sharedAction.clearFragment) { HomeActivitySharedAction.CloseGroup -> closeGroup()
replaceFragment(views.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true)
} else {
// nop
}
// we might want to delay that to avoid having the drawer animation lagging
// would be probably better to let the drawer do that? in the on closed callback?
}
is HomeActivitySharedAction.OpenSpacePreview -> {
startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId))
}
is HomeActivitySharedAction.AddSpace -> {
createSpaceResultLauncher.launch(SpaceCreationActivity.newIntent(this))
}
is HomeActivitySharedAction.ShowSpaceSettings -> {
// open bottom sheet
SpaceSettingsMenuBottomSheet
.newInstance(sharedAction.spaceId, object : SpaceSettingsMenuBottomSheet.InteractionListener {
override fun onShareSpaceSelected(spaceId: String) {
ShareSpaceBottomSheet.show(supportFragmentManager, spaceId)
}
})
.show(supportFragmentManager, "SPACE_SETTINGS")
}
is HomeActivitySharedAction.OpenSpaceInvite -> {
SpaceInviteBottomSheet.newInstance(sharedAction.spaceId)
.show(supportFragmentManager, "SPACE_INVITE")
}
HomeActivitySharedAction.SendSpaceFeedBack -> {
bugReporter.openBugReportScreen(this, ReportType.SPACE_BETA_FEEDBACK)
}
} }
} }
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
@ -272,6 +242,37 @@ class HomeActivity :
homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted) homeActivityViewModel.handle(HomeActivityViewActions.ViewStarted)
} }
private fun openGroup(shouldClearFragment: Boolean) {
views.drawerLayout.closeDrawer(GravityCompat.START)
// When switching from space to group or group to space, we need to reload the fragment
if (shouldClearFragment) {
replaceFragment(views.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true)
} else {
// do nothing
}
}
private fun showSpaceSettings(spaceId: String) {
// open bottom sheet
SpaceSettingsMenuBottomSheet
.newInstance(spaceId, object : SpaceSettingsMenuBottomSheet.InteractionListener {
override fun onShareSpaceSelected(spaceId: String) {
ShareSpaceBottomSheet.show(supportFragmentManager, spaceId)
}
})
.show(supportFragmentManager, "SPACE_SETTINGS")
}
private fun openSpaceInvite(spaceId: String) {
SpaceInviteBottomSheet.newInstance(spaceId)
.show(supportFragmentManager, "SPACE_INVITE")
}
private fun closeGroup() {
views.drawerLayout.openDrawer(GravityCompat.START)
}
private fun handleShowAnalyticsOptIn() { private fun handleShowAnalyticsOptIn() {
navigator.openAnalyticsOptIn(this) navigator.openAnalyticsOptIn(this)
} }

View file

@ -24,7 +24,8 @@ import im.vector.app.core.platform.VectorSharedAction
sealed class HomeActivitySharedAction : VectorSharedAction { sealed class HomeActivitySharedAction : VectorSharedAction {
object OpenDrawer : HomeActivitySharedAction() object OpenDrawer : HomeActivitySharedAction()
object CloseDrawer : HomeActivitySharedAction() object CloseDrawer : HomeActivitySharedAction()
data class OpenGroup(val clearFragment: Boolean) : HomeActivitySharedAction() data class OpenGroup(val shouldClearFragment: Boolean) : HomeActivitySharedAction()
object CloseGroup : HomeActivitySharedAction()
object AddSpace : HomeActivitySharedAction() object AddSpace : HomeActivitySharedAction()
data class OpenSpacePreview(val spaceId: String) : HomeActivitySharedAction() data class OpenSpacePreview(val spaceId: String) : HomeActivitySharedAction()
data class OpenSpaceInvite(val spaceId: String) : HomeActivitySharedAction() data class OpenSpaceInvite(val spaceId: String) : HomeActivitySharedAction()

View file

@ -33,6 +33,7 @@ import im.vector.app.R
import im.vector.app.RoomGroupingMethod import im.vector.app.RoomGroupingMethod
import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.commitTransaction
import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.platform.OnBackPressed
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
@ -69,7 +70,8 @@ class HomeDetailFragment @Inject constructor(
private val appStateHandler: AppStateHandler private val appStateHandler: AppStateHandler
) : VectorBaseFragment<FragmentHomeDetailBinding>(), ) : VectorBaseFragment<FragmentHomeDetailBinding>(),
KeysBackupBanner.Delegate, KeysBackupBanner.Delegate,
CurrentCallsView.Callback { CurrentCallsView.Callback,
OnBackPressed {
private val viewModel: HomeDetailViewModel by fragmentViewModel() private val viewModel: HomeDetailViewModel by fragmentViewModel()
private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel() private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel()
@ -130,12 +132,8 @@ class HomeDetailFragment @Inject constructor(
viewModel.onEach(HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod -> viewModel.onEach(HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod ->
when (roomGroupingMethod) { when (roomGroupingMethod) {
is RoomGroupingMethod.ByLegacyGroup -> { is RoomGroupingMethod.ByLegacyGroup -> onGroupChange(roomGroupingMethod.groupSummary)
onGroupChange(roomGroupingMethod.groupSummary) is RoomGroupingMethod.BySpace -> onSpaceChange(roomGroupingMethod.spaceSummary)
}
is RoomGroupingMethod.BySpace -> {
onSpaceChange(roomGroupingMethod.spaceSummary)
}
} }
} }
@ -157,7 +155,6 @@ class HomeDetailFragment @Inject constructor(
unknownDeviceDetectorSharedViewModel.onEach { state -> unknownDeviceDetectorSharedViewModel.onEach { state ->
state.unknownSessions.invoke()?.let { unknownDevices -> state.unknownSessions.invoke()?.let { unknownDevices ->
// Timber.v("## Detector Triggerred in fragment - ${unknownDevices.firstOrNull()}")
if (unknownDevices.firstOrNull()?.currentSessionTrust == true) { if (unknownDevices.firstOrNull()?.currentSessionTrust == true) {
val uid = "review_login" val uid = "review_login"
alertManager.cancelAlert(uid) alertManager.cancelAlert(uid)
@ -190,6 +187,27 @@ class HomeDetailFragment @Inject constructor(
} }
} }
private fun navigateBack() {
try {
val lastSpace = appStateHandler.getSpaceBackstack().removeLast()
setCurrentSpace(lastSpace)
} catch (e: NoSuchElementException) {
navigateUpOneSpace()
}
}
private fun setCurrentSpace(spaceId: String?) {
appStateHandler.setCurrentSpace(spaceId, isForwardNavigation = false)
sharedActionViewModel.post(HomeActivitySharedAction.CloseGroup)
}
private fun navigateUpOneSpace() {
val parentId = getCurrentSpace()?.flattenParentIds?.lastOrNull()
setCurrentSpace(parentId)
}
private fun getCurrentSpace() = (appStateHandler.getCurrentRoomGroupingMethod() as? RoomGroupingMethod.BySpace)?.spaceSummary
private fun handleCallStarted() { private fun handleCallStarted() {
dismissLoadingDialog() dismissLoadingDialog()
val fragmentTag = HomeTab.DialPad.toFragmentTag() val fragmentTag = HomeTab.DialPad.toFragmentTag()
@ -203,20 +221,16 @@ class HomeDetailFragment @Inject constructor(
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
// update notification tab if needed
updateTabVisibilitySafely(R.id.bottom_action_notification, vectorPreferences.labAddNotificationTab()) updateTabVisibilitySafely(R.id.bottom_action_notification, vectorPreferences.labAddNotificationTab())
callManager.checkForProtocolsSupportIfNeeded() callManager.checkForProtocolsSupportIfNeeded()
refreshSpaceState()
}
// Current space/group is not live so at least refresh toolbar on resume private fun refreshSpaceState() {
appStateHandler.getCurrentRoomGroupingMethod()?.let { roomGroupingMethod -> when (val roomGroupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) {
when (roomGroupingMethod) { is RoomGroupingMethod.ByLegacyGroup -> onGroupChange(roomGroupingMethod.groupSummary)
is RoomGroupingMethod.ByLegacyGroup -> { is RoomGroupingMethod.BySpace -> onSpaceChange(roomGroupingMethod.spaceSummary)
onGroupChange(roomGroupingMethod.groupSummary) else -> Unit
}
is RoomGroupingMethod.BySpace -> {
onSpaceChange(roomGroupingMethod.spaceSummary)
}
}
} }
} }
@ -260,12 +274,12 @@ class HomeDetailFragment @Inject constructor(
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer) viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
colorInt = colorProvider.getColorFromAttribute(R.attr.colorPrimary) colorInt = colorProvider.getColorFromAttribute(R.attr.colorPrimary)
contentAction = Runnable { contentAction = Runnable {
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let { (weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let { activity ->
// mark as ignored to avoid showing it again // mark as ignored to avoid showing it again
unknownDeviceDetectorSharedViewModel.handle( unknownDeviceDetectorSharedViewModel.handle(
UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(oldUnverified.mapNotNull { it.deviceId }) UnknownDeviceDetectorSharedViewModel.Action.IgnoreDevice(oldUnverified.mapNotNull { it.deviceId })
) )
it.navigator.openSettings(it, EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS) activity.navigator.openSettings(activity, EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS)
} }
} }
dismissedAction = Runnable { dismissedAction = Runnable {
@ -324,11 +338,11 @@ class HomeDetailFragment @Inject constructor(
withState(viewModel) { withState(viewModel) {
when (it.roomGroupingMethod) { when (it.roomGroupingMethod) {
is RoomGroupingMethod.ByLegacyGroup -> { is RoomGroupingMethod.ByLegacyGroup -> {
// nothing do far // do nothing
} }
is RoomGroupingMethod.BySpace -> { is RoomGroupingMethod.BySpace -> {
it.roomGroupingMethod.spaceSummary?.let { it.roomGroupingMethod.spaceSummary?.let { spaceSummary ->
sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(it.roomId)) sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(spaceSummary.roomId))
} }
} }
} }
@ -348,17 +362,6 @@ class HomeDetailFragment @Inject constructor(
viewModel.handle(HomeDetailAction.SwitchTab(tab)) viewModel.handle(HomeDetailAction.SwitchTab(tab))
true true
} }
// val menuView = bottomNavigationView.getChildAt(0) as BottomNavigationMenuView
// bottomNavigationView.getOrCreateBadge()
// menuView.forEachIndexed { index, view ->
// val itemView = view as BottomNavigationItemView
// val badgeLayout = LayoutInflater.from(requireContext()).inflate(R.layout.vector_home_badge_unread_layout, menuView, false)
// val unreadCounterBadgeView: UnreadCounterBadgeView = badgeLayout.findViewById(R.id.actionUnreadCounterBadgeView)
// itemView.addView(badgeLayout)
// unreadCounterBadgeViews.add(index, unreadCounterBadgeView)
// }
} }
private fun updateUIForTab(tab: HomeTab) { private fun updateUIForTab(tab: HomeTab) {
@ -436,7 +439,6 @@ class HomeDetailFragment @Inject constructor(
} }
override fun invalidate() = withState(viewModel) { override fun invalidate() = withState(viewModel) {
// Timber.v(it.toString())
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_people).render(it.notificationCountPeople, it.notificationHighlightPeople) views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_people).render(it.notificationCountPeople, it.notificationHighlightPeople)
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms) views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup) views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup)
@ -496,4 +498,11 @@ class HomeDetailFragment @Inject constructor(
} }
return this return this
} }
override fun onBackPressed(toolbarButton: Boolean) = if (getCurrentSpace() != null) {
navigateBack()
true
} else {
false
}
} }

View file

@ -159,3 +159,9 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), resources.displayMetrics).toInt() return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), resources.displayMetrics).toInt()
} }
} }
fun SocialLoginButtonsView.render(ssoProviders: List<SsoIdentityProvider>?, mode: SocialLoginButtonsView.Mode, listener: (String?) -> Unit) {
this.mode = mode
this.ssoIdentityProviders = ssoProviders?.sorted()
this.listener = SocialLoginButtonsView.InteractionListener { listener(it) }
}

View file

@ -19,7 +19,7 @@ package im.vector.app.features.onboarding
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.andThen import im.vector.app.core.extensions.andThen
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.onboarding.OnboardingAction.LoginOrRegister import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction.LoginDirect
import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.MatrixPatterns.getServerName
import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@ -33,8 +33,8 @@ class DirectLoginUseCase @Inject constructor(
private val uriFactory: UriFactory private val uriFactory: UriFactory
) { ) {
suspend fun execute(action: LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?): Result<Session> { suspend fun execute(action: LoginDirect, homeServerConnectionConfig: HomeServerConnectionConfig?): Result<Session> {
return fetchWellKnown(action.username, homeServerConnectionConfig) return fetchWellKnown(action.matrixId, homeServerConnectionConfig)
.andThen { wellKnown -> createSessionFor(wellKnown, action, homeServerConnectionConfig) } .andThen { wellKnown -> createSessionFor(wellKnown, action, homeServerConnectionConfig) }
} }
@ -42,13 +42,13 @@ class DirectLoginUseCase @Inject constructor(
authenticationService.getWellKnownData(matrixId, config) authenticationService.getWellKnownData(matrixId, config)
} }
private suspend fun createSessionFor(data: WellknownResult, action: LoginOrRegister, config: HomeServerConnectionConfig?) = when (data) { private suspend fun createSessionFor(data: WellknownResult, action: LoginDirect, config: HomeServerConnectionConfig?) = when (data) {
is WellknownResult.Prompt -> loginDirect(action, data, config) is WellknownResult.Prompt -> loginDirect(action, data, config)
is WellknownResult.FailPrompt -> handleFailPrompt(data, action, config) is WellknownResult.FailPrompt -> handleFailPrompt(data, action, config)
else -> onWellKnownError() else -> onWellKnownError()
} }
private suspend fun handleFailPrompt(data: WellknownResult.FailPrompt, action: LoginOrRegister, config: HomeServerConnectionConfig?): Result<Session> { private suspend fun handleFailPrompt(data: WellknownResult.FailPrompt, action: LoginDirect, config: HomeServerConnectionConfig?): Result<Session> {
// Relax on IS discovery if homeserver is valid // Relax on IS discovery if homeserver is valid
val isMissingInformationToLogin = data.homeServerUrl == null || data.wellKnown == null val isMissingInformationToLogin = data.homeServerUrl == null || data.wellKnown == null
return when { return when {
@ -57,12 +57,12 @@ class DirectLoginUseCase @Inject constructor(
} }
} }
private suspend fun loginDirect(action: LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt, config: HomeServerConnectionConfig?): Result<Session> { private suspend fun loginDirect(action: LoginDirect, wellKnownPrompt: WellknownResult.Prompt, config: HomeServerConnectionConfig?): Result<Session> {
val alteredHomeServerConnectionConfig = config?.updateWith(wellKnownPrompt) ?: fallbackConfig(action, wellKnownPrompt) val alteredHomeServerConnectionConfig = config?.updateWith(wellKnownPrompt) ?: fallbackConfig(action, wellKnownPrompt)
return runCatching { return runCatching {
authenticationService.directAuthentication( authenticationService.directAuthentication(
alteredHomeServerConnectionConfig, alteredHomeServerConnectionConfig,
action.username, action.matrixId,
action.password, action.password,
action.initialDeviceName action.initialDeviceName
) )
@ -74,8 +74,8 @@ class DirectLoginUseCase @Inject constructor(
identityServerUri = wellKnownPrompt.identityServerUrl?.let { uriFactory.parse(it) } identityServerUri = wellKnownPrompt.identityServerUrl?.let { uriFactory.parse(it) }
) )
private fun fallbackConfig(action: LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) = HomeServerConnectionConfig( private fun fallbackConfig(action: LoginDirect, wellKnownPrompt: WellknownResult.Prompt) = HomeServerConnectionConfig(
homeServerUri = uriFactory.parse("https://${action.username.getServerName()}"), homeServerUri = uriFactory.parse("https://${action.matrixId.getServerName()}"),
homeServerUriBase = uriFactory.parse(wellKnownPrompt.homeServerUrl), homeServerUriBase = uriFactory.parse(wellKnownPrompt.homeServerUrl),
identityServerUri = wellKnownPrompt.identityServerUrl?.let { uriFactory.parse(it) } identityServerUri = wellKnownPrompt.identityServerUrl?.let { uriFactory.parse(it) }
) )

View file

@ -46,9 +46,12 @@ sealed interface OnboardingAction : VectorViewModelAction {
data class ResetPassword(val email: String, val newPassword: String) : OnboardingAction data class ResetPassword(val email: String, val newPassword: String) : OnboardingAction
object ResetPasswordMailConfirmed : OnboardingAction object ResetPasswordMailConfirmed : OnboardingAction
// Login or Register, depending on the signMode sealed interface AuthenticateAction : OnboardingAction {
data class LoginOrRegister(val username: String, val password: String, val initialDeviceName: String) : OnboardingAction data class Register(val username: String, val password: String, val initialDeviceName: String) : AuthenticateAction
data class Register(val username: String, val password: String, val initialDeviceName: String) : OnboardingAction data class Login(val username: String, val password: String, val initialDeviceName: String) : AuthenticateAction
data class LoginDirect(val matrixId: String, val password: String, val initialDeviceName: String) : AuthenticateAction
}
object StopEmailValidationCheck : OnboardingAction object StopEmailValidationCheck : OnboardingAction
data class PostRegisterAction(val registerAction: RegisterAction) : OnboardingAction data class PostRegisterAction(val registerAction: RegisterAction) : OnboardingAction

View file

@ -37,6 +37,7 @@ sealed class OnboardingViewEvents : VectorViewEvents {
object OpenUseCaseSelection : OnboardingViewEvents() object OpenUseCaseSelection : OnboardingViewEvents()
object OpenServerSelection : OnboardingViewEvents() object OpenServerSelection : OnboardingViewEvents()
object OpenCombinedRegister : OnboardingViewEvents() object OpenCombinedRegister : OnboardingViewEvents()
object OpenCombinedLogin : OnboardingViewEvents()
object EditServerSelection : OnboardingViewEvents() object EditServerSelection : OnboardingViewEvents()
data class OnServerSelectionDone(val serverType: ServerType) : OnboardingViewEvents() data class OnServerSelectionDone(val serverType: ServerType) : OnboardingViewEvents()
object OnLoginFlowRetrieved : OnboardingViewEvents() object OnLoginFlowRetrieved : OnboardingViewEvents()

View file

@ -42,6 +42,7 @@ import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.login.ReAuthHelper
import im.vector.app.features.login.ServerType import im.vector.app.features.login.ServerType
import im.vector.app.features.login.SignMode import im.vector.app.features.login.SignMode
import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult
import im.vector.app.features.onboarding.ftueauth.MatrixOrgRegistrationStagesComparator import im.vector.app.features.onboarding.ftueauth.MatrixOrgRegistrationStagesComparator
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -139,8 +140,7 @@ class OnboardingViewModel @AssistedInject constructor(
is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action) is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action)
is OnboardingAction.InitWith -> handleInitWith(action) is OnboardingAction.InitWith -> handleInitWith(action)
is OnboardingAction.HomeServerChange -> withAction(action) { handleHomeserverChange(action) } is OnboardingAction.HomeServerChange -> withAction(action) { handleHomeserverChange(action) }
is OnboardingAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action } is AuthenticateAction -> withAction(action) { handleAuthenticateAction(action) }
is OnboardingAction.Register -> handleRegisterWith(action).also { lastAction = action }
is OnboardingAction.LoginWithToken -> handleLoginWithToken(action) is OnboardingAction.LoginWithToken -> handleLoginWithToken(action)
is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action) is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action)
is OnboardingAction.ResetPassword -> handleResetPassword(action) is OnboardingAction.ResetPassword -> handleResetPassword(action)
@ -165,6 +165,14 @@ class OnboardingViewModel @AssistedInject constructor(
block(action) block(action)
} }
private fun handleAuthenticateAction(action: AuthenticateAction) {
when (action) {
is AuthenticateAction.Register -> handleRegisterWith(action)
is AuthenticateAction.Login -> handleLogin(action)
is AuthenticateAction.LoginDirect -> handleDirectLogin(action, homeServerConnectionConfig = null)
}
}
private fun handleSplashAction(resetConfig: Boolean, onboardingFlow: OnboardingFlow) { private fun handleSplashAction(resetConfig: Boolean, onboardingFlow: OnboardingFlow) {
if (resetConfig) { if (resetConfig) {
loginConfig = null loginConfig = null
@ -188,16 +196,21 @@ class OnboardingViewModel @AssistedInject constructor(
} }
private fun continueToPageAfterSplash(onboardingFlow: OnboardingFlow) { private fun continueToPageAfterSplash(onboardingFlow: OnboardingFlow) {
val nextOnboardingStep = when (onboardingFlow) { when (onboardingFlow) {
OnboardingFlow.SignUp -> if (vectorFeatures.isOnboardingUseCaseEnabled()) { OnboardingFlow.SignUp -> {
_viewEvents.post(
if (vectorFeatures.isOnboardingUseCaseEnabled()) {
OnboardingViewEvents.OpenUseCaseSelection OnboardingViewEvents.OpenUseCaseSelection
} else { } else {
OnboardingViewEvents.OpenServerSelection OnboardingViewEvents.OpenServerSelection
} }
OnboardingFlow.SignIn, )
OnboardingFlow.SignInSignUp -> OnboardingViewEvents.OpenServerSelection }
OnboardingFlow.SignIn -> if (vectorFeatures.isOnboardingCombinedLoginEnabled()) {
handle(OnboardingAction.HomeServerChange.SelectHomeServer(defaultHomeserverUrl))
} else _viewEvents.post(OnboardingViewEvents.OpenServerSelection)
OnboardingFlow.SignInSignUp -> _viewEvents.post(OnboardingViewEvents.OpenServerSelection)
} }
_viewEvents.post(nextOnboardingStep)
} }
private fun handleUserAcceptCertificate(action: OnboardingAction.UserAcceptCertificate) { private fun handleUserAcceptCertificate(action: OnboardingAction.UserAcceptCertificate) {
@ -209,7 +222,7 @@ class OnboardingViewModel @AssistedInject constructor(
?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) } ?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) }
?.let { startAuthenticationFlow(finalLastAction, it) } ?.let { startAuthenticationFlow(finalLastAction, it) }
} }
is OnboardingAction.LoginOrRegister -> is AuthenticateAction.LoginDirect ->
handleDirectLogin( handleDirectLogin(
finalLastAction, finalLastAction,
HomeServerConnectionConfig.Builder() HomeServerConnectionConfig.Builder()
@ -307,7 +320,7 @@ class OnboardingViewModel @AssistedInject constructor(
private fun OnboardingViewState.hasSelectedMatrixOrg() = selectedHomeserver.userFacingUrl == matrixOrgUrl private fun OnboardingViewState.hasSelectedMatrixOrg() = selectedHomeserver.userFacingUrl == matrixOrgUrl
private fun handleRegisterWith(action: OnboardingAction.Register) { private fun handleRegisterWith(action: AuthenticateAction.Register) {
reAuthHelper.data = action.password reAuthHelper.data = action.password
handleRegisterAction( handleRegisterAction(
RegisterAction.CreateAccount( RegisterAction.CreateAccount(
@ -482,16 +495,7 @@ class OnboardingViewModel @AssistedInject constructor(
} }
} }
private fun handleLoginOrRegister(action: OnboardingAction.LoginOrRegister) = withState { state -> private fun handleDirectLogin(action: AuthenticateAction.LoginDirect, homeServerConnectionConfig: HomeServerConnectionConfig?) {
when (state.signMode) {
SignMode.Unknown -> error("Developer error, invalid sign mode")
SignMode.SignIn -> handleLogin(action)
SignMode.SignUp -> handleRegisterWith(OnboardingAction.Register(action.username, action.password, action.initialDeviceName))
SignMode.SignInWithMatrixId -> handleDirectLogin(action, null)
}
}
private fun handleDirectLogin(action: OnboardingAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) {
setState { copy(isLoading = true) } setState { copy(isLoading = true) }
currentJob = viewModelScope.launch { currentJob = viewModelScope.launch {
directLoginUseCase.execute(action, homeServerConnectionConfig).fold( directLoginUseCase.execute(action, homeServerConnectionConfig).fold(
@ -504,7 +508,7 @@ class OnboardingViewModel @AssistedInject constructor(
} }
} }
private fun handleLogin(action: OnboardingAction.LoginOrRegister) { private fun handleLogin(action: AuthenticateAction.Login) {
val safeLoginWizard = loginWizard val safeLoginWizard = loginWizard
if (safeLoginWizard == null) { if (safeLoginWizard == null) {
@ -648,7 +652,11 @@ class OnboardingViewModel @AssistedInject constructor(
when (trigger) { when (trigger) {
is OnboardingAction.HomeServerChange.EditHomeServer -> { is OnboardingAction.HomeServerChange.EditHomeServer -> {
when (awaitState().onboardingFlow) { when (awaitState().onboardingFlow) {
OnboardingFlow.SignUp -> internalRegisterAction(RegisterAction.StartRegistration) { _ -> OnboardingFlow.SignUp -> internalRegisterAction(RegisterAction.StartRegistration) {
updateServerSelection(config, serverTypeOverride, authResult)
_viewEvents.post(OnboardingViewEvents.OnHomeserverEdited)
}
OnboardingFlow.SignIn -> {
updateServerSelection(config, serverTypeOverride, authResult) updateServerSelection(config, serverTypeOverride, authResult)
_viewEvents.post(OnboardingViewEvents.OnHomeserverEdited) _viewEvents.post(OnboardingViewEvents.OnHomeserverEdited)
} }
@ -661,7 +669,10 @@ class OnboardingViewModel @AssistedInject constructor(
when (awaitState().onboardingFlow) { when (awaitState().onboardingFlow) {
OnboardingFlow.SignIn -> { OnboardingFlow.SignIn -> {
updateSignMode(SignMode.SignIn) updateSignMode(SignMode.SignIn)
_viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignIn)) when (vectorFeatures.isOnboardingCombinedLoginEnabled()) {
true -> _viewEvents.post(OnboardingViewEvents.OpenCombinedLogin)
false -> _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignIn))
}
} }
OnboardingFlow.SignUp -> { OnboardingFlow.SignUp -> {
updateSignMode(SignMode.SignUp) updateSignMode(SignMode.SignUp)

View file

@ -0,0 +1,161 @@
/*
* Copyright 2019 New Vector Ltd
*
* 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 im.vector.app.features.onboarding.ftueauth
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.autofill.HintConstants
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import im.vector.app.R
import im.vector.app.core.extensions.content
import im.vector.app.core.extensions.editText
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.hidePassword
import im.vector.app.core.extensions.realignPercentagesToParent
import im.vector.app.core.extensions.setOnImeDoneListener
import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentFtueCombinedLoginBinding
import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.SSORedirectRouterActivity
import im.vector.app.features.login.SocialLoginButtonsView
import im.vector.app.features.login.render
import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingViewEvents
import im.vector.app.features.onboarding.OnboardingViewState
import kotlinx.coroutines.flow.launchIn
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import javax.inject.Inject
class FtueAuthCombinedLoginFragment @Inject constructor(
private val loginFieldsValidation: LoginFieldsValidation,
private val loginErrorParser: LoginErrorParser
) : AbstractSSOFtueAuthFragment<FragmentFtueCombinedLoginBinding>() {
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueCombinedLoginBinding {
return FragmentFtueCombinedLoginBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupSubmitButton()
views.loginRoot.realignPercentagesToParent()
views.editServerButton.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) }
views.loginPasswordInput.setOnImeDoneListener { submit() }
}
private fun setupSubmitButton() {
views.loginSubmit.setOnClickListener { submit() }
observeContentChangesAndResetErrors(views.loginInput, views.loginPasswordInput, views.loginSubmit)
.launchIn(viewLifecycleOwner.lifecycleScope)
}
private fun submit() {
cleanupUi()
loginFieldsValidation.validate(views.loginInput.content(), views.loginPasswordInput.content())
.onUsernameOrIdError { views.loginInput.error = it }
.onPasswordError { views.loginPasswordInput.error = it }
.onValid { usernameOrId, password ->
val initialDeviceName = getString(R.string.login_default_session_public_name)
viewModel.handle(OnboardingAction.AuthenticateAction.Login(usernameOrId, password, initialDeviceName))
}
}
private fun cleanupUi() {
views.loginSubmit.hideKeyboard()
views.loginInput.error = null
views.loginPasswordInput.error = null
}
override fun resetViewModel() {
viewModel.handle(OnboardingAction.ResetAuthenticationAttempt)
}
override fun onError(throwable: Throwable) {
// Trick to display the error without text.
views.loginInput.error = " "
loginErrorParser.parse(throwable, views.loginPasswordInput.content())
.onUnknown { super.onError(it) }
.onUsernameOrIdError { views.loginInput.error = it }
.onPasswordError { views.loginPasswordInput.error = it }
}
override fun updateWithState(state: OnboardingViewState) {
setupUi(state)
setupAutoFill()
views.selectedServerName.text = state.selectedHomeserver.userFacingUrl.toReducedUrl()
views.selectedServerDescription.text = state.selectedHomeserver.description
if (state.isLoading) {
// Ensure password is hidden
views.loginPasswordInput.editText().hidePassword()
}
}
private fun setupUi(state: OnboardingViewState) {
when (state.selectedHomeserver.preferredLoginMode) {
is LoginMode.SsoAndPassword -> {
showUsernamePassword()
renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders)
}
is LoginMode.Sso -> {
hideUsernamePassword()
renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders)
}
else -> {
showUsernamePassword()
hideSsoProviders()
}
}
}
private fun renderSsoProviders(deviceId: String?, ssoProviders: List<SsoIdentityProvider>?) {
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
views.ssoButtonsHeader.isVisible = views.ssoGroup.isVisible && views.loginEntryGroup.isVisible
views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
viewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = deviceId,
providerId = id
)?.let { openInCustomTab(it) }
}
}
private fun hideSsoProviders() {
views.ssoGroup.isVisible = false
views.ssoButtons.ssoIdentityProviders = null
}
private fun hideUsernamePassword() {
views.loginEntryGroup.isVisible = false
}
private fun showUsernamePassword() {
views.loginEntryGroup.isVisible = true
}
private fun setupAutoFill() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
views.loginInput.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME)
views.loginPasswordInput.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
}
}
}

View file

@ -21,7 +21,6 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import androidx.autofill.HintConstants import androidx.autofill.HintConstants
import androidx.core.text.isDigitsOnly import androidx.core.text.isDigitsOnly
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -31,22 +30,22 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.content import im.vector.app.core.extensions.content
import im.vector.app.core.extensions.editText import im.vector.app.core.extensions.editText
import im.vector.app.core.extensions.hasContentFlow
import im.vector.app.core.extensions.hasSurroundingSpaces import im.vector.app.core.extensions.hasSurroundingSpaces
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.hidePassword import im.vector.app.core.extensions.hidePassword
import im.vector.app.core.extensions.realignPercentagesToParent import im.vector.app.core.extensions.realignPercentagesToParent
import im.vector.app.core.extensions.setOnImeDoneListener
import im.vector.app.core.extensions.toReducedUrl import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentFtueCombinedRegisterBinding import im.vector.app.databinding.FragmentFtueCombinedRegisterBinding
import im.vector.app.features.login.LoginMode import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.SSORedirectRouterActivity import im.vector.app.features.login.SSORedirectRouterActivity
import im.vector.app.features.login.SocialLoginButtonsView import im.vector.app.features.login.SocialLoginButtonsView
import im.vector.app.features.login.render
import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction
import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewEvents
import im.vector.app.features.onboarding.OnboardingViewState import im.vector.app.features.onboarding.OnboardingViewState
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.failure.isInvalidPassword
import org.matrix.android.sdk.api.failure.isInvalidUsername import org.matrix.android.sdk.api.failure.isInvalidUsername
@ -66,36 +65,16 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupSubmitButton() setupSubmitButton()
views.createAccountRoot.realignPercentagesToParent() views.createAccountRoot.realignPercentagesToParent()
views.editServerButton.debouncedClicks { views.editServerButton.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) }
viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) views.createAccountPasswordInput.setOnImeDoneListener { submit() }
}
views.createAccountPasswordInput.editText().setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
submit()
return@setOnEditorActionListener true
}
return@setOnEditorActionListener false
}
} }
private fun setupSubmitButton() { private fun setupSubmitButton() {
views.createAccountSubmit.setOnClickListener { submit() } views.createAccountSubmit.setOnClickListener { submit() }
observeInputFields() observeContentChangesAndResetErrors(views.createAccountInput, views.createAccountPasswordInput, views.createAccountSubmit)
.onEach {
views.createAccountPasswordInput.error = null
views.createAccountInput.error = null
views.createAccountSubmit.isEnabled = it
}
.launchIn(viewLifecycleOwner.lifecycleScope) .launchIn(viewLifecycleOwner.lifecycleScope)
} }
private fun observeInputFields() = combine(
views.createAccountInput.hasContentFlow { it.trim() },
views.createAccountPasswordInput.hasContentFlow(),
transform = { isLoginNotEmpty, isPasswordNotEmpty -> isLoginNotEmpty && isPasswordNotEmpty }
)
private fun submit() { private fun submit() {
withState(viewModel) { state -> withState(viewModel) { state ->
cleanupUi() cleanupUi()
@ -119,7 +98,7 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
} }
if (error == 0) { if (error == 0) {
viewModel.handle(OnboardingAction.Register(login, password, getString(R.string.login_default_session_public_name))) viewModel.handle(AuthenticateAction.Register(login, password, getString(R.string.login_default_session_public_name)))
} }
} }
} }
@ -185,9 +164,7 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
private fun renderSsoProviders(deviceId: String?, ssoProviders: List<SsoIdentityProvider>?) { private fun renderSsoProviders(deviceId: String?, ssoProviders: List<SsoIdentityProvider>?) {
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
views.ssoButtons.mode = SocialLoginButtonsView.Mode.MODE_CONTINUE views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
views.ssoButtons.ssoIdentityProviders = ssoProviders?.sorted()
views.ssoButtons.listener = SocialLoginButtonsView.InteractionListener { id ->
viewModel.getSsoUrl( viewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL, redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = deviceId, deviceId = deviceId,

View file

@ -26,6 +26,7 @@ import androidx.autofill.HintConstants
import androidx.core.text.isDigitsOnly import androidx.core.text.isDigitsOnly
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.hideKeyboard
@ -119,6 +120,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
} }
private fun submit() { private fun submit() {
withState(viewModel) { state ->
cleanupUi() cleanupUi()
val login = views.loginField.text.toString() val login = views.loginField.text.toString()
@ -152,7 +154,9 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
} }
if (error == 0) { if (error == 0) {
viewModel.handle(OnboardingAction.LoginOrRegister(login, password, getString(R.string.login_default_session_public_name))) val initialDeviceName = getString(R.string.login_default_session_public_name)
viewModel.handle(state.signMode.toAuthenticateAction(login, password, initialDeviceName))
}
} }
} }

View file

@ -228,9 +228,14 @@ class FtueAuthVariant(
) )
} }
OnboardingViewEvents.OnHomeserverEdited -> activity.popBackstack() OnboardingViewEvents.OnHomeserverEdited -> activity.popBackstack()
OnboardingViewEvents.OpenCombinedLogin -> onStartCombinedLogin()
} }
} }
private fun onStartCombinedLogin() {
addRegistrationStageFragmentToBackstack(FtueAuthCombinedLoginFragment::class.java)
}
private fun onRegistrationFlow(viewEvents: OnboardingViewEvents.RegistrationFlowResult) { private fun onRegistrationFlow(viewEvents: OnboardingViewEvents.RegistrationFlowResult) {
when { when {
registrationShouldFallback(viewEvents) -> displayFallbackWebDialog() registrationShouldFallback(viewEvents) -> displayFallbackWebDialog()

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* 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 im.vector.app.features.onboarding.ftueauth
import android.widget.Button
import com.google.android.material.textfield.TextInputLayout
import im.vector.app.core.extensions.hasContentFlow
import im.vector.app.features.login.SignMode
import im.vector.app.features.onboarding.OnboardingAction
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onEach
fun SignMode.toAuthenticateAction(login: String, password: String, initialDeviceName: String): OnboardingAction.AuthenticateAction {
return when (this) {
SignMode.Unknown -> error("developer error")
SignMode.SignUp -> OnboardingAction.AuthenticateAction.Register(username = login, password, initialDeviceName)
SignMode.SignIn -> OnboardingAction.AuthenticateAction.Login(username = login, password, initialDeviceName)
SignMode.SignInWithMatrixId -> OnboardingAction.AuthenticateAction.LoginDirect(matrixId = login, password, initialDeviceName)
}
}
/**
* A flow to monitor content changes from both username/id and password fields,
* clearing errors and enabling/disabling the submission button on non empty content changes.
*/
fun observeContentChangesAndResetErrors(username: TextInputLayout, password: TextInputLayout, submit: Button): Flow<*> {
return combine(
username.hasContentFlow { it.trim() },
password.hasContentFlow(),
transform = { usernameHasContent, passwordHasContent -> usernameHasContent && passwordHasContent }
).onEach {
username.error = null
password.error = null
submit.isEnabled = it
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* 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 im.vector.app.features.onboarding.ftueauth
import im.vector.app.R
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.onboarding.ftueauth.LoginErrorParser.LoginErrorResult
import org.matrix.android.sdk.api.failure.isInvalidPassword
import org.matrix.android.sdk.api.failure.isInvalidUsername
import org.matrix.android.sdk.api.failure.isLoginEmailUnknown
import javax.inject.Inject
class LoginErrorParser @Inject constructor(
private val errorFormatter: ErrorFormatter,
private val stringProvider: StringProvider,
) {
fun parse(throwable: Throwable, password: String): LoginErrorResult {
return when {
throwable.isInvalidUsername() -> {
LoginErrorResult(throwable, usernameOrIdError = errorFormatter.toHumanReadable(throwable))
}
throwable.isLoginEmailUnknown() -> {
LoginErrorResult(throwable, usernameOrIdError = stringProvider.getString(R.string.login_login_with_email_error))
}
throwable.isInvalidPassword() && password.hasSurroundingSpaces() -> {
LoginErrorResult(throwable, passwordError = stringProvider.getString(R.string.auth_invalid_login_param_space_in_password))
}
else -> {
LoginErrorResult(throwable)
}
}
}
private fun String.hasSurroundingSpaces() = trim() != this
data class LoginErrorResult(val cause: Throwable, val usernameOrIdError: String? = null, val passwordError: String? = null)
}
fun LoginErrorResult.onUnknown(action: (Throwable) -> Unit): LoginErrorResult {
when {
usernameOrIdError == null && passwordError == null -> action(cause)
}
return this
}
fun LoginErrorResult.onUsernameOrIdError(action: (String) -> Unit): LoginErrorResult {
usernameOrIdError?.let(action)
return this
}
fun LoginErrorResult.onPasswordError(action: (String) -> Unit): LoginErrorResult {
passwordError?.let(action)
return this
}

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* 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 im.vector.app.features.onboarding.ftueauth
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import javax.inject.Inject
class LoginFieldsValidation @Inject constructor(
private val stringProvider: StringProvider
) {
fun validate(usernameOrId: String, password: String): LoginValidationResult {
return LoginValidationResult(usernameOrId, password, validateUsernameOrId(usernameOrId), validatePassword(password))
}
private fun validateUsernameOrId(usernameOrId: String): String? {
val accountError = when {
usernameOrId.isEmpty() -> stringProvider.getString(R.string.error_empty_field_enter_user_name)
else -> null
}
return accountError
}
private fun validatePassword(password: String): String? {
val passwordError = when {
password.isEmpty() -> stringProvider.getString(R.string.error_empty_field_your_password)
else -> null
}
return passwordError
}
}
fun LoginValidationResult.onValid(action: (String, String) -> Unit): LoginValidationResult {
when {
usernameOrIdError == null && passwordError == null -> action(usernameOrId, password)
}
return this
}
fun LoginValidationResult.onUsernameOrIdError(action: (String) -> Unit): LoginValidationResult {
usernameOrIdError?.let(action)
return this
}
fun LoginValidationResult.onPasswordError(action: (String) -> Unit): LoginValidationResult {
passwordError?.let(action)
return this
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* 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 im.vector.app.features.onboarding.ftueauth
data class LoginValidationResult(
val usernameOrId: String,
val password: String,
val usernameOrIdError: String?,
val passwordError: String?
)

View file

@ -0,0 +1,244 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/LoginFormScrollView"
android:layout_height="match_parent"
android:background="?android:colorBackground"
android:fillViewport="true"
android:paddingTop="0dp"
android:paddingBottom="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/loginRoot"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/loginGutterStart"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_start_percent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/loginGutterEnd"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintGuide_percent="@dimen/ftue_auth_gutter_end_percent" />
<Space
android:id="@+id/headerSpacing"
android:layout_width="match_parent"
android:layout_height="52dp"
app:layout_constraintBottom_toTopOf="@id/loginHeaderTitle"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/loginHeaderTitle"
style="@style/Widget.Vector.TextView.Title.Medium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="@string/ftue_auth_welcome_back_title"
android:textColor="?vctr_content_primary"
app:layout_constraintBottom_toTopOf="@id/titleContentSpacing"
app:layout_constraintEnd_toEndOf="@id/loginGutterEnd"
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
app:layout_constraintTop_toBottomOf="@id/headerSpacing" />
<Space
android:id="@+id/titleContentSpacing"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/chooseYourServerHeader"
app:layout_constraintHeight_percent="0.03"
app:layout_constraintTop_toBottomOf="@id/loginHeaderTitle" />
<TextView
android:id="@+id/chooseYourServerHeader"
style="@style/Widget.Vector.TextView.Caption"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:text="@string/ftue_auth_create_account_choose_server_header"
android:textColor="?vctr_content_secondary"
app:layout_constraintBottom_toTopOf="@id/selectedServerName"
app:layout_constraintEnd_toStartOf="@id/editServerButton"
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
app:layout_constraintTop_toBottomOf="@id/titleContentSpacing" />
<TextView
android:id="@+id/selectedServerName"
style="@style/Widget.Vector.TextView.Subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:textColor="?vctr_content_primary"
app:layout_constraintBottom_toTopOf="@id/selectedServerDescription"
app:layout_constraintEnd_toStartOf="@id/editServerButton"
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
app:layout_constraintTop_toBottomOf="@id/chooseYourServerHeader" />
<TextView
android:id="@+id/selectedServerDescription"
style="@style/Widget.Vector.TextView.Micro"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:textColor="?vctr_content_tertiary"
app:layout_constraintBottom_toTopOf="@id/serverSelectionSpacing"
app:layout_constraintEnd_toStartOf="@id/editServerButton"
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
app:layout_constraintTop_toBottomOf="@id/selectedServerName" />
<Button
android:id="@+id/editServerButton"
style="@style/Widget.Vector.Button.Outlined"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="0dp"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:text="@string/ftue_auth_create_account_edit_server_selection"
android:textAllCaps="true"
app:layout_constraintBottom_toBottomOf="@id/selectedServerDescription"
app:layout_constraintEnd_toEndOf="@id/loginGutterEnd"
app:layout_constraintTop_toTopOf="@id/chooseYourServerHeader" />
<Space
android:id="@+id/serverSelectionSpacing"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/loginInput"
app:layout_constraintHeight_percent="0.05"
app:layout_constraintTop_toBottomOf="@id/selectedServerDescription" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:background="?vctr_content_quaternary"
app:layout_constraintBottom_toBottomOf="@id/serverSelectionSpacing"
app:layout_constraintEnd_toEndOf="@id/loginGutterEnd"
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
app:layout_constraintTop_toTopOf="@id/serverSelectionSpacing" />
<androidx.constraintlayout.widget.Group
android:id="@+id/loginEntryGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
app:constraint_referenced_ids="loginInput,loginPasswordInput,entrySpacing,actionSpacing,loginSubmit" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginInput"
style="@style/Widget.Vector.TextInputLayout.Username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/username"
app:layout_constraintBottom_toTopOf="@id/entrySpacing"
app:layout_constraintEnd_toEndOf="@id/loginGutterEnd"
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
app:layout_constraintTop_toBottomOf="@id/serverSelectionSpacing">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:imeOptions="actionNext"
android:inputType="text"
android:maxLines="1"
android:nextFocusForward="@id/loginPasswordInput" />
</com.google.android.material.textfield.TextInputLayout>
<Space
android:id="@+id/entrySpacing"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/loginPasswordInput"
app:layout_constraintHeight_percent="0.03"
app:layout_constraintTop_toBottomOf="@id/loginInput" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginPasswordInput"
style="@style/Widget.Vector.TextInputLayout.Password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/login_signup_password_hint"
app:layout_constraintBottom_toTopOf="@id/actionSpacing"
app:layout_constraintEnd_toEndOf="@id/loginGutterEnd"
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
app:layout_constraintTop_toBottomOf="@id/entrySpacing">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<Space
android:id="@+id/actionSpacing"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/loginSubmit"
app:layout_constraintHeight_percent="0.02"
app:layout_constraintTop_toBottomOf="@id/loginPasswordInput" />
<Button
android:id="@+id/loginSubmit"
style="@style/Widget.Vector.Button.Login"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/login_signup_submit"
android:textAllCaps="true"
app:layout_constraintBottom_toTopOf="@id/ssoButtonsHeader"
app:layout_constraintEnd_toEndOf="@id/loginGutterEnd"
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
app:layout_constraintTop_toBottomOf="@id/actionSpacing" />
<androidx.constraintlayout.widget.Group
android:id="@+id/ssoGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="ssoButtonsHeader,ssoButtons"
app:layout_constraintBottom_toTopOf="@id/ssoButtonsHeader"
app:layout_constraintTop_toBottomOf="@id/loginSubmit"
tools:visibility="visible" />
<TextView
android:id="@+id/ssoButtonsHeader"
style="@style/Widget.Vector.TextView.Subtitle.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="@string/ftue_auth_create_account_sso_section_header"
android:textColor="?vctr_content_secondary"
app:layout_constraintBottom_toTopOf="@id/ssoButtons"
app:layout_constraintEnd_toEndOf="@id/loginGutterEnd"
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
app:layout_constraintTop_toBottomOf="@id/loginSubmit" />
<im.vector.app.features.login.SocialLoginButtonsView
android:id="@+id/ssoButtons"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/loginGutterEnd"
app:layout_constraintStart_toStartOf="@id/loginGutterStart"
app:layout_constraintTop_toBottomOf="@id/ssoButtonsHeader"
tools:signMode="signup" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

View file

@ -19,6 +19,8 @@
<string name="ftue_auth_create_account_matrix_dot_org_server_description">Join millions for free on the largest public server</string> <string name="ftue_auth_create_account_matrix_dot_org_server_description">Join millions for free on the largest public server</string>
<string name="ftue_auth_create_account_edit_server_selection">Edit</string> <string name="ftue_auth_create_account_edit_server_selection">Edit</string>
<string name="ftue_auth_welcome_back_title">Welcome back!</string>
<string name="ftue_auth_choose_server_title">Choose your server</string> <string name="ftue_auth_choose_server_title">Choose your server</string>
<string name="ftue_auth_choose_server_subtitle">What is the address of your server? Server is like a home for all your data.</string> <string name="ftue_auth_choose_server_subtitle">What is the address of your server? Server is like a home for all your data.</string>
<string name="ftue_auth_choose_server_entry_hint">Server URL</string> <string name="ftue_auth_choose_server_entry_hint">Server URL</string>

View file

@ -32,13 +32,13 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.WellKnown import org.matrix.android.sdk.api.auth.data.WellKnown
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
private val A_LOGIN_OR_REGISTER_ACTION = OnboardingAction.LoginOrRegister("@a-user:id.org", "a-password", "a-device-name") private val A_DIRECT_LOGIN_ACTION = OnboardingAction.AuthenticateAction.LoginDirect("@a-user:id.org", "a-password", "a-device-name")
private val A_WELLKNOWN_SUCCESS_RESULT = WellknownResult.Prompt("https://homeserverurl.com", identityServerUrl = null, WellKnown()) private val A_WELLKNOWN_SUCCESS_RESULT = WellknownResult.Prompt("https://homeserverurl.com", identityServerUrl = null, WellKnown())
private val A_WELLKNOWN_FAILED_WITH_CONTENT_RESULT = WellknownResult.FailPrompt("https://homeserverurl.com", WellKnown()) private val A_WELLKNOWN_FAILED_WITH_CONTENT_RESULT = WellknownResult.FailPrompt("https://homeserverurl.com", WellKnown())
private val A_WELLKNOWN_FAILED_WITHOUT_CONTENT_RESULT = WellknownResult.FailPrompt(null, null) private val A_WELLKNOWN_FAILED_WITHOUT_CONTENT_RESULT = WellknownResult.FailPrompt(null, null)
private val NO_HOMESERVER_CONFIG: HomeServerConnectionConfig? = null private val NO_HOMESERVER_CONFIG: HomeServerConnectionConfig? = null
private val A_FALLBACK_CONFIG: HomeServerConnectionConfig = HomeServerConnectionConfig( private val A_FALLBACK_CONFIG: HomeServerConnectionConfig = HomeServerConnectionConfig(
homeServerUri = FakeUri("https://${A_LOGIN_OR_REGISTER_ACTION.username.getServerName()}").instance, homeServerUri = FakeUri("https://${A_DIRECT_LOGIN_ACTION.matrixId.getServerName()}").instance,
homeServerUriBase = FakeUri(A_WELLKNOWN_SUCCESS_RESULT.homeServerUrl).instance, homeServerUriBase = FakeUri(A_WELLKNOWN_SUCCESS_RESULT.homeServerUrl).instance,
identityServerUri = null identityServerUri = null
) )
@ -54,11 +54,11 @@ class DirectLoginUseCaseTest {
@Test @Test
fun `when logging in directly, then returns success with direct session result`() = runTest { fun `when logging in directly, then returns success with direct session result`() = runTest {
fakeAuthenticationService.givenWellKnown(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_SUCCESS_RESULT) fakeAuthenticationService.givenWellKnown(A_DIRECT_LOGIN_ACTION.matrixId, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_SUCCESS_RESULT)
val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION val (username, password, initialDeviceName) = A_DIRECT_LOGIN_ACTION
fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession) fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession)
val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG) val result = useCase.execute(A_DIRECT_LOGIN_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG)
result shouldBeEqualTo Result.success(fakeSession) result shouldBeEqualTo Result.success(fakeSession)
} }
@ -66,14 +66,14 @@ class DirectLoginUseCaseTest {
@Test @Test
fun `given wellknown fails with content, when logging in directly, then returns success with direct session result`() = runTest { fun `given wellknown fails with content, when logging in directly, then returns success with direct session result`() = runTest {
fakeAuthenticationService.givenWellKnown( fakeAuthenticationService.givenWellKnown(
A_LOGIN_OR_REGISTER_ACTION.username, A_DIRECT_LOGIN_ACTION.matrixId,
config = NO_HOMESERVER_CONFIG, config = NO_HOMESERVER_CONFIG,
result = A_WELLKNOWN_FAILED_WITH_CONTENT_RESULT result = A_WELLKNOWN_FAILED_WITH_CONTENT_RESULT
) )
val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION val (username, password, initialDeviceName) = A_DIRECT_LOGIN_ACTION
fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession) fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession)
val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG) val result = useCase.execute(A_DIRECT_LOGIN_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG)
result shouldBeEqualTo Result.success(fakeSession) result shouldBeEqualTo Result.success(fakeSession)
} }
@ -81,14 +81,14 @@ class DirectLoginUseCaseTest {
@Test @Test
fun `given wellknown fails without content, when logging in directly, then returns well known error`() = runTest { fun `given wellknown fails without content, when logging in directly, then returns well known error`() = runTest {
fakeAuthenticationService.givenWellKnown( fakeAuthenticationService.givenWellKnown(
A_LOGIN_OR_REGISTER_ACTION.username, A_DIRECT_LOGIN_ACTION.matrixId,
config = NO_HOMESERVER_CONFIG, config = NO_HOMESERVER_CONFIG,
result = A_WELLKNOWN_FAILED_WITHOUT_CONTENT_RESULT result = A_WELLKNOWN_FAILED_WITHOUT_CONTENT_RESULT
) )
val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION val (username, password, initialDeviceName) = A_DIRECT_LOGIN_ACTION
fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession) fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession)
val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG) val result = useCase.execute(A_DIRECT_LOGIN_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG)
result should { this.isFailure } result should { this.isFailure }
result should { this.exceptionOrNull() is Exception } result should { this.exceptionOrNull() is Exception }
@ -97,20 +97,20 @@ class DirectLoginUseCaseTest {
@Test @Test
fun `given wellknown throws, when logging in directly, then returns failure result with original cause`() = runTest { fun `given wellknown throws, when logging in directly, then returns failure result with original cause`() = runTest {
fakeAuthenticationService.givenWellKnownThrows(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, cause = AN_ERROR) fakeAuthenticationService.givenWellKnownThrows(A_DIRECT_LOGIN_ACTION.matrixId, config = NO_HOMESERVER_CONFIG, cause = AN_ERROR)
val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG) val result = useCase.execute(A_DIRECT_LOGIN_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG)
result shouldBeEqualTo Result.failure(AN_ERROR) result shouldBeEqualTo Result.failure(AN_ERROR)
} }
@Test @Test
fun `given direct authentication throws, when logging in directly, then returns failure result with original cause`() = runTest { fun `given direct authentication throws, when logging in directly, then returns failure result with original cause`() = runTest {
fakeAuthenticationService.givenWellKnown(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_SUCCESS_RESULT) fakeAuthenticationService.givenWellKnown(A_DIRECT_LOGIN_ACTION.matrixId, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_SUCCESS_RESULT)
val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION val (username, password, initialDeviceName) = A_DIRECT_LOGIN_ACTION
fakeAuthenticationService.givenDirectAuthenticationThrows(A_FALLBACK_CONFIG, username, password, initialDeviceName, cause = AN_ERROR) fakeAuthenticationService.givenDirectAuthenticationThrows(A_FALLBACK_CONFIG, username, password, initialDeviceName, cause = AN_ERROR)
val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG) val result = useCase.execute(A_DIRECT_LOGIN_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG)
result shouldBeEqualTo Result.failure(AN_ERROR) result shouldBeEqualTo Result.failure(AN_ERROR)
} }

View file

@ -59,7 +59,7 @@ private val A_RESULT_IGNORED_REGISTER_ACTION = RegisterAction.SendAgainThreePid
private val A_HOMESERVER_CAPABILITIES = aHomeServerCapabilities(canChangeDisplayName = true, canChangeAvatar = true) private val A_HOMESERVER_CAPABILITIES = aHomeServerCapabilities(canChangeDisplayName = true, canChangeAvatar = true)
private val AN_IGNORED_FLOW_RESULT = FlowResult(missingStages = emptyList(), completedStages = emptyList()) private val AN_IGNORED_FLOW_RESULT = FlowResult(missingStages = emptyList(), completedStages = emptyList())
private val ANY_CONTINUING_REGISTRATION_RESULT = RegistrationResult.NextStep(AN_IGNORED_FLOW_RESULT) private val ANY_CONTINUING_REGISTRATION_RESULT = RegistrationResult.NextStep(AN_IGNORED_FLOW_RESULT)
private val A_LOGIN_OR_REGISTER_ACTION = OnboardingAction.LoginOrRegister("@a-user:id.org", "a-password", "a-device-name") private val A_DIRECT_LOGIN = OnboardingAction.AuthenticateAction.LoginDirect("@a-user:id.org", "a-password", "a-device-name")
private const val A_HOMESERVER_URL = "https://edited-homeserver.org" private const val A_HOMESERVER_URL = "https://edited-homeserver.org"
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(FakeUri().instance) private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(FakeUri().instance)
private val SELECTED_HOMESERVER_STATE = SelectedHomeserverState(preferredLoginMode = LoginMode.Password) private val SELECTED_HOMESERVER_STATE = SelectedHomeserverState(preferredLoginMode = LoginMode.Password)
@ -142,11 +142,11 @@ class OnboardingViewModelTest {
@Test @Test
fun `given has sign in with matrix id sign mode, when handling login or register action, then logs in directly`() = runTest { fun `given has sign in with matrix id sign mode, when handling login or register action, then logs in directly`() = runTest {
viewModelWith(initialState.copy(signMode = SignMode.SignInWithMatrixId)) viewModelWith(initialState.copy(signMode = SignMode.SignInWithMatrixId))
fakeDirectLoginUseCase.givenSuccessResult(A_LOGIN_OR_REGISTER_ACTION, config = null, result = fakeSession) fakeDirectLoginUseCase.givenSuccessResult(A_DIRECT_LOGIN, config = null, result = fakeSession)
givenInitialisesSession(fakeSession) givenInitialisesSession(fakeSession)
val test = viewModel.test() val test = viewModel.test()
viewModel.handle(A_LOGIN_OR_REGISTER_ACTION) viewModel.handle(A_DIRECT_LOGIN)
test test
.assertStatesChanges( .assertStatesChanges(
@ -161,11 +161,11 @@ class OnboardingViewModelTest {
@Test @Test
fun `given has sign in with matrix id sign mode, when handling login or register action fails, then emits error`() = runTest { fun `given has sign in with matrix id sign mode, when handling login or register action fails, then emits error`() = runTest {
viewModelWith(initialState.copy(signMode = SignMode.SignInWithMatrixId)) viewModelWith(initialState.copy(signMode = SignMode.SignInWithMatrixId))
fakeDirectLoginUseCase.givenFailureResult(A_LOGIN_OR_REGISTER_ACTION, config = null, cause = AN_ERROR) fakeDirectLoginUseCase.givenFailureResult(A_DIRECT_LOGIN, config = null, cause = AN_ERROR)
givenInitialisesSession(fakeSession) givenInitialisesSession(fakeSession)
val test = viewModel.test() val test = viewModel.test()
viewModel.handle(A_LOGIN_OR_REGISTER_ACTION) viewModel.handle(A_DIRECT_LOGIN)
test test
.assertStatesChanges( .assertStatesChanges(

View file

@ -17,7 +17,7 @@
package im.vector.app.test.fakes package im.vector.app.test.fakes
import im.vector.app.features.onboarding.DirectLoginUseCase import im.vector.app.features.onboarding.DirectLoginUseCase
import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.mockk import io.mockk.mockk
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@ -25,11 +25,11 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
class FakeDirectLoginUseCase { class FakeDirectLoginUseCase {
val instance = mockk<DirectLoginUseCase>() val instance = mockk<DirectLoginUseCase>()
fun givenSuccessResult(action: OnboardingAction.LoginOrRegister, config: HomeServerConnectionConfig?, result: FakeSession) { fun givenSuccessResult(action: AuthenticateAction.LoginDirect, config: HomeServerConnectionConfig?, result: FakeSession) {
coEvery { instance.execute(action, config) } returns Result.success(result) coEvery { instance.execute(action, config) } returns Result.success(result)
} }
fun givenFailureResult(action: OnboardingAction.LoginOrRegister, config: HomeServerConnectionConfig?, cause: Throwable) { fun givenFailureResult(action: AuthenticateAction.LoginDirect, config: HomeServerConnectionConfig?, cause: Throwable) {
coEvery { instance.execute(action, config) } returns Result.failure(cause) coEvery { instance.execute(action, config) } returns Result.failure(cause)
} }
} }