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:
Valere 2022-07-26 16:12:43 +02:00 committed by GitHub
commit 8e2eb1903d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 178 additions and 26 deletions

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

@ -0,0 +1 @@
When there is no way to verify a device (no 4S nor other device) propose to reset verification keys

View 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()))
}
}

View file

@ -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(

View file

@ -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

View file

@ -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(),
)
)
}
}
}
}

View file

@ -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()

View file

@ -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()

View file

@ -48,4 +48,6 @@ sealed class DevicesViewEvents : VectorViewEvents {
) : DevicesViewEvents()
data class ShowManuallyVerify(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesViewEvents()
object PromptResetSecrets : DevicesViewEvents()
}

View file

@ -239,6 +239,7 @@ class DevicesViewModel @AssistedInject constructor(
uiaContinuation = null
pendingAuth = null
}
DevicesAction.ResetSecurity -> _viewEvents.post(DevicesViewEvents.PromptResetSecrets)
}
}

View file

@ -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)
}
}
}
}

View file

@ -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 wont 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>