Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle case when device cannot be verified #6475

Merged
merged 3 commits into from
Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/6466.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
When there is no way to verify a device (no 4S nor other device) propose to reset verification keys
75 changes: 75 additions & 0 deletions vector/src/androidTest/java/im/vector/app/CantVerifyTest.kt
Original file line number Diff line number Diff line change
@@ -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()))
}
}
16 changes: 14 additions & 2 deletions vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,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 -> {
Expand Down Expand Up @@ -422,7 +423,7 @@ class HomeActivity :
}
}

private fun handleOnNewSession(event: HomeActivityViewEvents.OnNewSession) {
private fun handleOnNewSession(event: HomeActivityViewEvents.CurrentSessionNotVerified) {
// We need to ask
promptSecurityEvent(
event.userItem,
Expand All @@ -437,6 +438,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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,14 +364,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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure that all the user devices are known by the SDK at this point?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a force download call few lines before. But maybe i should switch to crypto context to avoid outof sync db?

.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(),
)
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ sealed class DevicesViewEvents : VectorViewEvents {
) : DevicesViewEvents()

data class ShowManuallyVerify(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesViewEvents()

object PromptResetSecrets : DevicesViewEvents()
}
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ class DevicesViewModel @AssistedInject constructor(
uiaContinuation = null
pendingAuth = null
}
DevicesAction.ResetSecurity -> _viewEvents.post(DevicesViewEvents.PromptResetSecrets)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion vector/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2351,7 +2351,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>

Expand Down