mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Basic debug screen to setup keys
This commit is contained in:
parent
6cece03998
commit
ea6e8a6789
8 changed files with 448 additions and 71 deletions
|
@ -22,18 +22,9 @@ import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.text.InputType
|
|
||||||
import android.widget.EditText
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.Person
|
import androidx.core.app.Person
|
||||||
import butterknife.OnClick
|
import butterknife.OnClick
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
|
||||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
|
||||||
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
@ -178,61 +169,6 @@ class DebugMenuActivity : VectorBaseActivity() {
|
||||||
throw RuntimeException("Application crashed from user demand")
|
throw RuntimeException("Application crashed from user demand")
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnClick(R.id.debug_initialise_xsigning)
|
|
||||||
fun testXSigning() {
|
|
||||||
activeSessionHolder.getActiveSession().getCrossSigningService().initializeCrossSigning(null, object : MatrixCallback<Unit> {
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
if (failure is Failure.OtherServerError
|
|
||||||
&& failure.httpCode == 401
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
MoshiProvider.providesMoshi()
|
|
||||||
.adapter(RegistrationFlowResponse::class.java)
|
|
||||||
.fromJson(failure.errorBody)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}?.let {
|
|
||||||
// Retry with authentication
|
|
||||||
if (it.flows?.any { it.stages?.contains(LoginFlowTypes.PASSWORD) == true } == true) {
|
|
||||||
// Ask for password
|
|
||||||
val inflater = this@DebugMenuActivity.layoutInflater
|
|
||||||
val layout = inflater.inflate(R.layout.dialog_base_edit_text, null)
|
|
||||||
|
|
||||||
val input = layout.findViewById<EditText>(R.id.edit_text).also {
|
|
||||||
it.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD
|
|
||||||
}
|
|
||||||
|
|
||||||
val activeSession = activeSessionHolder.getActiveSession()
|
|
||||||
AlertDialog.Builder(this@DebugMenuActivity)
|
|
||||||
.setTitle("Confirm password")
|
|
||||||
.setView(layout)
|
|
||||||
.setPositiveButton(R.string.ok) { _, _ ->
|
|
||||||
val pass = input.text.toString()
|
|
||||||
|
|
||||||
activeSession.getCrossSigningService().initializeCrossSigning(
|
|
||||||
UserPasswordAuth(
|
|
||||||
session = it.session,
|
|
||||||
user = activeSession.myUserId,
|
|
||||||
password = pass
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.show()
|
|
||||||
} else {
|
|
||||||
// can't do this from here
|
|
||||||
AlertDialog.Builder(this@DebugMenuActivity)
|
|
||||||
.setTitle(R.string.dialog_title_error)
|
|
||||||
.setMessage("You cannot do that from mobile")
|
|
||||||
.setPositiveButton(R.string.ok, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.debug_scan_qr_code)
|
@OnClick(R.id.debug_scan_qr_code)
|
||||||
fun scanQRCode() {
|
fun scanQRCode() {
|
||||||
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
|
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
|
||||||
|
|
|
@ -61,13 +61,6 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Crash the app" />
|
android:text="Crash the app" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/debug_initialise_xsigning"
|
|
||||||
style="@style/VectorButtonStyle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Initialize XSigning" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/debug_scan_qr_code"
|
android:id="@+id/debug_scan_qr_code"
|
||||||
style="@style/VectorButtonStyle"
|
style="@style/VectorButtonStyle"
|
||||||
|
|
|
@ -68,6 +68,7 @@ import im.vector.riotx.features.settings.VectorSettingsNotificationPreferenceFra
|
||||||
import im.vector.riotx.features.settings.VectorSettingsNotificationsTroubleshootFragment
|
import im.vector.riotx.features.settings.VectorSettingsNotificationsTroubleshootFragment
|
||||||
import im.vector.riotx.features.settings.VectorSettingsPreferencesFragment
|
import im.vector.riotx.features.settings.VectorSettingsPreferencesFragment
|
||||||
import im.vector.riotx.features.settings.VectorSettingsSecurityPrivacyFragment
|
import im.vector.riotx.features.settings.VectorSettingsSecurityPrivacyFragment
|
||||||
|
import im.vector.riotx.features.settings.crosssigning.CrossSigningSettingsFragment
|
||||||
import im.vector.riotx.features.settings.devices.VectorSettingsDevicesFragment
|
import im.vector.riotx.features.settings.devices.VectorSettingsDevicesFragment
|
||||||
import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment
|
import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment
|
||||||
import im.vector.riotx.features.settings.push.PushGatewaysFragment
|
import im.vector.riotx.features.settings.push.PushGatewaysFragment
|
||||||
|
@ -331,4 +332,9 @@ interface FragmentModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(DeviceTrustInfoActionFragment::class)
|
@FragmentKey(DeviceTrustInfoActionFragment::class)
|
||||||
fun bindDeviceTrustInfoActionFragment(fragment: DeviceTrustInfoActionFragment): Fragment
|
fun bindDeviceTrustInfoActionFragment(fragment: DeviceTrustInfoActionFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(CrossSigningSettingsFragment::class)
|
||||||
|
fun bindCrossSigningSettingsFragment(fragment: CrossSigningSettingsFragment): Fragment
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
/*
|
||||||
|
* 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 com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
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 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
|
||||||
|
) : TypedEpoxyController<CrossSigningSettingsViewState>() {
|
||||||
|
|
||||||
|
interface InteractionListener {
|
||||||
|
fun onInitializeCrossSigningKeys()
|
||||||
|
fun onResetCrossSigningKeys()
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("resetkeys")
|
||||||
|
title("Reset keys")
|
||||||
|
titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
||||||
|
listener {
|
||||||
|
interactionListener?.onResetCrossSigningKeys()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (data.xSigningKeysAreTrusted) {
|
||||||
|
genericItem {
|
||||||
|
id("trusted")
|
||||||
|
titleIconResourceId(R.drawable.ic_shield_warning)
|
||||||
|
title(stringProvider.getString(R.string.encryption_information_dg_xsigning_trusted))
|
||||||
|
}
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("resetkeys")
|
||||||
|
title("Reset keys")
|
||||||
|
titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
||||||
|
listener {
|
||||||
|
interactionListener?.onResetCrossSigningKeys()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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("resetkeys")
|
||||||
|
title("Reset keys")
|
||||||
|
titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
||||||
|
listener {
|
||||||
|
interactionListener?.onResetCrossSigningKeys()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
genericItem {
|
||||||
|
id("not")
|
||||||
|
title(stringProvider.getString(R.string.encryption_information_dg_xsigning_disabled))
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("initKeys")
|
||||||
|
title("Initialize keys")
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when (data.crossSigningInfo) {
|
||||||
|
is Loading -> {
|
||||||
|
loadingItem {
|
||||||
|
id("loading")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
val crossSigningKeys = data.crossSigningInfo.invoke()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* 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 android.os.Bundle
|
||||||
|
import android.text.InputType
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.EditText
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||||
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.cleanup
|
||||||
|
import im.vector.riotx.core.extensions.configureWith
|
||||||
|
import im.vector.riotx.core.extensions.observeEvent
|
||||||
|
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||||
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
|
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CrossSigningSettingsFragment @Inject constructor(
|
||||||
|
private val epoxyController: CrossSigningEpoxyController,
|
||||||
|
val viewModelFactory: CrossSigningSettingsViewModel.Factory
|
||||||
|
) : VectorBaseFragment(), CrossSigningEpoxyController.InteractionListener {
|
||||||
|
|
||||||
|
override fun getLayoutResId() = R.layout.fragment_generic_recycler
|
||||||
|
|
||||||
|
private val viewModel: CrossSigningSettingsViewModel by fragmentViewModel()
|
||||||
|
|
||||||
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
viewModel.requestLiveData.observeEvent(this) {
|
||||||
|
when (it) {
|
||||||
|
is Fail -> {
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.dialog_title_error)
|
||||||
|
.setMessage(it.error.message)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
when (val action = it.invoke()) {
|
||||||
|
is CrossSigningAction.RequestPasswordAuth -> {
|
||||||
|
requestPassword(action.sessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.encryption_information_cross_signing_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
setupRecyclerView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
|
epoxyController.setData(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRecyclerView() {
|
||||||
|
recyclerView.configureWith(epoxyController, hasFixedSize = false, disableItemAnimation = true)
|
||||||
|
epoxyController.interactionListener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
recyclerView.cleanup()
|
||||||
|
epoxyController.interactionListener = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestPassword(sessionId: String) {
|
||||||
|
// Ask for password
|
||||||
|
val inflater = this.layoutInflater
|
||||||
|
val layout = inflater.inflate(R.layout.dialog_base_edit_text, null)
|
||||||
|
|
||||||
|
val input = layout.findViewById<EditText>(R.id.edit_text).also {
|
||||||
|
it.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle("Confirm password")
|
||||||
|
.setView(layout)
|
||||||
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
|
val pass = input.text.toString()
|
||||||
|
|
||||||
|
viewModel.handle(CrossSigningAction.InitializeCrossSigning(UserPasswordAuth(
|
||||||
|
session = sessionId,
|
||||||
|
password = pass
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInitializeCrossSigningKeys() {
|
||||||
|
viewModel.handle(CrossSigningAction.InitializeCrossSigning())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResetCrossSigningKeys() {
|
||||||
|
viewModel.handle(CrossSigningAction.InitializeCrossSigning())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
* 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 androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
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
|
||||||
|
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.failure.Failure
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
|
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||||
|
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.isVerified
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||||
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
|
import im.vector.matrix.rx.rx
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||||
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.core.utils.LiveEvent
|
||||||
|
|
||||||
|
data class CrossSigningSettingsViewState(
|
||||||
|
val crossSigningInfo: Async<MXCrossSigningInfo?> = Uninitialized,
|
||||||
|
val xSigningIsEnableInAccount: Boolean = false,
|
||||||
|
val xSigningKeysAreTrusted: Boolean = false,
|
||||||
|
val xSigningKeyCanSign: Boolean = true
|
||||||
|
) : MvRxState
|
||||||
|
|
||||||
|
sealed class CrossSigningAction : VectorViewModelAction {
|
||||||
|
data class InitializeCrossSigning(val auth: UserPasswordAuth? = null) : CrossSigningAction()
|
||||||
|
data class RequestPasswordAuth(val sessionId: String) : CrossSigningAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
class CrossSigningSettingsViewModel @AssistedInject constructor(@Assisted private val initialState: CrossSigningSettingsViewState,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val session: Session)
|
||||||
|
: VectorViewModel<CrossSigningSettingsViewState, CrossSigningAction>(initialState) {
|
||||||
|
|
||||||
|
// Can be used for several actions, for a one shot result
|
||||||
|
private val _requestLiveData = MutableLiveData<LiveEvent<Async<CrossSigningAction>>>()
|
||||||
|
val requestLiveData: LiveData<LiveEvent<Async<CrossSigningAction>>>
|
||||||
|
get() = _requestLiveData
|
||||||
|
|
||||||
|
init {
|
||||||
|
session.rx().liveCrossSigningInfo(session.myUserId)
|
||||||
|
.map {
|
||||||
|
it.getOrNull()
|
||||||
|
}
|
||||||
|
.execute {
|
||||||
|
val crossSigningKeys = it.invoke()
|
||||||
|
val xSigningIsEnableInAccount = crossSigningKeys != null
|
||||||
|
val xSigningKeysAreTrusted = session.getCrossSigningService().checkUserTrust(session.myUserId).isVerified()
|
||||||
|
val xSigningKeyCanSign = session.getCrossSigningService().canCrossSign()
|
||||||
|
copy(
|
||||||
|
crossSigningInfo = it,
|
||||||
|
xSigningIsEnableInAccount = xSigningIsEnableInAccount,
|
||||||
|
xSigningKeysAreTrusted = xSigningKeysAreTrusted,
|
||||||
|
xSigningKeyCanSign = xSigningKeyCanSign
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(initialState: CrossSigningSettingsViewState): CrossSigningSettingsViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: CrossSigningAction) {
|
||||||
|
when (action) {
|
||||||
|
is CrossSigningAction.InitializeCrossSigning -> {
|
||||||
|
initializeCrossSigning(action.auth?.also { it.user = session.myUserId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeCrossSigning(auth: UserPasswordAuth?) {
|
||||||
|
setState {
|
||||||
|
copy(crossSigningInfo = Loading())
|
||||||
|
}
|
||||||
|
session.getCrossSigningService().initializeCrossSigning(auth, object : MatrixCallback<Unit> {
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
if (failure is Failure.OtherServerError
|
||||||
|
&& failure.httpCode == 401
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
MoshiProvider.providesMoshi()
|
||||||
|
.adapter(RegistrationFlowResponse::class.java)
|
||||||
|
.fromJson(failure.errorBody)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}?.let { flowResponse ->
|
||||||
|
// Retry with authentication
|
||||||
|
if (flowResponse.flows?.any { it.stages?.contains(LoginFlowTypes.PASSWORD) == true } == true) {
|
||||||
|
_requestLiveData.postValue(LiveEvent(Success(CrossSigningAction.RequestPasswordAuth(flowResponse.session ?: ""))))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
_requestLiveData.postValue(LiveEvent(Fail(Throwable("You cannot do that from mobile"))))
|
||||||
|
// can't do this from here
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_requestLiveData.postValue(LiveEvent(Fail(Throwable("You cannot do that from mobile"))))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<CrossSigningSettingsViewModel, CrossSigningSettingsViewState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
override fun create(viewModelContext: ViewModelContext, state: CrossSigningSettingsViewState): CrossSigningSettingsViewModel? {
|
||||||
|
val fragment: CrossSigningSettingsFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
|
return fragment.viewModelFactory.create(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -134,4 +134,7 @@
|
||||||
<string name="verification_profile_device_new_signing">%1$s (%2$s) signed in using a new device:</string>
|
<string name="verification_profile_device_new_signing">%1$s (%2$s) signed in using a new device:</string>
|
||||||
<string name="verification_profile_device_untrust_info">Until this user trusts this device, messages sent to and from it are labelled with warnings. Alternatively, you can manually verify it.</string>
|
<string name="verification_profile_device_untrust_info">Until this user trusts this device, messages sent to and from it are labelled with warnings. Alternatively, you can manually verify it.</string>
|
||||||
|
|
||||||
|
|
||||||
|
<string name="initialize_cross_signing">Initialize CrossSigning</string>
|
||||||
|
<string name="reset_cross_signing">Reset Keys</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:title="@string/encryption_information_cross_signing_state"
|
android:title="@string/encryption_information_cross_signing_state"
|
||||||
tools:summary="@string/encryption_information_dg_xsigning_complete"
|
tools:summary="@string/encryption_information_dg_xsigning_complete"
|
||||||
|
app:fragment="im.vector.riotx.features.settings.crosssigning.CrossSigningSettingsFragment"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue