mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 18:35:40 +03:00
Ask user password to initialize CrossSigning
And migrate some logic to the ViewModel
This commit is contained in:
parent
8df7797f6d
commit
e4ac28877c
12 changed files with 140 additions and 93 deletions
|
@ -41,7 +41,7 @@ interface CrossSigningService {
|
|||
* Users needs to enter credentials
|
||||
*/
|
||||
fun initializeCrossSigning(authParams: UserPasswordAuth?,
|
||||
callback: MatrixCallback<Unit>? = null)
|
||||
callback: MatrixCallback<Unit>)
|
||||
|
||||
fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?,
|
||||
uskKeyPrivateKey: String?,
|
||||
|
|
|
@ -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<Unit>?) {
|
||||
override fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback<Unit>) {
|
||||
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<InitializeCrossSigningTask.Result> {
|
||||
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)
|
||||
|
|
|
@ -36,6 +36,7 @@ internal data class SignatureUploadResponse(
|
|||
|
||||
)
|
||||
|
||||
// TODO Not used. Remove?
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class UploadResponseFailure(
|
||||
@Json(name = "status")
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) {
|
||||
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)
|
||||
|
|
|
@ -25,6 +25,4 @@ sealed class HomeActivitySharedAction : VectorSharedAction {
|
|||
object OpenDrawer : HomeActivitySharedAction()
|
||||
object CloseDrawer : HomeActivitySharedAction()
|
||||
object OpenGroup : HomeActivitySharedAction()
|
||||
// TODO Remove?
|
||||
object PromptForSecurityBootstrap : HomeActivitySharedAction()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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<HomeActivityViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||
) : VectorViewModel<HomeActivityViewState, EmptyAction, HomeActivityViewEvents>(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<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) {
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue