From 1cd27d7f67e0752744a845fcaf1cc0e04b8223a7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Jun 2020 22:09:57 +0200 Subject: [PATCH 01/30] First commit to cleanup ReAuthHelper and it's usage Also add some comment and do some other cleanup --- .../crypto/tasks/UploadSigningKeysTask.kt | 49 +++++++------ .../riotx/core/utils/TemporaryStoreTest.kt | 37 ++++++++++ .../vector/riotx/core/utils/TemporaryStore.kt | 43 +++++++++++ .../recover/BackupToQuadSMigrationTask.kt | 2 +- .../crypto/recover/BootstrapActions.kt | 5 +- .../crypto/recover/BootstrapBottomSheet.kt | 2 +- .../recover/BootstrapCrossSigningTask.kt | 8 ++- .../recover/BootstrapSharedViewModel.kt | 71 +++++++++++-------- .../riotx/features/login/LoginViewModel.kt | 3 +- .../riotx/features/login/ReAuthHelper.kt | 31 ++------ 10 files changed, 165 insertions(+), 86 deletions(-) create mode 100644 vector/src/androidTest/java/im/vector/riotx/core/utils/TemporaryStoreTest.kt create mode 100644 vector/src/main/java/im/vector/riotx/core/utils/TemporaryStore.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSigningKeysTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSigningKeysTask.kt index a66a20617c..238a9c068d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSigningKeysTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSigningKeysTask.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.crypto.tasks +import im.vector.matrix.android.api.auth.data.LoginFlowTypes import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.toRegistrationFlowResponse import im.vector.matrix.android.internal.crypto.api.CryptoApi @@ -37,6 +38,15 @@ internal interface UploadSigningKeysTask : Task(eventBus) { - apiCall = cryptoApi.uploadSigningKeys(uploadQuery) - } - if (request.failures?.isNotEmpty() == true) { - throw UploadSigningKeys(request.failures) - } - return + doRequest(uploadQuery) } catch (throwable: Throwable) { val registrationFlowResponse = throwable.toRegistrationFlowResponse() if (registrationFlowResponse != null - && params.userPasswordAuth != null - /* Avoid infinite loop */ - && params.userPasswordAuth.session.isNullOrEmpty() + && registrationFlowResponse.flows.orEmpty().any { it.stages?.contains(LoginFlowTypes.PASSWORD) == true } + && params.userPasswordAuth?.password != null + && !paramsHaveSessionId ) { // Retry with authentication - val req = executeRequest(eventBus) { - apiCall = cryptoApi.uploadSigningKeys( - uploadQuery.copy(auth = params.userPasswordAuth.copy(session = registrationFlowResponse.session)) - ) - } - if (req.failures?.isNotEmpty() == true) { - throw UploadSigningKeys(req.failures) - } + doRequest(uploadQuery.copy(auth = params.userPasswordAuth.copy(session = registrationFlowResponse.session))) } else { // Other error throw throwable } } } + + private suspend fun doRequest(uploadQuery: UploadSigningKeysBody) { + val keysQueryResponse = executeRequest(eventBus) { + apiCall = cryptoApi.uploadSigningKeys(uploadQuery) + } + if (keysQueryResponse.failures?.isNotEmpty() == true) { + throw UploadSigningKeys(keysQueryResponse.failures) + } + } } diff --git a/vector/src/androidTest/java/im/vector/riotx/core/utils/TemporaryStoreTest.kt b/vector/src/androidTest/java/im/vector/riotx/core/utils/TemporaryStoreTest.kt new file mode 100644 index 0000000000..c0607bc00b --- /dev/null +++ b/vector/src/androidTest/java/im/vector/riotx/core/utils/TemporaryStoreTest.kt @@ -0,0 +1,37 @@ +/* + * 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.riotx.core.utils + +import org.amshove.kluent.shouldBe +import org.junit.Test +import java.lang.Thread.sleep + +class TemporaryStoreTest { + + @Test + fun testTemporaryStore() { + // Keep the data 30 millis + val store = TemporaryStore(30) + + store.data = "test" + store.data shouldBe "test" + sleep(15) + store.data shouldBe "test" + sleep(20) + store.data shouldBe null + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/core/utils/TemporaryStore.kt b/vector/src/main/java/im/vector/riotx/core/utils/TemporaryStore.kt new file mode 100644 index 0000000000..8484162bc8 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/utils/TemporaryStore.kt @@ -0,0 +1,43 @@ +/* + * 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.riotx.core.utils + +import java.util.Timer +import java.util.TimerTask + +const val THREE_MINUTES = 3 * 60_000L + +/** + * Store an object T for a specific period of time + */ +open class TemporaryStore(private val delay: Long = THREE_MINUTES) { + + private var timer: Timer? = null + + var data: T? = null + set(value) { + field = value + timer?.cancel() + timer = Timer().also { + it.schedule(object : TimerTask() { + override fun run() { + field = null + } + }, delay) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BackupToQuadSMigrationTask.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BackupToQuadSMigrationTask.kt index e29d0d636d..855ccb7bc7 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BackupToQuadSMigrationTask.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BackupToQuadSMigrationTask.kt @@ -97,7 +97,7 @@ class BackupToQuadSMigrationTask @Inject constructor( when { params.passphrase?.isNotEmpty() == true -> { reportProgress(params, R.string.bootstrap_progress_generating_ssss) - awaitCallback { + awaitCallback { quadS.generateKeyWithPassphrase( UUID.randomUUID().toString(), "ssss_key", diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt index 2d9440a77d..9e4f3ea527 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt @@ -16,7 +16,6 @@ package im.vector.riotx.features.crypto.recover -import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.riotx.core.platform.VectorViewModelAction import java.io.OutputStream @@ -29,8 +28,8 @@ sealed class BootstrapActions : VectorViewModelAction { object GoToCompleted : BootstrapActions() object GoToEnterAccountPassword : BootstrapActions() - data class DoInitialize(val passphrase: String, val auth: UserPasswordAuth? = null) : BootstrapActions() - data class DoInitializeGeneratedKey(val auth: UserPasswordAuth? = null) : BootstrapActions() + data class DoInitialize(val passphrase: String) : BootstrapActions() + object DoInitializeGeneratedKey : BootstrapActions() object TogglePasswordVisibility : BootstrapActions() data class UpdateCandidatePassphrase(val pass: String) : BootstrapActions() data class UpdateConfirmCandidatePassphrase(val pass: String) : BootstrapActions() diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt index e48c674159..d1138c20c7 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt @@ -90,7 +90,7 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { .apply { if (genKeyOption) { setNeutralButton(R.string.generate_message_key) { _, _ -> - viewModel.handle(BootstrapActions.DoInitializeGeneratedKey()) + viewModel.handle(BootstrapActions.DoInitializeGeneratedKey) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt index 3c22260f7f..c5dc24bf0a 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -58,7 +58,7 @@ sealed class BootstrapResult { class FailedToStorePrivateKeyInSSSS(failure: Throwable) : Failure(failure.localizedMessage) object MissingPrivateKey : Failure(null) - data class PasswordAuthFlowMissing(val sessionId: String, val userId: String) : Failure(null) + data class PasswordAuthFlowMissing(val sessionId: String) : Failure(null) } interface BootstrapProgressListener { @@ -232,9 +232,11 @@ class BootstrapCrossSigningTask @Inject constructor( } else { val registrationFlowResponse = failure.toRegistrationFlowResponse() if (registrationFlowResponse != null) { - if (registrationFlowResponse.flows?.any { it.stages?.contains(LoginFlowTypes.PASSWORD) == true } != true) { + return if (registrationFlowResponse.flows.orEmpty().any { it.stages?.contains(LoginFlowTypes.PASSWORD) == true }) { + BootstrapResult.PasswordAuthFlowMissing(registrationFlowResponse.session ?: "") + } else { // can't do this from here - return BootstrapResult.UnsupportedAuthFlow() + BootstrapResult.UnsupportedAuthFlow() } } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt index 6d96d9038e..78e5cc1fb3 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt @@ -30,6 +30,7 @@ import com.nulabinc.zxcvbn.Strength import com.nulabinc.zxcvbn.Zxcvbn import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo @@ -57,7 +58,6 @@ data class BootstrapViewState( val passphraseConfirmMatch: Async = Uninitialized, val recoveryKeyCreationInfo: SsssKeyCreationInfo? = null, val initializationWaitingViewData: WaitingViewData? = null, - val currentReAuth: UserPasswordAuth? = null, val recoverySaveFileProcess: Async = Uninitialized ) : MvRxState @@ -78,6 +78,8 @@ class BootstrapSharedViewModel @AssistedInject constructor( fun create(initialState: BootstrapViewState, args: BootstrapBottomSheet.Args): BootstrapSharedViewModel } + private var _pendingSession: String? = null + init { // need to check if user have an existing keybackup if (args.isNewAccount) { @@ -182,15 +184,15 @@ class BootstrapSharedViewModel @AssistedInject constructor( } is BootstrapActions.DoInitialize -> { if (state.passphrase == state.passphraseRepeat) { - val auth = action.auth ?: reAuthHelper.rememberedAuth() - if (auth == null) { + val userPassword = reAuthHelper.data + if (userPassword == null) { setState { copy( step = BootstrapStep.AccountPassword(false) ) } } else { - startInitializeFlow(action.auth) + startInitializeFlow(userPassword) } } else { setState { @@ -201,8 +203,8 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } is BootstrapActions.DoInitializeGeneratedKey -> { - val auth = action.auth ?: reAuthHelper.rememberedAuth() - if (auth == null) { + val userPassword = reAuthHelper.data + if (userPassword == null) { setState { copy( passphrase = null, @@ -217,7 +219,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( passphraseRepeat = null ) } - startInitializeFlow(action.auth) + startInitializeFlow(userPassword) } } BootstrapActions.RecoveryKeySaved -> { @@ -260,10 +262,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( } else return@withState } is BootstrapActions.ReAuth -> { - startInitializeFlow( - state.currentReAuth?.copy(password = action.pass) - ?: UserPasswordAuth(user = session.myUserId, password = action.pass) - ) + startInitializeFlow(action.pass) } is BootstrapActions.DoMigrateWithPassphrase -> { startMigrationFlow(state.step, action.passphrase, null) @@ -322,15 +321,15 @@ class BootstrapSharedViewModel @AssistedInject constructor( migrationRecoveryKey = recoveryKey ) } - val auth = reAuthHelper.rememberedAuth() - if (auth == null) { + val userPassword = reAuthHelper.data + if (userPassword == null) { setState { copy( step = BootstrapStep.AccountPassword(false) ) } } else { - startInitializeFlow(auth) + startInitializeFlow(userPassword) } } else { _viewEvents.post( @@ -350,7 +349,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } - private fun startInitializeFlow(auth: UserPasswordAuth?) { + private fun startInitializeFlow(userPassword: String?) { setState { copy(step = BootstrapStep.Initializing) } @@ -367,25 +366,37 @@ class BootstrapSharedViewModel @AssistedInject constructor( withState { state -> viewModelScope.launch(Dispatchers.IO) { - bootstrapTask.invoke(this, Params( - userPasswordAuth = auth ?: reAuthHelper.rememberedAuth(), - progressListener = progressListener, - passphrase = state.passphrase, - keySpec = state.migrationRecoveryKey?.let { extractCurveKeyFromRecoveryKey(it)?.let { RawBytesKeySpec(it) } } - )) { - when (it) { + val userPasswordAuth = userPassword?.let { + UserPasswordAuth( + // Note that _pendingSession may or may not be null, this is OK, it will be managed by the task + session = _pendingSession, + user = session.myUserId, + password = it + ) + } + + bootstrapTask.invoke(this, + Params( + userPasswordAuth = userPasswordAuth, + progressListener = progressListener, + passphrase = state.passphrase, + keySpec = state.migrationRecoveryKey?.let { extractCurveKeyFromRecoveryKey(it)?.let { RawBytesKeySpec(it) } } + ) + ) { bootstrapResult -> + when (bootstrapResult) { is BootstrapResult.Success -> { setState { copy( - recoveryKeyCreationInfo = it.keyInfo, + recoveryKeyCreationInfo = bootstrapResult.keyInfo, step = BootstrapStep.SaveRecoveryKey(false) ) } } is BootstrapResult.PasswordAuthFlowMissing -> { + // Ask the password to the user + _pendingSession = bootstrapResult.sessionId setState { copy( - currentReAuth = UserPasswordAuth(session = it.sessionId, user = it.userId), step = BootstrapStep.AccountPassword(false) ) } @@ -396,20 +407,20 @@ class BootstrapSharedViewModel @AssistedInject constructor( } is BootstrapResult.InvalidPasswordError -> { // it's a bad password + // We clear the auth session, to avoid 'Requested operation has changed during the UI authentication session' error + _pendingSession = null setState { copy( - // We clear the auth session, to avoid 'Requested operation has changed during the UI authentication session' error - currentReAuth = UserPasswordAuth(session = null, user = session.myUserId), step = BootstrapStep.AccountPassword(false, stringProvider.getString(R.string.auth_invalid_login_param)) ) } } is BootstrapResult.Failure -> { - if (it is BootstrapResult.GenericError - && it.failure is im.vector.matrix.android.api.failure.Failure.OtherServerError - && it.failure.httpCode == 401) { + if (bootstrapResult is BootstrapResult.GenericError + && bootstrapResult.failure is Failure.OtherServerError + && bootstrapResult.failure.httpCode == 401) { } else { - _viewEvents.post(BootstrapViewEvents.ModalError(it.error ?: stringProvider.getString(R.string.matrix_error))) + _viewEvents.post(BootstrapViewEvents.ModalError(bootstrapResult.error ?: stringProvider.getString(R.string.matrix_error))) setState { copy( step = BootstrapStep.ConfirmPassphrase(false) diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index abdea9698f..31b303c5b7 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -41,7 +41,6 @@ import im.vector.matrix.android.api.auth.registration.Stage import im.vector.matrix.android.api.auth.wellknown.WellknownResult import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.util.Cancelable -import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.extensions.configureAndStart @@ -289,7 +288,7 @@ class LoginViewModel @AssistedInject constructor( private fun handleRegisterWith(action: LoginAction.LoginOrRegister) { setState { copy(asyncRegistration = Loading()) } - reAuthHelper.rememberAuth(UserPasswordAuth(user = action.username, password = action.password)) + reAuthHelper.data = action.password currentTask = registrationWizard?.createAccount( action.username, action.password, diff --git a/vector/src/main/java/im/vector/riotx/features/login/ReAuthHelper.kt b/vector/src/main/java/im/vector/riotx/features/login/ReAuthHelper.kt index 3a6142bc08..78979930bd 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/ReAuthHelper.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/ReAuthHelper.kt @@ -16,33 +16,12 @@ package im.vector.riotx.features.login -import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth -import java.util.Timer -import java.util.TimerTask +import im.vector.riotx.core.utils.TemporaryStore import javax.inject.Inject import javax.inject.Singleton -const val THREE_MINUTES = 3 * 60_000L - +/** + * Will store the account password for 3 minutes + */ @Singleton -class ReAuthHelper @Inject constructor() { - - private var timer: Timer? = null - - private var rememberedInfo: UserPasswordAuth? = null - - fun rememberAuth(password: UserPasswordAuth?) { - timer?.cancel() - timer = null - rememberedInfo = password - timer = Timer().apply { - schedule(object : TimerTask() { - override fun run() { - rememberedInfo = null - } - }, THREE_MINUTES) - } - } - - fun rememberedAuth() = rememberedInfo -} +class ReAuthHelper @Inject constructor() : TemporaryStore() From 1365240f69778f245ecf79327b841873ef8b7bfb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Jun 2020 22:07:35 +0200 Subject: [PATCH 02/30] Cleanup --- .../riotx/features/home/HomeActivity.kt | 17 +++++---- .../features/home/HomeActivityViewModel.kt | 35 +++++++++++++++++++ .../home/HomeSharedActionViewModel.kt | 5 +-- 3 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt index 97ac5f933b..2b873b33a4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt @@ -27,6 +27,7 @@ import androidx.core.view.GravityCompat import androidx.core.view.isVisible import androidx.drawerlayout.widget.DrawerLayout import androidx.lifecycle.Observer +import com.airbnb.mvrx.viewModel import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.util.toMatrixItem @@ -59,6 +60,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet private lateinit var sharedActionViewModel: HomeSharedActionViewModel + private val homeActivityViewModel: HomeActivityViewModel by viewModel() + @Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler @Inject lateinit var pushManager: PushersManager @@ -117,7 +120,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } if (intent.getBooleanExtra(EXTRA_ACCOUNT_CREATION, false)) { sharedActionViewModel.post(HomeActivitySharedAction.PromptForSecurityBootstrap) - sharedActionViewModel.isAccountCreation = true + homeActivityViewModel.isAccountCreation = true intent.removeExtra(EXTRA_ACCOUNT_CREATION) } @@ -126,7 +129,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet waiting_view.isVisible = false promptCompleteSecurityIfNeeded() } else { - sharedActionViewModel.hasDisplayedCompleteSecurityPrompt = false + homeActivityViewModel.hasDisplayedCompleteSecurityPrompt = false Timber.v("${getString(status.statusText)} ${status.percentProgress}") waiting_view.setOnClickListener { // block interactions @@ -146,7 +149,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet }) // Ask again if the app is relaunched - if (!sharedActionViewModel.hasDisplayedCompleteSecurityPrompt + if (!homeActivityViewModel.hasDisplayedCompleteSecurityPrompt && activeSessionHolder.getSafeActiveSession()?.hasAlreadySynced() == true) { promptCompleteSecurityIfNeeded() } @@ -158,7 +161,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet private fun promptCompleteSecurityIfNeeded() { val session = activeSessionHolder.getSafeActiveSession() ?: return if (!session.hasAlreadySynced()) return - if (sharedActionViewModel.hasDisplayedCompleteSecurityPrompt) return + if (homeActivityViewModel.hasDisplayedCompleteSecurityPrompt) return // ensure keys are downloaded session.cryptoService().downloadKeys(listOf(session.myUserId), true, object : MatrixCallback> { @@ -175,7 +178,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet .getMyCrossSigningKeys() val crossSigningEnabledOnAccount = myCrossSigningKeys != null - if (!crossSigningEnabledOnAccount && !sharedActionViewModel.isAccountCreation) { + if (!crossSigningEnabledOnAccount && !homeActivityViewModel.isAccountCreation) { // Do not propose for SSO accounts, because we do not support yet confirming account credentials using SSO if (session.getHomeServerCapabilities().canChangePassword) { // We need to ask @@ -188,7 +191,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } } else { // Do not do it again - sharedActionViewModel.hasDisplayedCompleteSecurityPrompt = true + homeActivityViewModel.hasDisplayedCompleteSecurityPrompt = true } } else if (myCrossSigningKeys?.isTrusted() == false) { // We need to ask @@ -203,7 +206,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } private fun promptSecurityEvent(session: Session, titleRes: Int, descRes: Int, action: ((VectorBaseActivity) -> Unit)) { - sharedActionViewModel.hasDisplayedCompleteSecurityPrompt = true + homeActivityViewModel.hasDisplayedCompleteSecurityPrompt = true popupAlertManager.postVectorAlert( VerificationVectorAlert( uid = "upgradeSecurity", diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt new file mode 100644 index 0000000000..7ad6cb8528 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt @@ -0,0 +1,35 @@ +/* + * 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.riotx.features.home + +import com.airbnb.mvrx.MvRxState +import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.platform.EmptyViewEvents +import im.vector.riotx.core.platform.VectorViewModel + +data class EmptyState( + val dummy: Boolean = false +) : MvRxState + +class HomeActivityViewModel : VectorViewModel(EmptyState()) { + var hasDisplayedCompleteSecurityPrompt: Boolean = false + var isAccountCreation: Boolean = false + + override fun handle(action: EmptyAction) { + // NA + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeSharedActionViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeSharedActionViewModel.kt index 4af9262d05..cd81448a0a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeSharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeSharedActionViewModel.kt @@ -19,7 +19,4 @@ package im.vector.riotx.features.home import im.vector.riotx.core.platform.VectorSharedActionViewModel import javax.inject.Inject -class HomeSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() { - var hasDisplayedCompleteSecurityPrompt : Boolean = false - var isAccountCreation : Boolean = false -} +class HomeSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() From 48a30a7b82153a554ee4170cafd4809b24429701 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 9 Jun 2020 12:21:02 +0200 Subject: [PATCH 03/30] Create a ViewState for HomeActivity And disable the popup - WIP --- .../api/session/InitialSyncProgressService.kt | 13 +-- .../DefaultInitialSyncProgressService.kt | 12 ++- .../riotx/features/home/HomeActivity.kt | 87 +++++++++++-------- .../features/home/HomeActivitySharedAction.kt | 1 + .../features/home/HomeActivityViewModel.kt | 81 +++++++++++++++-- .../features/home/HomeActivityViewState.kt | 24 +++++ 6 files changed, 164 insertions(+), 54 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewState.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/InitialSyncProgressService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/InitialSyncProgressService.kt index 4d65179307..6aacd6cd19 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/InitialSyncProgressService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/InitialSyncProgressService.kt @@ -20,10 +20,13 @@ import androidx.lifecycle.LiveData interface InitialSyncProgressService { - fun getInitialSyncProgressStatus() : LiveData + fun getInitialSyncProgressStatus(): LiveData - data class Status( - @StringRes val statusText: Int, - val percentProgress: Int = 0 - ) + sealed class Status { + object Idle : Status() + data class Progressing( + @StringRes val statusText: Int, + val percentProgress: Int = 0 + ) : Status() + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultInitialSyncProgressService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultInitialSyncProgressService.kt index 3f653571b7..0d551bc99b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultInitialSyncProgressService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultInitialSyncProgressService.kt @@ -25,11 +25,11 @@ import javax.inject.Inject @SessionScope class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgressService { - private var status = MutableLiveData() + private val status = MutableLiveData() private var rootTask: TaskInfo? = null - override fun getInitialSyncProgressStatus(): LiveData { + override fun getInitialSyncProgressStatus(): LiveData { return status } @@ -63,13 +63,13 @@ class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgr parent?.setProgress(endedTask.offset + (endedTask.totalProgress * endedTask.parentWeight).toInt()) } if (endedTask?.parent == null) { - status.postValue(null) + status.postValue(InitialSyncProgressService.Status.Idle) } } fun endAll() { rootTask = null - status.postValue(null) + status.postValue(InitialSyncProgressService.Status.Idle) } private inner class TaskInfo(@StringRes var nameRes: Int, @@ -102,9 +102,7 @@ class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgr it.setProgress(offset + parentProgress) } ?: run { Timber.v("--- ${leaf().nameRes}: $currentProgress") - status.postValue( - InitialSyncProgressService.Status(leaf().nameRes, currentProgress) - ) + status.postValue(InitialSyncProgressService.Status.Progressing(leaf().nameRes, currentProgress)) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt index 2b873b33a4..526ae3a70d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt @@ -19,20 +19,16 @@ package im.vector.riotx.features.home import android.content.Context import android.content.Intent import android.os.Bundle +import android.os.Parcelable import android.view.MenuItem import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar -import androidx.core.content.ContextCompat import androidx.core.view.GravityCompat import androidx.core.view.isVisible import androidx.drawerlayout.widget.DrawerLayout -import androidx.lifecycle.Observer +import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.viewModel -import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.util.toMatrixItem -import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo -import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap +import im.vector.matrix.android.api.session.InitialSyncProgressService import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ScreenComponent @@ -46,21 +42,28 @@ import im.vector.riotx.features.crypto.recover.BootstrapBottomSheet import im.vector.riotx.features.disclaimer.showDisclaimerDialog import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.popup.PopupAlertManager -import im.vector.riotx.features.popup.VerificationVectorAlert import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.workers.signout.SignOutViewModel import im.vector.riotx.push.fcm.FcmHelper +import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import timber.log.Timber import javax.inject.Inject +@Parcelize +data class HomeActivityArgs( + val clearNotification: Boolean, + val accountCreation: Boolean +) : Parcelable + class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDetectorSharedViewModel.Factory { private lateinit var sharedActionViewModel: HomeSharedActionViewModel private val homeActivityViewModel: HomeActivityViewModel by viewModel() + @Inject lateinit var viewModelFactory: HomeActivityViewModel.Factory @Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler @@ -114,21 +117,33 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } .disposeOnDestroy() - if (intent.getBooleanExtra(EXTRA_CLEAR_EXISTING_NOTIFICATION, false)) { + val args = intent.getParcelableExtra(MvRx.KEY_ARG) + + if (args?.clearNotification == true) { notificationDrawerManager.clearAllEvents() - intent.removeExtra(EXTRA_CLEAR_EXISTING_NOTIFICATION) - } - if (intent.getBooleanExtra(EXTRA_ACCOUNT_CREATION, false)) { - sharedActionViewModel.post(HomeActivitySharedAction.PromptForSecurityBootstrap) - homeActivityViewModel.isAccountCreation = true - intent.removeExtra(EXTRA_ACCOUNT_CREATION) } - activeSessionHolder.getSafeActiveSession()?.getInitialSyncProgressStatus()?.observe(this, Observer { status -> - if (status == null) { + homeActivityViewModel.subscribe(this) { renderState(it) } + + /* + // TODO Remove + // Ask again if the app is relaunched + if (!homeActivityViewModel.hasDisplayedCompleteSecurityPrompt + && activeSessionHolder.getSafeActiveSession()?.hasAlreadySynced() == true) { + promptCompleteSecurityIfNeeded() + } + */ + + shortcutsHandler.observeRoomsAndBuildShortcuts() + .disposeOnDestroy() + } + + private fun renderState(state: HomeActivityViewState) { + when (val status = state.initialSyncProgressServiceStatus) { + is InitialSyncProgressService.Status.Idle -> { waiting_view.isVisible = false - promptCompleteSecurityIfNeeded() - } else { + } + is InitialSyncProgressService.Status.Progressing -> { homeActivityViewModel.hasDisplayedCompleteSecurityPrompt = false Timber.v("${getString(status.statusText)} ${status.percentProgress}") waiting_view.setOnClickListener { @@ -146,18 +161,11 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } waiting_view.isVisible = true } - }) - - // Ask again if the app is relaunched - if (!homeActivityViewModel.hasDisplayedCompleteSecurityPrompt - && activeSessionHolder.getSafeActiveSession()?.hasAlreadySynced() == true) { - promptCompleteSecurityIfNeeded() - } - - shortcutsHandler.observeRoomsAndBuildShortcuts() - .disposeOnDestroy() + }.exhaustive } + /* + // TODO Remove private fun promptCompleteSecurityIfNeeded() { val session = activeSessionHolder.getSafeActiveSession() ?: return if (!session.hasAlreadySynced()) return @@ -172,7 +180,10 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } }) } + */ + // TODO Remove + /* private fun alertCompleteSecurity(session: Session) { val myCrossSigningKeys = session.cryptoService().crossSigningService() .getMyCrossSigningKeys() @@ -204,7 +215,10 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } } } + */ + /* + // TODO Remove private fun promptSecurityEvent(session: Session, titleRes: Int, descRes: Int, action: ((VectorBaseActivity) -> Unit)) { homeActivityViewModel.hasDisplayedCompleteSecurityPrompt = true popupAlertManager.postVectorAlert( @@ -225,12 +239,12 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } ) } + */ override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - if (intent?.hasExtra(EXTRA_CLEAR_EXISTING_NOTIFICATION) == true) { + if (intent?.getParcelableExtra(MvRx.KEY_ARG)?.clearNotification == true) { notificationDrawerManager.clearAllEvents() - intent.removeExtra(EXTRA_CLEAR_EXISTING_NOTIFICATION) } } @@ -293,14 +307,15 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } companion object { - private const val EXTRA_CLEAR_EXISTING_NOTIFICATION = "EXTRA_CLEAR_EXISTING_NOTIFICATION" - private const val EXTRA_ACCOUNT_CREATION = "EXTRA_ACCOUNT_CREATION" - fun newIntent(context: Context, clearNotification: Boolean = false, accountCreation: Boolean = false): Intent { + val args = HomeActivityArgs( + clearNotification = clearNotification, + accountCreation = accountCreation + ) + return Intent(context, HomeActivity::class.java) .apply { - putExtra(EXTRA_CLEAR_EXISTING_NOTIFICATION, clearNotification) - putExtra(EXTRA_ACCOUNT_CREATION, accountCreation) + putExtra(MvRx.KEY_ARG, args) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivitySharedAction.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivitySharedAction.kt index a074c0e879..f4fa8f08a4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivitySharedAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivitySharedAction.kt @@ -25,5 +25,6 @@ sealed class HomeActivitySharedAction : VectorSharedAction { object OpenDrawer : HomeActivitySharedAction() object CloseDrawer : HomeActivitySharedAction() object OpenGroup : HomeActivitySharedAction() + // TODO Remove? object PromptForSecurityBootstrap : HomeActivitySharedAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt index 7ad6cb8528..9849a17b48 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt @@ -16,18 +16,87 @@ package im.vector.riotx.features.home -import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRx +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth +import im.vector.matrix.rx.asObservable +import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.features.login.ReAuthHelper +import timber.log.Timber -data class EmptyState( - val dummy: Boolean = false -) : MvRxState +class HomeActivityViewModel @AssistedInject constructor( + @Assisted initialState: HomeActivityViewState, + @Assisted private val args: HomeActivityArgs, + private val activeSessionHolder: ActiveSessionHolder, + private val reAuthHelper: ReAuthHelper +) : VectorViewModel(initialState) { -class HomeActivityViewModel : VectorViewModel(EmptyState()) { + @AssistedInject.Factory + interface Factory { + fun create(initialState: HomeActivityViewState, args: HomeActivityArgs): HomeActivityViewModel + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: HomeActivityViewState): HomeActivityViewModel? { + val activity: HomeActivity = viewModelContext.activity() + val args: HomeActivityArgs? = activity.intent.getParcelableExtra(MvRx.KEY_ARG) + return args?.let { activity.viewModelFactory.create(state, it) } + } + } + + // TODO Remove? var hasDisplayedCompleteSecurityPrompt: Boolean = false - var isAccountCreation: Boolean = false + + init { + observeInitialSync() + mayBeInitializeCrossSigning() + } + + private fun observeInitialSync() { + val session = activeSessionHolder.getSafeActiveSession() ?: return + + session.getInitialSyncProgressStatus() + .asObservable() + .subscribe { status -> + setState { + copy( + initialSyncProgressServiceStatus = status + ) + } + } + .disposeOnClear() + } + + private fun mayBeInitializeCrossSigning() { + if (args.accountCreation) { + val password = reAuthHelper.data ?: return Unit.also { + Timber.w("No password to init cross signing") + } + + val session = activeSessionHolder.getSafeActiveSession() ?: return Unit.also { + Timber.w("No session to init cross signing") + } + + // We do not use the viewModel context because we do not want to cancel this action + Timber.d("Initialize cross signing") + session.cryptoService().crossSigningService().initializeCrossSigning( + authParams = UserPasswordAuth( + session = null, + user = session.myUserId, + password = password + ) + ) + // TODO Download keys? + } + } override fun handle(action: EmptyAction) { // NA diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewState.kt new file mode 100644 index 0000000000..2b29305c71 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewState.kt @@ -0,0 +1,24 @@ +/* + * 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.riotx.features.home + +import com.airbnb.mvrx.MvRxState +import im.vector.matrix.android.api.session.InitialSyncProgressService + +data class HomeActivityViewState( + val initialSyncProgressServiceStatus: InitialSyncProgressService.Status = InitialSyncProgressService.Status.Idle +) : MvRxState From e3dc6e307f634ab707bb3c2c0e91ff19989931cf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 9 Jun 2020 13:06:21 +0200 Subject: [PATCH 04/30] Gossip MSK --- .../crosssigning/CrossSigningService.kt | 1 + .../internal/crypto/DefaultCryptoService.kt | 5 ++++ .../crypto/IncomingGossipingRequestManager.kt | 3 ++- .../DefaultCrossSigningService.kt | 27 +++++++++++++++++++ .../internal/crypto/store/IMXCryptoStore.kt | 1 + .../crypto/store/db/RealmCryptoStore.kt | 8 ++++++ .../DefaultVerificationService.kt | 5 +++- 7 files changed, 48 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt index f1c998cee5..6e4960b821 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt @@ -74,6 +74,7 @@ interface CrossSigningService { otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult + fun onSecretMSKGossip(mskPrivateKey: String) fun onSecretSSKGossip(sskPrivateKey: String) fun onSecretUSKGossip(uskPrivateKey: String) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index a588cdc66e..4b031211e1 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -34,6 +34,7 @@ import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener @@ -835,6 +836,10 @@ internal class DefaultCryptoService @Inject constructor( */ private fun handleSDKLevelGossip(secretName: String?, secretValue: String): Boolean { return when (secretName) { + MASTER_KEY_SSSS_NAME -> { + crossSigningService.onSecretMSKGossip(secretValue) + true + } SELF_SIGNING_KEY_SSSS_NAME -> { crossSigningService.onSecretSSKGossip(secretValue) true diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt index 38f81ba47d..addcc539e4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.crypto.MXCryptoConfig import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener @@ -310,8 +311,8 @@ internal class IncomingGossipingRequestManager @Inject constructor( val isDeviceLocallyVerified = cryptoStore.getUserDevice(userId, deviceId)?.trustLevel?.isLocallyVerified() - // Should SDK always Silently reject any request for the master key? when (secretName) { + MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 2fee8130fb..445c44de0e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -168,6 +168,33 @@ internal class DefaultCrossSigningService @Inject constructor( }.executeBy(taskExecutor) } + override fun onSecretMSKGossip(mskPrivateKey: String) { + Timber.i("## CrossSigning - onSecretSSKGossip") + val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { + Timber.e("## CrossSigning - onSecretMSKGossip() received secret but public key is not known") + } + + mskPrivateKey.fromBase64() + .let { privateKeySeed -> + val pkSigning = OlmPkSigning() + try { + if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { + masterPkSigning?.releaseSigning() + masterPkSigning = pkSigning + Timber.i("## CrossSigning - Loading MSK success") + cryptoStore.storeMSKPrivateKey(mskPrivateKey) + return + } else { + Timber.e("## CrossSigning - onSecretMSKGossip() private key do not match public key") + pkSigning.releaseSigning() + } + } catch (failure: Throwable) { + Timber.e("## CrossSigning - onSecretMSKGossip() ${failure.localizedMessage}") + pkSigning.releaseSigning() + } + } + } + override fun onSecretSSKGossip(sskPrivateKey: String) { Timber.i("## CrossSigning - onSecretSSKGossip") val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt index 69f0985391..7125cadebf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt @@ -397,6 +397,7 @@ internal interface IMXCryptoStore { fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean) fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) + fun storeMSKPrivateKey(msk: String?) fun storeSSKPrivateKey(ssk: String?) fun storeUSKPrivateKey(usk: String?) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index a78ab70b72..7910bfc72c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -395,6 +395,14 @@ internal class RealmCryptoStore @Inject constructor( } } + override fun storeMSKPrivateKey(msk: String?) { + doRealmTransaction(realmConfiguration) { realm -> + realm.where().findFirst()?.apply { + xSignMasterPrivateKey = msk + } + } + } + override fun storeSSKPrivateKey(ssk: String?) { doRealmTransaction(realmConfiguration) { realm -> realm.where().findFirst()?.apply { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt index 0f1666bd9d..1b50d3caa1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.verification.CancelCode @@ -809,6 +810,8 @@ internal class DefaultVerificationService @Inject constructor( ?.let { vt -> val otherDeviceId = vt.otherDeviceId if (!crossSigningService.canCrossSign()) { + outgoingGossipingRequestManager.sendSecretShareRequest(MASTER_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId + ?: "*"))) outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId @@ -821,7 +824,7 @@ internal class DefaultVerificationService @Inject constructor( } private fun handleDoneReceived(senderId: String, doneReq: ValidVerificationDone) { - Timber.v("## SAS Done receieved $doneReq") + Timber.v("## SAS Done received $doneReq") val existing = getExistingTransaction(senderId, doneReq.transactionId) if (existing == null) { Timber.e("## SAS Received invalid Done request") From aa2b62976e764593f152d057fe04e7c76989e959 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 9 Jun 2020 18:12:03 +0200 Subject: [PATCH 05/30] Fix bad comment --- .../android/internal/crypto/tasks/UploadSigningKeysTask.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSigningKeysTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSigningKeysTask.kt index 238a9c068d..5d08532f68 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSigningKeysTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSigningKeysTask.kt @@ -32,11 +32,11 @@ import javax.inject.Inject internal interface UploadSigningKeysTask : Task { data class Params( - // the device keys to send. + // the MSK val masterKey: CryptoCrossSigningKey, - // the one-time keys to send. + // the USK val userKey: CryptoCrossSigningKey, - // the explicit device_id to use for upload (default is to use the same as that used during auth). + // the SSK val selfSignedKey: CryptoCrossSigningKey, /** * - If null: From 8df7797f6d958a5d39625455e05cdbf9295d650e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 9 Jun 2020 22:48:01 +0200 Subject: [PATCH 06/30] Remove BootstrapStep.SetupPassphrase and BootstrapStep.ConfirmPassphrase --- .../im/vector/riotx/core/di/FragmentModule.kt | 12 -- .../crypto/recover/BootstrapActions.kt | 4 - .../crypto/recover/BootstrapBottomSheet.kt | 10 -- .../BootstrapConfirmPassphraseFragment.kt | 118 ---------------- .../recover/BootstrapCrossSigningTask.kt | 26 +--- .../BootstrapEnterPassphraseFragment.kt | 126 ----------------- .../recover/BootstrapMigrateBackupFragment.kt | 6 +- .../recover/BootstrapSharedViewModel.kt | 133 +++--------------- .../features/crypto/recover/BootstrapStep.kt | 38 +++-- 9 files changed, 47 insertions(+), 426 deletions(-) delete mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt delete mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index 0b7731ee70..6214677020 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -28,8 +28,6 @@ import im.vector.riotx.features.crypto.quads.SharedSecuredStorageKeyFragment import im.vector.riotx.features.crypto.quads.SharedSecuredStoragePassphraseFragment import im.vector.riotx.features.crypto.recover.BootstrapAccountPasswordFragment import im.vector.riotx.features.crypto.recover.BootstrapConclusionFragment -import im.vector.riotx.features.crypto.recover.BootstrapConfirmPassphraseFragment -import im.vector.riotx.features.crypto.recover.BootstrapEnterPassphraseFragment import im.vector.riotx.features.crypto.recover.BootstrapMigrateBackupFragment import im.vector.riotx.features.crypto.recover.BootstrapSaveRecoveryKeyFragment import im.vector.riotx.features.crypto.recover.BootstrapWaitingFragment @@ -453,16 +451,6 @@ interface FragmentModule { @FragmentKey(GossipingEventsPaperTrailFragment::class) fun bindGossipingEventsPaperTrailFragment(fragment: GossipingEventsPaperTrailFragment): Fragment - @Binds - @IntoMap - @FragmentKey(BootstrapEnterPassphraseFragment::class) - fun bindBootstrapEnterPassphraseFragment(fragment: BootstrapEnterPassphraseFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(BootstrapConfirmPassphraseFragment::class) - fun bindBootstrapConfirmPassphraseFragment(fragment: BootstrapConfirmPassphraseFragment): Fragment - @Binds @IntoMap @FragmentKey(BootstrapWaitingFragment::class) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt index 9e4f3ea527..b1e94e551c 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt @@ -24,15 +24,11 @@ sealed class BootstrapActions : VectorViewModelAction { // Navigation object GoBack : BootstrapActions() - data class GoToConfirmPassphrase(val passphrase: String) : BootstrapActions() object GoToCompleted : BootstrapActions() object GoToEnterAccountPassword : BootstrapActions() - data class DoInitialize(val passphrase: String) : BootstrapActions() object DoInitializeGeneratedKey : BootstrapActions() object TogglePasswordVisibility : BootstrapActions() - data class UpdateCandidatePassphrase(val pass: String) : BootstrapActions() - data class UpdateConfirmCandidatePassphrase(val pass: String) : BootstrapActions() data class ReAuth(val pass: String) : BootstrapActions() object RecoveryKeySaved : BootstrapActions() object Completed : BootstrapActions() diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt index d1138c20c7..15edf53a0d 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt @@ -127,16 +127,6 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { bootstrapTitleText.text = getString(R.string.upgrade_security) showFragment(BootstrapWaitingFragment::class, Bundle()) } - is BootstrapStep.SetupPassphrase -> { - bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_password)) - bootstrapTitleText.text = getString(R.string.set_recovery_passphrase, getString(R.string.recovery_passphrase)) - showFragment(BootstrapEnterPassphraseFragment::class, Bundle()) - } - is BootstrapStep.ConfirmPassphrase -> { - bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_password)) - bootstrapTitleText.text = getString(R.string.confirm_recovery_passphrase, getString(R.string.recovery_passphrase)) - showFragment(BootstrapConfirmPassphraseFragment::class, Bundle()) - } is BootstrapStep.AccountPassword -> { bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_user)) bootstrapTitleText.text = getString(R.string.account_password) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt deleted file mode 100644 index ebb6416317..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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.riotx.features.crypto.recover - -import android.os.Bundle -import android.view.View -import android.view.inputmethod.EditorInfo -import androidx.core.text.toSpannable -import androidx.core.view.isGone -import com.airbnb.mvrx.parentFragmentViewModel -import com.airbnb.mvrx.withState -import com.jakewharton.rxbinding3.widget.editorActionEvents -import com.jakewharton.rxbinding3.widget.textChanges -import im.vector.riotx.R -import im.vector.riotx.core.extensions.hideKeyboard -import im.vector.riotx.core.extensions.showPassword -import im.vector.riotx.core.platform.VectorBaseFragment -import im.vector.riotx.core.resources.ColorProvider -import im.vector.riotx.core.utils.colorizeMatchingText -import io.reactivex.android.schedulers.AndroidSchedulers -import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.* -import java.util.concurrent.TimeUnit -import javax.inject.Inject - -class BootstrapConfirmPassphraseFragment @Inject constructor( - private val colorProvider: ColorProvider -) : VectorBaseFragment() { - - override fun getLayoutResId() = R.layout.fragment_bootstrap_enter_passphrase - - val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - ssss_passphrase_security_progress.isGone = true - - val recPassPhrase = getString(R.string.recovery_passphrase) - bootstrapDescriptionText.text = getString(R.string.bootstrap_info_confirm_text, recPassPhrase) - .toSpannable() - .colorizeMatchingText(recPassPhrase, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) - - ssss_passphrase_enter_edittext.hint = getString(R.string.passphrase_confirm_passphrase) - - withState(sharedViewModel) { - // set initial value (useful when coming back) - ssss_passphrase_enter_edittext.setText(it.passphraseRepeat ?: "") - ssss_passphrase_enter_edittext.requestFocus() - } - - ssss_passphrase_enter_edittext.editorActionEvents() - .throttleFirst(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - if (it.actionId == EditorInfo.IME_ACTION_DONE) { - submit() - } - } - .disposeOnDestroyView() - - ssss_passphrase_enter_edittext.textChanges() - .subscribe { - ssss_passphrase_enter_til.error = null - sharedViewModel.handle(BootstrapActions.UpdateConfirmCandidatePassphrase(it?.toString() ?: "")) - } - .disposeOnDestroyView() - - sharedViewModel.observeViewEvents { - // when (it) { -// is SharedSecureStorageViewEvent.InlineError -> { -// ssss_passphrase_enter_til.error = it.message -// } -// } - } - - ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) } - bootstrapSubmit.debouncedClicks { submit() } - } - - private fun submit() = withState(sharedViewModel) { state -> - if (state.step !is BootstrapStep.ConfirmPassphrase) { - return@withState - } - val passphrase = ssss_passphrase_enter_edittext.text?.toString() - when { - passphrase.isNullOrBlank() -> - ssss_passphrase_enter_til.error = getString(R.string.passphrase_empty_error_message) - passphrase != state.passphrase -> - ssss_passphrase_enter_til.error = getString(R.string.passphrase_passphrase_does_not_match) - else -> { - view?.hideKeyboard() - sharedViewModel.handle(BootstrapActions.DoInitialize(passphrase)) - } - } - } - - override fun invalidate() = withState(sharedViewModel) { state -> - if (state.step is BootstrapStep.ConfirmPassphrase) { - val isPasswordVisible = state.step.isPasswordVisible - ssss_passphrase_enter_edittext.showPassword(isPasswordVisible, updateCursor = false) - ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black) - } - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt index c5dc24bf0a..bcb1f6fefd 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -68,7 +68,6 @@ interface BootstrapProgressListener { data class Params( val userPasswordAuth: UserPasswordAuth? = null, val progressListener: BootstrapProgressListener? = null, - val passphrase: String?, val keySpec: SsssKeySpec? = null ) @@ -105,24 +104,13 @@ class BootstrapCrossSigningTask @Inject constructor( ) try { keyInfo = awaitCallback { - params.passphrase?.let { passphrase -> - ssssService.generateKeyWithPassphrase( - UUID.randomUUID().toString(), - "ssss_key", - passphrase, - EmptyKeySigner(), - null, - it - ) - } ?: kotlin.run { - ssssService.generateKey( - UUID.randomUUID().toString(), - params.keySpec, - "ssss_key", - EmptyKeySigner(), - it - ) - } + ssssService.generateKey( + UUID.randomUUID().toString(), + params.keySpec, + "ssss_key", + EmptyKeySigner(), + it + ) } } catch (failure: Failure) { return BootstrapResult.FailedToCreateSSSSKey(failure) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt deleted file mode 100644 index 982f72c14e..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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.riotx.features.crypto.recover - -import android.os.Bundle -import android.view.View -import android.view.inputmethod.EditorInfo -import androidx.core.text.toSpannable -import com.airbnb.mvrx.parentFragmentViewModel -import com.airbnb.mvrx.withState -import com.jakewharton.rxbinding3.widget.editorActionEvents -import com.jakewharton.rxbinding3.widget.textChanges -import im.vector.riotx.R -import im.vector.riotx.core.extensions.showPassword -import im.vector.riotx.core.platform.VectorBaseFragment -import im.vector.riotx.core.resources.ColorProvider -import im.vector.riotx.core.utils.colorizeMatchingText -import im.vector.riotx.features.settings.VectorLocale -import io.reactivex.android.schedulers.AndroidSchedulers -import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.* -import java.util.concurrent.TimeUnit -import javax.inject.Inject - -class BootstrapEnterPassphraseFragment @Inject constructor( - private val colorProvider: ColorProvider -) : VectorBaseFragment() { - - override fun getLayoutResId() = R.layout.fragment_bootstrap_enter_passphrase - - val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val recPassPhrase = getString(R.string.recovery_passphrase) - bootstrapDescriptionText.text = getString(R.string.bootstrap_info_text, recPassPhrase) - .toSpannable() - .colorizeMatchingText(recPassPhrase, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) - - ssss_passphrase_enter_edittext.hint = getString(R.string.passphrase_enter_passphrase) - withState(sharedViewModel) { - // set initial value (useful when coming back) - ssss_passphrase_enter_edittext.setText(it.passphrase ?: "") - } - ssss_passphrase_enter_edittext.editorActionEvents() - .throttleFirst(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - if (it.actionId == EditorInfo.IME_ACTION_DONE) { - submit() - } - } - .disposeOnDestroyView() - - ssss_passphrase_enter_edittext.textChanges() - .subscribe { - // ssss_passphrase_enter_til.error = null - sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: "")) -// ssss_passphrase_submit.isEnabled = it.isNotBlank() - } - .disposeOnDestroyView() - - sharedViewModel.observeViewEvents { - // when (it) { -// is SharedSecureStorageViewEvent.InlineError -> { -// ssss_passphrase_enter_til.error = it.message -// } -// } - } - - ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) } - bootstrapSubmit.debouncedClicks { submit() } - } - - private fun submit() = withState(sharedViewModel) { state -> - if (state.step !is BootstrapStep.SetupPassphrase) { - return@withState - } - val score = state.passphraseStrength.invoke()?.score - val passphrase = ssss_passphrase_enter_edittext.text?.toString() - if (passphrase.isNullOrBlank()) { - ssss_passphrase_enter_til.error = getString(R.string.passphrase_empty_error_message) - } else if (score != 4) { - ssss_passphrase_enter_til.error = getString(R.string.passphrase_passphrase_too_weak) - } else { - sharedViewModel.handle(BootstrapActions.GoToConfirmPassphrase(passphrase)) - } - } - - override fun invalidate() = withState(sharedViewModel) { state -> - if (state.step is BootstrapStep.SetupPassphrase) { - val isPasswordVisible = state.step.isPasswordVisible - ssss_passphrase_enter_edittext.showPassword(isPasswordVisible, updateCursor = false) - ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black) - - state.passphraseStrength.invoke()?.let { strength -> - val score = strength.score - ssss_passphrase_security_progress.strength = score - if (score in 1..3) { - val hint = - strength.feedback?.getWarning(VectorLocale.applicationLocale)?.takeIf { it.isNotBlank() } - ?: strength.feedback?.getSuggestions(VectorLocale.applicationLocale)?.firstOrNull() - if (hint != null && hint != ssss_passphrase_enter_til.error.toString()) { - ssss_passphrase_enter_til.error = hint - } - } else { - ssss_passphrase_enter_til.error = null - } - } - } - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt index 0b8e201edd..ee583e0381 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt @@ -51,15 +51,11 @@ class BootstrapMigrateBackupFragment @Inject constructor( override fun getLayoutResId() = R.layout.fragment_bootstrap_migrate_backup - val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel() + private val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - withState(sharedViewModel) { - // set initial value (useful when coming back) - bootstrapMigrateEditText.setText(it.passphrase ?: "") - } bootstrapMigrateEditText.editorActionEvents() .throttleFirst(300, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt index 78e5cc1fb3..b887df90ca 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt @@ -26,8 +26,6 @@ import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext -import com.nulabinc.zxcvbn.Strength -import com.nulabinc.zxcvbn.Zxcvbn import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.failure.Failure @@ -49,13 +47,9 @@ import kotlinx.coroutines.launch import java.io.OutputStream data class BootstrapViewState( - val step: BootstrapStep = BootstrapStep.SetupPassphrase(false), - val passphrase: String? = null, + val step: BootstrapStep = BootstrapStep.AccountPassword(false), val migrationRecoveryKey: String? = null, - val passphraseRepeat: String? = null, val crossSigningInitialization: Async = Uninitialized, - val passphraseStrength: Async = Uninitialized, - val passphraseConfirmMatch: Async = Uninitialized, val recoveryKeyCreationInfo: SsssKeyCreationInfo? = null, val initializationWaitingViewData: WaitingViewData? = null, val recoverySaveFileProcess: Async = Uninitialized @@ -71,8 +65,6 @@ class BootstrapSharedViewModel @AssistedInject constructor( private val reAuthHelper: ReAuthHelper ) : VectorViewModel(initialState) { - private val zxcvbn = Zxcvbn() - @AssistedInject.Factory interface Factory { fun create(initialState: BootstrapViewState, args: BootstrapBottomSheet.Args): BootstrapSharedViewModel @@ -84,7 +76,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( // need to check if user have an existing keybackup if (args.isNewAccount) { setState { - copy(step = BootstrapStep.SetupPassphrase(false)) + copy(step = BootstrapStep.AccountPassword(false)) } } else { setState { @@ -99,7 +91,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( if (version == null) { // we just resume plain bootstrap setState { - copy(step = BootstrapStep.SetupPassphrase(false)) + copy(step = BootstrapStep.AccountPassword(false)) } } else { // we need to get existing backup passphrase/key and convert to SSSS @@ -128,19 +120,9 @@ class BootstrapSharedViewModel @AssistedInject constructor( override fun handle(action: BootstrapActions) = withState { state -> when (action) { - is BootstrapActions.GoBack -> queryBack() - BootstrapActions.TogglePasswordVisibility -> { + is BootstrapActions.GoBack -> queryBack() + BootstrapActions.TogglePasswordVisibility -> { when (state.step) { - is BootstrapStep.SetupPassphrase -> { - setState { - copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible)) - } - } - is BootstrapStep.ConfirmPassphrase -> { - setState { - copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible)) - } - } is BootstrapStep.AccountPassword -> { setState { copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible)) @@ -155,119 +137,64 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } } - - is BootstrapActions.UpdateCandidatePassphrase -> { - val strength = zxcvbn.measure(action.pass) - setState { - copy( - passphrase = action.pass, - passphraseStrength = Success(strength) - ) - } - } - is BootstrapActions.GoToConfirmPassphrase -> { - setState { - copy( - passphrase = action.passphrase, - step = BootstrapStep.ConfirmPassphrase( - isPasswordVisible = (state.step as? BootstrapStep.SetupPassphrase)?.isPasswordVisible ?: false - ) - ) - } - } - is BootstrapActions.UpdateConfirmCandidatePassphrase -> { - setState { - copy( - passphraseRepeat = action.pass - ) - } - } - is BootstrapActions.DoInitialize -> { - if (state.passphrase == state.passphraseRepeat) { - val userPassword = reAuthHelper.data - if (userPassword == null) { - setState { - copy( - step = BootstrapStep.AccountPassword(false) - ) - } - } else { - startInitializeFlow(userPassword) - } - } else { - setState { - copy( - passphraseConfirmMatch = Fail(Throwable(stringProvider.getString(R.string.passphrase_passphrase_does_not_match))) - ) - } - } - } - is BootstrapActions.DoInitializeGeneratedKey -> { + is BootstrapActions.DoInitializeGeneratedKey -> { val userPassword = reAuthHelper.data if (userPassword == null) { setState { copy( - passphrase = null, - passphraseRepeat = null, step = BootstrapStep.AccountPassword(false) ) } } else { - setState { - copy( - passphrase = null, - passphraseRepeat = null - ) - } startInitializeFlow(userPassword) } } - BootstrapActions.RecoveryKeySaved -> { + BootstrapActions.RecoveryKeySaved -> { _viewEvents.post(BootstrapViewEvents.RecoveryKeySaved) setState { copy(step = BootstrapStep.SaveRecoveryKey(true)) } } - BootstrapActions.Completed -> { + BootstrapActions.Completed -> { _viewEvents.post(BootstrapViewEvents.Dismiss) } - BootstrapActions.GoToCompleted -> { + BootstrapActions.GoToCompleted -> { setState { copy(step = BootstrapStep.DoneSuccess) } } - BootstrapActions.SaveReqQueryStarted -> { + BootstrapActions.SaveReqQueryStarted -> { setState { copy(recoverySaveFileProcess = Loading()) } } - is BootstrapActions.SaveKeyToUri -> { + is BootstrapActions.SaveKeyToUri -> { saveRecoveryKeyToUri(action.os) } - BootstrapActions.SaveReqFailed -> { + BootstrapActions.SaveReqFailed -> { setState { copy(recoverySaveFileProcess = Uninitialized) } } - BootstrapActions.GoToEnterAccountPassword -> { + BootstrapActions.GoToEnterAccountPassword -> { setState { copy(step = BootstrapStep.AccountPassword(false)) } } - BootstrapActions.HandleForgotBackupPassphrase -> { + BootstrapActions.HandleForgotBackupPassphrase -> { if (state.step is BootstrapStep.GetBackupSecretPassForMigration) { setState { copy(step = BootstrapStep.GetBackupSecretPassForMigration(state.step.isPasswordVisible, true)) } } else return@withState } - is BootstrapActions.ReAuth -> { + is BootstrapActions.ReAuth -> { startInitializeFlow(action.pass) } - is BootstrapActions.DoMigrateWithPassphrase -> { + is BootstrapActions.DoMigrateWithPassphrase -> { startMigrationFlow(state.step, action.passphrase, null) } - is BootstrapActions.DoMigrateWithRecoveryKey -> { + is BootstrapActions.DoMigrateWithRecoveryKey -> { startMigrationFlow(state.step, null, action.recoveryKey) } }.exhaustive @@ -316,8 +243,6 @@ class BootstrapSharedViewModel @AssistedInject constructor( if (it is BackupToQuadSMigrationTask.Result.Success) { setState { copy( - passphrase = passphrase, - passphraseRepeat = passphrase, migrationRecoveryKey = recoveryKey ) } @@ -365,6 +290,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( } withState { state -> + val previousStep = state.step viewModelScope.launch(Dispatchers.IO) { val userPasswordAuth = userPassword?.let { UserPasswordAuth( @@ -379,7 +305,6 @@ class BootstrapSharedViewModel @AssistedInject constructor( Params( userPasswordAuth = userPasswordAuth, progressListener = progressListener, - passphrase = state.passphrase, keySpec = state.migrationRecoveryKey?.let { extractCurveKeyFromRecoveryKey(it)?.let { RawBytesKeySpec(it) } } ) ) { bootstrapResult -> @@ -423,7 +348,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( _viewEvents.post(BootstrapViewEvents.ModalError(bootstrapResult.error ?: stringProvider.getString(R.string.matrix_error))) setState { copy( - step = BootstrapStep.ConfirmPassphrase(false) + step = previousStep ) } } @@ -459,25 +384,12 @@ class BootstrapSharedViewModel @AssistedInject constructor( // do we let you cancel from here? _viewEvents.post(BootstrapViewEvents.SkipBootstrap()) } - is BootstrapStep.SetupPassphrase -> { - // do we let you cancel from here? - _viewEvents.post(BootstrapViewEvents.SkipBootstrap()) - } - is BootstrapStep.ConfirmPassphrase -> { - setState { - copy( - step = BootstrapStep.SetupPassphrase( - isPasswordVisible = (state.step as? BootstrapStep.ConfirmPassphrase)?.isPasswordVisible ?: false - ) - ) - } - } is BootstrapStep.AccountPassword -> { - _viewEvents.post(BootstrapViewEvents.SkipBootstrap(state.passphrase != null)) + _viewEvents.post(BootstrapViewEvents.SkipBootstrap(false)) } BootstrapStep.Initializing -> { // do we let you cancel from here? - _viewEvents.post(BootstrapViewEvents.SkipBootstrap(state.passphrase != null)) + _viewEvents.post(BootstrapViewEvents.SkipBootstrap(false)) } is BootstrapStep.SaveRecoveryKey, BootstrapStep.DoneSuccess -> { @@ -490,7 +402,8 @@ class BootstrapSharedViewModel @AssistedInject constructor( // Companion, view model assisted creation // ====================================== - companion object : MvRxViewModelFactory { + companion object + : MvRxViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: BootstrapViewState): BootstrapSharedViewModel? { val fragment: BootstrapBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt index 2c649ae99c..1855a3b063 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt @@ -30,29 +30,25 @@ package im.vector.riotx.features.crypto.recover * └───────────────────────────────────┘ │ * │ │ * │ │ - * Existing ├─────────No ───────┐ │ - * ┌────Keybackup───────┘ KeyBackup │ │ - * │ │ │ - * │ ▼ ▼ - * ▼ ┌────────────────────────────────────┐ - * ┌─────────────────────────────────────────┐ │ BootstrapStep.SetupPassphrase │◀─┐ - * │BootstrapStep.GetBackupSecretForMigration│ └────────────────────────────────────┘ │ - * └─────────────────────────────────────────┘ │ │ - * │ │ ┌Back - * │ ▼ │ - * │ ┌────────────────────────────────────┤ - * │ │ BootstrapStep.ConfirmPassphrase │──┐ - * │ └────────────────────────────────────┘ │ - * │ │ │ - * │ is password needed? │ - * │ │ │ - * │ ▼ │ + * Existing ├─────────No ──────────────────┐ + * ┌────Keybackup───────┘ KeyBackup │ + * │ │ + * │ │ + * ▼ │ + * ┌─────────────────────────────────────────┐ │ + * │BootstrapStep.GetBackupSecretForMigration│ │ + * └─────────────────────────────────────────┘ │ + * │ │ + * │ │ + * │ is password needed? ─────────────┐ + * │ │ │ + * │ ▼ │ * │ ┌────────────────────────────────────┐ │ * │ │ BootstrapStep.AccountPassword │ │ * │ └────────────────────────────────────┘ │ - * │ │ │ - * │ │ │ - * │ ┌──────────────────┘ password not needed (in + * │ │ │ + * │ │ │ + * │ ┌──────────────┘ password not needed (in * │ │ memory) * │ │ │ * │ ▼ │ @@ -77,8 +73,6 @@ package im.vector.riotx.features.crypto.recover */ sealed class BootstrapStep { - data class SetupPassphrase(val isPasswordVisible: Boolean) : BootstrapStep() - data class ConfirmPassphrase(val isPasswordVisible: Boolean) : BootstrapStep() data class AccountPassword(val isPasswordVisible: Boolean, val failure: String? = null) : BootstrapStep() object CheckingMigration : BootstrapStep() From e4ac28877c3473f1212ebd98642e895529ed8790 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jun 2020 11:14:05 +0200 Subject: [PATCH 07/30] Ask user password to initialize CrossSigning And migrate some logic to the ViewModel --- .../crosssigning/CrossSigningService.kt | 2 +- .../DefaultCrossSigningService.kt | 6 +- .../model/rest/SignatureUploadResponse.kt | 1 + .../crypto/recover/BootstrapBottomSheet.kt | 7 +- .../recover/BootstrapSharedViewModel.kt | 2 +- .../riotx/features/home/HomeActivity.kt | 106 ++++++------------ .../features/home/HomeActivitySharedAction.kt | 2 - .../features/home/HomeActivityViewEvents.kt | 25 +++++ .../features/home/HomeActivityViewModel.kt | 74 +++++++++++- .../features/navigation/DefaultNavigator.kt | 4 +- .../riotx/features/navigation/Navigator.kt | 2 +- .../DeviceVerificationInfoEpoxyController.kt | 2 +- 12 files changed, 140 insertions(+), 93 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewEvents.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt index 6e4960b821..2f23646460 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt @@ -41,7 +41,7 @@ interface CrossSigningService { * Users needs to enter credentials */ fun initializeCrossSigning(authParams: UserPasswordAuth?, - callback: MatrixCallback? = null) + callback: MatrixCallback) fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, uskKeyPrivateKey: String?, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 445c44de0e..06355e6d72 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -140,7 +140,7 @@ internal class DefaultCrossSigningService @Inject constructor( * - Sign the keys and upload them * - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures */ - override fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback?) { + override fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback) { Timber.d("## CrossSigning initializeCrossSigning") val params = InitializeCrossSigningTask.Params( @@ -150,7 +150,7 @@ internal class DefaultCrossSigningService @Inject constructor( this.callbackThread = TaskThread.CRYPTO this.callback = object : MatrixCallback { override fun onFailure(failure: Throwable) { - callback?.onFailure(failure) + callback.onFailure(failure) } override fun onSuccess(data: InitializeCrossSigningTask.Result) { @@ -162,7 +162,7 @@ internal class DefaultCrossSigningService @Inject constructor( userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) } selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) } - callback?.onSuccess(Unit) + callback.onSuccess(Unit) } } }.executeBy(taskExecutor) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt index ef459fbc59..7bc5ecc6fc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt @@ -36,6 +36,7 @@ internal data class SignatureUploadResponse( ) +// TODO Not used. Remove? @JsonClass(generateAdapter = true) data class UploadResponseFailure( @Json(name = "status") diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt index 15edf53a0d..a5bab9be5a 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt @@ -44,7 +44,8 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { @Parcelize data class Args( - val isNewAccount: Boolean + val isNewAccount: Boolean, + val initCrossSigningOnly: Boolean ) : Parcelable override val showExpanded = true @@ -168,10 +169,10 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { const val EXTRA_ARGS = "EXTRA_ARGS" - fun show(fragmentManager: FragmentManager, isAccountCreation: Boolean) { + fun show(fragmentManager: FragmentManager, isAccountCreation: Boolean, initCrossSigningOnly: Boolean) { BootstrapBottomSheet().apply { isCancelable = false - arguments = Bundle().apply { this.putParcelable(EXTRA_ARGS, Args(isAccountCreation)) } + arguments = Bundle().apply { this.putParcelable(EXTRA_ARGS, Args(isAccountCreation, initCrossSigningOnly)) } }.show(fragmentManager, "BootstrapBottomSheet") } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt index b887df90ca..52ccd8a10d 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt @@ -408,7 +408,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(true) + ?: BootstrapBottomSheet.Args(isNewAccount = true, initCrossSigningOnly = true) return fragment.bootstrapViewModelFactory.create(state, args) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt index 526ae3a70d..8d5fc5f564 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt @@ -23,12 +23,14 @@ import android.os.Parcelable import android.view.MenuItem import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar +import androidx.core.content.ContextCompat import androidx.core.view.GravityCompat import androidx.core.view.isVisible import androidx.drawerlayout.widget.DrawerLayout import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.viewModel import im.vector.matrix.android.api.session.InitialSyncProgressService +import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ScreenComponent @@ -38,10 +40,10 @@ import im.vector.riotx.core.extensions.replaceFragment import im.vector.riotx.core.platform.ToolbarConfigurable import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.pushers.PushersManager -import im.vector.riotx.features.crypto.recover.BootstrapBottomSheet import im.vector.riotx.features.disclaimer.showDisclaimerDialog import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.popup.PopupAlertManager +import im.vector.riotx.features.popup.VerificationVectorAlert import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.workers.signout.SignOutViewModel @@ -104,15 +106,12 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet .observe() .subscribe { sharedAction -> when (sharedAction) { - is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) - is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START) - is HomeActivitySharedAction.OpenGroup -> { + is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) + is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START) + is HomeActivitySharedAction.OpenGroup -> { drawerLayout.closeDrawer(GravityCompat.START) replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java) } - is HomeActivitySharedAction.PromptForSecurityBootstrap -> { - BootstrapBottomSheet.show(supportFragmentManager, true) - } }.exhaustive } .disposeOnDestroy() @@ -123,16 +122,13 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet notificationDrawerManager.clearAllEvents() } - homeActivityViewModel.subscribe(this) { renderState(it) } - - /* - // TODO Remove - // Ask again if the app is relaunched - if (!homeActivityViewModel.hasDisplayedCompleteSecurityPrompt - && activeSessionHolder.getSafeActiveSession()?.hasAlreadySynced() == true) { - promptCompleteSecurityIfNeeded() + homeActivityViewModel.observeViewEvents { + when (it) { + is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it) + is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) + }.exhaustive } - */ + homeActivityViewModel.subscribe(this) { renderState(it) } shortcutsHandler.observeRoomsAndBuildShortcuts() .disposeOnDestroy() @@ -144,7 +140,6 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet waiting_view.isVisible = false } is InitialSyncProgressService.Status.Progressing -> { - homeActivityViewModel.hasDisplayedCompleteSecurityPrompt = false Timber.v("${getString(status.statusText)} ${status.percentProgress}") waiting_view.setOnClickListener { // block interactions @@ -164,63 +159,29 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet }.exhaustive } - /* - // TODO Remove - private fun promptCompleteSecurityIfNeeded() { - val session = activeSessionHolder.getSafeActiveSession() ?: return - if (!session.hasAlreadySynced()) return - if (homeActivityViewModel.hasDisplayedCompleteSecurityPrompt) return - - // ensure keys are downloaded - session.cryptoService().downloadKeys(listOf(session.myUserId), true, object : MatrixCallback> { - override fun onSuccess(data: MXUsersDevicesMap) { - runOnUiThread { - alertCompleteSecurity(session) - } - } - }) - } - */ - - // TODO Remove - /* - private fun alertCompleteSecurity(session: Session) { - val myCrossSigningKeys = session.cryptoService().crossSigningService() - .getMyCrossSigningKeys() - val crossSigningEnabledOnAccount = myCrossSigningKeys != null - - if (!crossSigningEnabledOnAccount && !homeActivityViewModel.isAccountCreation) { - // Do not propose for SSO accounts, because we do not support yet confirming account credentials using SSO - if (session.getHomeServerCapabilities().canChangePassword) { - // We need to ask - promptSecurityEvent( - session, - R.string.upgrade_security, - R.string.security_prompt_text - ) { - it.navigator.upgradeSessionSecurity(it) - } - } else { - // Do not do it again - homeActivityViewModel.hasDisplayedCompleteSecurityPrompt = true - } - } else if (myCrossSigningKeys?.isTrusted() == false) { - // We need to ask - promptSecurityEvent( - session, - R.string.crosssigning_verify_this_session, - R.string.confirm_your_identity - ) { - it.navigator.waitSessionVerification(it) - } + private fun handleAskPasswordToInitCrossSigning(events: HomeActivityViewEvents.AskPasswordToInitCrossSigning) { + // We need to ask + promptSecurityEvent( + events.userItem, + R.string.upgrade_security, + R.string.security_prompt_text + ) { + it.navigator.upgradeSessionSecurity(it, true) } } - */ - /* - // TODO Remove - private fun promptSecurityEvent(session: Session, titleRes: Int, descRes: Int, action: ((VectorBaseActivity) -> Unit)) { - homeActivityViewModel.hasDisplayedCompleteSecurityPrompt = true + private fun handleOnNewSession(event: HomeActivityViewEvents.OnNewSession) { + // We need to ask + promptSecurityEvent( + event.userItem, + R.string.crosssigning_verify_this_session, + R.string.confirm_your_identity + ) { + it.navigator.waitSessionVerification(it) + } + } + + private fun promptSecurityEvent(userItem: MatrixItem.UserItem?, titleRes: Int, descRes: Int, action: ((VectorBaseActivity) -> Unit)) { popupAlertManager.postVectorAlert( VerificationVectorAlert( uid = "upgradeSecurity", @@ -228,7 +189,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet description = getString(descRes), iconId = R.drawable.ic_shield_warning ).apply { - matrixItem = session.getUser(session.myUserId)?.toMatrixItem() + matrixItem = userItem colorInt = ContextCompat.getColor(this@HomeActivity, R.color.riotx_positive_accent) contentAction = Runnable { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let { @@ -239,7 +200,6 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } ) } - */ override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivitySharedAction.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivitySharedAction.kt index f4fa8f08a4..49c8de4d8f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivitySharedAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivitySharedAction.kt @@ -25,6 +25,4 @@ sealed class HomeActivitySharedAction : VectorSharedAction { object OpenDrawer : HomeActivitySharedAction() object CloseDrawer : HomeActivitySharedAction() object OpenGroup : HomeActivitySharedAction() - // TODO Remove? - object PromptForSecurityBootstrap : HomeActivitySharedAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewEvents.kt new file mode 100644 index 0000000000..2f1d8b2705 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewEvents.kt @@ -0,0 +1,25 @@ +/* + * 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.riotx.features.home + +import im.vector.matrix.android.api.util.MatrixItem +import im.vector.riotx.core.platform.VectorViewEvents + +sealed class HomeActivityViewEvents : VectorViewEvents { + data class AskPasswordToInitCrossSigning(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents() + data class OnNewSession(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt index 9849a17b48..ab90cd45ab 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt @@ -21,11 +21,16 @@ import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.NoOpMatrixCallback +import im.vector.matrix.android.api.session.InitialSyncProgressService +import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo +import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.matrix.rx.asObservable import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.platform.EmptyAction -import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.login.ReAuthHelper import timber.log.Timber @@ -35,7 +40,7 @@ class HomeActivityViewModel @AssistedInject constructor( @Assisted private val args: HomeActivityArgs, private val activeSessionHolder: ActiveSessionHolder, private val reAuthHelper: ReAuthHelper -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { @@ -52,8 +57,7 @@ class HomeActivityViewModel @AssistedInject constructor( } } - // TODO Remove? - var hasDisplayedCompleteSecurityPrompt: Boolean = false + private var checkBootstrap = false init { observeInitialSync() @@ -66,6 +70,19 @@ class HomeActivityViewModel @AssistedInject constructor( session.getInitialSyncProgressStatus() .asObservable() .subscribe { status -> + when (status) { + is InitialSyncProgressService.Status.Progressing -> { + // Schedule a check of the bootstrap when the init sync will be finished + checkBootstrap = true + } + is InitialSyncProgressService.Status.Idle -> { + if (checkBootstrap) { + checkBootstrap = false + maybeBootstrapCrossSigning() + } + } + } + setState { copy( initialSyncProgressServiceStatus = status @@ -92,12 +109,57 @@ class HomeActivityViewModel @AssistedInject constructor( session = null, user = session.myUserId, password = password - ) + ), + callback = NoOpMatrixCallback() ) - // TODO Download keys? } } + private fun maybeBootstrapCrossSigning() { + // In case of account creation, it is already done before + if (args.accountCreation) return + + val session = activeSessionHolder.getSafeActiveSession() ?: return + + // Ensure keys of the user are downloaded + session.cryptoService().downloadKeys(listOf(session.myUserId), true, object : MatrixCallback> { + override fun onSuccess(data: MXUsersDevicesMap) { + // Is there already cross signing keys here? + val mxCrossSigningInfo = session.cryptoService().crossSigningService().getMyCrossSigningKeys() + if (mxCrossSigningInfo != null) { + // Cross-signing is already set up for this user, is it trusted? + if(!mxCrossSigningInfo.isTrusted()) { + // New session + _viewEvents.post(HomeActivityViewEvents.OnNewSession(session.getUser(session.myUserId)?.toMatrixItem())) + } + } else { + // Initialize cross-signing + val password = reAuthHelper.data + + if (password == null) { + // Check this is not an SSO account + if (session.getHomeServerCapabilities().canChangePassword) { + // Ask password to the user: Upgrade security + _viewEvents.post(HomeActivityViewEvents.AskPasswordToInitCrossSigning(session.getUser(session.myUserId)?.toMatrixItem())) + } + // Else (SSO) just ignore for the moment + } else { + // We do not use the viewModel context because we do not want to cancel this action + Timber.d("Initialize cross signing") + session.cryptoService().crossSigningService().initializeCrossSigning( + authParams = UserPasswordAuth( + session = null, + user = session.myUserId, + password = password + ), + callback = NoOpMatrixCallback() + ) + } + } + } + }) + } + override fun handle(action: EmptyAction) { // NA } diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index a909e5becf..5b70d65523 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -124,9 +124,9 @@ class DefaultNavigator @Inject constructor( } } - override fun upgradeSessionSecurity(context: Context) { + override fun upgradeSessionSecurity(context: Context, initCrossSigningOnly: Boolean) { if (context is VectorBaseActivity) { - BootstrapBottomSheet.show(context.supportFragmentManager, false) + BootstrapBottomSheet.show(context.supportFragmentManager, false, initCrossSigningOnly) } } diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt index 916a46c041..ce4d5ef3ea 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt @@ -42,7 +42,7 @@ interface Navigator { fun waitSessionVerification(context: Context) - fun upgradeSessionSecurity(context: Context) + fun upgradeSessionSecurity(context: Context, initCrossSigningOnly: Boolean) fun openRoomForSharingAndFinish(activity: Activity, roomId: String, sharedData: SharedData) diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoEpoxyController.kt index 4123e260e2..ddec06b9a2 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoEpoxyController.kt @@ -89,7 +89,7 @@ class DeviceVerificationInfoEpoxyController @Inject constructor(private val stri description(stringProvider.getString(R.string.settings_active_sessions_verified_device_desc)) } } else { - // You need tomcomplete security + // You need to complete security genericItem { id("trust${cryptoDeviceInfo.deviceId}") style(GenericItem.STYLE.BIG_TEXT) From 5434c394536dcea8aa05e1b285f53b5147baea5b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jun 2020 14:14:49 +0200 Subject: [PATCH 08/30] Change --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index df0ddb6e64..3f2657a65c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ Features ✨: Improvements 🙌: - "Add Matrix app" menu is now always visible (#1495) - Handle `/op`, `/deop`, and `/nick` commands (#12) + - Prioritising Recovery key over Recovery passphrase (#1463) Bugfix 🐛: - Fix dark theme issue on login screen (#1097) From bddd7f40051df052363dbb2ab6b3e826ac0b611f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jun 2020 15:32:51 +0200 Subject: [PATCH 09/30] Use defined model (tested ok) --- .../internal/crypto/model/rest/SignatureUploadResponse.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt index 7bc5ecc6fc..b758489020 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt @@ -32,17 +32,15 @@ internal data class SignatureUploadResponse( * If a signature is not valid, the homeserver should set the corresponding entry in failures to a JSON object * with the errcode property set to M_INVALID_SIGNATURE. */ - val failures: Map>? = null - + val failures: Map>? = null ) -// TODO Not used. Remove? @JsonClass(generateAdapter = true) -data class UploadResponseFailure( +internal data class UploadResponseFailure( @Json(name = "status") val status: Int, - @Json(name = "errCode") + @Json(name = "errcode") val errCode: String, @Json(name = "message") From 369f40c804ae1af27ed7d7cce0f2e92b6fc388a4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jun 2020 16:09:27 +0200 Subject: [PATCH 10/30] Remove developer mode option to set up cross-signing --- .../CrossSigningEpoxyController.kt | 163 ------------------ .../CrossSigningSettingsAction.kt | 23 +++ .../CrossSigningSettingsController.kt | 139 +++++++++++++++ .../CrossSigningSettingsFragment.kt | 36 ++-- .../CrossSigningSettingsViewEvents.kt | 1 - .../CrossSigningSettingsViewModel.kt | 83 +-------- .../CrossSigningSettingsViewState.kt | 27 +++ 7 files changed, 202 insertions(+), 270 deletions(-) delete mode 100644 vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsAction.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewState.kt diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt deleted file mode 100644 index 5b7875d0ce..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 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.riotx.features.settings.crosssigning - -import com.airbnb.epoxy.TypedEpoxyController -import im.vector.riotx.R -import im.vector.riotx.core.epoxy.loadingItem -import im.vector.riotx.core.resources.ColorProvider -import im.vector.riotx.core.resources.StringProvider -import im.vector.riotx.core.ui.list.genericItem -import im.vector.riotx.core.ui.list.genericItemWithValue -import im.vector.riotx.core.utils.DimensionConverter -import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem -import im.vector.riotx.features.settings.VectorPreferences -import me.gujun.android.span.span -import javax.inject.Inject - -class CrossSigningEpoxyController @Inject constructor( - private val stringProvider: StringProvider, - private val colorProvider: ColorProvider, - private val dimensionConverter: DimensionConverter, - private val vectorPreferences: VectorPreferences -) : TypedEpoxyController() { - - interface InteractionListener { - fun onInitializeCrossSigningKeys() - fun verifySession() - } - - var interactionListener: InteractionListener? = null - - override fun buildModels(data: CrossSigningSettingsViewState?) { - if (data == null) return - if (data.xSigningKeyCanSign) { - genericItem { - id("can") - titleIconResourceId(R.drawable.ic_shield_trusted) - title(stringProvider.getString(R.string.encryption_information_dg_xsigning_complete)) - } - } else if (data.xSigningKeysAreTrusted) { - genericItem { - id("trusted") - titleIconResourceId(R.drawable.ic_shield_custom) - title(stringProvider.getString(R.string.encryption_information_dg_xsigning_trusted)) - } - if (!data.isUploadingKeys) { - bottomSheetVerificationActionItem { - id("verify") - title(stringProvider.getString(R.string.crosssigning_verify_this_session)) - titleColor(colorProvider.getColor(R.color.riotx_positive_accent)) - iconRes(R.drawable.ic_arrow_right) - iconColor(colorProvider.getColor(R.color.riotx_positive_accent)) - listener { - interactionListener?.verifySession() - } - } - } - } else if (data.xSigningIsEnableInAccount) { - genericItem { - id("enable") - titleIconResourceId(R.drawable.ic_shield_black) - title(stringProvider.getString(R.string.encryption_information_dg_xsigning_not_trusted)) - } - bottomSheetVerificationActionItem { - id("verify") - title(stringProvider.getString(R.string.crosssigning_verify_this_session)) - titleColor(colorProvider.getColor(R.color.riotx_positive_accent)) - iconRes(R.drawable.ic_arrow_right) - iconColor(colorProvider.getColor(R.color.riotx_positive_accent)) - listener { - interactionListener?.verifySession() - } - } - } else { - genericItem { - id("not") - title(stringProvider.getString(R.string.encryption_information_dg_xsigning_disabled)) - } - if (vectorPreferences.developerMode() && !data.isUploadingKeys) { - bottomSheetVerificationActionItem { - id("initKeys") - title(stringProvider.getString(R.string.initialize_cross_signing)) - titleColor(colorProvider.getColor(R.color.riotx_positive_accent)) - iconRes(R.drawable.ic_arrow_right) - iconColor(colorProvider.getColor(R.color.riotx_positive_accent)) - listener { - interactionListener?.onInitializeCrossSigningKeys() - } - } - } - } - - if (data.isUploadingKeys) { - loadingItem { - id("loading") - } - } else { - val crossSigningKeys = data.crossSigningInfo - - crossSigningKeys?.masterKey()?.let { - genericItemWithValue { - id("msk") - titleIconResourceId(R.drawable.key_small) - title( - span { - +"Master Key:\n" - span { - text = it.unpaddedBase64PublicKey ?: "" - textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary) - textSize = dimensionConverter.spToPx(12) - } - } - ) - } - } - crossSigningKeys?.userKey()?.let { - genericItemWithValue { - id("usk") - titleIconResourceId(R.drawable.key_small) - title( - span { - +"User Key:\n" - span { - text = it.unpaddedBase64PublicKey ?: "" - textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary) - textSize = dimensionConverter.spToPx(12) - } - } - ) - } - } - crossSigningKeys?.selfSigningKey()?.let { - genericItemWithValue { - id("ssk") - titleIconResourceId(R.drawable.key_small) - title( - span { - +"Self Signed Key:\n" - span { - text = it.unpaddedBase64PublicKey ?: "" - textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary) - textSize = dimensionConverter.spToPx(12) - } - } - ) - } - } - } - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsAction.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsAction.kt new file mode 100644 index 0000000000..8b92227465 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsAction.kt @@ -0,0 +1,23 @@ +/* + * 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.riotx.features.settings.crosssigning + +import im.vector.riotx.core.platform.VectorViewModelAction + +sealed class CrossSigningSettingsAction : VectorViewModelAction { + object VerifySession : CrossSigningSettingsAction() +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt new file mode 100644 index 0000000000..274f7c0933 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt @@ -0,0 +1,139 @@ +/* + * Copyright 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.riotx.features.settings.crosssigning + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.riotx.R +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.core.ui.list.genericItem +import im.vector.riotx.core.ui.list.genericItemWithValue +import im.vector.riotx.core.utils.DimensionConverter +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem +import me.gujun.android.span.span +import javax.inject.Inject + +class CrossSigningSettingsController @Inject constructor( + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider, + private val dimensionConverter: DimensionConverter +) : TypedEpoxyController() { + + interface InteractionListener { + fun verifySession() + } + + var interactionListener: InteractionListener? = null + + override fun buildModels(data: CrossSigningSettingsViewState?) { + if (data == null) return + if (data.xSigningKeyCanSign) { + genericItem { + id("can") + titleIconResourceId(R.drawable.ic_shield_trusted) + title(stringProvider.getString(R.string.encryption_information_dg_xsigning_complete)) + } + } else if (data.xSigningKeysAreTrusted) { + genericItem { + id("trusted") + titleIconResourceId(R.drawable.ic_shield_custom) + title(stringProvider.getString(R.string.encryption_information_dg_xsigning_trusted)) + } + bottomSheetVerificationActionItem { + id("verify") + title(stringProvider.getString(R.string.crosssigning_verify_this_session)) + titleColor(colorProvider.getColor(R.color.riotx_positive_accent)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColor(R.color.riotx_positive_accent)) + listener { + interactionListener?.verifySession() + } + } + } else if (data.xSigningIsEnableInAccount) { + genericItem { + id("enable") + titleIconResourceId(R.drawable.ic_shield_black) + title(stringProvider.getString(R.string.encryption_information_dg_xsigning_not_trusted)) + } + bottomSheetVerificationActionItem { + id("verify") + title(stringProvider.getString(R.string.crosssigning_verify_this_session)) + titleColor(colorProvider.getColor(R.color.riotx_positive_accent)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColor(R.color.riotx_positive_accent)) + listener { + interactionListener?.verifySession() + } + } + } else { + genericItem { + id("not") + title(stringProvider.getString(R.string.encryption_information_dg_xsigning_disabled)) + } + } + + val crossSigningKeys = data.crossSigningInfo + + crossSigningKeys?.masterKey()?.let { + genericItemWithValue { + id("msk") + titleIconResourceId(R.drawable.key_small) + title( + span { + +"Master Key:\n" + span { + text = it.unpaddedBase64PublicKey ?: "" + textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary) + textSize = dimensionConverter.spToPx(12) + } + } + ) + } + } + crossSigningKeys?.userKey()?.let { + genericItemWithValue { + id("usk") + titleIconResourceId(R.drawable.key_small) + title( + span { + +"User Key:\n" + span { + text = it.unpaddedBase64PublicKey ?: "" + textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary) + textSize = dimensionConverter.spToPx(12) + } + } + ) + } + } + crossSigningKeys?.selfSigningKey()?.let { + genericItemWithValue { + id("ssk") + titleIconResourceId(R.drawable.key_small) + title( + span { + +"Self Signed Key:\n" + span { + text = it.unpaddedBase64PublicKey ?: "" + textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary) + textSize = dimensionConverter.spToPx(12) + } + } + ) + } + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt index 1f81fd7c7b..e9ab3dfab6 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt @@ -21,7 +21,6 @@ import androidx.appcompat.app.AlertDialog import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R -import im.vector.riotx.core.dialogs.PromptPasswordDialog import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.exhaustive @@ -31,9 +30,9 @@ import kotlinx.android.synthetic.main.fragment_generic_recycler.* import javax.inject.Inject class CrossSigningSettingsFragment @Inject constructor( - private val epoxyController: CrossSigningEpoxyController, + private val controller: CrossSigningSettingsController, val viewModelFactory: CrossSigningSettingsViewModel.Factory -) : VectorBaseFragment(), CrossSigningEpoxyController.InteractionListener { +) : VectorBaseFragment(), CrossSigningSettingsController.InteractionListener { override fun getLayoutResId() = R.layout.fragment_generic_recycler @@ -43,7 +42,7 @@ class CrossSigningSettingsFragment @Inject constructor( super.onActivityCreated(savedInstanceState) viewModel.observeViewEvents { when (it) { - is CrossSigningSettingsViewEvents.Failure -> { + is CrossSigningSettingsViewEvents.Failure -> { AlertDialog.Builder(requireContext()) .setTitle(R.string.dialog_title_error) .setMessage(errorFormatter.toHumanReadable(it.throwable)) @@ -51,13 +50,8 @@ class CrossSigningSettingsFragment @Inject constructor( .show() Unit } - is CrossSigningSettingsViewEvents.RequestPassword -> { - requestPassword() - } - CrossSigningSettingsViewEvents.VerifySession -> { - (requireActivity() as? VectorBaseActivity)?.let { activity -> - activity.navigator.waitSessionVerification(activity) - } + CrossSigningSettingsViewEvents.VerifySession -> { + navigator.waitSessionVerification(requireActivity()) } }.exhaustive } @@ -74,31 +68,21 @@ class CrossSigningSettingsFragment @Inject constructor( } override fun invalidate() = withState(viewModel) { state -> - epoxyController.setData(state) + controller.setData(state) } private fun setupRecyclerView() { - recyclerView.configureWith(epoxyController, hasFixedSize = false, disableItemAnimation = true) - epoxyController.interactionListener = this + recyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true) + controller.interactionListener = this } override fun onDestroyView() { recyclerView.cleanup() - epoxyController.interactionListener = null + controller.interactionListener = null super.onDestroyView() } - private fun requestPassword() { - PromptPasswordDialog().show(requireActivity()) { password -> - viewModel.handle(CrossSigningAction.PasswordEntered(password)) - } - } - - override fun onInitializeCrossSigningKeys() { - viewModel.handle(CrossSigningAction.InitializeCrossSigning) - } - override fun verifySession() { - viewModel.handle(CrossSigningAction.VerifySession) + viewModel.handle(CrossSigningSettingsAction.VerifySession) } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt index 65a4a18485..0f5b1ff6f6 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt @@ -24,6 +24,5 @@ import im.vector.riotx.core.platform.VectorViewEvents sealed class CrossSigningSettingsViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : CrossSigningSettingsViewEvents() - object RequestPassword : CrossSigningSettingsViewEvents() object VerifySession : CrossSigningSettingsViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index f21a9c69d4..89a6aa9ef2 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -16,40 +16,19 @@ package im.vector.riotx.features.settings.crosssigning import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject -import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.auth.data.LoginFlowTypes -import im.vector.matrix.android.api.failure.toRegistrationFlowResponse import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.internal.crypto.crosssigning.isVerified -import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.matrix.rx.rx import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel -import im.vector.riotx.core.platform.VectorViewModelAction - -data class CrossSigningSettingsViewState( - val crossSigningInfo: MXCrossSigningInfo? = null, - val xSigningIsEnableInAccount: Boolean = false, - val xSigningKeysAreTrusted: Boolean = false, - val xSigningKeyCanSign: Boolean = true, - val isUploadingKeys: Boolean = false -) : MvRxState - -sealed class CrossSigningAction : VectorViewModelAction { - object InitializeCrossSigning : CrossSigningAction() - object VerifySession : CrossSigningAction() - data class PasswordEntered(val password: String) : CrossSigningAction() -} class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted private val initialState: CrossSigningSettingsViewState, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { init { session.rx().liveCrossSigningInfo(session.myUserId) @@ -67,75 +46,19 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted privat } } - // Storage when password is required - private var _pendingSession: String? = null - @AssistedInject.Factory interface Factory { fun create(initialState: CrossSigningSettingsViewState): CrossSigningSettingsViewModel } - override fun handle(action: CrossSigningAction) { + override fun handle(action: CrossSigningSettingsAction) { when (action) { - is CrossSigningAction.InitializeCrossSigning -> { - initializeCrossSigning(null) - } - is CrossSigningAction.PasswordEntered -> { - initializeCrossSigning(UserPasswordAuth( - session = _pendingSession, - user = session.myUserId, - password = action.password - )) - } - CrossSigningAction.VerifySession -> { + CrossSigningSettingsAction.VerifySession -> { _viewEvents.post(CrossSigningSettingsViewEvents.VerifySession) } }.exhaustive } - private fun initializeCrossSigning(auth: UserPasswordAuth?) { - _pendingSession = null - - setState { - copy(isUploadingKeys = true) - } - session.cryptoService().crossSigningService().initializeCrossSigning(auth, object : MatrixCallback { - override fun onSuccess(data: Unit) { - _pendingSession = null - - setState { - copy(isUploadingKeys = false) - } - } - - override fun onFailure(failure: Throwable) { - _pendingSession = null - - val registrationFlowResponse = failure.toRegistrationFlowResponse() - if (registrationFlowResponse != null) { - // Retry with authentication - if (registrationFlowResponse.flows?.any { it.stages?.contains(LoginFlowTypes.PASSWORD) == true } == true) { - _pendingSession = registrationFlowResponse.session ?: "" - _viewEvents.post(CrossSigningSettingsViewEvents.RequestPassword) - } else { - // can't do this from here - _viewEvents.post(CrossSigningSettingsViewEvents.Failure(Throwable("You cannot do that from mobile"))) - - setState { - copy(isUploadingKeys = false) - } - } - } else { - _viewEvents.post(CrossSigningSettingsViewEvents.Failure(failure)) - - setState { - copy(isUploadingKeys = false) - } - } - } - }) - } - companion object : MvRxViewModelFactory { @JvmStatic diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewState.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewState.kt new file mode 100644 index 0000000000..769e110410 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewState.kt @@ -0,0 +1,27 @@ +/* + * 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.riotx.features.settings.crosssigning + +import com.airbnb.mvrx.MvRxState +import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo + +data class CrossSigningSettingsViewState( + val crossSigningInfo: MXCrossSigningInfo? = null, + val xSigningIsEnableInAccount: Boolean = false, + val xSigningKeysAreTrusted: Boolean = false, + val xSigningKeyCanSign: Boolean = true +) : MvRxState From f3b464b88a80e5bd617d129a0784490ecea49878 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jun 2020 18:02:55 +0200 Subject: [PATCH 11/30] Bootstrap: Add an introduction step: BootstrapSetupRecoveryKeyFragment --- .../im/vector/riotx/core/di/FragmentModule.kt | 6 +++ .../crypto/recover/BootstrapActions.kt | 2 + .../crypto/recover/BootstrapBottomSheet.kt | 16 +++--- .../recover/BootstrapCrossSigningTask.kt | 12 +++-- .../BootstrapSaveRecoveryKeyFragment.kt | 10 ---- .../BootstrapSetupRecoveryKeyFragment.kt | 42 +++++++++++++++ .../recover/BootstrapSharedViewModel.kt | 17 ++---- .../features/crypto/recover/BootstrapStep.kt | 8 +++ .../crypto/recover/BootstrapViewState.kt | 32 +++++++++++ .../CrossSigningSettingsAction.kt | 1 + .../CrossSigningSettingsController.kt | 10 ++++ .../CrossSigningSettingsFragment.kt | 7 +++ .../CrossSigningSettingsViewEvents.kt | 1 + .../CrossSigningSettingsViewModel.kt | 3 ++ .../res/drawable/ic_secure_backup_24dp.xml | 20 +++++++ .../layout/dialog_recovery_key_saved_info.xml | 2 +- .../layout/fragment_bootstrap_save_key.xml | 7 ++- .../fragment_bootstrap_setup_recovery.xml | 53 +++++++++++++++++++ .../layout/fragment_ssss_access_from_key.xml | 2 +- .../fragment_ssss_access_from_passphrase.xml | 4 +- vector/src/main/res/values/strings.xml | 11 ++++ 21 files changed, 226 insertions(+), 40 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapViewState.kt create mode 100644 vector/src/main/res/drawable/ic_secure_backup_24dp.xml create mode 100644 vector/src/main/res/layout/fragment_bootstrap_setup_recovery.xml diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index 6214677020..c797588449 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -30,6 +30,7 @@ import im.vector.riotx.features.crypto.recover.BootstrapAccountPasswordFragment import im.vector.riotx.features.crypto.recover.BootstrapConclusionFragment import im.vector.riotx.features.crypto.recover.BootstrapMigrateBackupFragment import im.vector.riotx.features.crypto.recover.BootstrapSaveRecoveryKeyFragment +import im.vector.riotx.features.crypto.recover.BootstrapSetupRecoveryKeyFragment import im.vector.riotx.features.crypto.recover.BootstrapWaitingFragment import im.vector.riotx.features.crypto.verification.cancel.VerificationCancelFragment import im.vector.riotx.features.crypto.verification.cancel.VerificationNotMeFragment @@ -456,6 +457,11 @@ interface FragmentModule { @FragmentKey(BootstrapWaitingFragment::class) fun bindBootstrapWaitingFragment(fragment: BootstrapWaitingFragment): Fragment + @Binds + @IntoMap + @FragmentKey(BootstrapSetupRecoveryKeyFragment::class) + fun bindBootstrapSetupRecoveryKeyFragment(fragment: BootstrapSetupRecoveryKeyFragment): Fragment + @Binds @IntoMap @FragmentKey(BootstrapSaveRecoveryKeyFragment::class) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt index b1e94e551c..596dcaba73 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt @@ -27,6 +27,8 @@ sealed class BootstrapActions : VectorViewModelAction { object GoToCompleted : BootstrapActions() object GoToEnterAccountPassword : BootstrapActions() + object SetupRecoveryKey : BootstrapActions() + object DoInitializeGeneratedKey : BootstrapActions() object TogglePasswordVisibility : BootstrapActions() data class ReAuth(val pass: String) : BootstrapActions() diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt index a5bab9be5a..c7d7ad99f3 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt @@ -121,8 +121,12 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { } override fun invalidate() = withState(viewModel) { state -> - when (state.step) { + is BootstrapStep.SetupSecureBackup -> { + bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_secure_backup_24dp)) + bootstrapTitleText.text = getString(R.string.bottom_sheet_setup_secure_backup_title) + showFragment(BootstrapSetupRecoveryKeyFragment::class, Bundle()) + } is BootstrapStep.CheckingMigration -> { bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_password)) bootstrapTitleText.text = getString(R.string.upgrade_security) @@ -134,17 +138,17 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { showFragment(BootstrapAccountPasswordFragment::class, Bundle()) } is BootstrapStep.Initializing -> { - bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_key)) + bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_secure_backup_24dp)) bootstrapTitleText.text = getString(R.string.bootstrap_loading_title) showFragment(BootstrapWaitingFragment::class, Bundle()) } is BootstrapStep.SaveRecoveryKey -> { - bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_key)) - bootstrapTitleText.text = getString(R.string.keys_backup_setup_step3_please_make_copy) + bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_secure_backup_24dp)) + bootstrapTitleText.text = getString(R.string.bottom_sheet_save_your_recovery_key_title) showFragment(BootstrapSaveRecoveryKeyFragment::class, Bundle()) } is BootstrapStep.DoneSuccess -> { - bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_key)) + bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_secure_backup_24dp)) bootstrapTitleText.text = getString(R.string.bootstrap_finish_title) showFragment(BootstrapConclusionFragment::class, Bundle()) } @@ -153,7 +157,7 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { is BootstrapStep.GetBackupSecretPassForMigration -> state.step.useKey else -> true } - val drawableRes = if (isKey) R.drawable.ic_message_key else R.drawable.ic_message_password + val drawableRes = if (isKey) R.drawable.ic_secure_backup_24dp else R.drawable.ic_message_password bootstrapIcon.setImageDrawable(ContextCompat.getDrawable( requireContext(), drawableRes) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt index bcb1f6fefd..db25e093ce 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -16,10 +16,8 @@ package im.vector.riotx.features.crypto.recover -import im.vector.matrix.android.api.auth.data.LoginFlowTypes import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError -import im.vector.matrix.android.api.failure.toRegistrationFlowResponse import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME @@ -71,19 +69,23 @@ data class Params( val keySpec: SsssKeySpec? = null ) +// TODO Rename to CreateServerRecovery class BootstrapCrossSigningTask @Inject constructor( private val session: Session, private val stringProvider: StringProvider ) : ViewModelTask { override suspend fun execute(params: Params): BootstrapResult { + val crossSigningService = session.cryptoService().crossSigningService() + + // TODO Remove + /* params.progressListener?.onProgress( WaitingViewData( stringProvider.getString(R.string.bootstrap_crosssigning_progress_initializing), isIndeterminate = true ) ) - val crossSigningService = session.cryptoService().crossSigningService() try { awaitCallback { @@ -92,6 +94,7 @@ class BootstrapCrossSigningTask @Inject constructor( } catch (failure: Throwable) { return handleInitializeXSigningError(failure) } + */ val keyInfo: SsssKeyCreationInfo @@ -214,6 +217,8 @@ class BootstrapCrossSigningTask @Inject constructor( return BootstrapResult.Success(keyInfo) } + /* + TODO Remove private fun handleInitializeXSigningError(failure: Throwable): BootstrapResult { if (failure is Failure.ServerError && failure.error.code == MatrixError.M_FORBIDDEN) { return BootstrapResult.InvalidPasswordError(failure.error) @@ -230,4 +235,5 @@ class BootstrapCrossSigningTask @Inject constructor( } return BootstrapResult.GenericError(failure) } + */ } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt index 4faa4168b0..5246f5a882 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt @@ -21,14 +21,12 @@ import android.content.ActivityNotFoundException import android.content.Intent import android.os.Bundle import android.view.View -import androidx.core.text.toSpannable import androidx.core.view.isVisible import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.resources.ColorProvider -import im.vector.riotx.core.utils.colorizeMatchingText import im.vector.riotx.core.utils.startSharePlainTextIntent import im.vector.riotx.core.utils.toast import kotlinx.android.synthetic.main.fragment_bootstrap_save_key.* @@ -48,14 +46,6 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val messageKey = getString(R.string.message_key) - val recoveryPassphrase = getString(R.string.recovery_passphrase) - val color = colorProvider.getColorFromAttribute(R.attr.vctr_toolbar_link_text_color) - bootstrapSaveText.text = getString(R.string.bootstrap_save_key_description, messageKey, recoveryPassphrase) - .toSpannable() - .colorizeMatchingText(messageKey, color) - .colorizeMatchingText(recoveryPassphrase, color) - recoverySave.clickableView.debouncedClicks { downloadRecoveryKey() } recoveryCopy.clickableView.debouncedClicks { shareRecoveryKey() } recoveryContinue.clickableView.debouncedClicks { sharedViewModel.handle(BootstrapActions.GoToCompleted) } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt new file mode 100644 index 0000000000..38bc429bcf --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt @@ -0,0 +1,42 @@ +/* + * 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.riotx.features.crypto.recover + +import android.os.Bundle +import android.view.View +import com.airbnb.mvrx.parentFragmentViewModel +import im.vector.riotx.R +import im.vector.riotx.core.platform.VectorBaseFragment +import kotlinx.android.synthetic.main.fragment_bootstrap_setup_recovery.* +import javax.inject.Inject + +class BootstrapSetupRecoveryKeyFragment @Inject constructor() : VectorBaseFragment() { + + override fun getLayoutResId() = R.layout.fragment_bootstrap_setup_recovery + + val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + bootstrapSetupSecureSubmit.clickableView.debouncedClicks { setupRecoveryKey() } + } + + private fun setupRecoveryKey() { + sharedViewModel.handle(BootstrapActions.SetupRecoveryKey) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt index 52ccd8a10d..f2609bdf77 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt @@ -17,11 +17,9 @@ package im.vector.riotx.features.crypto.recover import androidx.lifecycle.viewModelScope -import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized @@ -31,7 +29,6 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec -import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth @@ -46,15 +43,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.io.OutputStream -data class BootstrapViewState( - val step: BootstrapStep = BootstrapStep.AccountPassword(false), - val migrationRecoveryKey: String? = null, - val crossSigningInitialization: Async = Uninitialized, - val recoveryKeyCreationInfo: SsssKeyCreationInfo? = null, - val initializationWaitingViewData: WaitingViewData? = null, - val recoverySaveFileProcess: Async = Uninitialized -) : MvRxState - class BootstrapSharedViewModel @AssistedInject constructor( @Assisted initialState: BootstrapViewState, @Assisted val args: BootstrapBottomSheet.Args, @@ -72,7 +60,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( private var _pendingSession: String? = null - init { + private fun startProcess() { // need to check if user have an existing keybackup if (args.isNewAccount) { setState { @@ -137,6 +125,9 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } } + BootstrapActions.SetupRecoveryKey -> { + startProcess() + } is BootstrapActions.DoInitializeGeneratedKey -> { val userPassword = reAuthHelper.data if (userPassword == null) { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt index 1855a3b063..9dc8710d2d 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt @@ -17,6 +17,12 @@ package im.vector.riotx.features.crypto.recover /** + * ┌───────────────────────────────────┐ + * │ BootstrapStep.SetupSecureBackup │ + * └───────────────────────────────────┘ + * │ + * │ + * ▼ * ┌─────────────────────────┐ * │ User has signing keys? │──────────── Account * └─────────────────────────┘ Creation ? @@ -73,6 +79,8 @@ package im.vector.riotx.features.crypto.recover */ sealed class BootstrapStep { + object SetupSecureBackup : BootstrapStep() + data class AccountPassword(val isPasswordVisible: Boolean, val failure: String? = null) : BootstrapStep() object CheckingMigration : BootstrapStep() diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapViewState.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapViewState.kt new file mode 100644 index 0000000000..4a53872ae5 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapViewState.kt @@ -0,0 +1,32 @@ +/* + * 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.riotx.features.crypto.recover + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo +import im.vector.riotx.core.platform.WaitingViewData + +data class BootstrapViewState( + val step: BootstrapStep = BootstrapStep.SetupSecureBackup, + val migrationRecoveryKey: String? = null, + val crossSigningInitialization: Async = Uninitialized, + val recoveryKeyCreationInfo: SsssKeyCreationInfo? = null, + val initializationWaitingViewData: WaitingViewData? = null, + val recoverySaveFileProcess: Async = Uninitialized +) : MvRxState diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsAction.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsAction.kt index 8b92227465..14f240e818 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsAction.kt @@ -19,5 +19,6 @@ package im.vector.riotx.features.settings.crosssigning import im.vector.riotx.core.platform.VectorViewModelAction sealed class CrossSigningSettingsAction : VectorViewModelAction { + object SetUpRecovery : CrossSigningSettingsAction() object VerifySession : CrossSigningSettingsAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt index 274f7c0933..6da5e09b9f 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt @@ -33,6 +33,7 @@ class CrossSigningSettingsController @Inject constructor( ) : TypedEpoxyController() { interface InteractionListener { + fun setupRecovery() fun verifySession() } @@ -68,6 +69,15 @@ class CrossSigningSettingsController @Inject constructor( titleIconResourceId(R.drawable.ic_shield_black) title(stringProvider.getString(R.string.encryption_information_dg_xsigning_not_trusted)) } + bottomSheetVerificationActionItem { + id("setup_recovery") + title(stringProvider.getString(R.string.settings_setup_secure_backup)) + titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) + iconRes(R.drawable.ic_arrow_right) + listener { + interactionListener?.setupRecovery() + } + } bottomSheetVerificationActionItem { id("verify") title(stringProvider.getString(R.string.crosssigning_verify_this_session)) diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt index e9ab3dfab6..ce6e73d44b 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt @@ -53,6 +53,9 @@ class CrossSigningSettingsFragment @Inject constructor( CrossSigningSettingsViewEvents.VerifySession -> { navigator.waitSessionVerification(requireActivity()) } + CrossSigningSettingsViewEvents.SetUpRecovery -> { + navigator.upgradeSessionSecurity(requireActivity(), false) + } }.exhaustive } } @@ -82,6 +85,10 @@ class CrossSigningSettingsFragment @Inject constructor( super.onDestroyView() } + override fun setupRecovery() { + viewModel.handle(CrossSigningSettingsAction.SetUpRecovery) + } + override fun verifySession() { viewModel.handle(CrossSigningSettingsAction.VerifySession) } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt index 0f5b1ff6f6..18092be92b 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt @@ -24,5 +24,6 @@ import im.vector.riotx.core.platform.VectorViewEvents sealed class CrossSigningSettingsViewEvents : VectorViewEvents { data class Failure(val throwable: Throwable) : CrossSigningSettingsViewEvents() + object SetUpRecovery : CrossSigningSettingsViewEvents() object VerifySession : CrossSigningSettingsViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index 89a6aa9ef2..b0899452f0 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -53,6 +53,9 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted privat override fun handle(action: CrossSigningSettingsAction) { when (action) { + CrossSigningSettingsAction.SetUpRecovery -> { + _viewEvents.post(CrossSigningSettingsViewEvents.SetUpRecovery) + } CrossSigningSettingsAction.VerifySession -> { _viewEvents.post(CrossSigningSettingsViewEvents.VerifySession) } diff --git a/vector/src/main/res/drawable/ic_secure_backup_24dp.xml b/vector/src/main/res/drawable/ic_secure_backup_24dp.xml new file mode 100644 index 0000000000..899bb8d2ae --- /dev/null +++ b/vector/src/main/res/drawable/ic_secure_backup_24dp.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/vector/src/main/res/layout/dialog_recovery_key_saved_info.xml b/vector/src/main/res/layout/dialog_recovery_key_saved_info.xml index 3e5b31bbb0..50ea0c3c98 100644 --- a/vector/src/main/res/layout/dialog_recovery_key_saved_info.xml +++ b/vector/src/main/res/layout/dialog_recovery_key_saved_info.xml @@ -29,7 +29,7 @@ android:layout_height="32dp" android:contentDescription="@string/avatar" android:scaleType="fitCenter" - android:src="@drawable/ic_message_key" /> + android:src="@drawable/ic_secure_backup_24dp" /> + android:textSize="14sp" /> diff --git a/vector/src/main/res/layout/fragment_bootstrap_setup_recovery.xml b/vector/src/main/res/layout/fragment_bootstrap_setup_recovery.xml new file mode 100644 index 0000000000..37c4e03df8 --- /dev/null +++ b/vector/src/main/res/layout/fragment_bootstrap_setup_recovery.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + 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 2f6945bc5b..efdad6fcac 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 @@ -19,7 +19,7 @@ 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_message_key" /> + android:src="@drawable/ic_secure_backup_24dp" /> + android:src="@drawable/ic_secure_backup_24dp" /> Mark as read Open Close + Copy Copied to clipboard Disable @@ -2478,4 +2479,14 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Stop the camera Start the camera + Set up Secure Backup + + Set up Secure Backup + Backup your encryption keys with your account data in case you lose access to your logins. + Your keys will be secured with a unique Recovery Key. + Set up + + Save your Recovery Key + Store your Recovery Key somewhere safe. It can be used to unlock your encrypted messages and data. + From bcd78a96bf767c96a4a3a94b8af3b288df26d150 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jun 2020 11:25:08 +0200 Subject: [PATCH 12/30] Add FIXME --- .../api/session/crypto/crosssigning/CrossSigningService.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt index 2f23646460..cb55235201 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt @@ -74,6 +74,7 @@ interface CrossSigningService { otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult + // FIXME Those method do not have to be in the service fun onSecretMSKGossip(mskPrivateKey: String) fun onSecretSSKGossip(sskPrivateKey: String) fun onSecretUSKGossip(uskPrivateKey: String) From 12a4f6f05b4a6159b626197f052c123bb977e514 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jun 2020 11:25:30 +0200 Subject: [PATCH 13/30] Create isRecoverySetup() --- .../api/session/crypto/CryptoService.kt | 2 + .../SharedSecretStorageService.kt | 15 ++++- .../internal/crypto/store/IMXCryptoStore.kt | 2 + .../VerificationBottomSheetViewModel.kt | 7 +-- .../CrossSigningSettingsController.kt | 61 ++++++++++--------- .../CrossSigningSettingsViewModel.kt | 24 ++++++-- .../CrossSigningSettingsViewState.kt | 5 +- 7 files changed, 74 insertions(+), 42 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt index 69fc332453..b3b07f20fb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt @@ -99,7 +99,9 @@ interface CryptoService { fun removeRoomKeysRequestListener(listener: GossipingRequestListener) fun fetchDevicesList(callback: MatrixCallback) + fun getMyDevicesInfo() : List + fun getLiveMyDevicesInfo() : LiveData> fun getDeviceInfo(deviceId: String, callback: MatrixCallback) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt index 5fd0975f1a..6644972aca 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt @@ -18,6 +18,9 @@ package im.vector.matrix.android.api.session.securestorage import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.listeners.ProgressListener +import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME /** * Some features may require clients to store encrypted data on the server so that it can be shared securely between clients. @@ -111,7 +114,17 @@ interface SharedSecretStorageService { */ fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback) - fun checkShouldBeAbleToAccessSecrets(secretNames: List, keyId: String?) : IntegrityResult + /** + * Return true if SSSS is configured + */ + fun isRecoverySetup(): Boolean { + return checkShouldBeAbleToAccessSecrets( + secretNames = listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME), + keyId = null + ) is IntegrityResult.Success + } + + fun checkShouldBeAbleToAccessSecrets(secretNames: List, keyId: String?): IntegrityResult fun requestSecret(name: String, myOtherDeviceId: String) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt index 7125cadebf..3d12835a41 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt @@ -212,7 +212,9 @@ internal interface IMXCryptoStore { fun getLiveDeviceList(): LiveData> fun getMyDevicesInfo() : List + fun getLiveMyDevicesInfo() : LiveData> + fun saveMyDevicesInfo(info: List) /** * Store the crypto algorithm for a room. diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt index dce33255ce..9b454436d9 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -44,7 +44,6 @@ import im.vector.matrix.android.api.session.crypto.verification.VerificationTran import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.LocalEcho import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams -import im.vector.matrix.android.api.session.securestorage.IntegrityResult import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64 @@ -118,10 +117,6 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( session.cryptoService().verificationService().getExistingTransaction(args.otherUserId, it) as? QrCodeVerificationTransaction } - val ssssOk = session.sharedSecretStorageService.checkShouldBeAbleToAccessSecrets( - listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME), - null // default key - ) is IntegrityResult.Success setState { copy( otherUserMxItem = userItem?.toMatrixItem(), @@ -133,7 +128,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( roomId = args.roomId, isMe = args.otherUserId == session.myUserId, currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(), - quadSContainsSecrets = ssssOk + quadSContainsSecrets = session.sharedSecretStorageService.isRecoverySetup() ) } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt index 6da5e09b9f..1f13a18a88 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt @@ -41,34 +41,37 @@ class CrossSigningSettingsController @Inject constructor( override fun buildModels(data: CrossSigningSettingsViewState?) { if (data == null) return - if (data.xSigningKeyCanSign) { - genericItem { - id("can") - titleIconResourceId(R.drawable.ic_shield_trusted) - title(stringProvider.getString(R.string.encryption_information_dg_xsigning_complete)) - } - } else if (data.xSigningKeysAreTrusted) { - genericItem { - id("trusted") - titleIconResourceId(R.drawable.ic_shield_custom) - title(stringProvider.getString(R.string.encryption_information_dg_xsigning_trusted)) - } - bottomSheetVerificationActionItem { - id("verify") - title(stringProvider.getString(R.string.crosssigning_verify_this_session)) - titleColor(colorProvider.getColor(R.color.riotx_positive_accent)) - iconRes(R.drawable.ic_arrow_right) - iconColor(colorProvider.getColor(R.color.riotx_positive_accent)) - listener { - interactionListener?.verifySession() + when { + data.xSigningKeyCanSign -> { + genericItem { + id("can") + titleIconResourceId(R.drawable.ic_shield_trusted) + title(stringProvider.getString(R.string.encryption_information_dg_xsigning_complete)) } } - } else if (data.xSigningIsEnableInAccount) { - genericItem { - id("enable") - titleIconResourceId(R.drawable.ic_shield_black) - title(stringProvider.getString(R.string.encryption_information_dg_xsigning_not_trusted)) + data.xSigningKeysAreTrusted -> { + genericItem { + id("trusted") + titleIconResourceId(R.drawable.ic_shield_custom) + title(stringProvider.getString(R.string.encryption_information_dg_xsigning_trusted)) + } } + data.xSigningIsEnableInAccount -> { + genericItem { + id("enable") + titleIconResourceId(R.drawable.ic_shield_black) + title(stringProvider.getString(R.string.encryption_information_dg_xsigning_not_trusted)) + } + } + else -> { + genericItem { + id("not") + title(stringProvider.getString(R.string.encryption_information_dg_xsigning_disabled)) + } + } + } + + if (data.recoveryHasToBeSetUp) { bottomSheetVerificationActionItem { id("setup_recovery") title(stringProvider.getString(R.string.settings_setup_secure_backup)) @@ -78,6 +81,9 @@ class CrossSigningSettingsController @Inject constructor( interactionListener?.setupRecovery() } } + } + + if (data.deviceHasToBeVerified) { bottomSheetVerificationActionItem { id("verify") title(stringProvider.getString(R.string.crosssigning_verify_this_session)) @@ -88,11 +94,6 @@ class CrossSigningSettingsController @Inject constructor( interactionListener?.verifySession() } } - } else { - genericItem { - id("not") - title(stringProvider.getString(R.string.encryption_information_dg_xsigning_disabled)) - } } val crossSigningKeys = data.crossSigningInfo diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index b0899452f0..4a6eec81d9 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -21,27 +21,43 @@ import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo +import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.internal.crypto.crosssigning.isVerified +import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.rx.rx import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel +import io.reactivex.Observable +import io.reactivex.functions.BiFunction class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted private val initialState: CrossSigningSettingsViewState, private val session: Session) : VectorViewModel(initialState) { init { - session.rx().liveCrossSigningInfo(session.myUserId) - .execute { - val crossSigningKeys = it.invoke()?.getOrNull() + Observable.combineLatest, Optional, Pair, Optional>>( + session.rx().liveMyDeviceInfo(), + session.rx().liveCrossSigningInfo(session.myUserId), + BiFunction { myDeviceInfo, mxCrossSigningInfo -> + (myDeviceInfo to mxCrossSigningInfo) + } + ) + .execute { data -> + val crossSigningKeys = data.invoke()?.second?.getOrNull() val xSigningIsEnableInAccount = crossSigningKeys != null val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified() val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign() + val hasSeveralDevices = data.invoke()?.first?.size ?: 0 > 1 + copy( crossSigningInfo = crossSigningKeys, xSigningIsEnableInAccount = xSigningIsEnableInAccount, xSigningKeysAreTrusted = xSigningKeysAreTrusted, - xSigningKeyCanSign = xSigningKeyCanSign + xSigningKeyCanSign = xSigningKeyCanSign, + + deviceHasToBeVerified = hasSeveralDevices && (xSigningIsEnableInAccount || xSigningKeysAreTrusted), + recoveryHasToBeSetUp = !session.sharedSecretStorageService.isRecoverySetup() ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewState.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewState.kt index 769e110410..d48ee8923f 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewState.kt @@ -23,5 +23,8 @@ data class CrossSigningSettingsViewState( val crossSigningInfo: MXCrossSigningInfo? = null, val xSigningIsEnableInAccount: Boolean = false, val xSigningKeysAreTrusted: Boolean = false, - val xSigningKeyCanSign: Boolean = true + val xSigningKeyCanSign: Boolean = true, + + val deviceHasToBeVerified: Boolean = false, + val recoveryHasToBeSetUp: Boolean = false ) : MvRxState From 957fe189dc4647b2fdf2050572496c9e366cc3b5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jun 2020 14:26:57 +0200 Subject: [PATCH 14/30] Fix issue in dark theme and a11y --- vector/src/main/res/layout/bottom_sheet_bootstrap.xml | 3 ++- .../src/main/res/layout/dialog_recovery_key_saved_info.xml | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/layout/bottom_sheet_bootstrap.xml b/vector/src/main/res/layout/bottom_sheet_bootstrap.xml index fda590517c..d5ced50ae7 100644 --- a/vector/src/main/res/layout/bottom_sheet_bootstrap.xml +++ b/vector/src/main/res/layout/bottom_sheet_bootstrap.xml @@ -20,9 +20,10 @@ android:layout_width="32dp" android:layout_height="32dp" android:layout_marginStart="16dp" - android:contentDescription="@string/avatar" + android:importantForAccessibility="no" android:scaleType="fitCenter" android:src="@drawable/ic_message_password" + android:tint="?riotx_text_primary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/vector/src/main/res/layout/dialog_recovery_key_saved_info.xml b/vector/src/main/res/layout/dialog_recovery_key_saved_info.xml index 50ea0c3c98..87faeda5c2 100644 --- a/vector/src/main/res/layout/dialog_recovery_key_saved_info.xml +++ b/vector/src/main/res/layout/dialog_recovery_key_saved_info.xml @@ -27,9 +27,10 @@ android:id="@+id/bootstrapIcon" android:layout_width="32dp" android:layout_height="32dp" - android:contentDescription="@string/avatar" + android:importantForAccessibility="no" android:scaleType="fitCenter" - android:src="@drawable/ic_secure_backup_24dp" /> + android:src="@drawable/ic_secure_backup_24dp" + android:tint="?riotx_text_primary" /> From e758ede7062ef30c817a4e655e27eca0a9f442b4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Jun 2020 14:38:22 +0200 Subject: [PATCH 15/30] Revert "Remove BootstrapStep.SetupPassphrase and BootstrapStep.ConfirmPassphrase" This reverts commit 23fa44b6a6a6d34b425e2c1adef4fd2beb9800a7. --- .../im/vector/riotx/core/di/FragmentModule.kt | 12 ++ .../crypto/recover/BootstrapActions.kt | 4 + .../crypto/recover/BootstrapBottomSheet.kt | 10 ++ .../BootstrapConfirmPassphraseFragment.kt | 118 ++++++++++++++++ .../recover/BootstrapCrossSigningTask.kt | 26 +++- .../BootstrapEnterPassphraseFragment.kt | 126 ++++++++++++++++++ .../recover/BootstrapMigrateBackupFragment.kt | 6 +- .../recover/BootstrapSharedViewModel.kt | 126 +++++++++++++++--- .../features/crypto/recover/BootstrapStep.kt | 39 +++--- .../crypto/recover/BootstrapViewState.kt | 7 +- 10 files changed, 427 insertions(+), 47 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index c797588449..21cff188d0 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -28,6 +28,8 @@ import im.vector.riotx.features.crypto.quads.SharedSecuredStorageKeyFragment import im.vector.riotx.features.crypto.quads.SharedSecuredStoragePassphraseFragment import im.vector.riotx.features.crypto.recover.BootstrapAccountPasswordFragment import im.vector.riotx.features.crypto.recover.BootstrapConclusionFragment +import im.vector.riotx.features.crypto.recover.BootstrapConfirmPassphraseFragment +import im.vector.riotx.features.crypto.recover.BootstrapEnterPassphraseFragment import im.vector.riotx.features.crypto.recover.BootstrapMigrateBackupFragment import im.vector.riotx.features.crypto.recover.BootstrapSaveRecoveryKeyFragment import im.vector.riotx.features.crypto.recover.BootstrapSetupRecoveryKeyFragment @@ -452,6 +454,16 @@ interface FragmentModule { @FragmentKey(GossipingEventsPaperTrailFragment::class) fun bindGossipingEventsPaperTrailFragment(fragment: GossipingEventsPaperTrailFragment): Fragment + @Binds + @IntoMap + @FragmentKey(BootstrapEnterPassphraseFragment::class) + fun bindBootstrapEnterPassphraseFragment(fragment: BootstrapEnterPassphraseFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(BootstrapConfirmPassphraseFragment::class) + fun bindBootstrapConfirmPassphraseFragment(fragment: BootstrapConfirmPassphraseFragment): Fragment + @Binds @IntoMap @FragmentKey(BootstrapWaitingFragment::class) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt index 596dcaba73..2f6cfcf799 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt @@ -24,13 +24,17 @@ sealed class BootstrapActions : VectorViewModelAction { // Navigation object GoBack : BootstrapActions() + data class GoToConfirmPassphrase(val passphrase: String) : BootstrapActions() object GoToCompleted : BootstrapActions() object GoToEnterAccountPassword : BootstrapActions() object SetupRecoveryKey : BootstrapActions() + data class DoInitialize(val passphrase: String) : BootstrapActions() object DoInitializeGeneratedKey : BootstrapActions() object TogglePasswordVisibility : BootstrapActions() + data class UpdateCandidatePassphrase(val pass: String) : BootstrapActions() + data class UpdateConfirmCandidatePassphrase(val pass: String) : BootstrapActions() data class ReAuth(val pass: String) : BootstrapActions() object RecoveryKeySaved : BootstrapActions() object Completed : BootstrapActions() diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt index c7d7ad99f3..4234fd38ee 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt @@ -132,6 +132,16 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { bootstrapTitleText.text = getString(R.string.upgrade_security) showFragment(BootstrapWaitingFragment::class, Bundle()) } + is BootstrapStep.SetupPassphrase -> { + bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_password)) + bootstrapTitleText.text = getString(R.string.set_recovery_passphrase, getString(R.string.recovery_passphrase)) + showFragment(BootstrapEnterPassphraseFragment::class, Bundle()) + } + is BootstrapStep.ConfirmPassphrase -> { + bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_password)) + bootstrapTitleText.text = getString(R.string.confirm_recovery_passphrase, getString(R.string.recovery_passphrase)) + showFragment(BootstrapConfirmPassphraseFragment::class, Bundle()) + } is BootstrapStep.AccountPassword -> { bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_user)) bootstrapTitleText.text = getString(R.string.account_password) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt new file mode 100644 index 0000000000..ebb6416317 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt @@ -0,0 +1,118 @@ +/* + * 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.riotx.features.crypto.recover + +import android.os.Bundle +import android.view.View +import android.view.inputmethod.EditorInfo +import androidx.core.text.toSpannable +import androidx.core.view.isGone +import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState +import com.jakewharton.rxbinding3.widget.editorActionEvents +import com.jakewharton.rxbinding3.widget.textChanges +import im.vector.riotx.R +import im.vector.riotx.core.extensions.hideKeyboard +import im.vector.riotx.core.extensions.showPassword +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.utils.colorizeMatchingText +import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.* +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class BootstrapConfirmPassphraseFragment @Inject constructor( + private val colorProvider: ColorProvider +) : VectorBaseFragment() { + + override fun getLayoutResId() = R.layout.fragment_bootstrap_enter_passphrase + + val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + ssss_passphrase_security_progress.isGone = true + + val recPassPhrase = getString(R.string.recovery_passphrase) + bootstrapDescriptionText.text = getString(R.string.bootstrap_info_confirm_text, recPassPhrase) + .toSpannable() + .colorizeMatchingText(recPassPhrase, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) + + ssss_passphrase_enter_edittext.hint = getString(R.string.passphrase_confirm_passphrase) + + withState(sharedViewModel) { + // set initial value (useful when coming back) + ssss_passphrase_enter_edittext.setText(it.passphraseRepeat ?: "") + ssss_passphrase_enter_edittext.requestFocus() + } + + ssss_passphrase_enter_edittext.editorActionEvents() + .throttleFirst(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + if (it.actionId == EditorInfo.IME_ACTION_DONE) { + submit() + } + } + .disposeOnDestroyView() + + ssss_passphrase_enter_edittext.textChanges() + .subscribe { + ssss_passphrase_enter_til.error = null + sharedViewModel.handle(BootstrapActions.UpdateConfirmCandidatePassphrase(it?.toString() ?: "")) + } + .disposeOnDestroyView() + + sharedViewModel.observeViewEvents { + // when (it) { +// is SharedSecureStorageViewEvent.InlineError -> { +// ssss_passphrase_enter_til.error = it.message +// } +// } + } + + ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) } + bootstrapSubmit.debouncedClicks { submit() } + } + + private fun submit() = withState(sharedViewModel) { state -> + if (state.step !is BootstrapStep.ConfirmPassphrase) { + return@withState + } + val passphrase = ssss_passphrase_enter_edittext.text?.toString() + when { + passphrase.isNullOrBlank() -> + ssss_passphrase_enter_til.error = getString(R.string.passphrase_empty_error_message) + passphrase != state.passphrase -> + ssss_passphrase_enter_til.error = getString(R.string.passphrase_passphrase_does_not_match) + else -> { + view?.hideKeyboard() + sharedViewModel.handle(BootstrapActions.DoInitialize(passphrase)) + } + } + } + + override fun invalidate() = withState(sharedViewModel) { state -> + if (state.step is BootstrapStep.ConfirmPassphrase) { + val isPasswordVisible = state.step.isPasswordVisible + ssss_passphrase_enter_edittext.showPassword(isPasswordVisible, updateCursor = false) + ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt index db25e093ce..7a610d8c85 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -66,6 +66,7 @@ interface BootstrapProgressListener { data class Params( val userPasswordAuth: UserPasswordAuth? = null, val progressListener: BootstrapProgressListener? = null, + val passphrase: String?, val keySpec: SsssKeySpec? = null ) @@ -107,13 +108,24 @@ class BootstrapCrossSigningTask @Inject constructor( ) try { keyInfo = awaitCallback { - ssssService.generateKey( - UUID.randomUUID().toString(), - params.keySpec, - "ssss_key", - EmptyKeySigner(), - it - ) + params.passphrase?.let { passphrase -> + ssssService.generateKeyWithPassphrase( + UUID.randomUUID().toString(), + "ssss_key", + passphrase, + EmptyKeySigner(), + null, + it + ) + } ?: kotlin.run { + ssssService.generateKey( + UUID.randomUUID().toString(), + params.keySpec, + "ssss_key", + EmptyKeySigner(), + it + ) + } } } catch (failure: Failure) { return BootstrapResult.FailedToCreateSSSSKey(failure) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt new file mode 100644 index 0000000000..982f72c14e --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt @@ -0,0 +1,126 @@ +/* + * 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.riotx.features.crypto.recover + +import android.os.Bundle +import android.view.View +import android.view.inputmethod.EditorInfo +import androidx.core.text.toSpannable +import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState +import com.jakewharton.rxbinding3.widget.editorActionEvents +import com.jakewharton.rxbinding3.widget.textChanges +import im.vector.riotx.R +import im.vector.riotx.core.extensions.showPassword +import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.utils.colorizeMatchingText +import im.vector.riotx.features.settings.VectorLocale +import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.* +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class BootstrapEnterPassphraseFragment @Inject constructor( + private val colorProvider: ColorProvider +) : VectorBaseFragment() { + + override fun getLayoutResId() = R.layout.fragment_bootstrap_enter_passphrase + + val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val recPassPhrase = getString(R.string.recovery_passphrase) + bootstrapDescriptionText.text = getString(R.string.bootstrap_info_text, recPassPhrase) + .toSpannable() + .colorizeMatchingText(recPassPhrase, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) + + ssss_passphrase_enter_edittext.hint = getString(R.string.passphrase_enter_passphrase) + withState(sharedViewModel) { + // set initial value (useful when coming back) + ssss_passphrase_enter_edittext.setText(it.passphrase ?: "") + } + ssss_passphrase_enter_edittext.editorActionEvents() + .throttleFirst(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + if (it.actionId == EditorInfo.IME_ACTION_DONE) { + submit() + } + } + .disposeOnDestroyView() + + ssss_passphrase_enter_edittext.textChanges() + .subscribe { + // ssss_passphrase_enter_til.error = null + sharedViewModel.handle(BootstrapActions.UpdateCandidatePassphrase(it?.toString() ?: "")) +// ssss_passphrase_submit.isEnabled = it.isNotBlank() + } + .disposeOnDestroyView() + + sharedViewModel.observeViewEvents { + // when (it) { +// is SharedSecureStorageViewEvent.InlineError -> { +// ssss_passphrase_enter_til.error = it.message +// } +// } + } + + ssss_view_show_password.debouncedClicks { sharedViewModel.handle(BootstrapActions.TogglePasswordVisibility) } + bootstrapSubmit.debouncedClicks { submit() } + } + + private fun submit() = withState(sharedViewModel) { state -> + if (state.step !is BootstrapStep.SetupPassphrase) { + return@withState + } + val score = state.passphraseStrength.invoke()?.score + val passphrase = ssss_passphrase_enter_edittext.text?.toString() + if (passphrase.isNullOrBlank()) { + ssss_passphrase_enter_til.error = getString(R.string.passphrase_empty_error_message) + } else if (score != 4) { + ssss_passphrase_enter_til.error = getString(R.string.passphrase_passphrase_too_weak) + } else { + sharedViewModel.handle(BootstrapActions.GoToConfirmPassphrase(passphrase)) + } + } + + override fun invalidate() = withState(sharedViewModel) { state -> + if (state.step is BootstrapStep.SetupPassphrase) { + val isPasswordVisible = state.step.isPasswordVisible + ssss_passphrase_enter_edittext.showPassword(isPasswordVisible, updateCursor = false) + ssss_view_show_password.setImageResource(if (isPasswordVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black) + + state.passphraseStrength.invoke()?.let { strength -> + val score = strength.score + ssss_passphrase_security_progress.strength = score + if (score in 1..3) { + val hint = + strength.feedback?.getWarning(VectorLocale.applicationLocale)?.takeIf { it.isNotBlank() } + ?: strength.feedback?.getSuggestions(VectorLocale.applicationLocale)?.firstOrNull() + if (hint != null && hint != ssss_passphrase_enter_til.error.toString()) { + ssss_passphrase_enter_til.error = hint + } + } else { + ssss_passphrase_enter_til.error = null + } + } + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt index ee583e0381..0b8e201edd 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt @@ -51,11 +51,15 @@ class BootstrapMigrateBackupFragment @Inject constructor( override fun getLayoutResId() = R.layout.fragment_bootstrap_migrate_backup - private val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel() + val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + withState(sharedViewModel) { + // set initial value (useful when coming back) + bootstrapMigrateEditText.setText(it.passphrase ?: "") + } bootstrapMigrateEditText.editorActionEvents() .throttleFirst(300, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt index f2609bdf77..ebbe502a65 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt @@ -24,6 +24,7 @@ import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext +import com.nulabinc.zxcvbn.Zxcvbn import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.failure.Failure @@ -43,6 +44,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.io.OutputStream + class BootstrapSharedViewModel @AssistedInject constructor( @Assisted initialState: BootstrapViewState, @Assisted val args: BootstrapBottomSheet.Args, @@ -53,6 +55,8 @@ class BootstrapSharedViewModel @AssistedInject constructor( private val reAuthHelper: ReAuthHelper ) : VectorViewModel(initialState) { + private val zxcvbn = Zxcvbn() + @AssistedInject.Factory interface Factory { fun create(initialState: BootstrapViewState, args: BootstrapBottomSheet.Args): BootstrapSharedViewModel @@ -64,7 +68,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( // need to check if user have an existing keybackup if (args.isNewAccount) { setState { - copy(step = BootstrapStep.AccountPassword(false)) + copy(step = BootstrapStep.SetupPassphrase(false)) } } else { setState { @@ -79,7 +83,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( if (version == null) { // we just resume plain bootstrap setState { - copy(step = BootstrapStep.AccountPassword(false)) + copy(step = BootstrapStep.SetupPassphrase(false)) } } else { // we need to get existing backup passphrase/key and convert to SSSS @@ -108,9 +112,19 @@ class BootstrapSharedViewModel @AssistedInject constructor( override fun handle(action: BootstrapActions) = withState { state -> when (action) { - is BootstrapActions.GoBack -> queryBack() - BootstrapActions.TogglePasswordVisibility -> { + is BootstrapActions.GoBack -> queryBack() + BootstrapActions.TogglePasswordVisibility -> { when (state.step) { + is BootstrapStep.SetupPassphrase -> { + setState { + copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible)) + } + } + is BootstrapStep.ConfirmPassphrase -> { + setState { + copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible)) + } + } is BootstrapStep.AccountPassword -> { setState { copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible)) @@ -128,64 +142,118 @@ class BootstrapSharedViewModel @AssistedInject constructor( BootstrapActions.SetupRecoveryKey -> { startProcess() } - is BootstrapActions.DoInitializeGeneratedKey -> { + is BootstrapActions.UpdateCandidatePassphrase -> { + val strength = zxcvbn.measure(action.pass) + setState { + copy( + passphrase = action.pass, + passphraseStrength = Success(strength) + ) + } + } + is BootstrapActions.GoToConfirmPassphrase -> { + setState { + copy( + passphrase = action.passphrase, + step = BootstrapStep.ConfirmPassphrase( + isPasswordVisible = (state.step as? BootstrapStep.SetupPassphrase)?.isPasswordVisible ?: false + ) + ) + } + } + is BootstrapActions.UpdateConfirmCandidatePassphrase -> { + setState { + copy( + passphraseRepeat = action.pass + ) + } + } + is BootstrapActions.DoInitialize -> { + if (state.passphrase == state.passphraseRepeat) { + val userPassword = reAuthHelper.data + if (userPassword == null) { + setState { + copy( + step = BootstrapStep.AccountPassword(false) + ) + } + } else { + startInitializeFlow(userPassword) + } + } else { + setState { + copy( + passphraseConfirmMatch = Fail(Throwable(stringProvider.getString(R.string.passphrase_passphrase_does_not_match))) + ) + } + } + } + is BootstrapActions.DoInitializeGeneratedKey -> { val userPassword = reAuthHelper.data if (userPassword == null) { setState { copy( + passphrase = null, + passphraseRepeat = null, step = BootstrapStep.AccountPassword(false) ) } } else { + setState { + copy( + passphrase = null, + passphraseRepeat = null + ) + } startInitializeFlow(userPassword) } } - BootstrapActions.RecoveryKeySaved -> { + BootstrapActions.RecoveryKeySaved -> { _viewEvents.post(BootstrapViewEvents.RecoveryKeySaved) setState { copy(step = BootstrapStep.SaveRecoveryKey(true)) } } - BootstrapActions.Completed -> { + BootstrapActions.Completed -> { _viewEvents.post(BootstrapViewEvents.Dismiss) } - BootstrapActions.GoToCompleted -> { + BootstrapActions.GoToCompleted -> { setState { copy(step = BootstrapStep.DoneSuccess) } } - BootstrapActions.SaveReqQueryStarted -> { + BootstrapActions.SaveReqQueryStarted -> { setState { copy(recoverySaveFileProcess = Loading()) } } - is BootstrapActions.SaveKeyToUri -> { + is BootstrapActions.SaveKeyToUri -> { saveRecoveryKeyToUri(action.os) } - BootstrapActions.SaveReqFailed -> { + BootstrapActions.SaveReqFailed -> { setState { copy(recoverySaveFileProcess = Uninitialized) } } - BootstrapActions.GoToEnterAccountPassword -> { + BootstrapActions.GoToEnterAccountPassword -> { setState { copy(step = BootstrapStep.AccountPassword(false)) } } - BootstrapActions.HandleForgotBackupPassphrase -> { + BootstrapActions.HandleForgotBackupPassphrase -> { if (state.step is BootstrapStep.GetBackupSecretPassForMigration) { setState { copy(step = BootstrapStep.GetBackupSecretPassForMigration(state.step.isPasswordVisible, true)) } } else return@withState } - is BootstrapActions.ReAuth -> { + is BootstrapActions.ReAuth -> { startInitializeFlow(action.pass) } - is BootstrapActions.DoMigrateWithPassphrase -> { + is BootstrapActions.DoMigrateWithPassphrase -> { startMigrationFlow(state.step, action.passphrase, null) } - is BootstrapActions.DoMigrateWithRecoveryKey -> { + is BootstrapActions.DoMigrateWithRecoveryKey -> { startMigrationFlow(state.step, null, action.recoveryKey) } }.exhaustive @@ -234,6 +302,8 @@ class BootstrapSharedViewModel @AssistedInject constructor( if (it is BackupToQuadSMigrationTask.Result.Success) { setState { copy( + passphrase = passphrase, + passphraseRepeat = passphrase, migrationRecoveryKey = recoveryKey ) } @@ -281,7 +351,6 @@ class BootstrapSharedViewModel @AssistedInject constructor( } withState { state -> - val previousStep = state.step viewModelScope.launch(Dispatchers.IO) { val userPasswordAuth = userPassword?.let { UserPasswordAuth( @@ -296,6 +365,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( Params( userPasswordAuth = userPasswordAuth, progressListener = progressListener, + passphrase = state.passphrase, keySpec = state.migrationRecoveryKey?.let { extractCurveKeyFromRecoveryKey(it)?.let { RawBytesKeySpec(it) } } ) ) { bootstrapResult -> @@ -339,7 +409,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( _viewEvents.post(BootstrapViewEvents.ModalError(bootstrapResult.error ?: stringProvider.getString(R.string.matrix_error))) setState { copy( - step = previousStep + step = BootstrapStep.ConfirmPassphrase(false) ) } } @@ -375,12 +445,25 @@ class BootstrapSharedViewModel @AssistedInject constructor( // do we let you cancel from here? _viewEvents.post(BootstrapViewEvents.SkipBootstrap()) } + is BootstrapStep.SetupPassphrase -> { + // do we let you cancel from here? + _viewEvents.post(BootstrapViewEvents.SkipBootstrap()) + } + is BootstrapStep.ConfirmPassphrase -> { + setState { + copy( + step = BootstrapStep.SetupPassphrase( + isPasswordVisible = (state.step as? BootstrapStep.ConfirmPassphrase)?.isPasswordVisible ?: false + ) + ) + } + } is BootstrapStep.AccountPassword -> { - _viewEvents.post(BootstrapViewEvents.SkipBootstrap(false)) + _viewEvents.post(BootstrapViewEvents.SkipBootstrap(state.passphrase != null)) } BootstrapStep.Initializing -> { // do we let you cancel from here? - _viewEvents.post(BootstrapViewEvents.SkipBootstrap(false)) + _viewEvents.post(BootstrapViewEvents.SkipBootstrap(state.passphrase != null)) } is BootstrapStep.SaveRecoveryKey, BootstrapStep.DoneSuccess -> { @@ -393,8 +476,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( // Companion, view model assisted creation // ====================================== - companion object - : MvRxViewModelFactory { + companion object : MvRxViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: BootstrapViewState): BootstrapSharedViewModel? { val fragment: BootstrapBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt index 9dc8710d2d..f480402b6c 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt @@ -36,25 +36,29 @@ package im.vector.riotx.features.crypto.recover * └───────────────────────────────────┘ │ * │ │ * │ │ - * Existing ├─────────No ──────────────────┐ - * ┌────Keybackup───────┘ KeyBackup │ - * │ │ - * │ │ - * ▼ │ - * ┌─────────────────────────────────────────┐ │ - * │BootstrapStep.GetBackupSecretForMigration│ │ - * └─────────────────────────────────────────┘ │ - * │ │ - * │ │ - * │ is password needed? ─────────────┐ - * │ │ │ - * │ ▼ │ + * Existing ├─────────No ───────┐ │ + * ┌────Keybackup───────┘ KeyBackup │ │ + * │ │ │ + * │ ▼ ▼ + * ▼ ┌────────────────────────────────────┐ + * ┌─────────────────────────────────────────┐ │ BootstrapStep.SetupPassphrase │◀─┐ + * │BootstrapStep.GetBackupSecretForMigration│ └────────────────────────────────────┘ │ + * └─────────────────────────────────────────┘ │ │ + * │ │ ┌Back + * │ ▼ │ + * │ ┌────────────────────────────────────┤ + * │ │ BootstrapStep.ConfirmPassphrase │──┐ + * │ └────────────────────────────────────┘ │ + * │ │ │ + * │ is password needed? │ + * │ │ │ + * │ ▼ │ * │ ┌────────────────────────────────────┐ │ * │ │ BootstrapStep.AccountPassword │ │ * │ └────────────────────────────────────┘ │ - * │ │ │ - * │ │ │ - * │ ┌──────────────┘ password not needed (in + * │ │ │ + * │ │ │ + * │ ┌──────────────────┘ password not needed (in * │ │ memory) * │ │ │ * │ ▼ │ @@ -81,6 +85,9 @@ package im.vector.riotx.features.crypto.recover sealed class BootstrapStep { object SetupSecureBackup : BootstrapStep() + data class SetupPassphrase(val isPasswordVisible: Boolean) : BootstrapStep() + data class ConfirmPassphrase(val isPasswordVisible: Boolean) : BootstrapStep() + data class AccountPassword(val isPasswordVisible: Boolean, val failure: String? = null) : BootstrapStep() object CheckingMigration : BootstrapStep() diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapViewState.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapViewState.kt index 4a53872ae5..8607bf1399 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapViewState.kt @@ -19,13 +19,18 @@ package im.vector.riotx.features.crypto.recover import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized +import com.nulabinc.zxcvbn.Strength import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo import im.vector.riotx.core.platform.WaitingViewData data class BootstrapViewState( - val step: BootstrapStep = BootstrapStep.SetupSecureBackup, + val step: BootstrapStep = BootstrapStep.SetupPassphrase(false), + val passphrase: String? = null, val migrationRecoveryKey: String? = null, + val passphraseRepeat: String? = null, val crossSigningInitialization: Async = Uninitialized, + val passphraseStrength: Async = Uninitialized, + val passphraseConfirmMatch: Async = Uninitialized, val recoveryKeyCreationInfo: SsssKeyCreationInfo? = null, val initializationWaitingViewData: WaitingViewData? = null, val recoverySaveFileProcess: Async = Uninitialized From a6e61f4de8163c94fe01977378244d20ad2ed920 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Jun 2020 09:45:33 +0200 Subject: [PATCH 16/30] Add link --- docs/voip_signaling.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/voip_signaling.md b/docs/voip_signaling.md index ba72e1b218..c80cdd6b96 100644 --- a/docs/voip_signaling.md +++ b/docs/voip_signaling.md @@ -1,3 +1,7 @@ +Useful links: +- https://codelabs.developers.google.com/codelabs/webrtc-web/#0 + + ╔════════════════════════════════════════════════╗ ║ ║ ║A] Placing a call offer ║ From e9706a3b64a44982b1dca32b4d4768dbeb24fd50 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Jun 2020 11:49:23 +0200 Subject: [PATCH 17/30] Update UI --- .../crypto/recover/BootstrapActions.kt | 4 +- .../crypto/recover/BootstrapBottomSheet.kt | 43 +++++++++------- .../BootstrapConfirmPassphraseFragment.kt | 15 ++---- .../BootstrapEnterPassphraseFragment.kt | 14 ++---- .../BootstrapSetupRecoveryKeyFragment.kt | 33 +++++++++++-- .../features/crypto/recover/BootstrapStep.kt | 9 +++- .../crypto/recover/BootstrapViewState.kt | 2 +- .../features/navigation/DefaultNavigator.kt | 2 +- .../src/main/res/drawable/ic_message_key.xml | 19 ------- .../main/res/drawable/ic_message_password.xml | 19 ------- .../res/drawable/ic_secure_backup_24dp.xml | 20 -------- .../res/drawable/ic_security_key_24dp.xml | 23 +++++++++ .../res/drawable/ic_security_phrase_24dp.xml | 23 +++++++++ vector/src/main/res/drawable/ic_user.xml | 33 +++++++------ .../res/layout/bottom_sheet_bootstrap.xml | 6 +-- .../layout/dialog_recovery_key_saved_info.xml | 2 +- .../fragment_bootstrap_enter_passphrase.xml | 2 +- .../fragment_bootstrap_setup_recovery.xml | 49 +++++++++++++------ .../layout/fragment_ssss_access_from_key.xml | 2 +- .../fragment_ssss_access_from_passphrase.xml | 4 +- vector/src/main/res/values/strings.xml | 25 +++++++--- 21 files changed, 199 insertions(+), 150 deletions(-) delete mode 100644 vector/src/main/res/drawable/ic_message_key.xml delete mode 100644 vector/src/main/res/drawable/ic_message_password.xml delete mode 100644 vector/src/main/res/drawable/ic_secure_backup_24dp.xml create mode 100644 vector/src/main/res/drawable/ic_security_key_24dp.xml create mode 100644 vector/src/main/res/drawable/ic_security_phrase_24dp.xml diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt index 2f6cfcf799..ae8dbdc9f4 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapActions.kt @@ -28,7 +28,9 @@ sealed class BootstrapActions : VectorViewModelAction { object GoToCompleted : BootstrapActions() object GoToEnterAccountPassword : BootstrapActions() - object SetupRecoveryKey : BootstrapActions() + data class Start(val userWantsToEnterPassphrase: Boolean) : BootstrapActions() + + object StartKeyBackupMigration : BootstrapActions() data class DoInitialize(val passphrase: String) : BootstrapActions() object DoInitializeGeneratedKey : BootstrapActions() diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt index 4234fd38ee..c2cc4299c5 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt @@ -26,6 +26,7 @@ import android.view.ViewGroup import android.view.WindowManager import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import com.airbnb.mvrx.fragmentViewModel @@ -44,7 +45,6 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { @Parcelize data class Args( - val isNewAccount: Boolean, val initCrossSigningOnly: Boolean ) : Parcelable @@ -122,43 +122,49 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { override fun invalidate() = withState(viewModel) { state -> when (state.step) { - is BootstrapStep.SetupSecureBackup -> { - bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_secure_backup_24dp)) + is BootstrapStep.CheckingMigration -> { + bootstrapIcon.isVisible = false + bootstrapTitleText.text = getString(R.string.bottom_sheet_setup_secure_backup_title) + showFragment(BootstrapWaitingFragment::class, Bundle()) + } + is BootstrapStep.FirstForm -> { + bootstrapIcon.isVisible = false bootstrapTitleText.text = getString(R.string.bottom_sheet_setup_secure_backup_title) showFragment(BootstrapSetupRecoveryKeyFragment::class, Bundle()) } - is BootstrapStep.CheckingMigration -> { - bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_password)) - bootstrapTitleText.text = getString(R.string.upgrade_security) - showFragment(BootstrapWaitingFragment::class, Bundle()) - } is BootstrapStep.SetupPassphrase -> { - bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_password)) - bootstrapTitleText.text = getString(R.string.set_recovery_passphrase, getString(R.string.recovery_passphrase)) + bootstrapIcon.isVisible = true + bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_security_phrase_24dp)) + bootstrapTitleText.text = getString(R.string.set_a_security_phrase_title) showFragment(BootstrapEnterPassphraseFragment::class, Bundle()) } is BootstrapStep.ConfirmPassphrase -> { - bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_message_password)) - bootstrapTitleText.text = getString(R.string.confirm_recovery_passphrase, getString(R.string.recovery_passphrase)) + bootstrapIcon.isVisible = true + bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_security_phrase_24dp)) + bootstrapTitleText.text = getString(R.string.set_a_security_phrase_title) showFragment(BootstrapConfirmPassphraseFragment::class, Bundle()) } is BootstrapStep.AccountPassword -> { + bootstrapIcon.isVisible = true bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_user)) bootstrapTitleText.text = getString(R.string.account_password) showFragment(BootstrapAccountPasswordFragment::class, Bundle()) } is BootstrapStep.Initializing -> { - bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_secure_backup_24dp)) + bootstrapIcon.isVisible = true + bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_security_key_24dp)) bootstrapTitleText.text = getString(R.string.bootstrap_loading_title) showFragment(BootstrapWaitingFragment::class, Bundle()) } is BootstrapStep.SaveRecoveryKey -> { - bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_secure_backup_24dp)) + bootstrapIcon.isVisible = true + bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_security_key_24dp)) bootstrapTitleText.text = getString(R.string.bottom_sheet_save_your_recovery_key_title) showFragment(BootstrapSaveRecoveryKeyFragment::class, Bundle()) } is BootstrapStep.DoneSuccess -> { - bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_secure_backup_24dp)) + bootstrapIcon.isVisible = true + bootstrapIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_security_key_24dp)) bootstrapTitleText.text = getString(R.string.bootstrap_finish_title) showFragment(BootstrapConclusionFragment::class, Bundle()) } @@ -167,7 +173,8 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { is BootstrapStep.GetBackupSecretPassForMigration -> state.step.useKey else -> true } - val drawableRes = if (isKey) R.drawable.ic_secure_backup_24dp else R.drawable.ic_message_password + val drawableRes = if (isKey) R.drawable.ic_security_key_24dp else R.drawable.ic_security_phrase_24dp + bootstrapIcon.isVisible = true bootstrapIcon.setImageDrawable(ContextCompat.getDrawable( requireContext(), drawableRes) @@ -183,10 +190,10 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { const val EXTRA_ARGS = "EXTRA_ARGS" - fun show(fragmentManager: FragmentManager, isAccountCreation: Boolean, initCrossSigningOnly: Boolean) { + fun show(fragmentManager: FragmentManager, initCrossSigningOnly: Boolean) { BootstrapBottomSheet().apply { isCancelable = false - arguments = Bundle().apply { this.putParcelable(EXTRA_ARGS, Args(isAccountCreation, initCrossSigningOnly)) } + arguments = Bundle().apply { this.putParcelable(EXTRA_ARGS, Args(initCrossSigningOnly)) } }.show(fragmentManager, "BootstrapBottomSheet") } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt index ebb6416317..e37f2763ab 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapConfirmPassphraseFragment.kt @@ -19,7 +19,6 @@ package im.vector.riotx.features.crypto.recover import android.os.Bundle import android.view.View import android.view.inputmethod.EditorInfo -import androidx.core.text.toSpannable import androidx.core.view.isGone import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState @@ -29,16 +28,12 @@ import im.vector.riotx.R import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.showPassword import im.vector.riotx.core.platform.VectorBaseFragment -import im.vector.riotx.core.resources.ColorProvider -import im.vector.riotx.core.utils.colorizeMatchingText import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.* import java.util.concurrent.TimeUnit import javax.inject.Inject -class BootstrapConfirmPassphraseFragment @Inject constructor( - private val colorProvider: ColorProvider -) : VectorBaseFragment() { +class BootstrapConfirmPassphraseFragment @Inject constructor() : VectorBaseFragment() { override fun getLayoutResId() = R.layout.fragment_bootstrap_enter_passphrase @@ -49,12 +44,8 @@ class BootstrapConfirmPassphraseFragment @Inject constructor( ssss_passphrase_security_progress.isGone = true - val recPassPhrase = getString(R.string.recovery_passphrase) - bootstrapDescriptionText.text = getString(R.string.bootstrap_info_confirm_text, recPassPhrase) - .toSpannable() - .colorizeMatchingText(recPassPhrase, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) - - ssss_passphrase_enter_edittext.hint = getString(R.string.passphrase_confirm_passphrase) + bootstrapDescriptionText.text = getString(R.string.set_a_security_phrase_again_notice) + ssss_passphrase_enter_edittext.hint = getString(R.string.set_a_security_phrase_hint) withState(sharedViewModel) { // set initial value (useful when coming back) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt index 982f72c14e..aad6ed2aa0 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapEnterPassphraseFragment.kt @@ -19,7 +19,6 @@ package im.vector.riotx.features.crypto.recover import android.os.Bundle import android.view.View import android.view.inputmethod.EditorInfo -import androidx.core.text.toSpannable import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import com.jakewharton.rxbinding3.widget.editorActionEvents @@ -27,17 +26,13 @@ import com.jakewharton.rxbinding3.widget.textChanges import im.vector.riotx.R import im.vector.riotx.core.extensions.showPassword import im.vector.riotx.core.platform.VectorBaseFragment -import im.vector.riotx.core.resources.ColorProvider -import im.vector.riotx.core.utils.colorizeMatchingText import im.vector.riotx.features.settings.VectorLocale import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.* import java.util.concurrent.TimeUnit import javax.inject.Inject -class BootstrapEnterPassphraseFragment @Inject constructor( - private val colorProvider: ColorProvider -) : VectorBaseFragment() { +class BootstrapEnterPassphraseFragment @Inject constructor() : VectorBaseFragment() { override fun getLayoutResId() = R.layout.fragment_bootstrap_enter_passphrase @@ -46,12 +41,9 @@ class BootstrapEnterPassphraseFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val recPassPhrase = getString(R.string.recovery_passphrase) - bootstrapDescriptionText.text = getString(R.string.bootstrap_info_text, recPassPhrase) - .toSpannable() - .colorizeMatchingText(recPassPhrase, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) + bootstrapDescriptionText.text = getString(R.string.set_a_security_phrase_notice) + ssss_passphrase_enter_edittext.hint = getString(R.string.set_a_security_phrase_hint) - ssss_passphrase_enter_edittext.hint = getString(R.string.passphrase_enter_passphrase) withState(sharedViewModel) { // set initial value (useful when coming back) ssss_passphrase_enter_edittext.setText(it.passphrase ?: "") diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt index 38bc429bcf..156acf845f 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSetupRecoveryKeyFragment.kt @@ -18,7 +18,9 @@ package im.vector.riotx.features.crypto.recover import android.os.Bundle import android.view.View +import androidx.core.view.isVisible import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.platform.VectorBaseFragment import kotlinx.android.synthetic.main.fragment_bootstrap_setup_recovery.* @@ -33,10 +35,35 @@ class BootstrapSetupRecoveryKeyFragment @Inject constructor() : VectorBaseFragme override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - bootstrapSetupSecureSubmit.clickableView.debouncedClicks { setupRecoveryKey() } + // Actions when a key backup exist + bootstrapSetupSecureSubmit.clickableView.debouncedClicks { + sharedViewModel.handle(BootstrapActions.StartKeyBackupMigration) + } + + // Actions when there is no key backup + bootstrapSetupSecureUseSecurityKey.clickableView.debouncedClicks { + sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = false)) + } + bootstrapSetupSecureUseSecurityPassphrase.clickableView.debouncedClicks { + sharedViewModel.handle(BootstrapActions.Start(userWantsToEnterPassphrase = true)) + } } - private fun setupRecoveryKey() { - sharedViewModel.handle(BootstrapActions.SetupRecoveryKey) + override fun invalidate() = withState(sharedViewModel) { state -> + if (state.step is BootstrapStep.FirstForm) { + if (state.step.keyBackUpExist) { + // Display the set up action + bootstrapSetupSecureSubmit.isVisible = true + bootstrapSetupSecureUseSecurityKey.isVisible = false + bootstrapSetupSecureUseSecurityPassphrase.isVisible = false + bootstrapSetupSecureUseSecurityPassphraseSeparator.isVisible = false + } else { + // Choose between create a passphrase or use a recovery key + bootstrapSetupSecureSubmit.isVisible = false + bootstrapSetupSecureUseSecurityKey.isVisible = true + bootstrapSetupSecureUseSecurityPassphrase.isVisible = true + bootstrapSetupSecureUseSecurityPassphraseSeparator.isVisible = true + } + } } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt index f480402b6c..9d8fc85e8d 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt @@ -17,6 +17,8 @@ package im.vector.riotx.features.crypto.recover /** + * TODO The schema is not up to date + * * ┌───────────────────────────────────┐ * │ BootstrapStep.SetupSecureBackup │ * └───────────────────────────────────┘ @@ -83,13 +85,16 @@ package im.vector.riotx.features.crypto.recover */ sealed class BootstrapStep { - object SetupSecureBackup : BootstrapStep() + // This is the first step + object CheckingMigration : BootstrapStep() + + // Use will be asked to choose between passphrase or recovery key, or to start process if a key backup exists + data class FirstForm(val keyBackUpExist: Boolean) : BootstrapStep() data class SetupPassphrase(val isPasswordVisible: Boolean) : BootstrapStep() data class ConfirmPassphrase(val isPasswordVisible: Boolean) : BootstrapStep() data class AccountPassword(val isPasswordVisible: Boolean, val failure: String? = null) : BootstrapStep() - object CheckingMigration : BootstrapStep() abstract class GetBackupSecretForMigration : BootstrapStep() data class GetBackupSecretPassForMigration(val isPasswordVisible: Boolean, val useKey: Boolean) : GetBackupSecretForMigration() diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapViewState.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapViewState.kt index 8607bf1399..4aa18eb7e7 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapViewState.kt @@ -24,7 +24,7 @@ import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo import im.vector.riotx.core.platform.WaitingViewData data class BootstrapViewState( - val step: BootstrapStep = BootstrapStep.SetupPassphrase(false), + val step: BootstrapStep = BootstrapStep.CheckingMigration, val passphrase: String? = null, val migrationRecoveryKey: String? = null, val passphraseRepeat: String? = null, diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index 5b70d65523..0b89ab8ec4 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -126,7 +126,7 @@ class DefaultNavigator @Inject constructor( override fun upgradeSessionSecurity(context: Context, initCrossSigningOnly: Boolean) { if (context is VectorBaseActivity) { - BootstrapBottomSheet.show(context.supportFragmentManager, false, initCrossSigningOnly) + BootstrapBottomSheet.show(context.supportFragmentManager, initCrossSigningOnly) } } diff --git a/vector/src/main/res/drawable/ic_message_key.xml b/vector/src/main/res/drawable/ic_message_key.xml deleted file mode 100644 index 9c5e53571d..0000000000 --- a/vector/src/main/res/drawable/ic_message_key.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - diff --git a/vector/src/main/res/drawable/ic_message_password.xml b/vector/src/main/res/drawable/ic_message_password.xml deleted file mode 100644 index f82603a320..0000000000 --- a/vector/src/main/res/drawable/ic_message_password.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - diff --git a/vector/src/main/res/drawable/ic_secure_backup_24dp.xml b/vector/src/main/res/drawable/ic_secure_backup_24dp.xml deleted file mode 100644 index 899bb8d2ae..0000000000 --- a/vector/src/main/res/drawable/ic_secure_backup_24dp.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - diff --git a/vector/src/main/res/drawable/ic_security_key_24dp.xml b/vector/src/main/res/drawable/ic_security_key_24dp.xml new file mode 100644 index 0000000000..8f7db4d798 --- /dev/null +++ b/vector/src/main/res/drawable/ic_security_key_24dp.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/vector/src/main/res/drawable/ic_security_phrase_24dp.xml b/vector/src/main/res/drawable/ic_security_phrase_24dp.xml new file mode 100644 index 0000000000..990f71a709 --- /dev/null +++ b/vector/src/main/res/drawable/ic_security_phrase_24dp.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/vector/src/main/res/drawable/ic_user.xml b/vector/src/main/res/drawable/ic_user.xml index 119c0a5170..fe97a83c57 100644 --- a/vector/src/main/res/drawable/ic_user.xml +++ b/vector/src/main/res/drawable/ic_user.xml @@ -1,21 +1,24 @@ - - + + diff --git a/vector/src/main/res/layout/bottom_sheet_bootstrap.xml b/vector/src/main/res/layout/bottom_sheet_bootstrap.xml index d5ced50ae7..69060a57da 100644 --- a/vector/src/main/res/layout/bottom_sheet_bootstrap.xml +++ b/vector/src/main/res/layout/bottom_sheet_bootstrap.xml @@ -22,7 +22,7 @@ android:layout_marginStart="16dp" android:importantForAccessibility="no" android:scaleType="fitCenter" - android:src="@drawable/ic_message_password" + android:src="@drawable/ic_security_key_24dp" android:tint="?riotx_text_primary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -31,7 +31,7 @@ android:id="@+id/bootstrapTitleText" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="8dp" + android:layout_marginStart="16dp" android:layout_marginEnd="16dp" android:ellipsize="end" android:textColor="?riotx_text_primary" @@ -40,7 +40,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/bootstrapIcon" app:layout_constraintTop_toTopOf="parent" - tools:text="@string/recovery_passphrase" /> + tools:text="@string/bottom_sheet_setup_secure_backup_title" /> - - + + + + + + + android:src="@drawable/ic_security_key_24dp" /> + android:src="@drawable/ic_security_phrase_24dp" /> Secure & unlock encrypted messages and trust with a %s. Enter your %s again to confirm it. - Don’t re-use your account password. + Don’t use your account password. + Enter a security phrase only you know, used to secure secrets on your server. This might take several seconds, please be patient. Setting up recovery. @@ -2481,12 +2482,24 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Set up Secure Backup - Set up Secure Backup - Backup your encryption keys with your account data in case you lose access to your logins. - Your keys will be secured with a unique Recovery Key. + Secure backup + Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server. Set up + Use a Security Key + Generate a security key to store somewhere safe like a password manager or a safe. + Use a Security Phrase + Enter a secret phrase only you know, and generate a key for backup. - Save your Recovery Key - Store your Recovery Key somewhere safe. It can be used to unlock your encrypted messages and data. + + Save your Security Key + Store your Security Key somewhere safe, like a password manager or a safe. + + Set a Security Phrase + Enter a security phrase only you know, used to secure secrets on your server. + Security Phrase + Enter your Security Phrase again to confirm it. + + Save your Security Key + Store your Security Key somewhere safe, like a password manager or a safe. From a66010a1d876d0ca4f81a8b1a1408e7fba1b055c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Jun 2020 12:59:40 +0200 Subject: [PATCH 18/30] Create isCrossSigningInitialized(). Do not display the conclusion Fragment anymore --- .../crosssigning/CrossSigningService.kt | 4 +- .../DefaultCrossSigningService.kt | 1 + .../recover/BootstrapCrossSigningTask.kt | 33 ++- .../BootstrapSaveRecoveryKeyFragment.kt | 7 +- .../recover/BootstrapSharedViewModel.kt | 230 ++++++++++-------- .../VectorSettingsSecurityPrivacyFragment.kt | 3 +- ...iceVerificationInfoBottomSheetViewModel.kt | 2 +- .../settings/devices/DevicesViewModel.kt | 2 +- 8 files changed, 156 insertions(+), 126 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt index cb55235201..8d856d0860 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt @@ -43,9 +43,11 @@ interface CrossSigningService { fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback) + fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null + fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, uskKeyPrivateKey: String?, - sskPrivateKey: String?) : UserTrustResult + sskPrivateKey: String?): UserTrustResult fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 06355e6d72..7c5f64182c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -150,6 +150,7 @@ internal class DefaultCrossSigningService @Inject constructor( this.callbackThread = TaskThread.CRYPTO this.callback = object : MatrixCallback { override fun onFailure(failure: Throwable) { + Timber.e(failure, "Error in initializeCrossSigning()") callback.onFailure(failure) } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt index 7a610d8c85..83b56b1b75 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -16,8 +16,10 @@ package im.vector.riotx.features.crypto.recover +import im.vector.matrix.android.api.auth.data.LoginFlowTypes import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError +import im.vector.matrix.android.api.failure.toRegistrationFlowResponse import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME @@ -79,23 +81,23 @@ class BootstrapCrossSigningTask @Inject constructor( override suspend fun execute(params: Params): BootstrapResult { val crossSigningService = session.cryptoService().crossSigningService() - // TODO Remove - /* - params.progressListener?.onProgress( - WaitingViewData( - stringProvider.getString(R.string.bootstrap_crosssigning_progress_initializing), - isIndeterminate = true - ) - ) + // Ensure cross-signing is initialized. Due to migration it is maybe not always correctly initialized + if (!crossSigningService.isCrossSigningInitialized()) { + params.progressListener?.onProgress( + WaitingViewData( + stringProvider.getString(R.string.bootstrap_crosssigning_progress_initializing), + isIndeterminate = true + ) + ) - try { - awaitCallback { - crossSigningService.initializeCrossSigning(params.userPasswordAuth, it) + try { + awaitCallback { + crossSigningService.initializeCrossSigning(params.userPasswordAuth, it) + } + } catch (failure: Throwable) { + return handleInitializeXSigningError(failure) } - } catch (failure: Throwable) { - return handleInitializeXSigningError(failure) } - */ val keyInfo: SsssKeyCreationInfo @@ -229,8 +231,6 @@ class BootstrapCrossSigningTask @Inject constructor( return BootstrapResult.Success(keyInfo) } - /* - TODO Remove private fun handleInitializeXSigningError(failure: Throwable): BootstrapResult { if (failure is Failure.ServerError && failure.error.code == MatrixError.M_FORBIDDEN) { return BootstrapResult.InvalidPasswordError(failure.error) @@ -247,5 +247,4 @@ class BootstrapCrossSigningTask @Inject constructor( } return BootstrapResult.GenericError(failure) } - */ } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt index 5246f5a882..3ab48e44ff 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSaveRecoveryKeyFragment.kt @@ -48,7 +48,12 @@ class BootstrapSaveRecoveryKeyFragment @Inject constructor( recoverySave.clickableView.debouncedClicks { downloadRecoveryKey() } recoveryCopy.clickableView.debouncedClicks { shareRecoveryKey() } - recoveryContinue.clickableView.debouncedClicks { sharedViewModel.handle(BootstrapActions.GoToCompleted) } + recoveryContinue.clickableView.debouncedClicks { + // We do not display the final Fragment anymore + // TODO Do some cleanup + // sharedViewModel.handle(BootstrapActions.GoToCompleted) + sharedViewModel.handle(BootstrapActions.Completed) + } } private fun downloadRecoveryKey() = withState(sharedViewModel) { _ -> diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt index ebbe502a65..25bc93102a 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt @@ -44,7 +44,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.io.OutputStream - class BootstrapSharedViewModel @AssistedInject constructor( @Assisted initialState: BootstrapViewState, @Assisted val args: BootstrapBottomSheet.Args, @@ -55,6 +54,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( private val reAuthHelper: ReAuthHelper ) : VectorViewModel(initialState) { + private var isBackupCreatedFromPassphrase: Boolean = false private val zxcvbn = Zxcvbn() @AssistedInject.Factory @@ -64,48 +64,48 @@ class BootstrapSharedViewModel @AssistedInject constructor( private var _pendingSession: String? = null - private fun startProcess() { + init { // need to check if user have an existing keybackup - if (args.isNewAccount) { + 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) + } + if (version == null) { + // we just resume plain bootstrap + setState { + copy(step = BootstrapStep.FirstForm(keyBackUpExist = false)) + } + } 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 { + isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null + setState { + copy(step = BootstrapStep.FirstForm(keyBackUpExist = true)) + } + } + } + } + } + + private fun handleStartMigratingKeyBackup() { + if (isBackupCreatedFromPassphrase) { setState { - copy(step = BootstrapStep.SetupPassphrase(false)) + copy(step = BootstrapStep.GetBackupSecretPassForMigration(isPasswordVisible = false, useKey = false)) } } else { 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) - } - if (version == null) { - // we just resume plain bootstrap - setState { - copy(step = BootstrapStep.SetupPassphrase(false)) - } - } 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 { - val isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null - if (isBackupCreatedFromPassphrase) { - setState { - copy(step = BootstrapStep.GetBackupSecretPassForMigration(isPasswordVisible = false, useKey = false)) - } - } else { - setState { - copy(step = BootstrapStep.GetBackupSecretKeyForMigration) - } - } - } - } + copy(step = BootstrapStep.GetBackupSecretKeyForMigration) } } } @@ -135,12 +135,14 @@ class BootstrapSharedViewModel @AssistedInject constructor( copy(step = state.step.copy(isPasswordVisible = !state.step.isPasswordVisible)) } } - else -> { - } + else -> Unit } } - BootstrapActions.SetupRecoveryKey -> { - startProcess() + BootstrapActions.StartKeyBackupMigration -> { + handleStartMigratingKeyBackup() + } + is BootstrapActions.Start -> { + handleStart(action) } is BootstrapActions.UpdateCandidatePassphrase -> { val strength = zxcvbn.measure(action.pass) @@ -259,6 +261,18 @@ class BootstrapSharedViewModel @AssistedInject constructor( }.exhaustive } + private fun handleStart(action: BootstrapActions.Start) = withState { + if (action.userWantsToEnterPassphrase) { + setState { + copy( + step = BootstrapStep.SetupPassphrase(isPasswordVisible = false) + ) + } + } else { + startInitializeFlow(null) + } + } + // ======================================= // Business Logic // ======================================= @@ -335,7 +349,9 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } - private fun startInitializeFlow(userPassword: String?) { + private fun startInitializeFlow(userPassword: String?) = withState { state -> + val previousStep = state.step + setState { copy(step = BootstrapStep.Initializing) } @@ -350,70 +366,71 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } - withState { state -> - viewModelScope.launch(Dispatchers.IO) { - val userPasswordAuth = userPassword?.let { - UserPasswordAuth( - // Note that _pendingSession may or may not be null, this is OK, it will be managed by the task - session = _pendingSession, - user = session.myUserId, - password = it - ) - } - bootstrapTask.invoke(this, - Params( - userPasswordAuth = userPasswordAuth, - progressListener = progressListener, - passphrase = state.passphrase, - keySpec = state.migrationRecoveryKey?.let { extractCurveKeyFromRecoveryKey(it)?.let { RawBytesKeySpec(it) } } - ) - ) { bootstrapResult -> - when (bootstrapResult) { - is BootstrapResult.Success -> { + viewModelScope.launch(Dispatchers.IO) { + val userPasswordAuth = userPassword?.let { + UserPasswordAuth( + // Note that _pendingSession may or may not be null, this is OK, it will be managed by the task + session = _pendingSession, + user = session.myUserId, + password = it + ) + } + + bootstrapTask.invoke(this, + Params( + userPasswordAuth = userPasswordAuth, + progressListener = progressListener, + passphrase = state.passphrase, + keySpec = state.migrationRecoveryKey?.let { extractCurveKeyFromRecoveryKey(it)?.let { RawBytesKeySpec(it) } } + ) + ) { bootstrapResult -> + when (bootstrapResult) { + is BootstrapResult.Success -> { + setState { + copy( + recoveryKeyCreationInfo = bootstrapResult.keyInfo, + step = BootstrapStep.SaveRecoveryKey(false) + ) + } + } + is BootstrapResult.PasswordAuthFlowMissing -> { + // Ask the password to the user + _pendingSession = bootstrapResult.sessionId + setState { + copy( + step = BootstrapStep.AccountPassword(false) + ) + } + } + is BootstrapResult.UnsupportedAuthFlow -> { + _viewEvents.post(BootstrapViewEvents.ModalError(stringProvider.getString(R.string.auth_flow_not_supported))) + _viewEvents.post(BootstrapViewEvents.Dismiss) + } + is BootstrapResult.InvalidPasswordError -> { + // it's a bad password + // We clear the auth session, to avoid 'Requested operation has changed during the UI authentication session' error + _pendingSession = null + setState { + copy( + step = BootstrapStep.AccountPassword(false, stringProvider.getString(R.string.auth_invalid_login_param)) + ) + } + } + is BootstrapResult.Failure -> { + if (bootstrapResult is BootstrapResult.GenericError + && bootstrapResult.failure is Failure.OtherServerError + && bootstrapResult.failure.httpCode == 401) { + // Ignore this error + } else { + _viewEvents.post(BootstrapViewEvents.ModalError(bootstrapResult.error ?: stringProvider.getString(R.string.matrix_error))) + // Not sure setState { copy( - recoveryKeyCreationInfo = bootstrapResult.keyInfo, - step = BootstrapStep.SaveRecoveryKey(false) + step = previousStep ) } } - is BootstrapResult.PasswordAuthFlowMissing -> { - // Ask the password to the user - _pendingSession = bootstrapResult.sessionId - setState { - copy( - step = BootstrapStep.AccountPassword(false) - ) - } - } - is BootstrapResult.UnsupportedAuthFlow -> { - _viewEvents.post(BootstrapViewEvents.ModalError(stringProvider.getString(R.string.auth_flow_not_supported))) - _viewEvents.post(BootstrapViewEvents.Dismiss) - } - is BootstrapResult.InvalidPasswordError -> { - // it's a bad password - // We clear the auth session, to avoid 'Requested operation has changed during the UI authentication session' error - _pendingSession = null - setState { - copy( - step = BootstrapStep.AccountPassword(false, stringProvider.getString(R.string.auth_invalid_login_param)) - ) - } - } - is BootstrapResult.Failure -> { - if (bootstrapResult is BootstrapResult.GenericError - && bootstrapResult.failure is Failure.OtherServerError - && bootstrapResult.failure.httpCode == 401) { - } else { - _viewEvents.post(BootstrapViewEvents.ModalError(bootstrapResult.error ?: stringProvider.getString(R.string.matrix_error))) - setState { - copy( - step = BootstrapStep.ConfirmPassphrase(false) - ) - } - } - } } } } @@ -469,7 +486,14 @@ class BootstrapSharedViewModel @AssistedInject constructor( BootstrapStep.DoneSuccess -> { // nop } - } + BootstrapStep.CheckingMigration -> Unit + is BootstrapStep.FirstForm -> { + _viewEvents.post(BootstrapViewEvents.SkipBootstrap()) + } + is BootstrapStep.GetBackupSecretForMigration -> { + _viewEvents.post(BootstrapViewEvents.SkipBootstrap()) + } + }.exhaustive } // ====================================== @@ -481,7 +505,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(isNewAccount = true, initCrossSigningOnly = true) + ?: BootstrapBottomSheet.Args(initCrossSigningOnly = true) return fragment.bootstrapViewModelFactory.create(state, args) } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt index dc8c17b08b..3a1d4859af 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -119,8 +119,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( } private fun refreshXSigningStatus() { - val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys() - val xSigningIsEnableInAccount = crossSigningKeys != null + val xSigningIsEnableInAccount = session.cryptoService().crossSigningService().isCrossSigningInitialized() val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified() val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign() diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt index 88cc4ed4bc..02209a4cb2 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheetViewModel.kt @@ -54,7 +54,7 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As setState { copy( - hasAccountCrossSigning = session.cryptoService().crossSigningService().getMyCrossSigningKeys() != null, + hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(), accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified() ) } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt index b8aa490aa9..8022b8988c 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DevicesViewModel.kt @@ -95,7 +95,7 @@ class DevicesViewModel @AssistedInject constructor( setState { copy( - hasAccountCrossSigning = session.cryptoService().crossSigningService().getMyCrossSigningKeys() != null, + hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized(), accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified(), myDeviceId = session.sessionParams.deviceId ?: "" ) From 3aaa425714fe76eed7b95a23916cd48348beaa3f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Jun 2020 13:07:09 +0200 Subject: [PATCH 19/30] Handle back navigation SetupPassphrase -> Firstform --- .../recover/BootstrapSharedViewModel.kt | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt index 25bc93102a..a6b4417884 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt @@ -54,6 +54,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( private val reAuthHelper: ReAuthHelper ) : VectorViewModel(initialState) { + private var doesKeyBackupExist: Boolean = false private var isBackupCreatedFromPassphrase: Boolean = false private val zxcvbn = Zxcvbn() @@ -77,8 +78,9 @@ class BootstrapSharedViewModel @AssistedInject constructor( } if (version == null) { // we just resume plain bootstrap + doesKeyBackupExist = false setState { - copy(step = BootstrapStep.FirstForm(keyBackUpExist = false)) + copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist)) } } else { // we need to get existing backup passphrase/key and convert to SSSS @@ -89,9 +91,10 @@ class BootstrapSharedViewModel @AssistedInject constructor( // strange case... just finish? _viewEvents.post(BootstrapViewEvents.Dismiss) } else { + doesKeyBackupExist = true isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null setState { - copy(step = BootstrapStep.FirstForm(keyBackUpExist = true)) + copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist)) } } } @@ -463,14 +466,20 @@ class BootstrapSharedViewModel @AssistedInject constructor( _viewEvents.post(BootstrapViewEvents.SkipBootstrap()) } is BootstrapStep.SetupPassphrase -> { - // do we let you cancel from here? - _viewEvents.post(BootstrapViewEvents.SkipBootstrap()) + setState { + copy( + step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist), + // Also reset the passphrase + passphrase = null, + passphraseRepeat = null + ) + } } is BootstrapStep.ConfirmPassphrase -> { setState { copy( step = BootstrapStep.SetupPassphrase( - isPasswordVisible = (state.step as? BootstrapStep.ConfirmPassphrase)?.isPasswordVisible ?: false + isPasswordVisible = state.step.isPasswordVisible ) ) } From 8e7166662b92a1bc131b859af1311925b85ecdde Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Jun 2020 14:26:23 +0200 Subject: [PATCH 20/30] Handle back navigation Recovery backup -> Firstform Also create BootstrapStep.GetBackupSecretForMigration.useKey() to reduce code duplication --- .../crypto/recover/BootstrapBottomSheet.kt | 5 +--- .../recover/BootstrapMigrateBackupFragment.kt | 23 +++++----------- .../recover/BootstrapSharedViewModel.kt | 26 ++++++++++++++----- .../features/crypto/recover/BootstrapStep.kt | 7 +++++ 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt index c2cc4299c5..5995879d06 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt @@ -169,10 +169,7 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { showFragment(BootstrapConclusionFragment::class, Bundle()) } is BootstrapStep.GetBackupSecretForMigration -> { - val isKey = when (state.step) { - is BootstrapStep.GetBackupSecretPassForMigration -> state.step.useKey - else -> true - } + val isKey = state.step.useKey() val drawableRes = if (isKey) R.drawable.ic_security_key_24dp else R.drawable.ic_security_phrase_24dp bootstrapIcon.isVisible = true bootstrapIcon.setImageDrawable(ContextCompat.getDrawable( diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt index 0b8e201edd..3e8f1c383e 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt @@ -86,17 +86,12 @@ class BootstrapMigrateBackupFragment @Inject constructor( } private fun submit() = withState(sharedViewModel) { state -> - if (state.step !is BootstrapStep.GetBackupSecretForMigration) { - return@withState - } - val isEnteringKey = - when (state.step) { - is BootstrapStep.GetBackupSecretPassForMigration -> state.step.useKey - else -> true - } + val getBackupSecretForMigration = state.step as? BootstrapStep.GetBackupSecretForMigration ?: return@withState + + val isEnteringKey = getBackupSecretForMigration.useKey() val secret = bootstrapMigrateEditText.text?.toString() - if (secret.isNullOrBlank()) { + if (secret.isNullOrEmpty()) { val errRes = if (isEnteringKey) R.string.recovery_key_empty_error_message else R.string.passphrase_empty_error_message bootstrapRecoveryKeyEnterTil.error = getString(errRes) } else if (isEnteringKey && !isValidRecoveryKey(secret)) { @@ -112,15 +107,9 @@ class BootstrapMigrateBackupFragment @Inject constructor( } override fun invalidate() = withState(sharedViewModel) { state -> - if (state.step !is BootstrapStep.GetBackupSecretForMigration) { - return@withState - } + val getBackupSecretForMigration = state.step as? BootstrapStep.GetBackupSecretForMigration ?: return@withState - val isEnteringKey = - when (state.step) { - is BootstrapStep.GetBackupSecretPassForMigration -> state.step.useKey - else -> true - } + val isEnteringKey = getBackupSecretForMigration.useKey() if (isEnteringKey) { bootstrapMigrateShowPassword.isVisible = false diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt index a6b4417884..a88067f5c9 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt @@ -458,13 +458,18 @@ class BootstrapSharedViewModel @AssistedInject constructor( ) } } else { - _viewEvents.post(BootstrapViewEvents.SkipBootstrap()) + setState { + copy( + step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist), + // Also reset the passphrase + passphrase = null, + passphraseRepeat = null, + // Also reset the key + migrationRecoveryKey = null + ) + } } } - is BootstrapStep.GetBackupSecretKeyForMigration -> { - // do we let you cancel from here? - _viewEvents.post(BootstrapViewEvents.SkipBootstrap()) - } is BootstrapStep.SetupPassphrase -> { setState { copy( @@ -500,7 +505,16 @@ class BootstrapSharedViewModel @AssistedInject constructor( _viewEvents.post(BootstrapViewEvents.SkipBootstrap()) } is BootstrapStep.GetBackupSecretForMigration -> { - _viewEvents.post(BootstrapViewEvents.SkipBootstrap()) + setState { + copy( + step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist), + // Also reset the passphrase + passphrase = null, + passphraseRepeat = null, + // Also reset the key + migrationRecoveryKey = null + ) + } } }.exhaustive } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt index 9d8fc85e8d..c7639068d1 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapStep.kt @@ -104,3 +104,10 @@ sealed class BootstrapStep { data class SaveRecoveryKey(val isSaved: Boolean) : BootstrapStep() object DoneSuccess : BootstrapStep() } + +fun BootstrapStep.GetBackupSecretForMigration.useKey(): Boolean { + return when (this) { + is BootstrapStep.GetBackupSecretPassForMigration -> useKey + else -> true + } +} From e046d17bf2d4e314343fb3a6dc552c83afe0cdba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Jun 2020 14:30:49 +0200 Subject: [PATCH 21/30] Remove colored part, as user think they are links. --- .../crypto/recover/BootstrapMigrateBackupFragment.kt | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt index 3e8f1c383e..710cee035c 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt @@ -24,7 +24,6 @@ import android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE import android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD import android.view.View import android.view.inputmethod.EditorInfo -import androidx.core.text.toSpannable import androidx.core.view.isVisible import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState @@ -36,8 +35,6 @@ import im.vector.riotx.R import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.showPassword import im.vector.riotx.core.platform.VectorBaseFragment -import im.vector.riotx.core.resources.ColorProvider -import im.vector.riotx.core.utils.colorizeMatchingText import im.vector.riotx.core.utils.startImportTextFromFileIntent import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.bootstrapDescriptionText @@ -45,9 +42,7 @@ import kotlinx.android.synthetic.main.fragment_bootstrap_migrate_backup.* import java.util.concurrent.TimeUnit import javax.inject.Inject -class BootstrapMigrateBackupFragment @Inject constructor( - private val colorProvider: ColorProvider -) : VectorBaseFragment() { +class BootstrapMigrateBackupFragment @Inject constructor() : VectorBaseFragment() { override fun getLayoutResId() = R.layout.fragment_bootstrap_migrate_backup @@ -117,8 +112,6 @@ class BootstrapMigrateBackupFragment @Inject constructor( val recKey = getString(R.string.bootstrap_migration_backup_recovery_key) bootstrapDescriptionText.text = getString(R.string.enter_account_password, recKey) - .toSpannable() - .colorizeMatchingText(recKey, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) bootstrapMigrateEditText.hint = recKey @@ -142,8 +135,6 @@ class BootstrapMigrateBackupFragment @Inject constructor( val recKey = getString(R.string.bootstrap_migration_use_recovery_key) bootstrapMigrateForgotPassphrase.text = getString(R.string.bootstrap_migration_with_passphrase_helper_with_link, recKey) - .toSpannable() - .colorizeMatchingText(recKey, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) bootstrapMigrateUseFile.isVisible = false } From 8c5ec2c57ffb70068b1ea52827e8150463f4c9b7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Jun 2020 14:30:49 +0200 Subject: [PATCH 22/30] Restore color for the second phrase --- .../crypto/recover/BootstrapMigrateBackupFragment.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt index 710cee035c..141edb2c58 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapMigrateBackupFragment.kt @@ -24,6 +24,7 @@ import android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE import android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD import android.view.View import android.view.inputmethod.EditorInfo +import androidx.core.text.toSpannable import androidx.core.view.isVisible import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState @@ -35,6 +36,8 @@ import im.vector.riotx.R import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.showPassword import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.utils.colorizeMatchingText import im.vector.riotx.core.utils.startImportTextFromFileIntent import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.bootstrapDescriptionText @@ -42,7 +45,9 @@ import kotlinx.android.synthetic.main.fragment_bootstrap_migrate_backup.* import java.util.concurrent.TimeUnit import javax.inject.Inject -class BootstrapMigrateBackupFragment @Inject constructor() : VectorBaseFragment() { +class BootstrapMigrateBackupFragment @Inject constructor( + private val colorProvider: ColorProvider +) : VectorBaseFragment() { override fun getLayoutResId() = R.layout.fragment_bootstrap_migrate_backup @@ -135,6 +140,8 @@ class BootstrapMigrateBackupFragment @Inject constructor() : VectorBaseFragment( val recKey = getString(R.string.bootstrap_migration_use_recovery_key) bootstrapMigrateForgotPassphrase.text = getString(R.string.bootstrap_migration_with_passphrase_helper_with_link, recKey) + .toSpannable() + .colorizeMatchingText(recKey, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) bootstrapMigrateUseFile.isVisible = false } From 6131c10d31ce307214611ced6c5dc6e7f8e3a011 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Jun 2020 15:07:34 +0200 Subject: [PATCH 23/30] Show a more appropriate error when user enter a wrong backup passphrase --- .../recover/BackupToQuadSMigrationTask.kt | 4 +- .../recover/BootstrapSharedViewModel.kt | 63 +++++++++++-------- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BackupToQuadSMigrationTask.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BackupToQuadSMigrationTask.kt index 855ccb7bc7..02bdab6990 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BackupToQuadSMigrationTask.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BackupToQuadSMigrationTask.kt @@ -44,11 +44,11 @@ class BackupToQuadSMigrationTask @Inject constructor( sealed class Result { object Success : Result() - abstract class Failure(val error: String?) : Result() + abstract class Failure(val throwable: Throwable?) : Result() object InvalidRecoverySecret : Failure(null) object NoKeyBackupVersion : Failure(null) object IllegalParams : Failure(null) - class ErrorFailure(throwable: Throwable) : Failure(throwable.localizedMessage) + class ErrorFailure(throwable: Throwable) : Failure(throwable) } data class Params( diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt index a88067f5c9..3940b47d66 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt @@ -35,6 +35,7 @@ import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyF import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.matrix.android.internal.util.awaitCallback import im.vector.riotx.R +import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.WaitingViewData @@ -48,6 +49,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( @Assisted initialState: BootstrapViewState, @Assisted val args: BootstrapBottomSheet.Args, private val stringProvider: StringProvider, + private val errorFormatter: ErrorFormatter, private val session: Session, private val bootstrapTask: BootstrapCrossSigningTask, private val migrationTask: BackupToQuadSMigrationTask, @@ -301,7 +303,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } - private fun startMigrationFlow(prevState: BootstrapStep, passphrase: String?, recoveryKey: String?) { + private fun startMigrationFlow(previousStep: BootstrapStep, passphrase: String?, recoveryKey: String?) {//TODO Rename param setState { copy(step = BootstrapStep.Initializing) } @@ -316,38 +318,37 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } migrationTask.invoke(this, BackupToQuadSMigrationTask.Params(passphrase, recoveryKey, progressListener)) { - if (it is BackupToQuadSMigrationTask.Result.Success) { - setState { - copy( - passphrase = passphrase, - passphraseRepeat = passphrase, - migrationRecoveryKey = recoveryKey - ) - } - val userPassword = reAuthHelper.data - if (userPassword == null) { + when (it) { + is BackupToQuadSMigrationTask.Result.Success -> { setState { copy( - step = BootstrapStep.AccountPassword(false) + passphrase = passphrase, + passphraseRepeat = passphrase, + migrationRecoveryKey = recoveryKey ) } - } else { - startInitializeFlow(userPassword) + val userPassword = reAuthHelper.data + if (userPassword == null) { + setState { + copy( + step = BootstrapStep.AccountPassword(false) + ) + } + } else { + startInitializeFlow(userPassword) + } } - } else { - _viewEvents.post( - BootstrapViewEvents.ModalError( - (it as? BackupToQuadSMigrationTask.Result.Failure)?.error - ?: stringProvider.getString(R.string.matrix_error - ) - ) - ) - setState { - copy( - step = prevState + is BackupToQuadSMigrationTask.Result.Failure -> { + _viewEvents.post( + BootstrapViewEvents.ModalError(it.toHumanReadable()) ) + setState { + copy( + step = previousStep + ) + } } - } + }.exhaustive } } } @@ -519,6 +520,16 @@ class BootstrapSharedViewModel @AssistedInject constructor( }.exhaustive } + private fun BackupToQuadSMigrationTask.Result.Failure.toHumanReadable(): String { + return when (this) { + is BackupToQuadSMigrationTask.Result.InvalidRecoverySecret -> stringProvider.getString(R.string.keys_backup_passphrase_error_decrypt) + is BackupToQuadSMigrationTask.Result.ErrorFailure -> errorFormatter.toHumanReadable(throwable) + // is BackupToQuadSMigrationTask.Result.NoKeyBackupVersion, + // is BackupToQuadSMigrationTask.Result.IllegalParams, + else -> stringProvider.getString(R.string.unexpected_error) + } + } + // ====================================== // Companion, view model assisted creation // ====================================== From 4ce2478e4459151c50606370d47d196731f7389d Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 26 Jun 2020 18:58:57 +0200 Subject: [PATCH 24/30] Fix 404 when uploading xsigning keys Own device keys was not yet uploaded, now it's done on session open . + added some cleaning of legacy things --- .../internal/crypto/DefaultCryptoService.kt | 85 +++++++++++++------ .../crypto/store/db/RealmCryptoStore.kt | 6 +- .../internal/session/DefaultSession.kt | 1 + .../session/sync/SyncResponseHandler.kt | 5 +- .../riotx/core/utils/TemporaryStoreTest.kt | 2 +- .../recover/BootstrapSharedViewModel.kt | 3 +- .../features/home/HomeActivityViewModel.kt | 2 +- 7 files changed, 68 insertions(+), 36 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 4b031211e1..9621ce203b 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -29,6 +29,7 @@ import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.NoOpMatrixCallback import im.vector.matrix.android.api.crypto.MXCryptoConfig +import im.vector.matrix.android.api.extensions.tryThis import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.api.session.crypto.CryptoService @@ -327,35 +328,65 @@ internal class DefaultCryptoService @Inject constructor( * and, then, if this is the first time, this new device will be announced to all other users * devices. * - * @param isInitialSync true if it starts from an initial sync */ - fun start(isInitialSync: Boolean) { - if (isStarted.get() || isStarting.get()) { - return - } - isStarting.set(true) - + fun start() { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - internalStart(isInitialSync) + internalStart() } // Just update fetchDevicesList(NoOpMatrixCallback()) } - private suspend fun internalStart(isInitialSync: Boolean) { - // Open the store - cryptoStore.open() - runCatching { + fun ensureDevice() { + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + // Open the store + cryptoStore.open() + // TODO why do that everytime? we should mark that it was done uploadDeviceKeys() oneTimeKeysUploader.maybeUploadOneTimeKeys() - keysBackupService.checkAndStartKeysBackup() - if (isInitialSync) { - // refresh the devices list for each known room members - deviceListManager.invalidateAllDeviceLists() - deviceListManager.refreshOutdatedDeviceLists() - } else { - incomingGossipingRequestManager.processReceivedGossipingRequests() + // this can throw if no backup + tryThis { + keysBackupService.checkAndStartKeysBackup() } + } + } + + fun onSyncWillProcess(isInitialSync: Boolean) { + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + if (isInitialSync) { + try { + // On initial sync, we start all our tracking from + // scratch, so mark everything as untracked. onCryptoEvent will + // be called for all e2e rooms during the processing of the sync, + // at which point we'll start tracking all the users of that room. + deviceListManager.invalidateAllDeviceLists() + deviceListManager.refreshOutdatedDeviceLists() + } catch (failure: Throwable) { + Timber.e(failure, "## CRYPTO onSyncWillProcess ") + } + } + } + } + + private fun internalStart() { + if (isStarted.get() || isStarting.get()) { + return + } + isStarting.set(true) + + // Open the store + cryptoStore.open() + + runCatching { +// if (isInitialSync) { +// // refresh the devices list for each known room members +// deviceListManager.invalidateAllDeviceLists() +// deviceListManager.refreshOutdatedDeviceLists() +// } else { + + // Why would we do that? it will be called at end of syn + incomingGossipingRequestManager.processReceivedGossipingRequests() +// } }.fold( { isStarting.set(false) @@ -624,10 +655,10 @@ internal class DefaultCryptoService @Inject constructor( roomId: String, callback: MatrixCallback) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - if (!isStarted()) { - Timber.v("## CRYPTO | encryptEventContent() : wait after e2e init") - internalStart(false) - } +// if (!isStarted()) { +// Timber.v("## CRYPTO | encryptEventContent() : wait after e2e init") +// internalStart(false) +// } val userIds = getRoomUserIds(roomId) var alg = roomEncryptorsStore.get(roomId) if (alg == null) { @@ -1158,10 +1189,10 @@ internal class DefaultCryptoService @Inject constructor( } cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - if (!isStarted()) { - Timber.v("## CRYPTO | requestRoomKeyForEvent() : wait after e2e init") - internalStart(false) - } +// if (!isStarted()) { +// Timber.v("## CRYPTO | requestRoomKeyForEvent() : wait after e2e init") +// internalStart(false) +// } roomDecryptorProvider .getOrCreateRoomDecryptor(event.roomId, wireContent.algorithm) ?.requestKeysForEvent(event) ?: run { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index 7910bfc72c..5e4d82bb67 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -174,7 +174,11 @@ internal class RealmCryptoStore @Inject constructor( } override fun open() { - realmLocker = Realm.getInstance(realmConfiguration) + synchronized(this) { + if (realmLocker == null) { + realmLocker = Realm.getInstance(realmConfiguration) + } + } } override fun close() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index ed9b51fc9f..e32ba7e63c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -145,6 +145,7 @@ internal class DefaultSession @Inject constructor( override fun open() { assert(!isOpen) isOpen = true + cryptoService.get().ensureDevice() uiHandler.post { lifecycleObservers.forEach { it.onStart() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt index b4c29be4da..2fbde5b15a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt @@ -49,10 +49,7 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private val reporter = initialSyncProgressService.takeIf { isInitialSync } measureTimeMillis { - if (!cryptoService.isStarted()) { - Timber.v("Should start cryptoService") - cryptoService.start(isInitialSync) - } + cryptoService.onSyncWillProcess(isInitialSync) }.also { Timber.v("Finish handling start cryptoService in $it ms") } diff --git a/vector/src/androidTest/java/im/vector/riotx/core/utils/TemporaryStoreTest.kt b/vector/src/androidTest/java/im/vector/riotx/core/utils/TemporaryStoreTest.kt index c0607bc00b..020899204d 100644 --- a/vector/src/androidTest/java/im/vector/riotx/core/utils/TemporaryStoreTest.kt +++ b/vector/src/androidTest/java/im/vector/riotx/core/utils/TemporaryStoreTest.kt @@ -34,4 +34,4 @@ class TemporaryStoreTest { sleep(20) store.data shouldBe null } -} \ No newline at end of file +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt index 3940b47d66..b13f848ddf 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt @@ -303,7 +303,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } - private fun startMigrationFlow(previousStep: BootstrapStep, passphrase: String?, recoveryKey: String?) {//TODO Rename param + private fun startMigrationFlow(previousStep: BootstrapStep, passphrase: String?, recoveryKey: String?) { // TODO Rename param setState { copy(step = BootstrapStep.Initializing) } @@ -370,7 +370,6 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } - viewModelScope.launch(Dispatchers.IO) { val userPasswordAuth = userPassword?.let { UserPasswordAuth( diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt index ab90cd45ab..fdf0936d58 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivityViewModel.kt @@ -128,7 +128,7 @@ class HomeActivityViewModel @AssistedInject constructor( val mxCrossSigningInfo = session.cryptoService().crossSigningService().getMyCrossSigningKeys() if (mxCrossSigningInfo != null) { // Cross-signing is already set up for this user, is it trusted? - if(!mxCrossSigningInfo.isTrusted()) { + if (!mxCrossSigningInfo.isTrusted()) { // New session _viewEvents.post(HomeActivityViewEvents.OnNewSession(session.getUser(session.myUserId)?.toMatrixItem())) } From 6643923981df613f697ad0d2f6f7670b8ec5fcfb Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 29 Jun 2020 10:20:19 +0200 Subject: [PATCH 25/30] Fix/ pass not save in reauth after login --- .../main/java/im/vector/riotx/features/login/LoginViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index 31b303c5b7..98cff4f6d9 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -568,6 +568,7 @@ class LoginViewModel @AssistedInject constructor( action.initialDeviceName, object : MatrixCallback { override fun onSuccess(data: Session) { + reAuthHelper.data = action.password onSessionCreated(data) } From 5c091339f00dbe7c3b572253b6dd8674a751cc4b Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 29 Jun 2020 10:22:24 +0200 Subject: [PATCH 26/30] Fix / bootstrap crosssigning only was not supported --- .../recover/BootstrapCrossSigningTask.kt | 12 ++++ .../recover/BootstrapSharedViewModel.kt | 64 +++++++++++-------- 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt index 83b56b1b75..07c3cb0388 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -40,12 +40,14 @@ import im.vector.riotx.core.platform.ViewModelTask import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.core.resources.StringProvider import timber.log.Timber +import java.lang.IllegalArgumentException import java.util.UUID import javax.inject.Inject sealed class BootstrapResult { data class Success(val keyInfo: SsssKeyCreationInfo) : BootstrapResult() + object SuccessCrossSigningOnly : BootstrapResult() abstract class Failure(val error: String?) : BootstrapResult() @@ -67,6 +69,7 @@ interface BootstrapProgressListener { data class Params( val userPasswordAuth: UserPasswordAuth? = null, + val initOnlyCrossSigning: Boolean = false, val progressListener: BootstrapProgressListener? = null, val passphrase: String?, val keySpec: SsssKeySpec? = null @@ -94,11 +97,20 @@ class BootstrapCrossSigningTask @Inject constructor( awaitCallback { crossSigningService.initializeCrossSigning(params.userPasswordAuth, it) } + if (params.initOnlyCrossSigning) { + return BootstrapResult.SuccessCrossSigningOnly + } } catch (failure: Throwable) { return handleInitializeXSigningError(failure) } + } else { + // not sure how this can happen?? + if (params.initOnlyCrossSigning) { + return handleInitializeXSigningError(IllegalArgumentException("Cross signing already setup")) + } } + val keyInfo: SsssKeyCreationInfo val ssssService = session.sharedSecretStorageService diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt index b13f848ddf..c457a9b3cc 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt @@ -68,36 +68,45 @@ class BootstrapSharedViewModel @AssistedInject constructor( private var _pendingSession: String? = null init { - // 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) + if (args.initCrossSigningOnly) { + // Go straight to account password + setState { + copy(step = BootstrapStep.AccountPassword(false)) } - if (version == null) { - // we just resume plain bootstrap - doesKeyBackupExist = false - setState { - copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist)) + + } 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) } - } 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) + } else { + doesKeyBackupExist = true + isBackupCreatedFromPassphrase = keyVersion.getAuthDataAsMegolmBackupAuthData()?.privateKeySalt != null + setState { + copy(step = BootstrapStep.FirstForm(keyBackUpExist = doesKeyBackupExist)) + } + } } } } @@ -383,12 +392,17 @@ 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) } } ) ) { bootstrapResult -> when (bootstrapResult) { + is BootstrapResult.SuccessCrossSigningOnly -> { + // TPD + _viewEvents.post(BootstrapViewEvents.Dismiss) + } is BootstrapResult.Success -> { setState { copy( @@ -435,7 +449,7 @@ class BootstrapSharedViewModel @AssistedInject constructor( } } } - } + }.exhaustive } } } From 51228a3a5c4992c3b0ed072081c34b7403dc0c4d Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 29 Jun 2020 10:23:11 +0200 Subject: [PATCH 27/30] Fix / settings showing bad options when xsigning not init --- .../CrossSigningSettingsAction.kt | 1 + .../CrossSigningSettingsController.kt | 29 ++++++++++++++----- .../CrossSigningSettingsFragment.kt | 7 +++++ .../CrossSigningSettingsViewEvents.kt | 1 + .../CrossSigningSettingsViewModel.kt | 6 ++-- vector/src/main/res/values/strings.xml | 1 + 6 files changed, 36 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsAction.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsAction.kt index 14f240e818..fbc5ba05cc 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsAction.kt @@ -21,4 +21,5 @@ import im.vector.riotx.core.platform.VectorViewModelAction sealed class CrossSigningSettingsAction : VectorViewModelAction { object SetUpRecovery : CrossSigningSettingsAction() object VerifySession : CrossSigningSettingsAction() + object SetupCrossSigning : CrossSigningSettingsAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt index 1f13a18a88..5f0fb912cf 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsController.kt @@ -35,6 +35,7 @@ class CrossSigningSettingsController @Inject constructor( interface InteractionListener { fun setupRecovery() fun verifySession() + fun initCrossSigning() } var interactionListener: InteractionListener? = null @@ -72,13 +73,27 @@ class CrossSigningSettingsController @Inject constructor( } if (data.recoveryHasToBeSetUp) { - bottomSheetVerificationActionItem { - id("setup_recovery") - title(stringProvider.getString(R.string.settings_setup_secure_backup)) - titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) - iconRes(R.drawable.ic_arrow_right) - listener { - interactionListener?.setupRecovery() + if (data.xSigningIsEnableInAccount) { + bottomSheetVerificationActionItem { + id("setup_recovery") + title(stringProvider.getString(R.string.settings_setup_secure_backup)) + titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) + iconRes(R.drawable.ic_arrow_right) + listener { + interactionListener?.setupRecovery() + } + } + } else { + // Propose to setup cross signing + bottomSheetVerificationActionItem { + id("setup_xSgning") + title(stringProvider.getString(R.string.setup_cross_signing)) + titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) + subTitle(stringProvider.getString(R.string.security_prompt_text)) + iconRes(R.drawable.ic_arrow_right) + listener { + interactionListener?.initCrossSigning() + } } } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt index ce6e73d44b..37d9677f7f 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsFragment.kt @@ -56,6 +56,9 @@ class CrossSigningSettingsFragment @Inject constructor( CrossSigningSettingsViewEvents.SetUpRecovery -> { navigator.upgradeSessionSecurity(requireActivity(), false) } + CrossSigningSettingsViewEvents.SetupCrossSigning -> { + navigator.upgradeSessionSecurity(requireActivity(), true) + } }.exhaustive } } @@ -92,4 +95,8 @@ class CrossSigningSettingsFragment @Inject constructor( override fun verifySession() { viewModel.handle(CrossSigningSettingsAction.VerifySession) } + + override fun initCrossSigning() { + viewModel.handle(CrossSigningSettingsAction.SetupCrossSigning) + } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt index 18092be92b..584116239a 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewEvents.kt @@ -26,4 +26,5 @@ sealed class CrossSigningSettingsViewEvents : VectorViewEvents { object SetUpRecovery : CrossSigningSettingsViewEvents() object VerifySession : CrossSigningSettingsViewEvents() + object SetupCrossSigning : CrossSigningSettingsViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt index 4a6eec81d9..29c1ed3371 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningSettingsViewModel.kt @@ -55,8 +55,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted privat xSigningIsEnableInAccount = xSigningIsEnableInAccount, xSigningKeysAreTrusted = xSigningKeysAreTrusted, xSigningKeyCanSign = xSigningKeyCanSign, - - deviceHasToBeVerified = hasSeveralDevices && (xSigningIsEnableInAccount || xSigningKeysAreTrusted), + deviceHasToBeVerified = hasSeveralDevices && (xSigningIsEnableInAccount && !xSigningKeyCanSign), recoveryHasToBeSetUp = !session.sharedSecretStorageService.isRecoverySetup() ) } @@ -75,6 +74,9 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted privat CrossSigningSettingsAction.VerifySession -> { _viewEvents.post(CrossSigningSettingsViewEvents.VerifySession) } + CrossSigningSettingsAction.SetupCrossSigning -> { + _viewEvents.post(CrossSigningSettingsViewEvents.SetupCrossSigning) + } }.exhaustive } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 811fc134d7..0fd3ba8d51 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2372,6 +2372,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Message… Encryption upgrade available + Enable Cross Signing Verify yourself & others to keep your chats safe From 51b7a0aeae8d28b5d7eef889dbad8546aa97e228 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 29 Jun 2020 11:21:10 +0200 Subject: [PATCH 28/30] Fix / device change detection broken --- .../matrix/android/internal/crypto/DefaultCryptoService.kt | 2 ++ .../matrix/android/internal/crypto/DeviceListManager.kt | 2 ++ .../android/internal/session/sync/SyncResponseHandler.kt | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 9621ce203b..012a1d9c12 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -360,6 +360,8 @@ internal class DefaultCryptoService @Inject constructor( // be called for all e2e rooms during the processing of the sync, // at which point we'll start tracking all the users of that room. deviceListManager.invalidateAllDeviceLists() + // always track my devices? + deviceListManager.startTrackingDeviceList(listOf(userId)) deviceListManager.refreshOutdatedDeviceLists() } catch (failure: Throwable) { Timber.e(failure, "## CRYPTO onSyncWillProcess ") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt index 680539d057..722a751ecd 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt @@ -156,6 +156,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM * @param left the user ids list which left a room */ fun handleDeviceListsChanges(changed: Collection, left: Collection) { + Timber.v("## CRYPTO: handleDeviceListsChanges changed:$changed / left:$left") var isUpdated = false val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() @@ -483,6 +484,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM * This method must be called on getEncryptingThreadHandler() thread. */ suspend fun refreshOutdatedDeviceLists() { + Timber.v("## CRYPTO | refreshOutdatedDeviceLists()") val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() val users = deviceTrackingStatuses.keys.filterTo(mutableListOf()) { userId -> diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt index 2fbde5b15a..5e8ef5a608 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt @@ -49,6 +49,10 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private val reporter = initialSyncProgressService.takeIf { isInitialSync } measureTimeMillis { + if (!cryptoService.isStarted()) { + Timber.v("Should start cryptoService") + cryptoService.start() + } cryptoService.onSyncWillProcess(isInitialSync) }.also { Timber.v("Finish handling start cryptoService in $it ms") From caa7709090d812cf67debc9ecfb6495c0e8deb2d Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 29 Jun 2020 11:39:46 +0200 Subject: [PATCH 29/30] Fix copy of cancel bootstrap --- .../features/crypto/recover/BootstrapBottomSheet.kt | 13 +++---------- vector/src/main/res/values/strings.xml | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt index 5995879d06..f14d27b3d9 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapBottomSheet.kt @@ -77,24 +77,17 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() { KeepItSafeDialog().show(requireActivity()) } is BootstrapViewEvents.SkipBootstrap -> { - promptSkip(event.genKeyOption) + promptSkip() } } } } - private fun promptSkip(genKeyOption: Boolean) { + private fun promptSkip() { AlertDialog.Builder(requireContext()) .setTitle(R.string.are_you_sure) - .setMessage(if (genKeyOption) R.string.bootstrap_skip_text else R.string.bootstrap_skip_text_no_gen_key) + .setMessage(R.string.bootstrap_cancel_text) .setPositiveButton(R.string._continue, null) - .apply { - if (genKeyOption) { - setNeutralButton(R.string.generate_message_key) { _, _ -> - viewModel.handle(BootstrapActions.DoInitializeGeneratedKey) - } - } - } .setNegativeButton(R.string.skip) { _, _ -> dismiss() } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 0fd3ba8d51..afdde12db0 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2341,7 +2341,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Setting a Recovery Passphrase lets you secure & unlock encrypted messages and trust.\n\nIf you don’t want to set a Message Password, generate a Message Key instead. Setting a Recovery Passphrase lets you secure & unlock encrypted messages and trust. - + If you cancel now, you may lose encrypted messages & data if you lose access to your logins.\n\nYou can also set up Secure Backup & manage your keys in Settings. Encryption enabled Messages in this room are end-to-end encrypted. Learn more & verify users in their profile. From b0034f91b0c81aa6118b7f42f9ecf6488189f255 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 29 Jun 2020 15:29:19 +0200 Subject: [PATCH 30/30] code cleaning --- .../features/crypto/recover/BootstrapCrossSigningTask.kt | 1 - .../features/crypto/recover/BootstrapSharedViewModel.kt | 1 - vector/src/main/res/values/strings.xml | 9 +-------- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt index 07c3cb0388..6a3fadbcb3 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -110,7 +110,6 @@ class BootstrapCrossSigningTask @Inject constructor( } } - val keyInfo: SsssKeyCreationInfo val ssssService = session.sharedSecretStorageService diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt index c457a9b3cc..3a95a575f4 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/recover/BootstrapSharedViewModel.kt @@ -74,7 +74,6 @@ class BootstrapSharedViewModel @AssistedInject constructor( setState { copy(step = BootstrapStep.AccountPassword(false)) } - } else { // need to check if user have an existing keybackup setState { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index afdde12db0..5ac50b2484 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2,7 +2,6 @@ - en US @@ -35,7 +34,6 @@ Verify session - Keys backup is not finished, please wait… You’ll lose your encrypted messages if you sign out now Key backup in progress. If you sign out now you’ll lose access to your encrypted messages. @@ -46,7 +44,6 @@ Are you sure? Back up You’ll lose access to your encrypted messages unless you back up your keys before signing out. - Third party licences @@ -2079,8 +2076,6 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming You accepted Verification Sent Verification Request - - Verify this session Manually verify @@ -2167,7 +2162,6 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Messages with this user are end-to-end encrypted and can\'t be read by third parties. Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted. - Cross-Signing Cross-Signing is enabled\nPrivate Keys on device. Cross-Signing is enabled\nKeys are trusted.\nPrivate keys are not known @@ -2491,7 +2485,6 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Use a Security Phrase Enter a secret phrase only you know, and generate a key for backup. - Save your Security Key Store your Security Key somewhere safe, like a password manager or a safe. @@ -2503,4 +2496,4 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Save your Security Key Store your Security Key somewhere safe, like a password manager or a safe. - + \ No newline at end of file