mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-25 10:55:55 +03:00
Merge pull request #4115 from vector-im/feature/adm/login-key-verification-flow
Skipping passphrase screen when no passphrase is available
This commit is contained in:
commit
357d7ee338
11 changed files with 381 additions and 9 deletions
2
changelog.d/3898.bugfix
Normal file
2
changelog.d/3898.bugfix
Normal file
|
@ -0,0 +1,2 @@
|
|||
Fixes the passphrase screen being incorrectly shown when pressing back on the key verification screen.
|
||||
When the user doesn't have a passphrase set we don't show the passphrase screen.
|
|
@ -76,7 +76,6 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
init {
|
||||
|
||||
setState {
|
||||
copy(userId = session.myUserId)
|
||||
}
|
||||
|
@ -167,10 +166,14 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
|
|||
if (state.checkingSSSSAction is Loading) return@withState // ignore
|
||||
when (state.step) {
|
||||
SharedSecureStorageViewState.Step.EnterKey -> {
|
||||
setState {
|
||||
copy(
|
||||
step = SharedSecureStorageViewState.Step.EnterPassphrase
|
||||
)
|
||||
if (state.hasPassphrase) {
|
||||
setState {
|
||||
copy(
|
||||
step = SharedSecureStorageViewState.Step.EnterPassphrase
|
||||
)
|
||||
}
|
||||
} else {
|
||||
_viewEvents.post(SharedSecureStorageViewEvent.Dismiss)
|
||||
}
|
||||
}
|
||||
SharedSecureStorageViewState.Step.ResetAll -> {
|
||||
|
|
44
vector/src/test/java/androidx/lifecycle/LifecycleRegistry.kt
Normal file
44
vector/src/test/java/androidx/lifecycle/LifecycleRegistry.kt
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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 androidx.lifecycle
|
||||
|
||||
/**
|
||||
* Manual test override to stop BaseMvRxViewModel from interacting with the android looper/main thread
|
||||
* Tests will run on their original test worker threads
|
||||
*
|
||||
* This has been fixed is newer versions of Mavericks via LifecycleRegistry.createUnsafe
|
||||
* https://github.com/airbnb/mavericks/blob/master/mvrx-rxjava2/src/main/kotlin/com/airbnb/mvrx/BaseMvRxViewModel.kt#L61
|
||||
*/
|
||||
@Suppress("UNUSED")
|
||||
class LifecycleRegistry(@Suppress("UNUSED_PARAMETER") lifecycleOwner: LifecycleOwner) : Lifecycle() {
|
||||
|
||||
private var state = State.INITIALIZED
|
||||
|
||||
fun setCurrentState(state: State) {
|
||||
this.state = state
|
||||
}
|
||||
|
||||
override fun addObserver(observer: LifecycleObserver) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun removeObserver(observer: LifecycleObserver) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getCurrentState() = state
|
||||
}
|
|
@ -40,7 +40,7 @@ class KeysExporterTest {
|
|||
private val cryptoService = FakeCryptoService()
|
||||
private val context = FakeContext()
|
||||
private val keysExporter = KeysExporter(
|
||||
session = FakeSession(cryptoService = cryptoService),
|
||||
session = FakeSession(fakeCryptoService = cryptoService),
|
||||
context = context.instance,
|
||||
dispatchers = CoroutineDispatchers(Dispatchers.Unconfined)
|
||||
)
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.crypto.quads
|
||||
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.test.InstantRxRule
|
||||
import im.vector.app.test.fakes.FakeSession
|
||||
import im.vector.app.test.fakes.FakeStringProvider
|
||||
import im.vector.app.test.test
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.session.securestorage.IntegrityResult
|
||||
import org.matrix.android.sdk.api.session.securestorage.KeyInfo
|
||||
import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult
|
||||
import org.matrix.android.sdk.api.session.securestorage.SecretStorageKeyContent
|
||||
import org.matrix.android.sdk.api.session.securestorage.SsssPassphrase
|
||||
|
||||
private const val IGNORED_PASSPHRASE_INTEGRITY = false
|
||||
private val KEY_INFO_WITH_PASSPHRASE = KeyInfo(
|
||||
id = "id",
|
||||
content = SecretStorageKeyContent(passphrase = SsssPassphrase(null, 0, null))
|
||||
)
|
||||
private val KEY_INFO_WITHOUT_PASSPHRASE = KeyInfo(id = "id", content = SecretStorageKeyContent(passphrase = null))
|
||||
|
||||
class SharedSecureStorageViewModelTest {
|
||||
|
||||
@get:Rule
|
||||
val instantRx = InstantRxRule()
|
||||
|
||||
private val stringProvider = FakeStringProvider()
|
||||
private val session = FakeSession()
|
||||
|
||||
@Test
|
||||
fun `given a key info with passphrase when initialising then step is EnterPassphrase`() {
|
||||
givenKey(KEY_INFO_WITH_PASSPHRASE)
|
||||
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.test().assertState(aViewState(
|
||||
hasPassphrase = true,
|
||||
step = SharedSecureStorageViewState.Step.EnterPassphrase
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a key info without passphrase when initialising then step is EnterKey`() {
|
||||
givenKey(KEY_INFO_WITHOUT_PASSPHRASE)
|
||||
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.test().assertState(aViewState(
|
||||
hasPassphrase = false,
|
||||
step = SharedSecureStorageViewState.Step.EnterKey
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given on EnterKey step when going back then dismisses`() {
|
||||
givenKey(KEY_INFO_WITHOUT_PASSPHRASE)
|
||||
|
||||
val viewModel = createViewModel()
|
||||
val test = viewModel.test()
|
||||
|
||||
viewModel.handle(SharedSecureStorageAction.Back)
|
||||
|
||||
test.assertEvents(SharedSecureStorageViewEvent.Dismiss)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given on passphrase step when using key then step is EnterKey`() {
|
||||
givenKey(KEY_INFO_WITH_PASSPHRASE)
|
||||
val viewModel = createViewModel()
|
||||
val test = viewModel.test()
|
||||
|
||||
viewModel.handle(SharedSecureStorageAction.UseKey)
|
||||
|
||||
test.assertState(aViewState(
|
||||
hasPassphrase = true,
|
||||
step = SharedSecureStorageViewState.Step.EnterKey
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a key info with passphrase and on EnterKey step when going back then step is EnterPassphrase`() {
|
||||
givenKey(KEY_INFO_WITH_PASSPHRASE)
|
||||
val viewModel = createViewModel()
|
||||
val test = viewModel.test()
|
||||
|
||||
viewModel.handle(SharedSecureStorageAction.UseKey)
|
||||
viewModel.handle(SharedSecureStorageAction.Back)
|
||||
|
||||
test.assertState(aViewState(
|
||||
hasPassphrase = true,
|
||||
step = SharedSecureStorageViewState.Step.EnterPassphrase
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given on passphrase step when going back then dismisses`() {
|
||||
givenKey(KEY_INFO_WITH_PASSPHRASE)
|
||||
val viewModel = createViewModel()
|
||||
val test = viewModel.test()
|
||||
|
||||
viewModel.handle(SharedSecureStorageAction.Back)
|
||||
|
||||
test.assertEvents(SharedSecureStorageViewEvent.Dismiss)
|
||||
}
|
||||
|
||||
private fun createViewModel() = SharedSecureStorageViewModel(
|
||||
SharedSecureStorageViewState(),
|
||||
SharedSecureStorageActivity.Args(keyId = null, emptyList(), "alias"),
|
||||
stringProvider.instance,
|
||||
session
|
||||
)
|
||||
|
||||
private fun aViewState(hasPassphrase: Boolean, step: SharedSecureStorageViewState.Step) = SharedSecureStorageViewState(
|
||||
ready = true,
|
||||
hasPassphrase = hasPassphrase,
|
||||
checkingSSSSAction = Uninitialized,
|
||||
step = step,
|
||||
activeDeviceCount = 0,
|
||||
showResetAllAction = false,
|
||||
userId = ""
|
||||
)
|
||||
|
||||
private fun givenKey(keyInfo: KeyInfo) {
|
||||
givenHasAccessToSecrets()
|
||||
session.fakeSharedSecretStorageService._defaultKey = KeyInfoResult.Success(keyInfo)
|
||||
}
|
||||
|
||||
private fun givenHasAccessToSecrets() {
|
||||
session.fakeSharedSecretStorageService.integrityResult = IntegrityResult.Success(passphraseBased = IGNORED_PASSPHRASE_INTEGRITY)
|
||||
}
|
||||
}
|
|
@ -16,4 +16,31 @@
|
|||
|
||||
package im.vector.app.test
|
||||
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
import io.reactivex.observers.TestObserver
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
|
||||
fun String.trimIndentOneLine() = trimIndent().replace("\n", "")
|
||||
|
||||
fun <S : MvRxState, VA : VectorViewModelAction, VE : VectorViewEvents> VectorViewModel<S, VA, VE>.test(): ViewModelTest<S, VE> {
|
||||
val state = { com.airbnb.mvrx.withState(this) { it } }
|
||||
val viewEvents = viewEvents.observe().test()
|
||||
return ViewModelTest(state, viewEvents)
|
||||
}
|
||||
|
||||
class ViewModelTest<S, VE>(
|
||||
val state: () -> S,
|
||||
val viewEvents: TestObserver<VE>
|
||||
) {
|
||||
|
||||
fun assertEvents(vararg expected: VE) {
|
||||
viewEvents.assertValues(*expected)
|
||||
}
|
||||
|
||||
fun assertState(expected: S) {
|
||||
state() shouldBeEqualTo expected
|
||||
}
|
||||
}
|
||||
|
|
32
vector/src/test/java/im/vector/app/test/InstantRxRule.kt
Normal file
32
vector/src/test/java/im/vector/app/test/InstantRxRule.kt
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.test
|
||||
|
||||
import io.reactivex.android.plugins.RxAndroidPlugins
|
||||
import io.reactivex.plugins.RxJavaPlugins
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.Description
|
||||
import org.junit.runners.model.Statement
|
||||
|
||||
class InstantRxRule : TestRule {
|
||||
override fun apply(base: Statement, description: Description?): Statement {
|
||||
RxJavaPlugins.setInitNewThreadSchedulerHandler { Schedulers.trampoline() }
|
||||
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
|
||||
return base
|
||||
}
|
||||
}
|
|
@ -16,12 +16,23 @@
|
|||
|
||||
package im.vector.app.test.fakes
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||
|
||||
class FakeCryptoService : CryptoService by mockk() {
|
||||
|
||||
var roomKeysExport = ByteArray(size = 1)
|
||||
var cryptoDeviceInfos = mutableMapOf<String, CryptoDeviceInfo>()
|
||||
|
||||
override suspend fun exportRoomKeys(password: String) = roomKeysExport
|
||||
|
||||
override fun getLiveCryptoDeviceInfo() = MutableLiveData(cryptoDeviceInfos.values.toList())
|
||||
|
||||
override fun getLiveCryptoDeviceInfo(userId: String) = getLiveCryptoDeviceInfo(listOf(userId))
|
||||
|
||||
override fun getLiveCryptoDeviceInfo(userIds: List<String>) = MutableLiveData(
|
||||
cryptoDeviceInfos.filterKeys { userIds.contains(it) }.values.toList()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -18,10 +18,11 @@ package im.vector.app.test.fakes
|
|||
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
|
||||
class FakeSession(
|
||||
private val cryptoService: CryptoService = FakeCryptoService()
|
||||
val fakeCryptoService: FakeCryptoService = FakeCryptoService(),
|
||||
val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService()
|
||||
) : Session by mockk(relaxed = true) {
|
||||
override fun cryptoService() = cryptoService
|
||||
override fun cryptoService() = fakeCryptoService
|
||||
override val sharedSecretStorageService = fakeSharedSecretStorageService
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.test.fakes
|
||||
|
||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||
import org.matrix.android.sdk.api.session.securestorage.IntegrityResult
|
||||
import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult
|
||||
import org.matrix.android.sdk.api.session.securestorage.KeySigner
|
||||
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageError
|
||||
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
||||
import org.matrix.android.sdk.api.session.securestorage.SsssKeyCreationInfo
|
||||
import org.matrix.android.sdk.api.session.securestorage.SsssKeySpec
|
||||
|
||||
class FakeSharedSecretStorageService : SharedSecretStorageService {
|
||||
|
||||
var integrityResult: IntegrityResult = IntegrityResult.Error(SharedSecretStorageError.OtherError(IllegalStateException()))
|
||||
var _defaultKey: KeyInfoResult = KeyInfoResult.Error(SharedSecretStorageError.OtherError(IllegalStateException()))
|
||||
|
||||
override suspend fun generateKey(keyId: String, key: SsssKeySpec?, keyName: String, keySigner: KeySigner?): SsssKeyCreationInfo {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun generateKeyWithPassphrase(keyId: String, keyName: String, passphrase: String, keySigner: KeySigner, progressListener: ProgressListener?): SsssKeyCreationInfo {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getKey(keyId: String): KeyInfoResult {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getDefaultKey() = _defaultKey
|
||||
|
||||
override suspend fun setDefaultKey(keyId: String) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun hasKey(keyId: String): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun storeSecret(name: String, secretBase64: String, keys: List<SharedSecretStorageService.KeyRef>) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getAlgorithmsForSecret(name: String): List<KeyInfoResult> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun checkShouldBeAbleToAccessSecrets(secretNames: List<String>, keyId: String?) = integrityResult
|
||||
|
||||
override fun requestSecret(name: String, myOtherDeviceId: String) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2021 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.test.fakes
|
||||
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
||||
class FakeStringProvider {
|
||||
|
||||
val instance = mockk<StringProvider>()
|
||||
|
||||
init {
|
||||
every { instance.getString(any()) } answers {
|
||||
"test-${args[0]}"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue