mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-25 02:45:53 +03:00
Merge branch 'develop' of github.com:vector-im/element-android into michaelk/sonarqube_fixes
This commit is contained in:
commit
d18e7ad001
65 changed files with 1382 additions and 729 deletions
1
changelog.d/5283.wip
Normal file
1
changelog.d/5283.wip
Normal file
|
@ -0,0 +1 @@
|
|||
FTUE - Adds the redesigned Sign In screen
|
1
changelog.d/6073.feature
Normal file
1
changelog.d/6073.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Adds up navigation in spaces
|
1
changelog.d/6077.sdk
Normal file
1
changelog.d/6077.sdk
Normal file
|
@ -0,0 +1 @@
|
|||
Improve replay attacks and reduce duplicate message index errors
|
1
changelog.d/6148.bugfix
Normal file
1
changelog.d/6148.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix decrypting redacted event from sending errors
|
|
@ -199,7 +199,7 @@ dependencies {
|
|||
implementation libs.apache.commonsImaging
|
||||
|
||||
// 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 'org.robolectric:robolectric:4.7.3'
|
||||
|
|
|
@ -24,8 +24,8 @@ 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.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.SessionTestParams
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
|
||||
|
@ -34,32 +34,22 @@ import org.matrix.android.sdk.common.TestConstants
|
|||
@LargeTest
|
||||
class AccountCreationTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
@Test
|
||||
fun createAccountTest() {
|
||||
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
|
||||
|
||||
commonTestHelper.signOutAndClose(session)
|
||||
fun createAccountTest() = runSessionTest(context()) { commonTestHelper ->
|
||||
commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
|
||||
}
|
||||
|
||||
@Test
|
||||
@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))
|
||||
|
||||
// Log again to the same account
|
||||
val session2 = commonTestHelper.logIntoAccount(session.myUserId, SessionTestParams(withInitialSync = true))
|
||||
|
||||
commonTestHelper.signOutAndClose(session)
|
||||
commonTestHelper.signOutAndClose(session2)
|
||||
commonTestHelper.logIntoAccount(session.myUserId, SessionTestParams(withInitialSync = true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun simpleE2eTest() {
|
||||
val res = cryptoTestHelper.doE2ETestWithAliceInARoom()
|
||||
|
||||
res.cleanUp(commonTestHelper)
|
||||
fun simpleE2eTest() = runCryptoTest(context()) { cryptoTestHelper, _ ->
|
||||
cryptoTestHelper.doE2ETestWithAliceInARoom()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.junit.runners.JUnit4
|
|||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
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.TestConstants
|
||||
|
||||
|
@ -34,14 +34,12 @@ import org.matrix.android.sdk.common.TestConstants
|
|||
@Ignore("This test will be ignored until it is fixed")
|
||||
class ChangePasswordTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
|
||||
companion object {
|
||||
private const val NEW_PASSWORD = "this is a new password"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun changePasswordTest() {
|
||||
fun changePasswordTest() = runSessionTest(context()) { commonTestHelper ->
|
||||
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false))
|
||||
|
||||
// Change password
|
||||
|
@ -54,9 +52,6 @@ class ChangePasswordTest : InstrumentedTest {
|
|||
throwable.isInvalidPassword().shouldBeTrue()
|
||||
|
||||
// Try to login with the new password, should work
|
||||
val session2 = commonTestHelper.logIntoAccount(session.myUserId, NEW_PASSWORD, SessionTestParams(withInitialSync = false))
|
||||
|
||||
commonTestHelper.signOutAndClose(session)
|
||||
commonTestHelper.signOutAndClose(session2)
|
||||
commonTestHelper.logIntoAccount(session.myUserId, NEW_PASSWORD, SessionTestParams(withInitialSync = false))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.failure.Failure
|
||||
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.TestConstants
|
||||
import kotlin.coroutines.Continuation
|
||||
|
@ -39,10 +39,8 @@ import kotlin.coroutines.resume
|
|||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class DeactivateAccountTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
|
||||
@Test
|
||||
fun deactivateAccountTest() {
|
||||
fun deactivateAccountTest() = runSessionTest(context(), false /* session will be deactivated */) { commonTestHelper ->
|
||||
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
|
||||
|
||||
// Deactivate the account
|
||||
|
|
|
@ -23,7 +23,7 @@ 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.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import timber.log.Timber
|
||||
|
@ -32,10 +32,8 @@ import timber.log.Timber
|
|||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class ApiInterceptorTest : InstrumentedTest {
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
|
||||
@Test
|
||||
fun apiInterceptorTest() {
|
||||
fun apiInterceptorTest() = runSessionTest(context()) { commonTestHelper ->
|
||||
val responses = mutableListOf<String>()
|
||||
|
||||
val listener = object : ApiInterceptorListener {
|
||||
|
|
|
@ -54,12 +54,39 @@ import java.util.concurrent.TimeUnit
|
|||
* This class exposes methods to be used in common cases
|
||||
* 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
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
private var accountNumber = 0
|
||||
|
||||
private val trackedSessions = mutableListOf<Session>()
|
||||
|
||||
fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
|
||||
|
||||
init {
|
||||
|
@ -84,6 +111,15 @@ class CommonTestHelper(context: Context) {
|
|||
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
|
||||
*/
|
||||
|
@ -245,7 +281,9 @@ class CommonTestHelper(context: Context) {
|
|||
testParams
|
||||
)
|
||||
assertNotNull(session)
|
||||
return session
|
||||
return session.also {
|
||||
trackedSessions.add(session)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -261,7 +299,9 @@ class CommonTestHelper(context: Context) {
|
|||
testParams: SessionTestParams): Session {
|
||||
val session = logAccountAndSync(userId, password, testParams)
|
||||
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 signOutAndClose(session: Session) {
|
||||
trackedSessions.remove(session)
|
||||
runBlockingTest(timeout = 60_000) {
|
||||
session.signOutService().signOut(true)
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ import java.util.UUID
|
|||
import kotlin.coroutines.Continuation
|
||||
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 messagesFromBob: List<String> = listOf("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera.")
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.timeline.TimelineSettings
|
||||
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.SessionTestParams
|
||||
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
|
||||
*/
|
||||
@Test
|
||||
fun testSendingE2EEMessages() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun testSendingE2EEMessages() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
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
|
||||
fun testKeyGossipingIsEnabledByDefault() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
fun testKeyGossipingIsEnabledByDefault() = runSessionTest(context()) { testHelper ->
|
||||
val session = testHelper.createAccount("alice", SessionTestParams(true))
|
||||
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
|
||||
*/
|
||||
@Test
|
||||
fun testBasicBackupImport() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun testBasicBackupImport() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -346,8 +334,6 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
|
||||
// ensure bob can now decrypt
|
||||
cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
|
||||
|
||||
testHelper.signOutAndClose(newBobSession)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -355,9 +341,7 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
* get them from an older one.
|
||||
*/
|
||||
@Test
|
||||
fun testSimpleGossip() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun testSimpleGossip() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -451,18 +435,13 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
}
|
||||
|
||||
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
|
||||
fun testForwardBetterKey() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun testForwardBetterKey() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -578,10 +557,6 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
canDecryptFirst && canDecryptSecond
|
||||
}
|
||||
}
|
||||
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
testHelper.signOutAndClose(bobSessionWithBetterKey)
|
||||
testHelper.signOutAndClose(newBobSession)
|
||||
}
|
||||
|
||||
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
|
||||
fun testSelfInteractiveVerificationAndGossip() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun testASelfInteractiveVerificationAndGossip() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
|
||||
val aliceSession = testHelper.createAccount("alice", SessionTestParams(true))
|
||||
cryptoTestHelper.bootstrapSecurity(aliceSession)
|
||||
|
@ -753,9 +726,6 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
aliceSession.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) {
|
||||
|
|
|
@ -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.getRoom
|
||||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class PreShareKeysTest : InstrumentedTest {
|
||||
|
||||
private val testHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
@Test
|
||||
fun ensure_outbound_session_happy_path() {
|
||||
fun ensure_outbound_session_happy_path() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val e2eRoomID = testData.roomId
|
||||
val aliceSession = testData.firstSession
|
||||
|
@ -94,7 +90,5 @@ class PreShareKeysTest : InstrumentedTest {
|
|||
bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE
|
||||
}
|
||||
}
|
||||
|
||||
testData.cleanUp(testHelper)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
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.TestConstants
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
|
||||
|
@ -65,8 +64,6 @@ import kotlin.coroutines.resume
|
|||
class UnwedgingTest : InstrumentedTest {
|
||||
|
||||
private lateinit var messagesReceivedByBob: List<TimelineEvent>
|
||||
private val testHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
|
@ -87,7 +84,7 @@ class UnwedgingTest : InstrumentedTest {
|
|||
* -> This is automatically fixed after SDKs restarted the olm session
|
||||
*/
|
||||
@Test
|
||||
fun testUnwedging() {
|
||||
fun testUnwedging() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -242,8 +239,6 @@ class UnwedgingTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
bobTimeline.dispose()
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
private fun createEventListener(latch: CountDownLatch, expectedNumberOfMessages: Int): Timeline.Listener {
|
||||
|
|
|
@ -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.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
||||
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.SessionTestParams
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import kotlin.coroutines.Continuation
|
||||
|
@ -51,11 +51,8 @@ import kotlin.coroutines.resume
|
|||
@Ignore
|
||||
class XSigningTest : InstrumentedTest {
|
||||
|
||||
private val testHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
@Test
|
||||
fun test_InitializeAndStoreKeys() {
|
||||
fun test_InitializeAndStoreKeys() = runSessionTest(context()) { testHelper ->
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
testHelper.doSync<Unit> {
|
||||
|
@ -89,7 +86,7 @@ class XSigningTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun test_CrossSigningCheckBobSeesTheKeys() {
|
||||
fun test_CrossSigningCheckBobSeesTheKeys() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -139,12 +136,10 @@ class XSigningTest : InstrumentedTest {
|
|||
)
|
||||
|
||||
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_CrossSigningTestAliceTrustBobNewDevice() {
|
||||
fun test_CrossSigningTestAliceTrustBobNewDevice() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -218,9 +213,5 @@ class XSigningTest : InstrumentedTest {
|
|||
|
||||
val result = aliceSession.cryptoService().crossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
|
||||
assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
|
||||
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
testHelper.signOutAndClose(bobSession)
|
||||
testHelper.signOutAndClose(bobSession2)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.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
|
||||
|
||||
|
@ -42,12 +43,10 @@ import java.util.concurrent.CountDownLatch
|
|||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class EncryptionTest : InstrumentedTest {
|
||||
|
||||
private val testHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
|
||||
@Test
|
||||
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)
|
||||
room.sendService().sendEvent(
|
||||
eventType = EventType.STATE_ROOM_ENCRYPTION,
|
||||
|
@ -55,10 +54,12 @@ class EncryptionTest : InstrumentedTest {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_EncryptionStateEvent() {
|
||||
performTest(roomShouldBeEncrypted = true) { room ->
|
||||
runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
performTest(cryptoTestHelper, testHelper, roomShouldBeEncrypted = true) { room ->
|
||||
runBlocking {
|
||||
// Send an encryption Event as a State Event
|
||||
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 aliceSession = cryptoTestData.firstSession
|
||||
|
@ -109,6 +111,5 @@ class EncryptionTest : InstrumentedTest {
|
|||
room.roomCryptoService().isEncrypted() shouldBe roomShouldBeEncrypted
|
||||
it.countDown()
|
||||
}
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||
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.RetryTestRule
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
|
@ -58,9 +57,7 @@ class KeyShareTests : InstrumentedTest {
|
|||
@get:Rule val rule = RetryTestRule(3)
|
||||
|
||||
@Test
|
||||
fun test_DoNotSelfShareIfNotTrusted() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
fun test_DoNotSelfShareIfNotTrusted() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
|
||||
val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
Log.v("TEST", "=======> AliceSession 1 is ${aliceSession.sessionParams.deviceId}")
|
||||
|
@ -196,9 +193,7 @@ class KeyShareTests : InstrumentedTest {
|
|||
* if the key was originally shared with him
|
||||
*/
|
||||
@Test
|
||||
fun test_reShareIfWasIntendedToBeShared() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
fun test_reShareIfWasIntendedToBeShared() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val aliceSession = testData.firstSession
|
||||
|
@ -229,9 +224,7 @@ class KeyShareTests : InstrumentedTest {
|
|||
* if the key was originally shared with him
|
||||
*/
|
||||
@Test
|
||||
fun test_reShareToUnverifiedIfWasIntendedToBeShared() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
fun test_reShareToUnverifiedIfWasIntendedToBeShared() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceInARoom(true)
|
||||
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
|
||||
*/
|
||||
@Test
|
||||
fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
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
|
||||
*/
|
||||
@Test
|
||||
fun test_dontCancelToEarly() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
fun test_dontCancelToEarly() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val aliceSession = testData.firstSession
|
||||
val bobSession = testData.secondSession!!
|
||||
|
|
|
@ -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.getRoom
|
||||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||
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.MockOkHttpInterceptor
|
||||
import org.matrix.android.sdk.common.RetryTestRule
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
|
@ -54,9 +53,7 @@ class WithHeldTests : InstrumentedTest {
|
|||
@get:Rule val rule = RetryTestRule(3)
|
||||
|
||||
@Test
|
||||
fun test_WithHeldUnverifiedReason() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun test_WithHeldUnverifiedReason() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
|
||||
// =============================
|
||||
// ARRANGE
|
||||
|
@ -155,16 +152,10 @@ class WithHeldTests : InstrumentedTest {
|
|||
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
||||
}
|
||||
}
|
||||
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
testHelper.signOutAndClose(bobSession)
|
||||
testHelper.signOutAndClose(bobUnverifiedSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_WithHeldNoOlm() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun test_WithHeldNoOlm() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
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)
|
||||
|
||||
aliceInterceptor.clearRules()
|
||||
testData.cleanUp(testHelper)
|
||||
testHelper.signOutAndClose(bobSecondSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_WithHeldKeyRequest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun test_WithHeldKeyRequest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
|
||||
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
val aliceSession = testData.firstSession
|
||||
|
@ -295,8 +282,5 @@ class WithHeldTests : InstrumentedTest {
|
|||
wc?.code == WithHeldCode.UNAUTHORISED
|
||||
}
|
||||
}
|
||||
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
testHelper.signOutAndClose(bobSecondSession)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.model.ImportRoomKeysResult
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
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.TestConstants
|
||||
import org.matrix.android.sdk.common.TestMatrixCallback
|
||||
|
@ -67,9 +67,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - Reset keys backup markers
|
||||
*/
|
||||
@Test
|
||||
fun roomKeysTest_testBackupStore_ok() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun roomKeysTest_testBackupStore_ok() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
||||
|
@ -108,8 +106,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* Check that prepareKeysBackupVersionWithPassword returns valid data
|
||||
*/
|
||||
@Test
|
||||
fun prepareKeysBackupVersionTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
fun prepareKeysBackupVersionTest() = runSessionTest(context()) { testHelper ->
|
||||
|
||||
val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
|
||||
|
||||
|
@ -131,16 +128,13 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertNotNull(megolmBackupCreationInfo.recoveryKey)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
testHelper.signOutAndClose(bobSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating a keys backup version and check that createKeysBackupVersion() returns valid data
|
||||
*/
|
||||
@Test
|
||||
fun createKeysBackupVersionTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun createKeysBackupVersionTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
|
||||
val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
|
||||
cryptoTestHelper.initializeCrossSigning(bobSession)
|
||||
|
@ -199,7 +193,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
testHelper.signOutAndClose(bobSession)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -207,9 +200,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - Check the backup completes
|
||||
*/
|
||||
@Test
|
||||
fun backupAfterCreateKeysBackupVersionTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun backupAfterCreateKeysBackupVersionTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
@ -244,16 +235,13 @@ class KeysBackupTest : InstrumentedTest {
|
|||
KeysBackupState.ReadyToBackUp
|
||||
)
|
||||
)
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that backupAllGroupSessions() returns valid data
|
||||
*/
|
||||
@Test
|
||||
fun backupAllGroupSessionsTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun backupAllGroupSessionsTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
@ -287,7 +275,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -299,9 +286,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - Compare the decrypted megolm key with the original one
|
||||
*/
|
||||
@Test
|
||||
fun testEncryptAndDecryptKeysBackupData() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun testEncryptAndDecryptKeysBackupData() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages()
|
||||
|
@ -336,7 +321,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
keysBackupTestHelper.assertKeysEquals(session.exportKeys(), sessionData)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -346,9 +330,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - Restore must be successful
|
||||
*/
|
||||
@Test
|
||||
fun restoreKeysBackupTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun restoreKeysBackupTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
@ -434,9 +416,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - It must be trusted and must have with 2 signatures now
|
||||
*/
|
||||
@Test
|
||||
fun trustKeyBackupVersionTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun trustKeyBackupVersionTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
// - Do an e2e backup to the homeserver with a recovery key
|
||||
|
@ -483,7 +463,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(2, keysBackupVersionTrust.signatures.size)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
testData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -497,9 +476,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - It must be trusted and must have with 2 signatures now
|
||||
*/
|
||||
@Test
|
||||
fun trustKeyBackupVersionWithRecoveryKeyTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun trustKeyBackupVersionWithRecoveryKeyTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
// - Do an e2e backup to the homeserver with a recovery key
|
||||
|
@ -546,7 +523,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(2, keysBackupVersionTrust.signatures.size)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
testData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -558,9 +534,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - The backup must still be untrusted and disabled
|
||||
*/
|
||||
@Test
|
||||
fun trustKeyBackupVersionWithWrongRecoveryKeyTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun trustKeyBackupVersionWithWrongRecoveryKeyTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
// - 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)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
testData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -603,9 +576,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - It must be trusted and must have with 2 signatures now
|
||||
*/
|
||||
@Test
|
||||
fun trustKeyBackupVersionWithPasswordTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun trustKeyBackupVersionWithPasswordTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val password = "Password"
|
||||
|
@ -654,7 +625,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(2, keysBackupVersionTrust.signatures.size)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
testData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -666,9 +636,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - The backup must still be untrusted and disabled
|
||||
*/
|
||||
@Test
|
||||
fun trustKeyBackupVersionWithWrongPasswordTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun trustKeyBackupVersionWithWrongPasswordTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val password = "Password"
|
||||
|
@ -700,7 +668,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().state)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
testData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -710,9 +677,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - It must fail
|
||||
*/
|
||||
@Test
|
||||
fun restoreKeysBackupWithAWrongRecoveryKeyTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun restoreKeysBackupWithAWrongRecoveryKeyTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
@ -736,8 +701,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
// onSuccess may not have been called
|
||||
assertNull(importRoomKeysResult)
|
||||
|
||||
testData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -747,9 +710,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - Restore must be successful
|
||||
*/
|
||||
@Test
|
||||
fun testBackupWithPassword() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun testBackupWithPassword() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val password = "password"
|
||||
|
@ -796,8 +757,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(100, (steps[104] as StepProgressListener.Step.ImportingKey).progress)
|
||||
|
||||
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
|
||||
testData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -807,9 +766,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - It must fail
|
||||
*/
|
||||
@Test
|
||||
fun restoreKeysBackupWithAWrongPasswordTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun restoreKeysBackupWithAWrongPasswordTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val password = "password"
|
||||
|
@ -836,8 +793,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
// onSuccess may not have been called
|
||||
assertNull(importRoomKeysResult)
|
||||
|
||||
testData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -847,9 +802,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - Restore must be successful
|
||||
*/
|
||||
@Test
|
||||
fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val password = "password"
|
||||
|
@ -869,8 +822,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
|
||||
testData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -880,9 +831,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - It must fail
|
||||
*/
|
||||
@Test
|
||||
fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
@ -906,8 +855,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
// onSuccess may not have been called
|
||||
assertNull(importRoomKeysResult)
|
||||
|
||||
testData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -915,9 +862,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - Check the returned KeysVersionResult is trusted
|
||||
*/
|
||||
@Test
|
||||
fun testIsKeysBackupTrusted() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun testIsKeysBackupTrusted() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
// - Create a backup version
|
||||
|
@ -951,7 +896,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -963,9 +907,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* -> That must fail and her backup state must be WrongBackUpVersion
|
||||
*/
|
||||
@Test
|
||||
fun testBackupWhenAnotherBackupWasCreated() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun testBackupWhenAnotherBackupWasCreated() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
// - Create a backup version
|
||||
|
@ -1022,7 +964,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1038,9 +979,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* -> It must success
|
||||
*/
|
||||
@Test
|
||||
fun testBackupAfterVerifyingADevice() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun testBackupAfterVerifyingADevice() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
// - Create a backup version
|
||||
|
@ -1131,8 +1070,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
stateObserver2.stopAndCheckStates(null)
|
||||
testHelper.signOutAndClose(aliceSession2)
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1140,9 +1077,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
* - Delete the backup
|
||||
*/
|
||||
@Test
|
||||
fun deleteKeysBackupTest() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun deleteKeysBackupTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
|
||||
|
||||
// - Create a backup version
|
||||
|
@ -1165,6 +1100,5 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertFalse(keysBackup.isEnabled)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.toBase64NoPadding
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
|
||||
|
@ -55,8 +56,7 @@ class QuadSTests : InstrumentedTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun test_Generate4SKey() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
fun test_Generate4SKey() = runSessionTest(context()) { testHelper ->
|
||||
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
|
@ -108,12 +108,11 @@ class QuadSTests : InstrumentedTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun test_StoreSecret() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
fun test_StoreSecret() = runSessionTest(context()) { testHelper ->
|
||||
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
val keyId = "My.Key"
|
||||
val info = generatedSecret(aliceSession, keyId, true)
|
||||
val info = generatedSecret(testHelper, aliceSession, keyId, true)
|
||||
|
||||
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<*, *>
|
||||
assertNotNull("Element should be encrypted", encryptedContent)
|
||||
|
@ -149,12 +148,10 @@ class QuadSTests : InstrumentedTest {
|
|||
}
|
||||
|
||||
assertEquals("Secret mismatch", clearSecret, decryptedSecret)
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_SetDefaultLocalEcho() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
fun test_SetDefaultLocalEcho() = runSessionTest(context()) { testHelper ->
|
||||
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
|
@ -170,19 +167,16 @@ class QuadSTests : InstrumentedTest {
|
|||
testHelper.runBlockingTest {
|
||||
quadS.setDefaultKey(TEST_KEY_ID)
|
||||
}
|
||||
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_StoreSecretWithMultipleKey() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
fun test_StoreSecretWithMultipleKey() = runSessionTest(context()) { testHelper ->
|
||||
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
val keyId1 = "Key.1"
|
||||
val key1Info = generatedSecret(aliceSession, keyId1, true)
|
||||
val key1Info = generatedSecret(testHelper, aliceSession, keyId1, true)
|
||||
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"
|
||||
|
||||
|
@ -221,19 +215,16 @@ class QuadSTests : InstrumentedTest {
|
|||
RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!
|
||||
)
|
||||
}
|
||||
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Test is working locally, not in GitHub actions")
|
||||
fun test_GetSecretWithBadPassphrase() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
fun test_GetSecretWithBadPassphrase() = runSessionTest(context()) { testHelper ->
|
||||
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
val keyId1 = "Key.1"
|
||||
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"
|
||||
|
||||
|
@ -275,13 +266,9 @@ class QuadSTests : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
testHelper.signOutAndClose(aliceSession)
|
||||
}
|
||||
|
||||
private fun assertAccountData(session: Session, type: String): UserAccountDataEvent {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
|
||||
private fun assertAccountData(testHelper: CommonTestHelper, session: Session, type: String): UserAccountDataEvent {
|
||||
var accountData: UserAccountDataEvent? = null
|
||||
testHelper.waitWithLatch {
|
||||
val liveAccountData = session.accountDataService().getLiveUserAccountDataEvent(type)
|
||||
|
@ -297,29 +284,27 @@ class QuadSTests : InstrumentedTest {
|
|||
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 testHelper = CommonTestHelper(context())
|
||||
|
||||
val creationInfo = testHelper.runBlockingTest {
|
||||
quadS.generateKey(keyId, null, keyId, emptyKeySigner)
|
||||
}
|
||||
|
||||
assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
|
||||
assertAccountData(testHelper, session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId")
|
||||
|
||||
if (asDefault) {
|
||||
testHelper.runBlockingTest {
|
||||
quadS.setDefaultKey(keyId)
|
||||
}
|
||||
assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
|
||||
assertAccountData(testHelper, session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
|
||||
}
|
||||
|
||||
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 testHelper = CommonTestHelper(context())
|
||||
|
||||
val creationInfo = testHelper.runBlockingTest {
|
||||
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) {
|
||||
testHelper.runBlockingTest {
|
||||
quadS.setDefaultKey(keyId)
|
||||
}
|
||||
assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
|
||||
assertAccountData(testHelper, session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
|
||||
}
|
||||
|
||||
return creationInfo
|
||||
|
|
|
@ -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.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
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.internal.crypto.model.rest.KeyVerificationCancel
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.toValue
|
||||
|
@ -57,9 +56,7 @@ import java.util.concurrent.CountDownLatch
|
|||
class SASTest : InstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun test_aliceStartThenAliceCancel() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun test_aliceStartThenAliceCancel() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -136,15 +133,11 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
|
||||
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun test_key_agreement_protocols_must_include_curve25519() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun test_key_agreement_protocols_must_include_curve25519() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
fail("Not passing for the moment")
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
|
@ -196,15 +189,11 @@ class SASTest : InstrumentedTest {
|
|||
testHelper.await(cancelLatch)
|
||||
|
||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun test_key_agreement_macs_Must_include_hmac_sha256() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun test_key_agreement_macs_Must_include_hmac_sha256() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
fail("Not passing for the moment")
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
|
@ -237,15 +226,11 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun test_key_agreement_short_code_include_decimal() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun test_key_agreement_short_code_include_decimal() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
fail("Not passing for the moment")
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
|
@ -278,8 +263,6 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
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.
|
||||
// If a device has two verifications in progress with the same device, then it should cancel both verifications.
|
||||
@Test
|
||||
fun test_aliceStartTwoRequests() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun test_aliceStartTwoRequests() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -358,9 +339,7 @@ class SASTest : InstrumentedTest {
|
|||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun test_aliceAndBobAgreement() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun test_aliceAndBobAgreement() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -414,14 +393,10 @@ class SASTest : InstrumentedTest {
|
|||
accepted!!.shortAuthenticationStrings.forEach {
|
||||
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
|
||||
}
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_aliceAndBobSASCode() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun test_aliceAndBobSASCode() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -474,14 +449,10 @@ class SASTest : InstrumentedTest {
|
|||
"Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
|
||||
bobTx.getShortCodeRepresentation(SasMode.DECIMAL)
|
||||
)
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_happyPath() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun test_happyPath() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
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("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_ConcurrentStart() {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
fun test_ConcurrentStart() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -647,7 +615,5 @@ class SASTest : InstrumentedTest {
|
|||
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
||||
}
|
||||
}
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.VerificationMethod
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
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.SessionTestParams
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
@ -156,9 +156,7 @@ class VerificationTest : InstrumentedTest {
|
|||
private fun doTest(aliceSupportedMethods: List<VerificationMethod>,
|
||||
bobSupportedMethods: List<VerificationMethod>,
|
||||
expectedResultForAlice: ExpectedResult,
|
||||
expectedResultForBob: ExpectedResult) {
|
||||
val testHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
expectedResultForBob: ExpectedResult) = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -253,14 +251,11 @@ class VerificationTest : InstrumentedTest {
|
|||
pr.otherCanShowQrCode() shouldBe expectedResultForBob.otherCanShowQrCode
|
||||
pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode
|
||||
}
|
||||
|
||||
cryptoTestData.cleanUp(testHelper)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_selfVerificationAcceptedCancelsItForOtherSessions() {
|
||||
fun test_selfVerificationAcceptedCancelsItForOtherSessions() = runSessionTest(context()) { testHelper ->
|
||||
val defaultSessionParams = SessionTestParams(true)
|
||||
val testHelper = CommonTestHelper(context())
|
||||
|
||||
val aliceSessionToVerify = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
val aliceSessionThatVerifies = testHelper.logIntoAccount(aliceSessionToVerify.myUserId, TestConstants.PASSWORD, defaultSessionParams)
|
||||
|
|
|
@ -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.room.timeline.Timeline
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
|
@ -44,9 +43,7 @@ import java.util.concurrent.CountDownLatch
|
|||
class ThreadMessagingTest : InstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun reply_in_thread_should_create_a_thread() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
fun reply_in_thread_should_create_a_thread() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -104,9 +101,7 @@ class ThreadMessagingTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun reply_in_thread_should_create_a_thread_from_other_user() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
fun reply_in_thread_should_create_a_thread_from_other_user() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -179,9 +174,7 @@ class ThreadMessagingTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun reply_in_thread_to_timeline_message_multiple_times() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
fun reply_in_thread_to_timeline_message_multiple_times() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -244,9 +237,7 @@ class ThreadMessagingTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun thread_summary_advanced_validation_after_multiple_messages_in_multiple_threads() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
fun thread_summary_advanced_validation_after_multiple_messages_in_multiple_threads() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
|
|
@ -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.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
|
@ -47,9 +46,7 @@ import java.util.concurrent.CountDownLatch
|
|||
class PollAggregationTest : InstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun testAllPollUseCases() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
fun testAllPollUseCases() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -138,7 +135,6 @@ class PollAggregationTest : InstrumentedTest {
|
|||
|
||||
aliceSession.stopSync()
|
||||
aliceTimeline.dispose()
|
||||
cryptoTestData.cleanUp(commonTestHelper)
|
||||
}
|
||||
|
||||
private fun testInitialPollConditions(pollContent: MessagePollContent, pollSummary: PollResponseAggregatedSummary?) {
|
||||
|
|
|
@ -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.timeline.Timeline
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
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.checkSendOrder
|
||||
import timber.log.Timber
|
||||
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
|
||||
*/
|
||||
@Test
|
||||
fun forwardPaginationTest() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
fun forwardPaginationTest() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
val numberOfMessagesToSend = 90
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
|
||||
|
||||
|
@ -177,7 +174,5 @@ class TimelineForwardPaginationTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
aliceTimeline.dispose()
|
||||
|
||||
cryptoTestData.cleanUp(commonTestHelper)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.TimelineSettings
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import org.matrix.android.sdk.common.checkSendOrder
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
@ -48,9 +47,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
|
|||
*/
|
||||
|
||||
@Test
|
||||
fun previousLastForwardTest() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
fun previousLastForwardTest() = CommonTestHelper.runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
|
||||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
|
@ -242,7 +239,5 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
bobTimeline.dispose()
|
||||
|
||||
cryptoTestData.cleanUp(commonTestHelper)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.timeline.Timeline
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
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.TestConstants
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
|
@ -44,9 +43,7 @@ import org.matrix.android.sdk.common.TestConstants
|
|||
class TimelineSimpleBackPaginationTest : InstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun timeline_backPaginate_shouldReachEndOfTimeline() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
fun timeline_backPaginate_shouldReachEndOfTimeline() = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
val numberOfMessagesToSent = 200
|
||||
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
|
||||
|
@ -104,6 +101,5 @@ class TimelineSimpleBackPaginationTest : InstrumentedTest {
|
|||
assertEquals(numberOfMessagesToSent, onlySentEvents.size)
|
||||
|
||||
bobTimeline.dispose()
|
||||
cryptoTestData.cleanUp(commonTestHelper)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
/** !! Not working with the new timeline
|
||||
|
@ -47,15 +46,12 @@ class TimelineWithManyMembersTest : InstrumentedTest {
|
|||
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.
|
||||
*/
|
||||
|
||||
@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 sessionForFirstMember = cryptoTestData.firstSession
|
||||
|
|
|
@ -26,9 +26,8 @@ import org.matrix.android.sdk.InstrumentedTest
|
|||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
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.CryptoTestHelper
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
|
@ -74,9 +73,7 @@ class SearchMessagesTest : InstrumentedTest {
|
|||
}
|
||||
}
|
||||
|
||||
private fun doTest(block: suspend (CryptoTestData) -> SearchResult) {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
private fun doTest(block: suspend (CryptoTestData) -> SearchResult) = runCryptoTest(context()) { cryptoTestHelper, commonTestHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceRoomId = cryptoTestData.roomId
|
||||
|
@ -99,7 +96,5 @@ class SearchMessagesTest : InstrumentedTest {
|
|||
(it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse()
|
||||
}.orFalse()
|
||||
)
|
||||
|
||||
cryptoTestData.cleanUp(commonTestHelper)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.RoomCreateContent
|
||||
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
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
|
@ -50,8 +50,7 @@ import org.matrix.android.sdk.common.SessionTestParams
|
|||
class SpaceCreationTest : InstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun createSimplePublicSpace() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
fun createSimplePublicSpace() = runSessionTest(context()) { commonTestHelper ->
|
||||
val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true))
|
||||
val roomName = "My Space"
|
||||
val topic = "A public space for test"
|
||||
|
@ -97,13 +96,11 @@ class SpaceCreationTest : InstrumentedTest {
|
|||
?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility
|
||||
|
||||
assertEquals("Public space room should be world readable", RoomHistoryVisibility.WORLD_READABLE, historyVisibility)
|
||||
|
||||
commonTestHelper.signOutAndClose(session)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJoinSimplePublicSpace() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
@Ignore
|
||||
fun testJoinSimplePublicSpace() = runSessionTest(context()) { commonTestHelper ->
|
||||
|
||||
val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
|
||||
val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true))
|
||||
|
@ -135,9 +132,7 @@ class SpaceCreationTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
fun testSimplePublicSpaceWithChildren() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
fun testSimplePublicSpaceWithChildren() = runSessionTest(context()) { commonTestHelper ->
|
||||
val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
|
||||
val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true))
|
||||
|
||||
|
@ -206,8 +201,5 @@ class SpaceCreationTest : InstrumentedTest {
|
|||
// ).size
|
||||
//
|
||||
// assertEquals("Unexpected number of joined children", 1, childCount)
|
||||
|
||||
commonTestHelper.signOutAndClose(aliceSession)
|
||||
commonTestHelper.signOutAndClose(bobSession)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
|
@ -55,8 +56,7 @@ import org.matrix.android.sdk.common.SessionTestParams
|
|||
class SpaceHierarchyTest : InstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun createCanonicalChildRelation() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
fun createCanonicalChildRelation() = runSessionTest(context()) { commonTestHelper ->
|
||||
|
||||
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||
val spaceName = "My Space"
|
||||
|
@ -173,8 +173,7 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
// }
|
||||
|
||||
@Test
|
||||
fun testFilteringBySpace() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
fun testFilteringBySpace() = CommonTestHelper.runSessionTest(context()) { commonTestHelper ->
|
||||
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||
|
||||
val spaceAInfo = createPublicSpace(
|
||||
|
@ -256,8 +255,7 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun testBreakCycle() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
fun testBreakCycle() = CommonTestHelper.runSessionTest(context()) { commonTestHelper ->
|
||||
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||
|
||||
val spaceAInfo = createPublicSpace(
|
||||
|
@ -302,8 +300,7 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun testLiveFlatChildren() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
fun testLiveFlatChildren() = CommonTestHelper.runSessionTest(context()) { commonTestHelper ->
|
||||
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||
|
||||
val spaceAInfo = createPublicSpace(
|
||||
|
@ -395,9 +392,9 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
childInfo: List<Triple<String, Boolean, Boolean?>>
|
||||
/** Name, auto-join, canonical*/
|
||||
): TestSpaceCreationResult {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
var spaceId = ""
|
||||
var roomIds: List<String> = emptyList()
|
||||
runSessionTest(context()) { commonTestHelper ->
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
spaceId = session.spaceService().createSpace(spaceName, "Test Topic", null, true)
|
||||
val syncedSpace = session.spaceService().getSpace(spaceId)
|
||||
|
@ -415,6 +412,7 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
}
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
return TestSpaceCreationResult(spaceId, roomIds)
|
||||
}
|
||||
|
||||
|
@ -423,9 +421,9 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
childInfo: List<Triple<String, Boolean, Boolean?>>
|
||||
/** Name, auto-join, canonical*/
|
||||
): TestSpaceCreationResult {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
var spaceId = ""
|
||||
var roomIds: List<String> = emptyList()
|
||||
runSessionTest(context()) { commonTestHelper ->
|
||||
commonTestHelper.waitWithLatch { latch ->
|
||||
spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false)
|
||||
val syncedSpace = session.spaceService().getSpace(spaceId)
|
||||
|
@ -454,12 +452,12 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
}
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
return TestSpaceCreationResult(spaceId, roomIds)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRootSpaces() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
fun testRootSpaces() = runSessionTest(context()) { commonTestHelper ->
|
||||
val session = commonTestHelper.createAccount("John", SessionTestParams(true))
|
||||
|
||||
/* val spaceAInfo = */ createPublicSpace(
|
||||
|
@ -506,13 +504,10 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
}
|
||||
|
||||
assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size)
|
||||
|
||||
commonTestHelper.signOutAndClose(session)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParentRelation() {
|
||||
val commonTestHelper = CommonTestHelper(context())
|
||||
fun testParentRelation() = runSessionTest(context()) { commonTestHelper ->
|
||||
val aliceSession = commonTestHelper.createAccount("Alice", SessionTestParams(true))
|
||||
val bobSession = commonTestHelper.createAccount("Bib", SessionTestParams(true))
|
||||
|
||||
|
@ -604,8 +599,5 @@ class SpaceHierarchyTest : InstrumentedTest {
|
|||
bobSession.getRoomSummary(bobRoomId)?.flattenParentIds?.contains(spaceAInfo.spaceId) == true
|
||||
}
|
||||
}
|
||||
|
||||
commonTestHelper.signOutAndClose(aliceSession)
|
||||
commonTestHelper.signOutAndClose(bobSession)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.EventType
|
||||
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.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
|
||||
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 val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
|
||||
private val loggerTag = LoggerTag("EventDecryptor", LoggerTag.CRYPTO)
|
||||
|
||||
@SessionScope
|
||||
internal class EventDecryptor @Inject constructor(
|
||||
|
@ -110,6 +111,16 @@ internal class EventDecryptor @Inject constructor(
|
|||
if (eventContent == null) {
|
||||
Timber.tag(loggerTag.value).e("decryptEvent : empty event content")
|
||||
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 {
|
||||
val algorithm = eventContent["algorithm"]?.toString()
|
||||
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
|
||||
|
|
|
@ -96,8 +96,9 @@ internal class MXOlmDevice @Inject constructor(
|
|||
// So, store these message indexes per timeline id.
|
||||
//
|
||||
// The first level keys are timeline ids.
|
||||
// The second level keys are strings of form "<senderKey>|<session_id>|<message_index>"
|
||||
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableSet<String>> = HashMap()
|
||||
// The second level values is a Map that represents:
|
||||
// "<senderKey>|<session_id>|<roomId>|<message_index>" --> eventId
|
||||
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, String>> = HashMap()
|
||||
|
||||
init {
|
||||
// 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 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 eventId the eventId of the message that will be decrypted
|
||||
* @param sessionId the session identifier.
|
||||
* @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)
|
||||
suspend fun decryptGroupMessage(body: String,
|
||||
roomId: String,
|
||||
timeline: String?,
|
||||
eventId: String,
|
||||
sessionId: String,
|
||||
senderKey: String): OlmDecryptionResult {
|
||||
val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId)
|
||||
val wrapper = sessionHolder.wrapper
|
||||
val inboundGroupSession = wrapper.olmInboundGroupSession
|
||||
?: 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
|
||||
// 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 {
|
||||
sessionHolder.mutex.withLock {
|
||||
inboundGroupSession.decryptMessage(body)
|
||||
|
@ -781,20 +788,24 @@ internal class MXOlmDevice @Inject constructor(
|
|||
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) {
|
||||
val timelineSet = inboundGroupSessionMessageIndexes.getOrPut(timeline) { mutableSetOf() }
|
||||
|
||||
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
|
||||
|
||||
if (timelineSet.contains(messageIndexKey)) {
|
||||
val replayAttackMap = inboundGroupSessionMessageIndexes.getOrPut(timeline) { mutableMapOf() }
|
||||
if (replayAttackMap.contains(messageIndexKey) && replayAttackMap[messageIndexKey] != eventId) {
|
||||
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
|
||||
Timber.tag(loggerTag.value).e("## decryptGroupMessage() timelineId=$timeline: $reason")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)
|
||||
}
|
||||
|
||||
timelineSet.add(messageIndexKey)
|
||||
replayAttackMap[messageIndexKey] = eventId
|
||||
}
|
||||
|
||||
inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey)
|
||||
val payload = try {
|
||||
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
||||
|
@ -811,11 +822,6 @@ internal class MXOlmDevice @Inject constructor(
|
|||
senderKey,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -78,6 +78,7 @@ internal class MXMegolmDecryption(
|
|||
encryptedEventContent.ciphertext,
|
||||
event.roomId,
|
||||
timeline,
|
||||
eventId = event.eventId.orEmpty(),
|
||||
encryptedEventContent.sessionId,
|
||||
encryptedEventContent.senderKey
|
||||
)
|
||||
|
|
|
@ -520,9 +520,10 @@ internal class RoomSyncHandler @Inject constructor(
|
|||
|
||||
private fun decryptIfNeeded(event: Event, roomId: String) {
|
||||
try {
|
||||
val timelineId = generateTimelineId(roomId)
|
||||
// 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
|
||||
val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), "") }
|
||||
val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), timelineId) }
|
||||
event.mxDecryptionResult = OlmDecryptionResult(
|
||||
payload = result.clearEvent,
|
||||
senderKey = result.senderCurve25519Key,
|
||||
|
@ -537,6 +538,10 @@ internal class RoomSyncHandler @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun generateTimelineId(roomId: String): String {
|
||||
return "RoomSyncHandler$roomId"
|
||||
}
|
||||
|
||||
data class EphemeralResult(
|
||||
val typingUserIds: List<String> = emptyList()
|
||||
)
|
||||
|
|
|
@ -382,7 +382,7 @@ dependencies {
|
|||
implementation 'com.facebook.stetho:stetho:1.6.0'
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.48'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.49'
|
||||
|
||||
// FlowBinding
|
||||
implementation libs.github.flowBinding
|
||||
|
|
|
@ -60,6 +60,11 @@ class DebugFeaturesStateFactory @Inject constructor(
|
|||
key = DebugFeatureKeys.onboardingCombinedRegister,
|
||||
factory = VectorFeatures::isOnboardingCombinedRegisterEnabled
|
||||
),
|
||||
createBooleanFeature(
|
||||
label = "FTUE Combined login",
|
||||
key = DebugFeatureKeys.onboardingCombinedLogin,
|
||||
factory = VectorFeatures::isOnboardingCombinedLoginEnabled
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -57,6 +57,9 @@ class DebugVectorFeatures(
|
|||
override fun isOnboardingCombinedRegisterEnabled(): Boolean = read(DebugFeatureKeys.onboardingCombinedRegister)
|
||||
?: vectorFeatures.isOnboardingCombinedRegisterEnabled()
|
||||
|
||||
override fun isOnboardingCombinedLoginEnabled(): Boolean = read(DebugFeatureKeys.onboardingCombinedLogin)
|
||||
?: vectorFeatures.isOnboardingCombinedLoginEnabled()
|
||||
|
||||
override fun isScreenSharingEnabled(): Boolean = read(DebugFeatureKeys.screenSharing)
|
||||
?: vectorFeatures.isScreenSharingEnabled()
|
||||
|
||||
|
@ -113,6 +116,7 @@ object DebugFeatureKeys {
|
|||
val onboardingUseCase = booleanPreferencesKey("onboarding-splash-carousel")
|
||||
val onboardingPersonalize = booleanPreferencesKey("onboarding-personalize")
|
||||
val onboardingCombinedRegister = booleanPreferencesKey("onboarding-combined-register")
|
||||
val onboardingCombinedLogin = booleanPreferencesKey("onboarding-combined-login")
|
||||
val liveLocationSharing = booleanPreferencesKey("live-location-sharing")
|
||||
val screenSharing = booleanPreferencesKey("screen-sharing")
|
||||
}
|
||||
|
|
|
@ -72,6 +72,8 @@ class AppStateHandler @Inject constructor(
|
|||
|
||||
val selectedRoomGroupingFlow = selectedSpaceDataSource.stream()
|
||||
|
||||
private val spaceBackstack = ArrayDeque<String?>()
|
||||
|
||||
fun getCurrentRoomGroupingMethod(): RoomGroupingMethod? {
|
||||
// XXX we should somehow make it live :/ just a work around
|
||||
// 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
|
||||
if (selectedSpaceDataSource.currentValue?.orNull() is RoomGroupingMethod.BySpace &&
|
||||
spaceId == selectedSpaceDataSource.currentValue?.orNull()?.space()?.roomId) return
|
||||
if (currentSpace != null && spaceId == currentSpace.roomId) return
|
||||
val spaceSum = spaceId?.let { uSession.getRoomSummary(spaceId) }
|
||||
|
||||
if (isForwardNavigation) {
|
||||
spaceBackstack.addLast(currentSpace?.roomId)
|
||||
}
|
||||
|
||||
if (persistNow) {
|
||||
uiStateRepository.storeGroupingMethod(true, uSession.sessionId)
|
||||
uiStateRepository.storeSelectedSpace(spaceSum?.roomId, uSession.sessionId)
|
||||
|
@ -151,6 +157,8 @@ class AppStateHandler @Inject constructor(
|
|||
}.launchIn(session.coroutineScope)
|
||||
}
|
||||
|
||||
fun getSpaceBackstack() = spaceBackstack
|
||||
|
||||
fun safeActiveSpaceId(): String? {
|
||||
return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.BySpace)?.spaceSummary?.roomId
|
||||
}
|
||||
|
|
|
@ -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.FtueAuthChooseDisplayNameFragment
|
||||
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.FtueAuthGenericTextInputFormFragment
|
||||
import im.vector.app.features.onboarding.ftueauth.FtueAuthLegacyStyleCaptchaFragment
|
||||
|
@ -521,6 +524,21 @@ interface FragmentModule {
|
|||
@FragmentKey(FtueAuthPersonalizationCompleteFragment::class)
|
||||
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
|
||||
@IntoMap
|
||||
@FragmentKey(UserListFragment::class)
|
||||
|
|
|
@ -26,6 +26,7 @@ interface VectorFeatures {
|
|||
fun isOnboardingUseCaseEnabled(): Boolean
|
||||
fun isOnboardingPersonalizeEnabled(): Boolean
|
||||
fun isOnboardingCombinedRegisterEnabled(): Boolean
|
||||
fun isOnboardingCombinedLoginEnabled(): Boolean
|
||||
fun isScreenSharingEnabled(): Boolean
|
||||
|
||||
enum class OnboardingVariant {
|
||||
|
@ -42,5 +43,6 @@ class DefaultVectorFeatures : VectorFeatures {
|
|||
override fun isOnboardingUseCaseEnabled() = true
|
||||
override fun isOnboardingPersonalizeEnabled() = false
|
||||
override fun isOnboardingCombinedRegisterEnabled() = false
|
||||
override fun isOnboardingCombinedLoginEnabled() = false
|
||||
override fun isScreenSharingEnabled(): Boolean = true
|
||||
}
|
||||
|
|
|
@ -199,43 +199,13 @@ class HomeActivity :
|
|||
when (sharedAction) {
|
||||
is HomeActivitySharedAction.OpenDrawer -> views.drawerLayout.openDrawer(GravityCompat.START)
|
||||
is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START)
|
||||
is HomeActivitySharedAction.OpenGroup -> {
|
||||
views.drawerLayout.closeDrawer(GravityCompat.START)
|
||||
|
||||
// Temporary
|
||||
// When switching from space to group or group to space, we need to reload the fragment
|
||||
// To be removed when dropping legacy groups
|
||||
if (sharedAction.clearFragment) {
|
||||
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)
|
||||
}
|
||||
is HomeActivitySharedAction.OpenGroup -> openGroup(sharedAction.shouldClearFragment)
|
||||
is HomeActivitySharedAction.OpenSpacePreview -> startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId))
|
||||
is HomeActivitySharedAction.AddSpace -> createSpaceResultLauncher.launch(SpaceCreationActivity.newIntent(this))
|
||||
is HomeActivitySharedAction.ShowSpaceSettings -> showSpaceSettings(sharedAction.spaceId)
|
||||
is HomeActivitySharedAction.OpenSpaceInvite -> openSpaceInvite(sharedAction.spaceId)
|
||||
HomeActivitySharedAction.SendSpaceFeedBack -> bugReporter.openBugReportScreen(this, ReportType.SPACE_BETA_FEEDBACK)
|
||||
HomeActivitySharedAction.CloseGroup -> closeGroup()
|
||||
}
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
|
@ -272,6 +242,37 @@ class HomeActivity :
|
|||
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() {
|
||||
navigator.openAnalyticsOptIn(this)
|
||||
}
|
||||
|
|
|
@ -24,7 +24,8 @@ import im.vector.app.core.platform.VectorSharedAction
|
|||
sealed class HomeActivitySharedAction : VectorSharedAction {
|
||||
object OpenDrawer : HomeActivitySharedAction()
|
||||
object CloseDrawer : HomeActivitySharedAction()
|
||||
data class OpenGroup(val clearFragment: Boolean) : HomeActivitySharedAction()
|
||||
data class OpenGroup(val shouldClearFragment: Boolean) : HomeActivitySharedAction()
|
||||
object CloseGroup : HomeActivitySharedAction()
|
||||
object AddSpace : HomeActivitySharedAction()
|
||||
data class OpenSpacePreview(val spaceId: String) : HomeActivitySharedAction()
|
||||
data class OpenSpaceInvite(val spaceId: String) : HomeActivitySharedAction()
|
||||
|
|
|
@ -33,6 +33,7 @@ import im.vector.app.R
|
|||
import im.vector.app.RoomGroupingMethod
|
||||
import im.vector.app.core.extensions.commitTransaction
|
||||
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.VectorBaseFragment
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
|
@ -69,7 +70,8 @@ class HomeDetailFragment @Inject constructor(
|
|||
private val appStateHandler: AppStateHandler
|
||||
) : VectorBaseFragment<FragmentHomeDetailBinding>(),
|
||||
KeysBackupBanner.Delegate,
|
||||
CurrentCallsView.Callback {
|
||||
CurrentCallsView.Callback,
|
||||
OnBackPressed {
|
||||
|
||||
private val viewModel: HomeDetailViewModel by fragmentViewModel()
|
||||
private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel()
|
||||
|
@ -130,12 +132,8 @@ class HomeDetailFragment @Inject constructor(
|
|||
|
||||
viewModel.onEach(HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod ->
|
||||
when (roomGroupingMethod) {
|
||||
is RoomGroupingMethod.ByLegacyGroup -> {
|
||||
onGroupChange(roomGroupingMethod.groupSummary)
|
||||
}
|
||||
is RoomGroupingMethod.BySpace -> {
|
||||
onSpaceChange(roomGroupingMethod.spaceSummary)
|
||||
}
|
||||
is RoomGroupingMethod.ByLegacyGroup -> onGroupChange(roomGroupingMethod.groupSummary)
|
||||
is RoomGroupingMethod.BySpace -> onSpaceChange(roomGroupingMethod.spaceSummary)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,7 +155,6 @@ class HomeDetailFragment @Inject constructor(
|
|||
|
||||
unknownDeviceDetectorSharedViewModel.onEach { state ->
|
||||
state.unknownSessions.invoke()?.let { unknownDevices ->
|
||||
// Timber.v("## Detector Triggerred in fragment - ${unknownDevices.firstOrNull()}")
|
||||
if (unknownDevices.firstOrNull()?.currentSessionTrust == true) {
|
||||
val uid = "review_login"
|
||||
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() {
|
||||
dismissLoadingDialog()
|
||||
val fragmentTag = HomeTab.DialPad.toFragmentTag()
|
||||
|
@ -203,20 +221,16 @@ class HomeDetailFragment @Inject constructor(
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// update notification tab if needed
|
||||
updateTabVisibilitySafely(R.id.bottom_action_notification, vectorPreferences.labAddNotificationTab())
|
||||
callManager.checkForProtocolsSupportIfNeeded()
|
||||
refreshSpaceState()
|
||||
}
|
||||
|
||||
// Current space/group is not live so at least refresh toolbar on resume
|
||||
appStateHandler.getCurrentRoomGroupingMethod()?.let { roomGroupingMethod ->
|
||||
when (roomGroupingMethod) {
|
||||
is RoomGroupingMethod.ByLegacyGroup -> {
|
||||
onGroupChange(roomGroupingMethod.groupSummary)
|
||||
}
|
||||
is RoomGroupingMethod.BySpace -> {
|
||||
onSpaceChange(roomGroupingMethod.spaceSummary)
|
||||
}
|
||||
}
|
||||
private fun refreshSpaceState() {
|
||||
when (val roomGroupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) {
|
||||
is RoomGroupingMethod.ByLegacyGroup -> onGroupChange(roomGroupingMethod.groupSummary)
|
||||
is RoomGroupingMethod.BySpace -> onSpaceChange(roomGroupingMethod.spaceSummary)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,12 +274,12 @@ class HomeDetailFragment @Inject constructor(
|
|||
viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer)
|
||||
colorInt = colorProvider.getColorFromAttribute(R.attr.colorPrimary)
|
||||
contentAction = Runnable {
|
||||
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
|
||||
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let { activity ->
|
||||
// mark as ignored to avoid showing it again
|
||||
unknownDeviceDetectorSharedViewModel.handle(
|
||||
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 {
|
||||
|
@ -324,11 +338,11 @@ class HomeDetailFragment @Inject constructor(
|
|||
withState(viewModel) {
|
||||
when (it.roomGroupingMethod) {
|
||||
is RoomGroupingMethod.ByLegacyGroup -> {
|
||||
// nothing do far
|
||||
// do nothing
|
||||
}
|
||||
is RoomGroupingMethod.BySpace -> {
|
||||
it.roomGroupingMethod.spaceSummary?.let {
|
||||
sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(it.roomId))
|
||||
it.roomGroupingMethod.spaceSummary?.let { spaceSummary ->
|
||||
sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(spaceSummary.roomId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -348,17 +362,6 @@ class HomeDetailFragment @Inject constructor(
|
|||
viewModel.handle(HomeDetailAction.SwitchTab(tab))
|
||||
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) {
|
||||
|
@ -436,7 +439,6 @@ class HomeDetailFragment @Inject constructor(
|
|||
}
|
||||
|
||||
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_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
|
||||
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup)
|
||||
|
@ -496,4 +498,11 @@ class HomeDetailFragment @Inject constructor(
|
|||
}
|
||||
return this
|
||||
}
|
||||
|
||||
override fun onBackPressed(toolbarButton: Boolean) = if (getCurrentSpace() != null) {
|
||||
navigateBack()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,3 +159,9 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
|
|||
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) }
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ package im.vector.app.features.onboarding
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.andThen
|
||||
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.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
|
@ -33,8 +33,8 @@ class DirectLoginUseCase @Inject constructor(
|
|||
private val uriFactory: UriFactory
|
||||
) {
|
||||
|
||||
suspend fun execute(action: LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?): Result<Session> {
|
||||
return fetchWellKnown(action.username, homeServerConnectionConfig)
|
||||
suspend fun execute(action: LoginDirect, homeServerConnectionConfig: HomeServerConnectionConfig?): Result<Session> {
|
||||
return fetchWellKnown(action.matrixId, homeServerConnectionConfig)
|
||||
.andThen { wellKnown -> createSessionFor(wellKnown, action, homeServerConnectionConfig) }
|
||||
}
|
||||
|
||||
|
@ -42,13 +42,13 @@ class DirectLoginUseCase @Inject constructor(
|
|||
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.FailPrompt -> handleFailPrompt(data, action, config)
|
||||
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
|
||||
val isMissingInformationToLogin = data.homeServerUrl == null || data.wellKnown == null
|
||||
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)
|
||||
return runCatching {
|
||||
authenticationService.directAuthentication(
|
||||
alteredHomeServerConnectionConfig,
|
||||
action.username,
|
||||
action.matrixId,
|
||||
action.password,
|
||||
action.initialDeviceName
|
||||
)
|
||||
|
@ -74,8 +74,8 @@ class DirectLoginUseCase @Inject constructor(
|
|||
identityServerUri = wellKnownPrompt.identityServerUrl?.let { uriFactory.parse(it) }
|
||||
)
|
||||
|
||||
private fun fallbackConfig(action: LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) = HomeServerConnectionConfig(
|
||||
homeServerUri = uriFactory.parse("https://${action.username.getServerName()}"),
|
||||
private fun fallbackConfig(action: LoginDirect, wellKnownPrompt: WellknownResult.Prompt) = HomeServerConnectionConfig(
|
||||
homeServerUri = uriFactory.parse("https://${action.matrixId.getServerName()}"),
|
||||
homeServerUriBase = uriFactory.parse(wellKnownPrompt.homeServerUrl),
|
||||
identityServerUri = wellKnownPrompt.identityServerUrl?.let { uriFactory.parse(it) }
|
||||
)
|
||||
|
|
|
@ -46,9 +46,12 @@ sealed interface OnboardingAction : VectorViewModelAction {
|
|||
data class ResetPassword(val email: String, val newPassword: String) : OnboardingAction
|
||||
object ResetPasswordMailConfirmed : OnboardingAction
|
||||
|
||||
// Login or Register, depending on the signMode
|
||||
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) : OnboardingAction
|
||||
sealed interface AuthenticateAction : OnboardingAction {
|
||||
data class Register(val username: String, val password: String, val initialDeviceName: String) : AuthenticateAction
|
||||
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
|
||||
|
||||
data class PostRegisterAction(val registerAction: RegisterAction) : OnboardingAction
|
||||
|
|
|
@ -37,6 +37,7 @@ sealed class OnboardingViewEvents : VectorViewEvents {
|
|||
object OpenUseCaseSelection : OnboardingViewEvents()
|
||||
object OpenServerSelection : OnboardingViewEvents()
|
||||
object OpenCombinedRegister : OnboardingViewEvents()
|
||||
object OpenCombinedLogin : OnboardingViewEvents()
|
||||
object EditServerSelection : OnboardingViewEvents()
|
||||
data class OnServerSelectionDone(val serverType: ServerType) : OnboardingViewEvents()
|
||||
object OnLoginFlowRetrieved : OnboardingViewEvents()
|
||||
|
|
|
@ -42,6 +42,7 @@ import im.vector.app.features.login.LoginMode
|
|||
import im.vector.app.features.login.ReAuthHelper
|
||||
import im.vector.app.features.login.ServerType
|
||||
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.ftueauth.MatrixOrgRegistrationStagesComparator
|
||||
import kotlinx.coroutines.Job
|
||||
|
@ -139,8 +140,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action)
|
||||
is OnboardingAction.InitWith -> handleInitWith(action)
|
||||
is OnboardingAction.HomeServerChange -> withAction(action) { handleHomeserverChange(action) }
|
||||
is OnboardingAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action }
|
||||
is OnboardingAction.Register -> handleRegisterWith(action).also { lastAction = action }
|
||||
is AuthenticateAction -> withAction(action) { handleAuthenticateAction(action) }
|
||||
is OnboardingAction.LoginWithToken -> handleLoginWithToken(action)
|
||||
is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
||||
is OnboardingAction.ResetPassword -> handleResetPassword(action)
|
||||
|
@ -165,6 +165,14 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
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) {
|
||||
if (resetConfig) {
|
||||
loginConfig = null
|
||||
|
@ -188,16 +196,21 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun continueToPageAfterSplash(onboardingFlow: OnboardingFlow) {
|
||||
val nextOnboardingStep = when (onboardingFlow) {
|
||||
OnboardingFlow.SignUp -> if (vectorFeatures.isOnboardingUseCaseEnabled()) {
|
||||
when (onboardingFlow) {
|
||||
OnboardingFlow.SignUp -> {
|
||||
_viewEvents.post(
|
||||
if (vectorFeatures.isOnboardingUseCaseEnabled()) {
|
||||
OnboardingViewEvents.OpenUseCaseSelection
|
||||
} else {
|
||||
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) {
|
||||
|
@ -209,7 +222,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) }
|
||||
?.let { startAuthenticationFlow(finalLastAction, it) }
|
||||
}
|
||||
is OnboardingAction.LoginOrRegister ->
|
||||
is AuthenticateAction.LoginDirect ->
|
||||
handleDirectLogin(
|
||||
finalLastAction,
|
||||
HomeServerConnectionConfig.Builder()
|
||||
|
@ -307,7 +320,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
|
||||
private fun OnboardingViewState.hasSelectedMatrixOrg() = selectedHomeserver.userFacingUrl == matrixOrgUrl
|
||||
|
||||
private fun handleRegisterWith(action: OnboardingAction.Register) {
|
||||
private fun handleRegisterWith(action: AuthenticateAction.Register) {
|
||||
reAuthHelper.data = action.password
|
||||
handleRegisterAction(
|
||||
RegisterAction.CreateAccount(
|
||||
|
@ -482,16 +495,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleLoginOrRegister(action: OnboardingAction.LoginOrRegister) = withState { state ->
|
||||
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?) {
|
||||
private fun handleDirectLogin(action: AuthenticateAction.LoginDirect, homeServerConnectionConfig: HomeServerConnectionConfig?) {
|
||||
setState { copy(isLoading = true) }
|
||||
currentJob = viewModelScope.launch {
|
||||
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
|
||||
|
||||
if (safeLoginWizard == null) {
|
||||
|
@ -648,7 +652,11 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
when (trigger) {
|
||||
is OnboardingAction.HomeServerChange.EditHomeServer -> {
|
||||
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)
|
||||
_viewEvents.post(OnboardingViewEvents.OnHomeserverEdited)
|
||||
}
|
||||
|
@ -661,7 +669,10 @@ class OnboardingViewModel @AssistedInject constructor(
|
|||
when (awaitState().onboardingFlow) {
|
||||
OnboardingFlow.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 -> {
|
||||
updateSignMode(SignMode.SignUp)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,7 +21,6 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.autofill.HintConstants
|
||||
import androidx.core.text.isDigitsOnly
|
||||
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.core.extensions.content
|
||||
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.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.FragmentFtueCombinedRegisterBinding
|
||||
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.OnboardingAction.AuthenticateAction
|
||||
import im.vector.app.features.onboarding.OnboardingViewEvents
|
||||
import im.vector.app.features.onboarding.OnboardingViewState
|
||||
import kotlinx.coroutines.flow.combine
|
||||
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.failure.isInvalidPassword
|
||||
import org.matrix.android.sdk.api.failure.isInvalidUsername
|
||||
|
@ -66,36 +65,16 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
setupSubmitButton()
|
||||
views.createAccountRoot.realignPercentagesToParent()
|
||||
views.editServerButton.debouncedClicks {
|
||||
viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection))
|
||||
}
|
||||
|
||||
views.createAccountPasswordInput.editText().setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
submit()
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
return@setOnEditorActionListener false
|
||||
}
|
||||
views.editServerButton.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) }
|
||||
views.createAccountPasswordInput.setOnImeDoneListener { submit() }
|
||||
}
|
||||
|
||||
private fun setupSubmitButton() {
|
||||
views.createAccountSubmit.setOnClickListener { submit() }
|
||||
observeInputFields()
|
||||
.onEach {
|
||||
views.createAccountPasswordInput.error = null
|
||||
views.createAccountInput.error = null
|
||||
views.createAccountSubmit.isEnabled = it
|
||||
}
|
||||
observeContentChangesAndResetErrors(views.createAccountInput, views.createAccountPasswordInput, views.createAccountSubmit)
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
}
|
||||
|
||||
private fun observeInputFields() = combine(
|
||||
views.createAccountInput.hasContentFlow { it.trim() },
|
||||
views.createAccountPasswordInput.hasContentFlow(),
|
||||
transform = { isLoginNotEmpty, isPasswordNotEmpty -> isLoginNotEmpty && isPasswordNotEmpty }
|
||||
)
|
||||
|
||||
private fun submit() {
|
||||
withState(viewModel) { state ->
|
||||
cleanupUi()
|
||||
|
@ -119,7 +98,7 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
|
|||
}
|
||||
|
||||
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>?) {
|
||||
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
|
||||
views.ssoButtons.mode = SocialLoginButtonsView.Mode.MODE_CONTINUE
|
||||
views.ssoButtons.ssoIdentityProviders = ssoProviders?.sorted()
|
||||
views.ssoButtons.listener = SocialLoginButtonsView.InteractionListener { id ->
|
||||
views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
|
||||
viewModel.getSsoUrl(
|
||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||
deviceId = deviceId,
|
||||
|
|
|
@ -26,6 +26,7 @@ import androidx.autofill.HintConstants
|
|||
import androidx.core.text.isDigitsOnly
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.airbnb.mvrx.withState
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.hideKeyboard
|
||||
|
@ -119,6 +120,7 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
|
|||
}
|
||||
|
||||
private fun submit() {
|
||||
withState(viewModel) { state ->
|
||||
cleanupUi()
|
||||
|
||||
val login = views.loginField.text.toString()
|
||||
|
@ -152,7 +154,9 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
|
|||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -228,9 +228,14 @@ class FtueAuthVariant(
|
|||
)
|
||||
}
|
||||
OnboardingViewEvents.OnHomeserverEdited -> activity.popBackstack()
|
||||
OnboardingViewEvents.OpenCombinedLogin -> onStartCombinedLogin()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onStartCombinedLogin() {
|
||||
addRegistrationStageFragmentToBackstack(FtueAuthCombinedLoginFragment::class.java)
|
||||
}
|
||||
|
||||
private fun onRegistrationFlow(viewEvents: OnboardingViewEvents.RegistrationFlowResult) {
|
||||
when {
|
||||
registrationShouldFallback(viewEvents) -> displayFallbackWebDialog()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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?
|
||||
)
|
244
vector/src/main/res/layout/fragment_ftue_combined_login.xml
Normal file
244
vector/src/main/res/layout/fragment_ftue_combined_login.xml
Normal 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>
|
|
@ -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_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_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>
|
||||
|
|
|
@ -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.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_FAILED_WITH_CONTENT_RESULT = WellknownResult.FailPrompt("https://homeserverurl.com", WellKnown())
|
||||
private val A_WELLKNOWN_FAILED_WITHOUT_CONTENT_RESULT = WellknownResult.FailPrompt(null, null)
|
||||
private val NO_HOMESERVER_CONFIG: HomeServerConnectionConfig? = null
|
||||
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,
|
||||
identityServerUri = null
|
||||
)
|
||||
|
@ -54,11 +54,11 @@ class DirectLoginUseCaseTest {
|
|||
|
||||
@Test
|
||||
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)
|
||||
val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION
|
||||
fakeAuthenticationService.givenWellKnown(A_DIRECT_LOGIN_ACTION.matrixId, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_SUCCESS_RESULT)
|
||||
val (username, password, initialDeviceName) = A_DIRECT_LOGIN_ACTION
|
||||
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)
|
||||
}
|
||||
|
@ -66,14 +66,14 @@ class DirectLoginUseCaseTest {
|
|||
@Test
|
||||
fun `given wellknown fails with content, when logging in directly, then returns success with direct session result`() = runTest {
|
||||
fakeAuthenticationService.givenWellKnown(
|
||||
A_LOGIN_OR_REGISTER_ACTION.username,
|
||||
A_DIRECT_LOGIN_ACTION.matrixId,
|
||||
config = NO_HOMESERVER_CONFIG,
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -81,14 +81,14 @@ class DirectLoginUseCaseTest {
|
|||
@Test
|
||||
fun `given wellknown fails without content, when logging in directly, then returns well known error`() = runTest {
|
||||
fakeAuthenticationService.givenWellKnown(
|
||||
A_LOGIN_OR_REGISTER_ACTION.username,
|
||||
A_DIRECT_LOGIN_ACTION.matrixId,
|
||||
config = NO_HOMESERVER_CONFIG,
|
||||
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)
|
||||
|
||||
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.exceptionOrNull() is Exception }
|
||||
|
@ -97,20 +97,20 @@ class DirectLoginUseCaseTest {
|
|||
|
||||
@Test
|
||||
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)
|
||||
}
|
||||
|
||||
@Test
|
||||
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)
|
||||
val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION
|
||||
fakeAuthenticationService.givenWellKnown(A_DIRECT_LOGIN_ACTION.matrixId, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_SUCCESS_RESULT)
|
||||
val (username, password, initialDeviceName) = A_DIRECT_LOGIN_ACTION
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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 AN_IGNORED_FLOW_RESULT = FlowResult(missingStages = emptyList(), completedStages = emptyList())
|
||||
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 val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(FakeUri().instance)
|
||||
private val SELECTED_HOMESERVER_STATE = SelectedHomeserverState(preferredLoginMode = LoginMode.Password)
|
||||
|
@ -142,11 +142,11 @@ class OnboardingViewModelTest {
|
|||
@Test
|
||||
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))
|
||||
fakeDirectLoginUseCase.givenSuccessResult(A_LOGIN_OR_REGISTER_ACTION, config = null, result = fakeSession)
|
||||
fakeDirectLoginUseCase.givenSuccessResult(A_DIRECT_LOGIN, config = null, result = fakeSession)
|
||||
givenInitialisesSession(fakeSession)
|
||||
val test = viewModel.test()
|
||||
|
||||
viewModel.handle(A_LOGIN_OR_REGISTER_ACTION)
|
||||
viewModel.handle(A_DIRECT_LOGIN)
|
||||
|
||||
test
|
||||
.assertStatesChanges(
|
||||
|
@ -161,11 +161,11 @@ class OnboardingViewModelTest {
|
|||
@Test
|
||||
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))
|
||||
fakeDirectLoginUseCase.givenFailureResult(A_LOGIN_OR_REGISTER_ACTION, config = null, cause = AN_ERROR)
|
||||
fakeDirectLoginUseCase.givenFailureResult(A_DIRECT_LOGIN, config = null, cause = AN_ERROR)
|
||||
givenInitialisesSession(fakeSession)
|
||||
val test = viewModel.test()
|
||||
|
||||
viewModel.handle(A_LOGIN_OR_REGISTER_ACTION)
|
||||
viewModel.handle(A_DIRECT_LOGIN)
|
||||
|
||||
test
|
||||
.assertStatesChanges(
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
package im.vector.app.test.fakes
|
||||
|
||||
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.mockk
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
|
@ -25,11 +25,11 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
|||
class FakeDirectLoginUseCase {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue