Merge pull request #6475 from vector-im/feature/bca/crypto_unable_self_verify
Handle case when device cannot be verified
This commit is contained in:
commit
8e2eb1903d
1
changelog.d/6466.bugfix
Normal file
1
changelog.d/6466.bugfix
Normal file
@ -0,0 +1 @@
|
||||
When there is no way to verify a device (no 4S nor other device) propose to reset verification keys
|
75
vector/src/androidTest/java/im/vector/app/CantVerifyTest.kt
Normal file
75
vector/src/androidTest/java/im/vector/app/CantVerifyTest.kt
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import android.view.View
|
||||
import androidx.test.espresso.Espresso
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.LargeTest
|
||||
import im.vector.app.features.MainActivity
|
||||
import im.vector.app.ui.robot.ElementRobot
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.UUID
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@LargeTest
|
||||
class CantVerifyTest : VerificationTestBase() {
|
||||
|
||||
@get:Rule
|
||||
val activityRule = ActivityScenarioRule(MainActivity::class.java)
|
||||
|
||||
private val elementRobot = ElementRobot()
|
||||
var userName: String = "loginTest_${UUID.randomUUID()}"
|
||||
|
||||
@Test
|
||||
fun checkCantVerifyPopup() {
|
||||
// Let' create an account
|
||||
// This first session will create cross signing keys then logout
|
||||
elementRobot.signUp(userName)
|
||||
Espresso.onView(ViewMatchers.isRoot()).perform(SleepViewAction.sleep(2000))
|
||||
|
||||
elementRobot.signout(false)
|
||||
Espresso.onView(ViewMatchers.isRoot()).perform(SleepViewAction.sleep(2000))
|
||||
|
||||
// Let's login again now
|
||||
// There are no methods to verify (no other devices, nor 4S)
|
||||
// So it should ask to reset all
|
||||
elementRobot.login(userName)
|
||||
|
||||
val activity = EspressoHelper.getCurrentActivity()!!
|
||||
Espresso.onView(ViewMatchers.isRoot())
|
||||
.perform(waitForView(ViewMatchers.withText(R.string.crosssigning_cannot_verify_this_session)))
|
||||
|
||||
// check that the text is correct
|
||||
val popup = activity.findViewById<View>(com.tapadoo.alerter.R.id.llAlertBackground)!!
|
||||
activity.runOnUiThread { popup.performClick() }
|
||||
|
||||
// ensure that it's the 4S setup bottomsheet
|
||||
Espresso.onView(ViewMatchers.withId(R.id.bottomSheetFragmentContainer))
|
||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||
|
||||
Espresso.onView(ViewMatchers.isRoot()).perform(SleepViewAction.sleep(2000))
|
||||
|
||||
Espresso.onView(ViewMatchers.withText(R.string.bottom_sheet_setup_secure_backup_title))
|
||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||
}
|
||||
}
|
@ -240,7 +240,8 @@ class HomeActivity :
|
||||
homeActivityViewModel.observeViewEvents {
|
||||
when (it) {
|
||||
is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it)
|
||||
is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it)
|
||||
is HomeActivityViewEvents.CurrentSessionNotVerified -> handleOnNewSession(it)
|
||||
is HomeActivityViewEvents.CurrentSessionCannotBeVerified -> handleCantVerify(it)
|
||||
HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush()
|
||||
HomeActivityViewEvents.StartRecoverySetupFlow -> handleStartRecoverySetup()
|
||||
is HomeActivityViewEvents.ForceVerification -> {
|
||||
@ -424,7 +425,7 @@ class HomeActivity :
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOnNewSession(event: HomeActivityViewEvents.OnNewSession) {
|
||||
private fun handleOnNewSession(event: HomeActivityViewEvents.CurrentSessionNotVerified) {
|
||||
// We need to ask
|
||||
promptSecurityEvent(
|
||||
event.userItem,
|
||||
@ -439,6 +440,17 @@ class HomeActivity :
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCantVerify(event: HomeActivityViewEvents.CurrentSessionCannotBeVerified) {
|
||||
// We need to ask
|
||||
promptSecurityEvent(
|
||||
event.userItem,
|
||||
R.string.crosssigning_cannot_verify_this_session,
|
||||
R.string.crosssigning_cannot_verify_this_session_desc
|
||||
) {
|
||||
it.navigator.open4SSetup(it, SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePromptToEnablePush() {
|
||||
popupAlertManager.postVectorAlert(
|
||||
DefaultVectorAlert(
|
||||
|
@ -21,7 +21,13 @@ import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
||||
sealed interface HomeActivityViewEvents : VectorViewEvents {
|
||||
data class AskPasswordToInitCrossSigning(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents
|
||||
data class OnNewSession(val userItem: MatrixItem.UserItem?, val waitForIncomingRequest: Boolean = true) : HomeActivityViewEvents
|
||||
data class CurrentSessionNotVerified(
|
||||
val userItem: MatrixItem.UserItem?,
|
||||
val waitForIncomingRequest: Boolean = true,
|
||||
) : HomeActivityViewEvents
|
||||
data class CurrentSessionCannotBeVerified(
|
||||
val userItem: MatrixItem.UserItem?,
|
||||
) : HomeActivityViewEvents
|
||||
data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
|
||||
object PromptToEnableSessionPush : HomeActivityViewEvents
|
||||
object ShowAnalyticsOptIn : HomeActivityViewEvents
|
||||
|
@ -362,14 +362,30 @@ class HomeActivityViewModel @AssistedInject constructor(
|
||||
// If 4S is forced, force verification
|
||||
_viewEvents.post(HomeActivityViewEvents.ForceVerification(true))
|
||||
} else {
|
||||
// New session
|
||||
_viewEvents.post(
|
||||
HomeActivityViewEvents.OnNewSession(
|
||||
session.getUser(session.myUserId)?.toMatrixItem(),
|
||||
// Always send request instead of waiting for an incoming as per recent EW changes
|
||||
false
|
||||
)
|
||||
)
|
||||
// we wan't to check if there is a way to actually verify this session,
|
||||
// that means that there is another session to verify against, or
|
||||
// secure backup is setup
|
||||
val hasTargetDeviceToVerifyAgainst = session
|
||||
.cryptoService()
|
||||
.getUserDevices(session.myUserId)
|
||||
.size >= 2 // this one + another
|
||||
val is4Ssetup = session.sharedSecretStorageService().isRecoverySetup()
|
||||
if (hasTargetDeviceToVerifyAgainst || is4Ssetup) {
|
||||
// New session
|
||||
_viewEvents.post(
|
||||
HomeActivityViewEvents.CurrentSessionNotVerified(
|
||||
session.getUser(session.myUserId)?.toMatrixItem(),
|
||||
// Always send request instead of waiting for an incoming as per recent EW changes
|
||||
false
|
||||
)
|
||||
)
|
||||
} else {
|
||||
_viewEvents.post(
|
||||
HomeActivityViewEvents.CurrentSessionCannotBeVerified(
|
||||
session.getUser(session.myUserId)?.toMatrixItem(),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -106,6 +106,18 @@ class DeviceVerificationInfoBottomSheetController @Inject constructor(
|
||||
.toEpoxyCharSequence()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
genericItem {
|
||||
id("reset${cryptoDeviceInfo.deviceId}")
|
||||
style(ItemStyle.BIG_TEXT)
|
||||
titleIconResourceId(shield)
|
||||
title(host.stringProvider.getString(R.string.crosssigning_cannot_verify_this_session).toEpoxyCharSequence())
|
||||
description(
|
||||
host.stringProvider
|
||||
.getString(R.string.crosssigning_cannot_verify_this_session_desc)
|
||||
.toEpoxyCharSequence()
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!currentSessionIsTrusted) {
|
||||
@ -141,22 +153,42 @@ class DeviceVerificationInfoBottomSheetController @Inject constructor(
|
||||
description("(${cryptoDeviceInfo.deviceId})".toEpoxyCharSequence())
|
||||
}
|
||||
|
||||
if (isMine && !currentSessionIsTrusted && data.canVerifySession) {
|
||||
// Add complete security
|
||||
bottomSheetDividerItem {
|
||||
id("completeSecurityDiv")
|
||||
}
|
||||
bottomSheetVerificationActionItem {
|
||||
id("completeSecurity")
|
||||
title(host.stringProvider.getString(R.string.crosssigning_verify_this_session))
|
||||
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||
iconRes(R.drawable.ic_arrow_right)
|
||||
iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||
listener {
|
||||
host.callback?.onAction(DevicesAction.CompleteSecurity)
|
||||
if (isMine) {
|
||||
if (!currentSessionIsTrusted) {
|
||||
if (data.canVerifySession) {
|
||||
// Add complete security
|
||||
bottomSheetDividerItem {
|
||||
id("completeSecurityDiv")
|
||||
}
|
||||
bottomSheetVerificationActionItem {
|
||||
id("completeSecurity")
|
||||
title(host.stringProvider.getString(R.string.crosssigning_verify_this_session))
|
||||
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||
iconRes(R.drawable.ic_arrow_right)
|
||||
iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||
listener {
|
||||
host.callback?.onAction(DevicesAction.CompleteSecurity)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bottomSheetDividerItem {
|
||||
id("resetSecurityDiv")
|
||||
}
|
||||
bottomSheetVerificationActionItem {
|
||||
id("resetSecurity")
|
||||
title(host.stringProvider.getString(R.string.secure_backup_reset_all))
|
||||
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||
iconRes(R.drawable.ic_arrow_right)
|
||||
iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||
listener {
|
||||
host.callback?.onAction(DevicesAction.ResetSecurity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!isMine) {
|
||||
} else
|
||||
/** if (!isMine) **/
|
||||
{
|
||||
if (currentSessionIsTrusted) {
|
||||
// we can propose to verify it
|
||||
val isVerified = cryptoDeviceInfo.trustLevel?.crossSigningVerified.orFalse()
|
||||
|
@ -30,6 +30,7 @@ sealed class DevicesAction : VectorViewModelAction {
|
||||
data class VerifyMyDevice(val deviceId: String) : DevicesAction()
|
||||
data class VerifyMyDeviceManually(val deviceId: String) : DevicesAction()
|
||||
object CompleteSecurity : DevicesAction()
|
||||
object ResetSecurity : DevicesAction()
|
||||
data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction()
|
||||
|
||||
object SsoAuthDone : DevicesAction()
|
||||
|
@ -48,4 +48,6 @@ sealed class DevicesViewEvents : VectorViewEvents {
|
||||
) : DevicesViewEvents()
|
||||
|
||||
data class ShowManuallyVerify(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesViewEvents()
|
||||
|
||||
object PromptResetSecrets : DevicesViewEvents()
|
||||
}
|
||||
|
@ -239,6 +239,7 @@ class DevicesViewModel @AssistedInject constructor(
|
||||
uiaContinuation = null
|
||||
pendingAuth = null
|
||||
}
|
||||
DevicesAction.ResetSecurity -> _viewEvents.post(DevicesViewEvents.PromptResetSecrets)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,7 @@ import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.DialogBaseEditTextBinding
|
||||
import im.vector.app.databinding.FragmentGenericRecyclerBinding
|
||||
import im.vector.app.features.auth.ReAuthActivity
|
||||
import im.vector.app.features.crypto.recover.SetupMode
|
||||
import im.vector.app.features.crypto.verification.VerificationBottomSheet
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||
@ -89,6 +90,9 @@ class VectorSettingsDevicesFragment @Inject constructor(
|
||||
viewModel.handle(DevicesAction.MarkAsManuallyVerified(it.cryptoDeviceInfo))
|
||||
}
|
||||
}
|
||||
is DevicesViewEvents.PromptResetSecrets -> {
|
||||
navigator.open4SSetup(requireContext(), SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2352,7 +2352,9 @@
|
||||
<item quantity="other">%d active sessions</item>
|
||||
</plurals>
|
||||
|
||||
<string name="crosssigning_verify_this_session">Verify this login</string>
|
||||
<string name="crosssigning_verify_this_session">Verify this device</string>
|
||||
<string name="crosssigning_cannot_verify_this_session">Unable to verify this device</string>
|
||||
<string name="crosssigning_cannot_verify_this_session_desc">You won’t be able to access encrypted message history. Reset your Secure Message Backup and verification keys to start fresh.</string>
|
||||
|
||||
<string name="verification_open_other_to_verify">Use an existing session to verify this one, granting it access to encrypted messages.</string>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user