From c20517599ed111e76d92d14980894a9820f2d055 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 29 Sep 2020 17:04:45 +0200 Subject: [PATCH 01/10] Add option to reset 4S if lost pass/key --- .../im/vector/app/core/di/FragmentModule.kt | 6 + .../VectorBaseBottomSheetDialogFragment.kt | 19 +++ .../core/ui/views/BottomSheetActionButton.kt | 8 ++ .../crypto/quads/SharedSecureStorageAction.kt | 3 + .../quads/SharedSecureStorageActivity.kt | 38 ++++-- .../quads/SharedSecureStorageViewModel.kt | 84 ++++++++++--- .../quads/SharedSecuredStorageKeyFragment.kt | 6 +- .../SharedSecuredStoragePassphraseFragment.kt | 4 + .../SharedSecuredStorageResetAllFragment.kt | 69 +++++++++++ .../crypto/recover/BootstrapBottomSheet.kt | 25 ++-- .../recover/BootstrapCrossSigningTask.kt | 35 ++++-- .../recover/BootstrapSharedViewModel.kt | 94 +++++++------- .../crypto/recover/BootstrapViewEvents.kt | 2 +- .../app/features/crypto/recover/SetupMode.kt | 50 ++++++++ .../crypto/verification/VerificationAction.kt | 1 + .../verification/VerificationBottomSheet.kt | 24 +++- .../VerificationBottomSheetViewModel.kt | 14 ++- .../features/navigation/DefaultNavigator.kt | 17 ++- .../app/features/navigation/Navigator.kt | 4 + .../devices/DeviceListBottomSheet.kt | 12 +- .../devices/DeviceListBottomSheetViewModel.kt | 21 ++-- .../VectorSettingsSecurityPrivacyFragment.kt | 5 +- .../SignOutBottomSheetDialogFragment.kt | 3 +- .../layout/fragment_ssss_access_from_key.xml | 26 +++- .../fragment_ssss_access_from_passphrase.xml | 21 +++- .../res/layout/fragment_ssss_reset_all.xml | 116 ++++++++++++++++++ vector/src/main/res/values/attrs.xml | 1 + vector/src/main/res/values/strings.xml | 10 ++ 28 files changed, 595 insertions(+), 123 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/crypto/recover/SetupMode.kt create mode 100644 vector/src/main/res/layout/fragment_ssss_reset_all.xml diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 75f61a7b01..86d59b630b 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -27,6 +27,7 @@ import im.vector.app.features.contactsbook.ContactsBookFragment import im.vector.app.features.crypto.keysbackup.settings.KeysBackupSettingsFragment import im.vector.app.features.crypto.quads.SharedSecuredStorageKeyFragment import im.vector.app.features.crypto.quads.SharedSecuredStoragePassphraseFragment +import im.vector.app.features.crypto.quads.SharedSecuredStorageResetAllFragment import im.vector.app.features.crypto.recover.BootstrapAccountPasswordFragment import im.vector.app.features.crypto.recover.BootstrapConclusionFragment import im.vector.app.features.crypto.recover.BootstrapConfirmPassphraseFragment @@ -530,6 +531,11 @@ interface FragmentModule { @FragmentKey(SharedSecuredStorageKeyFragment::class) fun bindSharedSecuredStorageKeyFragment(fragment: SharedSecuredStorageKeyFragment): Fragment + @Binds + @IntoMap + @FragmentKey(SharedSecuredStorageResetAllFragment::class) + fun bindSharedSecuredStorageResetAllFragment(fragment: SharedSecuredStorageResetAllFragment): Fragment + @Binds @IntoMap @FragmentKey(SetIdentityServerFragment::class) diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt index 9ed5c5c455..761bb7227c 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -17,6 +17,7 @@ package im.vector.app.core.platform import android.app.Dialog import android.content.Context +import android.content.DialogInterface import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater @@ -86,6 +87,24 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment() open val showExpanded = false + interface ResultListener { + fun onBottomSheetResult(resultCode: Int, data: Any?) + + companion object { + const val RESULT_OK = 1 + const val RESULT_CANCEL = 0 + } + } + + var resultListener : ResultListener? = null + var bottomSheetResult: Int = ResultListener.RESULT_OK + var bottomSheetResultData: Any? = null + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + resultListener?.onBottomSheetResult(bottomSheetResult, bottomSheetResultData) + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(getLayoutResId(), container, false) unBinder = ButterKnife.bind(this, view) diff --git a/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt b/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt index 0259898ee3..d418822b7f 100644 --- a/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt +++ b/vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt @@ -24,6 +24,7 @@ import android.view.View import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView +import androidx.core.content.ContextCompat import androidx.core.content.withStyledAttributes import androidx.core.view.isGone import androidx.core.view.isInvisible @@ -107,6 +108,12 @@ class BottomSheetActionButton @JvmOverloads constructor( leftIconImageView.imageTintList = value?.let { ColorStateList.valueOf(value) } } + var titleTextColor: Int? = null + set(value) { + field = value + value?.let { actionTextView.setTextColor(it) } + } + init { inflate(context, R.layout.item_verification_action, this) ButterKnife.bind(this) @@ -120,6 +127,7 @@ class BottomSheetActionButton @JvmOverloads constructor( rightIcon = getDrawable(R.styleable.BottomSheetActionButton_rightIcon) tint = getColor(R.styleable.BottomSheetActionButton_tint, ThemeUtils.getColor(context, android.R.attr.textColor)) + titleTextColor = getColor(R.styleable.BottomSheetActionButton_titleTextColor, ContextCompat.getColor(context, R.color.riotx_accent)) } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageAction.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageAction.kt index b47b7dc3a9..30a7ab3cc0 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageAction.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageAction.kt @@ -28,6 +28,8 @@ sealed class SharedSecureStorageAction : VectorViewModelAction { object Cancel : SharedSecureStorageAction() data class SubmitPassphrase(val passphrase: String) : SharedSecureStorageAction() data class SubmitKey(val recoveryKey: String) : SharedSecureStorageAction() + object ForgotResetAll : SharedSecureStorageAction() + object DoResetAll : SharedSecureStorageAction() } sealed class SharedSecureStorageViewEvent : VectorViewEvents { @@ -40,4 +42,5 @@ sealed class SharedSecureStorageViewEvent : VectorViewEvents { object ShowModalLoading : SharedSecureStorageViewEvent() object HideModalLoading : SharedSecureStorageViewEvent() data class UpdateLoadingState(val waitingData: WaitingViewData) : SharedSecureStorageViewEvent() + object ShowResetBottomSheet : SharedSecureStorageViewEvent() } diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt index bca7a63470..0cc28609c9 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt @@ -31,12 +31,14 @@ import im.vector.app.core.di.ScreenComponent import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.platform.SimpleFragmentActivity +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.app.features.crypto.recover.SetupMode import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.activity.* import javax.inject.Inject import kotlin.reflect.KClass -class SharedSecureStorageActivity : SimpleFragmentActivity() { +class SharedSecureStorageActivity : SimpleFragmentActivity(), VectorBaseBottomSheetDialogFragment.ResultListener { @Parcelize data class Args( @@ -69,18 +71,22 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() { private fun renderState(state: SharedSecureStorageViewState) { if (!state.ready) return - val fragment = if (state.hasPassphrase) { - if (state.useKey) SharedSecuredStorageKeyFragment::class else SharedSecuredStoragePassphraseFragment::class - } else SharedSecuredStorageKeyFragment::class + val fragment = + when (state.step) { + SharedSecureStorageViewState.Step.EnterPassphrase -> SharedSecuredStoragePassphraseFragment::class + SharedSecureStorageViewState.Step.EnterKey -> SharedSecuredStorageKeyFragment::class + SharedSecureStorageViewState.Step.ResetAll -> SharedSecuredStorageResetAllFragment::class + } + showFragment(fragment, Bundle()) } private fun observeViewEvents(it: SharedSecureStorageViewEvent?) { when (it) { - is SharedSecureStorageViewEvent.Dismiss -> { + is SharedSecureStorageViewEvent.Dismiss -> { finish() } - is SharedSecureStorageViewEvent.Error -> { + is SharedSecureStorageViewEvent.Error -> { AlertDialog.Builder(this) .setTitle(getString(R.string.dialog_title_error)) .setMessage(it.message) @@ -92,21 +98,24 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() { } .show() } - is SharedSecureStorageViewEvent.ShowModalLoading -> { + is SharedSecureStorageViewEvent.ShowModalLoading -> { showWaitingView() } - is SharedSecureStorageViewEvent.HideModalLoading -> { + is SharedSecureStorageViewEvent.HideModalLoading -> { hideWaitingView() } - is SharedSecureStorageViewEvent.UpdateLoadingState -> { + is SharedSecureStorageViewEvent.UpdateLoadingState -> { updateWaitingView(it.waitingData) } - is SharedSecureStorageViewEvent.FinishSuccess -> { + is SharedSecureStorageViewEvent.FinishSuccess -> { val dataResult = Intent() dataResult.putExtra(EXTRA_DATA_RESULT, it.cypherResult) setResult(Activity.RESULT_OK, dataResult) finish() } + is SharedSecureStorageViewEvent.ShowResetBottomSheet -> { + navigator.open4SSetup(this, SetupMode.HARD_RESET, this) + } } } @@ -124,6 +133,7 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() { companion object { const val EXTRA_DATA_RESULT = "EXTRA_DATA_RESULT" + const val EXTRA_DATA_RESET = "EXTRA_DATA_RESET" const val DEFAULT_RESULT_KEYSTORE_ALIAS = "SharedSecureStorageActivity" fun newIntent(context: Context, @@ -140,4 +150,12 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() { } } } + + override fun onBottomSheetResult(resultCode: Int, data: Any?) { + if (resultCode == VectorBaseBottomSheetDialogFragment.ResultListener.RESULT_OK) { + // the 4S has been reset + setResult(Activity.RESULT_OK, Intent().apply { putExtra(EXTRA_DATA_RESET, true) }) + finish() + } + } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index 5bc87dfce7..951ff4ede6 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -33,6 +33,9 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.resources.StringProvider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.securestorage.IntegrityResult @@ -40,19 +43,26 @@ import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.internal.util.awaitCallback -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import org.matrix.android.sdk.rx.rx import timber.log.Timber import java.io.ByteArrayOutputStream data class SharedSecureStorageViewState( val ready: Boolean = false, val hasPassphrase: Boolean = true, - val useKey: Boolean = false, val passphraseVisible: Boolean = false, - val checkingSSSSAction: Async = Uninitialized -) : MvRxState + val checkingSSSSAction: Async = Uninitialized, + val step: Step = Step.EnterPassphrase, + val activeDeviceCount: Int = 0, + val showResetAllAction: Boolean = false, + val userId: String = "" +) : MvRxState { + enum class Step { + EnterPassphrase, + EnterKey, + ResetAll + } +} class SharedSecureStorageViewModel @AssistedInject constructor( @Assisted initialState: SharedSecureStorageViewState, @@ -67,6 +77,10 @@ class SharedSecureStorageViewModel @AssistedInject constructor( } init { + + setState { + copy(userId = session.myUserId) + } val isValid = session.sharedSecretStorageService.checkShouldBeAbleToAccessSecrets(args.requestedSecrets, args.keyId) is IntegrityResult.Success if (!isValid) { _viewEvents.post( @@ -86,20 +100,30 @@ class SharedSecureStorageViewModel @AssistedInject constructor( if (info.content.passphrase != null) { setState { copy( - ready = true, hasPassphrase = true, - useKey = false + ready = true, + step = SharedSecureStorageViewState.Step.EnterPassphrase ) } } else { setState { copy( + hasPassphrase = false, ready = true, - hasPassphrase = false + step = SharedSecureStorageViewState.Step.EnterKey ) } } } + + session.rx() + .liveUserCryptoDevices(session.myUserId) + .distinctUntilChanged() + .execute { + copy( + activeDeviceCount = it.invoke()?.size ?: 0 + ) + } } override fun handle(action: SharedSecureStorageAction) = withState { @@ -110,27 +134,52 @@ class SharedSecureStorageViewModel @AssistedInject constructor( SharedSecureStorageAction.UseKey -> handleUseKey() is SharedSecureStorageAction.SubmitKey -> handleSubmitKey(action) SharedSecureStorageAction.Back -> handleBack() + SharedSecureStorageAction.ForgotResetAll -> handleResetAll() + SharedSecureStorageAction.DoResetAll -> handleDoResetAll() }.exhaustive } + private fun handleDoResetAll() { + _viewEvents.post(SharedSecureStorageViewEvent.ShowResetBottomSheet) + } + + private fun handleResetAll() { + setState { + copy( + step = SharedSecureStorageViewState.Step.ResetAll + ) + } + } + private fun handleUseKey() { setState { copy( - useKey = true + step = SharedSecureStorageViewState.Step.EnterKey ) } } private fun handleBack() = withState { state -> if (state.checkingSSSSAction is Loading) return@withState // ignore - if (state.hasPassphrase && state.useKey) { - setState { - copy( - useKey = false - ) + when (state.step) { + SharedSecureStorageViewState.Step.EnterKey -> { + setState { + copy( + step = SharedSecureStorageViewState.Step.EnterPassphrase + ) + } + } + SharedSecureStorageViewState.Step.ResetAll -> { + setState { + copy( + step = if (state.hasPassphrase) SharedSecureStorageViewState.Step.EnterPassphrase + else SharedSecureStorageViewState.Step.EnterKey + ) + } + } + else -> { + _viewEvents.post(SharedSecureStorageViewEvent.Dismiss) } - } else { - _viewEvents.post(SharedSecureStorageViewEvent.Dismiss) } } @@ -158,6 +207,7 @@ class SharedSecureStorageViewModel @AssistedInject constructor( val keySpec = RawBytesKeySpec.fromRecoveryKey(recoveryKey) ?: return@launch Unit.also { _viewEvents.post(SharedSecureStorageViewEvent.KeyInlineError(stringProvider.getString(R.string.bootstrap_invalid_recovery_key))) _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) + setState { copy(checkingSSSSAction = Fail(IllegalArgumentException(stringProvider.getString(R.string.bootstrap_invalid_recovery_key)))) } } withContext(Dispatchers.IO) { diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt index ee47a4d8e9..366c979155 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageKeyFragment.kt @@ -27,9 +27,9 @@ import com.jakewharton.rxbinding3.widget.textChanges import im.vector.app.R import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.startImportTextFromFileIntent -import org.matrix.android.sdk.api.extensions.tryOrNull import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.fragment_ssss_access_from_key.* +import org.matrix.android.sdk.api.extensions.tryOrNull import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -63,6 +63,10 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment ssss_key_use_file.debouncedClicks { startImportTextFromFileIntent(this, IMPORT_FILE_REQ) } + ssss_key_reset.clickableView.debouncedClicks { + sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll) + } + sharedViewModel.observeViewEvents { when (it) { is SharedSecureStorageViewEvent.KeyInlineError -> { diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt index 09e67948d0..97047fbc65 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt @@ -74,6 +74,10 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor( } .disposeOnDestroyView() + ssss_passphrase_reset.clickableView.debouncedClicks { + sharedViewModel.handle(SharedSecureStorageAction.ForgotResetAll) + } + sharedViewModel.observeViewEvents { when (it) { is SharedSecureStorageViewEvent.InlineError -> { diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt new file mode 100644 index 0000000000..314abda38c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020 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 android.os.Bundle +import android.view.View +import androidx.core.view.isVisible +import com.airbnb.mvrx.activityViewModel +import com.airbnb.mvrx.withState +import im.vector.app.R +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet +import kotlinx.android.synthetic.main.fragment_ssss_reset_all.* +import javax.inject.Inject + +class SharedSecuredStorageResetAllFragment @Inject constructor( + private val stringProvider: StringProvider +) : VectorBaseFragment() { + + override fun getLayoutResId() = R.layout.fragment_ssss_reset_all + + val sharedViewModel: SharedSecureStorageViewModel by activityViewModel() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + ssss_reset_button_reset.debouncedClicks { + sharedViewModel.handle(SharedSecureStorageAction.DoResetAll) + } + + ssss_reset_button_cancel.debouncedClicks { + sharedViewModel.handle(SharedSecureStorageAction.Back) + } + + ssss_reset_other_devices.debouncedClicks { + withState(sharedViewModel) { + DeviceListBottomSheet.newInstance(it.userId, false).show(childFragmentManager, "DEV_LIST") + } + } + + sharedViewModel.subscribe(this) { + if (it.activeDeviceCount == 0) { + ssss_reset_other_devices.isVisible = false + } else { + ssss_reset_other_devices.isVisible = true + ssss_reset_other_devices.text = stringProvider.getQuantityString( + R.plurals.secure_backup_reset_devices_you_can_verify, + it.activeDeviceCount, + it.activeDeviceCount + ) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt index 1b9beabe9c..5b5b4e1931 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt @@ -45,8 +45,10 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { @Parcelize data class Args( - val initCrossSigningOnly: Boolean, - val forceReset4S: Boolean +// val initCrossSigningOnly: Boolean, + val setUpMode: SetupMode = SetupMode.NORMAL +// val forceReset4S: Boolean, +// val resetAllUnknownSecrets: Boolean ) : Parcelable override val showExpanded = true @@ -66,7 +68,10 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { super.onViewCreated(view, savedInstanceState) viewModel.observeViewEvents { event -> when (event) { - is BootstrapViewEvents.Dismiss -> dismiss() + is BootstrapViewEvents.Dismiss -> { + bottomSheetResult = if (event.success) ResultListener.RESULT_OK else ResultListener.RESULT_CANCEL + dismiss() + } is BootstrapViewEvents.ModalError -> { AlertDialog.Builder(requireActivity()) .setTitle(R.string.dialog_title_error) @@ -90,6 +95,7 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { .setMessage(R.string.bootstrap_cancel_text) .setPositiveButton(R.string._continue, null) .setNegativeButton(R.string.skip) { _, _ -> + bottomSheetResult = ResultListener.RESULT_CANCEL dismiss() } .show() @@ -181,16 +187,15 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { const val EXTRA_ARGS = "EXTRA_ARGS" - fun show(fragmentManager: FragmentManager, initCrossSigningOnly: Boolean, forceReset4S: Boolean) { - BootstrapBottomSheet().apply { + fun show(fragmentManager: FragmentManager, mode: SetupMode): BootstrapBottomSheet { + return BootstrapBottomSheet().apply { isCancelable = false arguments = Bundle().apply { - this.putParcelable(EXTRA_ARGS, Args( - initCrossSigningOnly, - forceReset4S - )) + this.putParcelable(EXTRA_ARGS, Args(setUpMode = mode)) } - }.show(fragmentManager, "BootstrapBottomSheet") + }.also { + it.show(fragmentManager, "BootstrapBottomSheet") + } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt index 5da788583e..05e9a836f6 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -69,10 +69,12 @@ interface BootstrapProgressListener { data class Params( val userPasswordAuth: UserPasswordAuth? = null, - val initOnlyCrossSigning: Boolean = false, +// val initOnlyCrossSigning: Boolean = false, val progressListener: BootstrapProgressListener? = null, val passphrase: String?, - val keySpec: SsssKeySpec? = null + val keySpec: SsssKeySpec? = null, +// val resetAllIfNeeded: Boolean = false, + val setupMode: SetupMode ) // TODO Rename to CreateServerRecovery @@ -84,9 +86,13 @@ class BootstrapCrossSigningTask @Inject constructor( override suspend fun execute(params: Params): BootstrapResult { val crossSigningService = session.cryptoService().crossSigningService() - Timber.d("## BootstrapCrossSigningTask: initXSOnly:${params.initOnlyCrossSigning} Starting...") + Timber.d("## BootstrapCrossSigningTask: mode:${params.setupMode} Starting...") // Ensure cross-signing is initialized. Due to migration it is maybe not always correctly initialized - if (!crossSigningService.isCrossSigningInitialized()) { + + val shouldSetCrossSigning = !crossSigningService.isCrossSigningInitialized() + || (params.setupMode == SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET && !crossSigningService.allPrivateKeysKnown()) + || (params.setupMode == SetupMode.HARD_RESET) + if (shouldSetCrossSigning) { Timber.d("## BootstrapCrossSigningTask: Cross signing not enabled, so initialize") params.progressListener?.onProgress( WaitingViewData( @@ -99,7 +105,7 @@ class BootstrapCrossSigningTask @Inject constructor( awaitCallback { crossSigningService.initializeCrossSigning(params.userPasswordAuth, it) } - if (params.initOnlyCrossSigning) { + if (params.setupMode == SetupMode.CROSS_SIGNING_ONLY) { return BootstrapResult.SuccessCrossSigningOnly } } catch (failure: Throwable) { @@ -107,7 +113,7 @@ class BootstrapCrossSigningTask @Inject constructor( } } else { Timber.d("## BootstrapCrossSigningTask: Cross signing already setup, go to 4S setup") - if (params.initOnlyCrossSigning) { + if (params.setupMode == SetupMode.CROSS_SIGNING_ONLY) { // not sure how this can happen?? return handleInitializeXSigningError(IllegalArgumentException("Cross signing already setup")) } @@ -236,7 +242,13 @@ class BootstrapCrossSigningTask @Inject constructor( val serverVersion = awaitCallback { session.cryptoService().keysBackupService().getCurrentVersion(it) } - if (serverVersion == null) { + + val knownMegolmSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() + val isMegolmBackupSecretKnown = knownMegolmSecret != null && knownMegolmSecret.version == serverVersion?.version + val shouldCreateKeyBackup = serverVersion == null + || (params.setupMode == SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET && !isMegolmBackupSecretKnown) + || (params.setupMode == SetupMode.HARD_RESET) + if (shouldCreateKeyBackup) { Timber.d("## BootstrapCrossSigningTask: Creating 4S - Create megolm backup") val creationInfo = awaitCallback { session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it) @@ -260,16 +272,15 @@ class BootstrapCrossSigningTask @Inject constructor( } else { Timber.d("## BootstrapCrossSigningTask: Creating 4S - Existing megolm backup found") // ensure we store existing backup secret if we have it! - val knownSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() - if (knownSecret != null && knownSecret.version == serverVersion.version) { + if (isMegolmBackupSecretKnown) { // check it matches val isValid = awaitCallback { - session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownSecret.recoveryKey, it) + session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownMegolmSecret!!.recoveryKey, it) } if (isValid) { Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known") awaitCallback { - extractCurveKeyFromRecoveryKey(knownSecret.recoveryKey)?.toBase64NoPadding()?.let { secret -> + extractCurveKeyFromRecoveryKey(knownMegolmSecret!!.recoveryKey)?.toBase64NoPadding()?.let { secret -> ssssService.storeSecret( KEYBACKUP_SECRET_SSSS_NAME, secret, @@ -286,7 +297,7 @@ class BootstrapCrossSigningTask @Inject constructor( Timber.e("## BootstrapCrossSigningTask: Failed to init keybackup") } - Timber.d("## BootstrapCrossSigningTask: initXSOnly:${params.initOnlyCrossSigning} Finished") + Timber.d("## BootstrapCrossSigningTask: mode:${params.setupMode} Finished") return BootstrapResult.Success(keyInfo) } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt index 32b4771286..7f3f7c0667 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt @@ -34,6 +34,8 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.resources.StringProvider import im.vector.app.features.login.ReAuthHelper +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec @@ -41,8 +43,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionR import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth import org.matrix.android.sdk.internal.util.awaitCallback -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import java.io.OutputStream class BootstrapSharedViewModel @AssistedInject constructor( @@ -69,46 +69,52 @@ class BootstrapSharedViewModel @AssistedInject constructor( init { - if (args.forceReset4S) { - setState { - copy(step = BootstrapStep.FirstForm(keyBackUpExist = false, reset = true)) - } - } else if (args.initCrossSigningOnly) { - // Go straight to account password - setState { - copy(step = BootstrapStep.AccountPassword(false)) - } - } else { - // need to check if user have an existing keybackup - setState { - copy(step = BootstrapStep.CheckingMigration) - } - - // We need to check if there is an existing backup - viewModelScope.launch(Dispatchers.IO) { - val version = awaitCallback { - session.cryptoService().keysBackupService().getCurrentVersion(it) + when (args.setUpMode) { + SetupMode.PASSPHRASE_RESET, + SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET, + SetupMode.HARD_RESET -> { + setState { + copy(step = BootstrapStep.FirstForm(keyBackUpExist = false, reset = true)) } - if (version == null) { - // we just resume plain bootstrap - doesKeyBackupExist = false - setState { - copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist)) + } + SetupMode.CROSS_SIGNING_ONLY -> { + // Go straight to account password + setState { + copy(step = BootstrapStep.AccountPassword(false)) + } + } + SetupMode.NORMAL -> { + // need to check if user have an existing keybackup + setState { + copy(step = BootstrapStep.CheckingMigration) + } + + // We need to check if there is an existing backup + viewModelScope.launch(Dispatchers.IO) { + val version = awaitCallback { + session.cryptoService().keysBackupService().getCurrentVersion(it) } - } else { - // we need to get existing backup passphrase/key and convert to SSSS - val keyVersion = awaitCallback { - session.cryptoService().keysBackupService().getVersion(version.version ?: "", it) - } - if (keyVersion == null) { - // strange case... just finish? - _viewEvents.post(BootstrapViewEvents.Dismiss) - } else { - doesKeyBackupExist = true - isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null + if (version == null) { + // we just resume plain bootstrap + doesKeyBackupExist = false setState { copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist)) } + } else { + // we need to get existing backup passphrase/key and convert to SSSS + val keyVersion = awaitCallback { + session.cryptoService().keysBackupService().getVersion(version.version ?: "", it) + } + if (keyVersion == null) { + // strange case... just finish? + _viewEvents.post(BootstrapViewEvents.Dismiss(false)) + } else { + doesKeyBackupExist = true + isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null + setState { + copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist)) + } + } } } } @@ -234,7 +240,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } BootstrapActions.Completed -> { - _viewEvents.post(BootstrapViewEvents.Dismiss) + _viewEvents.post(BootstrapViewEvents.Dismiss(true)) } BootstrapActions.GoToCompleted -> { setState { @@ -395,16 +401,16 @@ class BootstrapSharedViewModel @AssistedInject constructor( bootstrapTask.invoke(this, Params( userPasswordAuth = userPasswordAuth, - initOnlyCrossSigning = args.initCrossSigningOnly, progressListener = progressListener, passphrase = state.passphrase, - keySpec = state.migrationRecoveryKey?.let { extractCurveKeyFromRecoveryKey(it)?.let { RawBytesKeySpec(it) } } + keySpec = state.migrationRecoveryKey?.let { extractCurveKeyFromRecoveryKey(it)?.let { RawBytesKeySpec(it) } }, + setupMode = args.setUpMode ) ) { bootstrapResult -> when (bootstrapResult) { - is BootstrapResult.SuccessCrossSigningOnly -> { + is BootstrapResult.SuccessCrossSigningOnly -> { // TPD - _viewEvents.post(BootstrapViewEvents.Dismiss) + _viewEvents.post(BootstrapViewEvents.Dismiss(true)) } is BootstrapResult.Success -> { setState { @@ -428,7 +434,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( } is BootstrapResult.UnsupportedAuthFlow -> { _viewEvents.post(BootstrapViewEvents.ModalError(stringProvider.getString(R.string.auth_flow_not_supported))) - _viewEvents.post(BootstrapViewEvents.Dismiss) + _viewEvents.post(BootstrapViewEvents.Dismiss(false)) } is BootstrapResult.InvalidPasswordError -> { // it's a bad password @@ -558,7 +564,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( override fun create(viewModelContext: ViewModelContext, state: BootstrapViewState): BootstrapSharedViewModel? { val fragment: BootstrapBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() val args: BootstrapBottomSheet.Args = fragment.arguments?.getParcelable(BootstrapBottomSheet.EXTRA_ARGS) - ?: BootstrapBottomSheet.Args(initCrossSigningOnly = true, forceReset4S = false) + ?: BootstrapBottomSheet.Args(SetupMode.CROSS_SIGNING_ONLY) return fragment.bootstrapViewModelFactory.create(state, args) } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapViewEvents.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapViewEvents.kt index 58bc64a9ad..be523a0ce3 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapViewEvents.kt @@ -19,7 +19,7 @@ package im.vector.app.features.crypto.recover import im.vector.app.core.platform.VectorViewEvents sealed class BootstrapViewEvents : VectorViewEvents { - object Dismiss : BootstrapViewEvents() + data class Dismiss(val success: Boolean) : BootstrapViewEvents() data class ModalError(val error: String) : BootstrapViewEvents() object RecoveryKeySaved: BootstrapViewEvents() data class SkipBootstrap(val genKeyOption: Boolean = true): BootstrapViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/SetupMode.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/SetupMode.kt new file mode 100644 index 0000000000..0879490e79 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/SetupMode.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 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.recover + +enum class SetupMode { + + /** + * Only setup cross signing, no 4S or megolm backup + */ + CROSS_SIGNING_ONLY, + + /** + * Normal setup mode. + */ + NORMAL, + + /** + * Only reset the 4S passphrase/key, but do not touch + * to existing cross-signing or megolm backup + * It take the local known secrets and put them in 4S + */ + PASSPHRASE_RESET, + + /** + * Resets the passphrase/key, and all missing secrets + * are re-created. Meaning that if cross signing is setup and the secrets + * keys are not known, cross signing will be reset (if secret is known we just keep same cross signing) + * Same apply to megolm + */ + PASSPHRASE_AND_NEEDED_SECRETS_RESET, + + /** + * Resets the passphrase/key, cross signing and megolm backup + */ + HARD_RESET +} diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt index 1c6ea413cb..d0fd825ebb 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt @@ -31,4 +31,5 @@ sealed class VerificationAction : VectorViewModelAction { object SkipVerification : VerificationAction() object VerifyFromPassphrase : VerificationAction() data class GotResultFromSsss(val cypherData: String, val alias: String) : VerificationAction() + object SecuredStorageHasBeenReseted : VerificationAction() } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt index f979539f2e..c170809b51 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt @@ -48,6 +48,7 @@ import im.vector.app.features.crypto.verification.qrconfirmation.VerificationQrS import im.vector.app.features.crypto.verification.request.VerificationRequestFragment import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.settings.VectorSettingsActivity +import kotlinx.android.parcel.Parcelize import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME @@ -55,7 +56,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_S import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.verification.CancelCode import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState -import kotlinx.android.parcel.Parcelize import timber.log.Timber import javax.inject.Inject import kotlin.reflect.KClass @@ -76,6 +76,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { @Inject lateinit var verificationViewModelFactory: VerificationBottomSheetViewModel.Factory + @Inject lateinit var avatarRenderer: AvatarRenderer @@ -146,8 +147,13 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (resultCode == Activity.RESULT_OK && requestCode == SECRET_REQUEST_CODE) { - data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)?.let { - viewModel.handle(VerificationAction.GotResultFromSsss(it, SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS)) + val result = data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT) + val reseted = data?.getBooleanExtra(SharedSecureStorageActivity.EXTRA_DATA_RESET, false) ?: false + if (result != null) { + viewModel.handle(VerificationAction.GotResultFromSsss(result, SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS)) + } else if (reseted) { + // all have been reset, so we are verified? + viewModel.handle(VerificationAction.SecuredStorageHasBeenReseted) } } super.onActivityResult(requestCode, resultCode, data) @@ -182,6 +188,17 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } } + if (state.quadsHasBeenReseted) { + showFragment(VerificationConclusionFragment::class, Bundle().apply { + putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args( + isSuccessFull = true, + isMe = true, + cancelReason = null + )) + }) + return@withState + } + if (state.userThinkItsNotHim) { otherUserNameText.text = getString(R.string.dialog_title_warning) showFragment(VerificationNotMeFragment::class, Bundle()) @@ -356,6 +373,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } } } + fun forSelfVerification(session: Session, outgoingRequest: String): VerificationBottomSheet { return VerificationBottomSheet().apply { arguments = Bundle().apply { diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt index a4ce9bd38d..1638450725 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -31,6 +31,7 @@ import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import kotlinx.coroutines.Dispatchers import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME @@ -74,7 +75,8 @@ data class VerificationBottomSheetViewState( val currentDeviceCanCrossSign: Boolean = false, val userWantsToCancel: Boolean = false, val userThinkItsNotHim: Boolean = false, - val quadSContainsSecrets: Boolean = true + val quadSContainsSecrets: Boolean = true, + val quadsHasBeenReseted: Boolean = false ) : MvRxState class VerificationBottomSheetViewModel @AssistedInject constructor( @@ -349,6 +351,14 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( is VerificationAction.GotResultFromSsss -> { handleSecretBackFromSSSS(action) } + VerificationAction.SecuredStorageHasBeenReseted -> { + if (session.cryptoService().crossSigningService().allPrivateKeysKnown()) { + setState { + copy(quadsHasBeenReseted = true) + } + } + Unit + } }.exhaustive } @@ -393,7 +403,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( } private fun tentativeRestoreBackup(res: Map?) { - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { try { val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also { Timber.v("## Keybackup secret not restored from SSSS") diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 5de8796c06..66dd9544b4 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -30,6 +30,7 @@ import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.error.fatalError import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.core.utils.toast import im.vector.app.features.call.conference.JitsiCallViewModel import im.vector.app.features.call.conference.VectorJitsiActivity @@ -37,6 +38,7 @@ import im.vector.app.features.createdirect.CreateDirectRoomActivity import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity import im.vector.app.features.crypto.recover.BootstrapBottomSheet +import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.debug.DebugMenuActivity @@ -153,7 +155,10 @@ class DefaultNavigator @Inject constructor( override fun upgradeSessionSecurity(context: Context, initCrossSigningOnly: Boolean) { if (context is VectorBaseActivity) { - BootstrapBottomSheet.show(context.supportFragmentManager, initCrossSigningOnly, false) + BootstrapBottomSheet.show( + context.supportFragmentManager, + if (initCrossSigningOnly) SetupMode.CROSS_SIGNING_ONLY else SetupMode.NORMAL + ) } } @@ -226,13 +231,21 @@ class DefaultNavigator @Inject constructor( // if cross signing is enabled we should propose full 4S sessionHolder.getSafeActiveSession()?.let { session -> if (session.cryptoService().crossSigningService().canCrossSign() && context is VectorBaseActivity) { - BootstrapBottomSheet.show(context.supportFragmentManager, initCrossSigningOnly = false, forceReset4S = false) + BootstrapBottomSheet.show(context.supportFragmentManager, SetupMode.NORMAL) } else { context.startActivity(KeysBackupSetupActivity.intent(context, showManualExport)) } } } + override fun open4SSetup(context: Context, setupMode: SetupMode, listener: VectorBaseBottomSheetDialogFragment.ResultListener?) { + if (context is VectorBaseActivity) { + BootstrapBottomSheet.show(context.supportFragmentManager, setupMode).also { + it.resultListener = listener + } + } + } + override fun openKeysBackupManager(context: Context) { context.startActivity(KeysBackupManageActivity.intent(context)) } diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index ed710a124f..3f255f72ef 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -21,6 +21,8 @@ import android.content.Context import android.view.View import androidx.core.util.Pair import androidx.fragment.app.Fragment +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.home.room.detail.widget.WidgetRequestCodes import im.vector.app.features.media.AttachmentData import im.vector.app.features.pin.PinActivity @@ -71,6 +73,8 @@ interface Navigator { fun openKeysBackupSetup(context: Context, showManualExport: Boolean) + fun open4SSetup(context: Context, setupMode: SetupMode, listener: VectorBaseBottomSheetDialogFragment.ResultListener?) + fun openKeysBackupManager(context: Context) fun openGroupDetail(groupId: String, context: Context, buildTask: Boolean = false) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt index a8c6842c08..9ba1c59983 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheet.kt @@ -18,6 +18,7 @@ package im.vector.app.features.roommemberprofile.devices import android.content.DialogInterface import android.os.Bundle +import android.os.Parcelable import android.view.KeyEvent import androidx.fragment.app.Fragment import com.airbnb.mvrx.MvRx @@ -29,6 +30,7 @@ import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.features.crypto.verification.VerificationBottomSheet +import kotlinx.android.parcel.Parcelize import javax.inject.Inject import kotlin.reflect.KClass @@ -104,10 +106,16 @@ class DeviceListBottomSheet : VectorBaseBottomSheetDialogFragment() { } } + @Parcelize + data class Args( + val userId: String, + val allowDeviceAction: Boolean + ) : Parcelable + companion object { - fun newInstance(userId: String): DeviceListBottomSheet { + fun newInstance(userId: String, allowDeviceAction: Boolean = true): DeviceListBottomSheet { val args = Bundle() - args.putString(MvRx.KEY_ARG, userId) + args.putParcelable(MvRx.KEY_ARG, Args(userId, allowDeviceAction)) return DeviceListBottomSheet().apply { arguments = args } } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt index db97399f1b..28af45797e 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/devices/DeviceListBottomSheetViewModel.kt @@ -22,6 +22,7 @@ import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext +import com.airbnb.mvrx.args import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.app.core.di.HasScreenInjector @@ -44,24 +45,24 @@ data class DeviceListViewState( ) : MvRxState class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted private val initialState: DeviceListViewState, - @Assisted private val userId: String, + @Assisted private val args: DeviceListBottomSheet.Args, private val session: Session) : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { - fun create(initialState: DeviceListViewState, userId: String): DeviceListBottomSheetViewModel + fun create(initialState: DeviceListViewState, args: DeviceListBottomSheet.Args): DeviceListBottomSheetViewModel } init { - session.rx().liveUserCryptoDevices(userId) + session.rx().liveUserCryptoDevices(args.userId) .execute { copy(cryptoDevices = it).also { refreshSelectedId() } } - session.rx().liveCrossSigningInfo(userId) + session.rx().liveCrossSigningInfo(args.userId) .execute { copy(memberCrossSigningKey = it.invoke()?.getOrNull()) } @@ -88,6 +89,7 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva } private fun selectDevice(action: DeviceListAction.SelectDevice) { + if (!args.allowDeviceAction) return setState { copy(selectedDevice = action.device) } @@ -100,8 +102,9 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva } private fun manuallyVerify(action: DeviceListAction.ManuallyVerify) { - session.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, userId, action.deviceId, null)?.let { txID -> - _viewEvents.post(DeviceListBottomSheetViewEvents.Verify(userId, txID)) + if (!args.allowDeviceAction) return + session.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, args.userId, action.deviceId, null)?.let { txID -> + _viewEvents.post(DeviceListBottomSheetViewEvents.Verify(args.userId, txID)) } } @@ -109,12 +112,12 @@ class DeviceListBottomSheetViewModel @AssistedInject constructor(@Assisted priva @JvmStatic override fun create(viewModelContext: ViewModelContext, state: DeviceListViewState): DeviceListBottomSheetViewModel? { val fragment: DeviceListBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() - val userId = viewModelContext.args() - return fragment.viewModelFactory.create(state, userId) + val args = viewModelContext.args() + return fragment.viewModelFactory.create(state, args) } override fun initialState(viewModelContext: ViewModelContext): DeviceListViewState? { - val userId = viewModelContext.args() + val userId = viewModelContext.args().userId val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession() return session.getUser(userId)?.toMatrixItem()?.let { DeviceListViewState( diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 44cef8f974..b0ec7426a7 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -53,6 +53,7 @@ import im.vector.app.features.crypto.keys.KeysExporter import im.vector.app.features.crypto.keys.KeysImporter import im.vector.app.features.crypto.keysbackup.settings.KeysBackupManageActivity import im.vector.app.features.crypto.recover.BootstrapBottomSheet +import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.navigation.Navigator import im.vector.app.features.pin.PinActivity import im.vector.app.features.pin.PinCodeStore @@ -193,7 +194,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( secureBackupCategory.isVisible = true secureBackupPreference.title = getString(R.string.settings_secure_backup_setup) secureBackupPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - BootstrapBottomSheet.show(parentFragmentManager, initCrossSigningOnly = false, forceReset4S = false) + BootstrapBottomSheet.show(parentFragmentManager, SetupMode.NORMAL) true } } else { @@ -212,7 +213,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( secureBackupCategory.isVisible = true secureBackupPreference.title = getString(R.string.settings_secure_backup_reset) secureBackupPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - BootstrapBottomSheet.show(parentFragmentManager, initCrossSigningOnly = false, forceReset4S = true) + BootstrapBottomSheet.show(parentFragmentManager, SetupMode.PASSPHRASE_RESET) true } } else if (!info.megolmSecretKnown) { diff --git a/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetDialogFragment.kt index c17c1a1cf8..ac4d495a8c 100644 --- a/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/features/workers/signout/SignOutBottomSheetDialogFragment.kt @@ -44,6 +44,7 @@ import im.vector.app.core.extensions.queryExportKeys import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity import im.vector.app.features.crypto.recover.BootstrapBottomSheet +import im.vector.app.features.crypto.recover.SetupMode import timber.log.Timber import javax.inject.Inject @@ -121,7 +122,7 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(), super.onActivityCreated(savedInstanceState) setupRecoveryButton.action = { - BootstrapBottomSheet.show(parentFragmentManager, initCrossSigningOnly = false, forceReset4S = false) + BootstrapBottomSheet.show(parentFragmentManager, SetupMode.NORMAL) } exitAnywayButton.action = { diff --git a/vector/src/main/res/layout/fragment_ssss_access_from_key.xml b/vector/src/main/res/layout/fragment_ssss_access_from_key.xml index b6bdb2586a..bbe9282cc1 100644 --- a/vector/src/main/res/layout/fragment_ssss_access_from_key.xml +++ b/vector/src/main/res/layout/fragment_ssss_access_from_key.xml @@ -15,11 +15,11 @@ android:layout_width="24dp" android:layout_height="24dp" android:layout_marginStart="16dp" + android:src="@drawable/ic_security_key_24dp" android:tint="?riotx_text_primary" app:layout_constraintBottom_toBottomOf="@+id/ssss_restore_with_key" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="@+id/ssss_restore_with_key" - android:src="@drawable/ic_security_key_24dp" /> + app:layout_constraintTop_toTopOf="@+id/ssss_restore_with_key" /> + + + + app:layout_constraintTop_toBottomOf="@id/ssss_key_flow" + app:leftIcon="@drawable/ic_alert_triangle" + app:tint="@color/vector_error_color" + app:titleTextColor="?riotx_text_secondary" /> + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_ssss_access_from_passphrase.xml b/vector/src/main/res/layout/fragment_ssss_access_from_passphrase.xml index e5482f0ec7..09bd823257 100644 --- a/vector/src/main/res/layout/fragment_ssss_access_from_passphrase.xml +++ b/vector/src/main/res/layout/fragment_ssss_access_from_passphrase.xml @@ -109,16 +109,33 @@ tools:ignore="MissingConstraints" /> + + + app:layout_constraintTop_toBottomOf="@id/ssss_passphrase_flow" + app:leftIcon="@drawable/ic_alert_triangle" + app:tint="@color/vector_error_color" + app:titleTextColor="?riotx_text_secondary" /> + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_ssss_reset_all.xml b/vector/src/main/res/layout/fragment_ssss_reset_all.xml new file mode 100644 index 0000000000..6caef9fe66 --- /dev/null +++ b/vector/src/main/res/layout/fragment_ssss_reset_all.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/attrs.xml b/vector/src/main/res/values/attrs.xml index 8c71fb26b2..bf393b779a 100644 --- a/vector/src/main/res/values/attrs.xml +++ b/vector/src/main/res/values/attrs.xml @@ -52,6 +52,7 @@ + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index a5d9bae0ae..3233fb2b22 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -78,6 +78,7 @@ Play Pause Dismiss + Reset @@ -2434,6 +2435,15 @@ Select your Recovery Key, or input it manually by typing it or pasting from your clipboard Backup could not be decrypted with this Recovery Key: please verify that you entered the correct Recovery Key. Failed to access secure storage + Forgot or lost all recovery options? Reset everything + Reset everything + Only do this if you have no other device you can verify this device with. + If you reset everything + You will restart with no history, no messages, trusted devices or trusted users + + Show the device you can verify with now + Show %d devices you can verify with now + Unencrypted Encrypted by an unverified device From 9f26d015ba0092912b12d0eb4d761b884db7c72b Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 29 Sep 2020 17:07:56 +0200 Subject: [PATCH 02/10] Update change log Fixes #2052 --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 56f22bd4a9..d7fd9f6598 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ Features ✨: Improvements 🙌: - PIN code: request PIN code if phone has been locked - Small optimisation of scrolling experience in timeline (#2114) + - Allow user to reset cross signing if he has no way to recover (#2052) Bugfix 🐛: - Improve support for image selection with intent changes (#1376) From 1e3bdd6a2eb264f03f9eeddecc22a883dae45b65 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 29 Sep 2020 18:00:15 +0200 Subject: [PATCH 03/10] Fix test --- .../java/im/vector/app/VerifySessionPassphraseTest.kt | 4 +++- vector/src/main/res/layout/fragment_ssss_reset_all.xml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt b/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt index 5405c086eb..f8c2a89ea8 100644 --- a/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt +++ b/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt @@ -38,6 +38,7 @@ import im.vector.app.features.MainActivity import im.vector.app.features.crypto.quads.SharedSecureStorageActivity import im.vector.app.features.crypto.recover.BootstrapCrossSigningTask import im.vector.app.features.crypto.recover.Params +import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.home.HomeActivity import kotlinx.coroutines.runBlocking import org.junit.Before @@ -77,7 +78,8 @@ class VerifySessionPassphraseTest : VerificationTestBase() { runBlocking { task.execute(Params( userPasswordAuth = UserPasswordAuth(password = password), - passphrase = passphrase + passphrase = passphrase, + setupMode = SetupMode.NORMAL )) } } diff --git a/vector/src/main/res/layout/fragment_ssss_reset_all.xml b/vector/src/main/res/layout/fragment_ssss_reset_all.xml index 6caef9fe66..a1851f3728 100644 --- a/vector/src/main/res/layout/fragment_ssss_reset_all.xml +++ b/vector/src/main/res/layout/fragment_ssss_reset_all.xml @@ -48,7 +48,7 @@ android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:layout_marginEnd="16dp" - android:drawableLeft="@drawable/ic_smartphone" + android:drawableStart="@drawable/ic_smartphone" android:drawablePadding="8dp" app:layout_constraintTop_toBottomOf="@id/ssss_reset_all_description" tools:text="Show 2 devices you can verify with now" /> From 2908a5d3455a958fe93a159c6110d26d43852d8d Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 30 Sep 2020 10:13:59 +0200 Subject: [PATCH 04/10] Added first ui bootstrap test --- vector/build.gradle | 1 + .../im/vector/app/SecurityBootstrapTest.kt | 181 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt diff --git a/vector/build.gradle b/vector/build.gradle index 222c0c5cb3..87c8696b02 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -453,6 +453,7 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version" androidTestImplementation "androidx.test.espresso:espresso-contrib:$espresso_version" + androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version" androidTestImplementation "org.amshove.kluent:kluent-android:$kluent_version" androidTestImplementation "androidx.arch.core:core-testing:$arch_version" // Plant Timber tree for test diff --git a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt new file mode 100644 index 0000000000..a71af59edf --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2020 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.app.Activity +import android.app.Instrumentation.ActivityResult +import android.content.Intent +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.closeSoftKeyboard +import androidx.test.espresso.action.ViewActions.pressBack +import androidx.test.espresso.action.ViewActions.replaceText +import androidx.test.espresso.action.ViewActions.typeText +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.Intents.intending +import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction +import androidx.test.espresso.intent.matcher.IntentMatchers.isInternal +import androidx.test.espresso.matcher.RootMatchers +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.platform.app.InstrumentationRegistry +import im.vector.app.features.MainActivity +import im.vector.app.features.crypto.recover.SetupMode +import im.vector.app.features.home.HomeActivity +import org.hamcrest.CoreMatchers.not +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.matrix.android.sdk.api.Matrix +import org.matrix.android.sdk.api.session.Session + +@RunWith(AndroidJUnit4::class) +@LargeTest +class SecurityBootstrapTest : VerificationTestBase() { + + var existingSession: Session? = null + + @get:Rule + val activityRule = ActivityScenarioRule(MainActivity::class.java) + + @Before + fun createSessionWithCrossSigning() { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val matrix = Matrix.getInstance(context) + val userName = "foobar_${System.currentTimeMillis()}" + existingSession = createAccountAndSync(matrix, userName, password, true) + stubAllExternalIntents() + } + + private fun stubAllExternalIntents() { + // By default Espresso Intents does not stub any Intents. Stubbing needs to be setup before + // every test run. In this case all external Intents will be blocked. + Intents.init() + intending(not(isInternal())).respondWith(ActivityResult(Activity.RESULT_OK, null)) + } + + @Test + fun testBasicBootstrap() { + val userId: String = existingSession!!.myUserId + + doLogin(homeServerUrl, userId, password) + + // Thread.sleep(6000) + withIdlingResource(activityIdlingResource(HomeActivity::class.java)) { + onView(withId(R.id.roomListContainer)) + .check(matches(isDisplayed())) + .perform(closeSoftKeyboard()) + } + + val activity = EspressoHelper.getCurrentActivity()!! + val uiSession = (activity as HomeActivity).activeSessionHolder.getActiveSession() + + withIdlingResource(initialSyncIdlingResource(uiSession)) { + onView(withId(R.id.roomListContainer)) + .check(matches(isDisplayed())) + } + + activity.navigator.open4SSetup(activity, SetupMode.NORMAL, null) + + Thread.sleep(1000) + + onView(withId(R.id.bootstrapSetupSecureUseSecurityKey)) + .check(matches(isDisplayed())) + + onView(withId(R.id.bootstrapSetupSecureUseSecurityPassphrase)) + .check(matches(isDisplayed())) + .perform(click()) + + onView(isRoot()) + .perform(waitForView(withText(R.string.bootstrap_info_text_2))) + + // test back + onView(isRoot()).perform(pressBack()) + + + Thread.sleep(1000) + + onView(withId(R.id.bootstrapSetupSecureUseSecurityKey)) + .check(matches(isDisplayed())) + + onView(withId(R.id.bootstrapSetupSecureUseSecurityPassphrase)) + .check(matches(isDisplayed())) + .perform(click()) + + onView(isRoot()) + .perform(waitForView(withText(R.string.bootstrap_info_text_2))) + + onView(withId(R.id.ssss_passphrase_enter_edittext)) + .perform(typeText("person woman man camera tv")) + + onView(withId(R.id.bootstrapSubmit)) + .perform(closeSoftKeyboard(), click()) + + // test bad pass + onView(withId(R.id.ssss_passphrase_enter_edittext)) + .perform(typeText("person woman man cmera tv")) + + onView(withId(R.id.bootstrapSubmit)) + .perform(closeSoftKeyboard(), click()) + + onView(withText(R.string.passphrase_passphrase_does_not_match)).check(matches(isDisplayed())) + + onView(withId(R.id.ssss_passphrase_enter_edittext)) + .perform(replaceText("person woman man camera tv")) + + + onView(withId(R.id.bootstrapSubmit)) + .perform(closeSoftKeyboard(), click()) + + onView(withId(R.id.bottomSheetScrollView)) + .perform(waitForView(withText(R.string.bottom_sheet_save_your_recovery_key_content))) + + + + intending(hasAction(Intent.ACTION_SEND)).respondWith(ActivityResult(Activity.RESULT_OK, null)) + + onView(withId(R.id.recoveryCopy)) + .perform(click()) + + Thread.sleep(1000) + + // Dismiss dialog + onView(withText(R.string.ok)).inRoot(RootMatchers.isDialog()).perform(click()) + + + onView(withId(R.id.bottomSheetScrollView)) + .perform(waitForView(withText(R.string.bottom_sheet_save_your_recovery_key_content))) + + onView(withText(R.string._continue)).perform(click()) + + // Assert that all is configured + assert(uiSession.cryptoService().crossSigningService().isCrossSigningInitialized()) + assert(uiSession.cryptoService().crossSigningService().canCrossSign()) + assert(uiSession.cryptoService().crossSigningService().allPrivateKeysKnown()) + assert(uiSession.cryptoService().keysBackupService().isEnabled) + assert(uiSession.cryptoService().keysBackupService().currentBackupVersion != null) + assert(uiSession.sharedSecretStorageService.isRecoverySetup()) + assert(uiSession.sharedSecretStorageService.isMegolmKeyInBackup()) + } +} From 3642ca5b4a1a6943ca5bcc77c2b7ec8e85f5ba3d Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 30 Sep 2020 11:05:06 +0200 Subject: [PATCH 05/10] cleaning --- tools/check/forbidden_strings_in_code.txt | 2 +- .../androidTest/java/im/vector/app/SecurityBootstrapTest.kt | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index 3ced7de7e2..1d3aaa9f69 100644 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -164,7 +164,7 @@ Formatter\.formatShortFileSize===1 # android\.text\.TextUtils ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If it is ok, change the value in file forbidden_strings_in_code.txt -enum class===78 +enum class===80 ### Do not import temporary legacy classes import org.matrix.android.sdk.internal.legacy.riot===3 diff --git a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt index a71af59edf..3bf17aa043 100644 --- a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt +++ b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt @@ -113,7 +113,6 @@ class SecurityBootstrapTest : VerificationTestBase() { // test back onView(isRoot()).perform(pressBack()) - Thread.sleep(1000) onView(withId(R.id.bootstrapSetupSecureUseSecurityKey)) @@ -144,15 +143,12 @@ class SecurityBootstrapTest : VerificationTestBase() { onView(withId(R.id.ssss_passphrase_enter_edittext)) .perform(replaceText("person woman man camera tv")) - onView(withId(R.id.bootstrapSubmit)) .perform(closeSoftKeyboard(), click()) onView(withId(R.id.bottomSheetScrollView)) .perform(waitForView(withText(R.string.bottom_sheet_save_your_recovery_key_content))) - - intending(hasAction(Intent.ACTION_SEND)).respondWith(ActivityResult(Activity.RESULT_OK, null)) onView(withId(R.id.recoveryCopy)) @@ -163,7 +159,6 @@ class SecurityBootstrapTest : VerificationTestBase() { // Dismiss dialog onView(withText(R.string.ok)).inRoot(RootMatchers.isDialog()).perform(click()) - onView(withId(R.id.bottomSheetScrollView)) .perform(waitForView(withText(R.string.bottom_sheet_save_your_recovery_key_content))) From a4e163885d8f352c161c2e33564bee3e51a53467 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 30 Sep 2020 16:17:28 +0200 Subject: [PATCH 06/10] Better rotation support --- .../core/platform/VectorBaseBottomSheetDialogFragment.kt | 2 +- .../features/crypto/quads/SharedSecureStorageActivity.kt | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt index 761bb7227c..9f4924ebb2 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -97,7 +97,7 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment() } var resultListener : ResultListener? = null - var bottomSheetResult: Int = ResultListener.RESULT_OK + var bottomSheetResult: Int = ResultListener.RESULT_CANCEL var bottomSheetResultData: Any? = null override fun onDismiss(dialog: DialogInterface) { diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt index 0cc28609c9..6f7ea1fd46 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt @@ -119,6 +119,13 @@ class SharedSecureStorageActivity : SimpleFragmentActivity(), VectorBaseBottomSh } } + override fun onAttachFragment(fragment: Fragment) { + super.onAttachFragment(fragment) + if (fragment is VectorBaseBottomSheetDialogFragment) { + fragment.resultListener = this + } + } + private fun showFragment(fragmentClass: KClass, bundle: Bundle) { if (supportFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) { supportFragmentManager.commitTransaction { From 18950a6b46fbd71962a08a483065e04d90fbcb62 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 30 Sep 2020 16:36:17 +0200 Subject: [PATCH 07/10] Some cleanup --- .../SharedSecuredStorageResetAllFragment.kt | 24 +++++++------------ .../crypto/recover/BootstrapBottomSheet.kt | 3 --- .../recover/BootstrapCrossSigningTask.kt | 2 -- .../verification/VerificationBottomSheet.kt | 2 +- .../VerificationBottomSheetViewModel.kt | 4 ++-- .../res/layout/fragment_ssss_reset_all.xml | 13 ++++++---- 6 files changed, 19 insertions(+), 29 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt index 314abda38c..d7db779230 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStorageResetAllFragment.kt @@ -18,19 +18,16 @@ package im.vector.app.features.crypto.quads import android.os.Bundle import android.view.View -import androidx.core.view.isVisible import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import im.vector.app.R +import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.core.resources.StringProvider import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet import kotlinx.android.synthetic.main.fragment_ssss_reset_all.* import javax.inject.Inject -class SharedSecuredStorageResetAllFragment @Inject constructor( - private val stringProvider: StringProvider -) : VectorBaseFragment() { +class SharedSecuredStorageResetAllFragment @Inject constructor() : VectorBaseFragment() { override fun getLayoutResId() = R.layout.fragment_ssss_reset_all @@ -53,17 +50,12 @@ class SharedSecuredStorageResetAllFragment @Inject constructor( } } - sharedViewModel.subscribe(this) { - if (it.activeDeviceCount == 0) { - ssss_reset_other_devices.isVisible = false - } else { - ssss_reset_other_devices.isVisible = true - ssss_reset_other_devices.text = stringProvider.getQuantityString( - R.plurals.secure_backup_reset_devices_you_can_verify, - it.activeDeviceCount, - it.activeDeviceCount - ) - } + sharedViewModel.subscribe(this) { state -> + ssss_reset_other_devices.setTextOrHide( + state.activeDeviceCount + .takeIf { it > 0 } + ?.let { resources.getQuantityString(R.plurals.secure_backup_reset_devices_you_can_verify, it, it) } + ) } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt index 5b5b4e1931..e6260b6e7e 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapBottomSheet.kt @@ -45,10 +45,7 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { @Parcelize data class Args( -// val initCrossSigningOnly: Boolean, val setUpMode: SetupMode = SetupMode.NORMAL -// val forceReset4S: Boolean, -// val resetAllUnknownSecrets: Boolean ) : Parcelable override val showExpanded = true diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt index 05e9a836f6..4dc2b92751 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -69,11 +69,9 @@ interface BootstrapProgressListener { data class Params( val userPasswordAuth: UserPasswordAuth? = null, -// val initOnlyCrossSigning: Boolean = false, val progressListener: BootstrapProgressListener? = null, val passphrase: String?, val keySpec: SsssKeySpec? = null, -// val resetAllIfNeeded: Boolean = false, val setupMode: SetupMode ) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt index c170809b51..03c988115c 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt @@ -188,7 +188,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } } - if (state.quadsHasBeenReseted) { + if (state.quadSHasBeenReset) { showFragment(VerificationConclusionFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args( isSuccessFull = true, diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt index 1638450725..a7f47b1172 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -76,7 +76,7 @@ data class VerificationBottomSheetViewState( val userWantsToCancel: Boolean = false, val userThinkItsNotHim: Boolean = false, val quadSContainsSecrets: Boolean = true, - val quadsHasBeenReseted: Boolean = false + val quadSHasBeenReset: Boolean = false ) : MvRxState class VerificationBottomSheetViewModel @AssistedInject constructor( @@ -354,7 +354,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( VerificationAction.SecuredStorageHasBeenReseted -> { if (session.cryptoService().crossSigningService().allPrivateKeysKnown()) { setState { - copy(quadsHasBeenReseted = true) + copy(quadSHasBeenReset = true) } } Unit diff --git a/vector/src/main/res/layout/fragment_ssss_reset_all.xml b/vector/src/main/res/layout/fragment_ssss_reset_all.xml index a1851f3728..a3b2984bce 100644 --- a/vector/src/main/res/layout/fragment_ssss_reset_all.xml +++ b/vector/src/main/res/layout/fragment_ssss_reset_all.xml @@ -44,14 +44,18 @@ + tools:text="Show 2 devices you can verify with now" + tools:visibility="visible" /> Date: Wed, 30 Sep 2020 16:47:34 +0200 Subject: [PATCH 08/10] No warning when cancelling the Reset of 4s --- .../features/crypto/recover/BootstrapSharedViewModel.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt index 7f3f7c0667..a3955d561e 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapSharedViewModel.kt @@ -528,7 +528,13 @@ class BootstrapSharedViewModel @AssistedInject constructor( } BootstrapStep.CheckingMigration -> Unit is BootstrapStep.FirstForm -> { - _viewEvents.post(BootstrapViewEvents.SkipBootstrap()) + _viewEvents.post( + when (args.setUpMode) { + SetupMode.CROSS_SIGNING_ONLY, + SetupMode.NORMAL -> BootstrapViewEvents.SkipBootstrap() + else -> BootstrapViewEvents.Dismiss(success = false) + } + ) } is BootstrapStep.GetBackupSecretForMigration -> { setState { From 482bb51640ceea8fffbe8a7b1504df7614ef0fe5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 30 Sep 2020 17:50:36 +0200 Subject: [PATCH 09/10] More cleanup --- .../androidTest/java/im/vector/app/SecurityBootstrapTest.kt | 2 +- .../features/crypto/quads/SharedSecureStorageActivity.kt | 2 +- .../app/features/crypto/verification/VerificationAction.kt | 2 +- .../features/crypto/verification/VerificationBottomSheet.kt | 6 +++--- .../crypto/verification/VerificationBottomSheetViewModel.kt | 2 +- .../im/vector/app/features/navigation/DefaultNavigator.kt | 6 ++---- .../java/im/vector/app/features/navigation/Navigator.kt | 3 +-- 7 files changed, 10 insertions(+), 13 deletions(-) diff --git a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt index 3bf17aa043..3ab8fe7dd9 100644 --- a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt +++ b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt @@ -96,7 +96,7 @@ class SecurityBootstrapTest : VerificationTestBase() { .check(matches(isDisplayed())) } - activity.navigator.open4SSetup(activity, SetupMode.NORMAL, null) + activity.navigator.open4SSetup(activity, SetupMode.NORMAL) Thread.sleep(1000) diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt index 6f7ea1fd46..42d000ecc3 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageActivity.kt @@ -114,7 +114,7 @@ class SharedSecureStorageActivity : SimpleFragmentActivity(), VectorBaseBottomSh finish() } is SharedSecureStorageViewEvent.ShowResetBottomSheet -> { - navigator.open4SSetup(this, SetupMode.HARD_RESET, this) + navigator.open4SSetup(this, SetupMode.HARD_RESET) } } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt index d0fd825ebb..a32a9de97f 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt @@ -31,5 +31,5 @@ sealed class VerificationAction : VectorViewModelAction { object SkipVerification : VerificationAction() object VerifyFromPassphrase : VerificationAction() data class GotResultFromSsss(val cypherData: String, val alias: String) : VerificationAction() - object SecuredStorageHasBeenReseted : VerificationAction() + object SecuredStorageHasBeenReset : VerificationAction() } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt index 03c988115c..c7ce4d6f01 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt @@ -148,12 +148,12 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (resultCode == Activity.RESULT_OK && requestCode == SECRET_REQUEST_CODE) { val result = data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT) - val reseted = data?.getBooleanExtra(SharedSecureStorageActivity.EXTRA_DATA_RESET, false) ?: false + val reset = data?.getBooleanExtra(SharedSecureStorageActivity.EXTRA_DATA_RESET, false) ?: false if (result != null) { viewModel.handle(VerificationAction.GotResultFromSsss(result, SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS)) - } else if (reseted) { + } else if (reset) { // all have been reset, so we are verified? - viewModel.handle(VerificationAction.SecuredStorageHasBeenReseted) + viewModel.handle(VerificationAction.SecuredStorageHasBeenReset) } } super.onActivityResult(requestCode, resultCode, data) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt index a7f47b1172..3c00478ab0 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -351,7 +351,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( is VerificationAction.GotResultFromSsss -> { handleSecretBackFromSSSS(action) } - VerificationAction.SecuredStorageHasBeenReseted -> { + VerificationAction.SecuredStorageHasBeenReset -> { if (session.cryptoService().crossSigningService().allPrivateKeysKnown()) { setState { copy(quadSHasBeenReset = true) diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 66dd9544b4..8a60e4a176 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -238,11 +238,9 @@ class DefaultNavigator @Inject constructor( } } - override fun open4SSetup(context: Context, setupMode: SetupMode, listener: VectorBaseBottomSheetDialogFragment.ResultListener?) { + override fun open4SSetup(context: Context, setupMode: SetupMode) { if (context is VectorBaseActivity) { - BootstrapBottomSheet.show(context.supportFragmentManager, setupMode).also { - it.resultListener = listener - } + BootstrapBottomSheet.show(context.supportFragmentManager, setupMode) } } diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 3f255f72ef..e92fea267f 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -21,7 +21,6 @@ import android.content.Context import android.view.View import androidx.core.util.Pair import androidx.fragment.app.Fragment -import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.home.room.detail.widget.WidgetRequestCodes import im.vector.app.features.media.AttachmentData @@ -73,7 +72,7 @@ interface Navigator { fun openKeysBackupSetup(context: Context, showManualExport: Boolean) - fun open4SSetup(context: Context, setupMode: SetupMode, listener: VectorBaseBottomSheetDialogFragment.ResultListener?) + fun open4SSetup(context: Context, setupMode: SetupMode) fun openKeysBackupManager(context: Context) From 37d6a9b722b366905ad8fb443e9f4a83b46049f8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 30 Sep 2020 18:20:42 +0200 Subject: [PATCH 10/10] ktlint --- .../java/im/vector/app/features/navigation/DefaultNavigator.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 8a60e4a176..3a8d302fc7 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -30,7 +30,6 @@ import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.error.fatalError import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.core.utils.toast import im.vector.app.features.call.conference.JitsiCallViewModel import im.vector.app.features.call.conference.VectorJitsiActivity