Merge pull request #1296 from vector-im/feature/untrusted_session_shields
Update manage sessions screen
This commit is contained in:
commit
c02cfb2f4f
|
@ -28,6 +28,7 @@ Improvements 🙌:
|
|||
- Restart broken Olm sessions ([MSC1719](https://github.com/matrix-org/matrix-doc/pull/1719))
|
||||
- Cross-Signing | Hide Use recovery key when 4S is not setup (#1007)
|
||||
- Cross-Signing | Trust account xSigning keys by entering Recovery Key (select file or copy) #1199
|
||||
- Manage Session Settings / Cross Signing update (#1295)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix summary notification staying after "mark as read"
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.dialogs
|
||||
|
||||
import android.app.Activity
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import im.vector.matrix.android.api.extensions.getFingerprintHumanReadable
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.riotx.R
|
||||
|
||||
object ManuallyVerifyDialog {
|
||||
|
||||
fun show(activity: Activity, cryptoDeviceInfo: CryptoDeviceInfo, onVerified: (() -> Unit)) {
|
||||
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_device_verify, null)
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.cross_signing_verify_by_text)
|
||||
.setView(dialogLayout)
|
||||
.setPositiveButton(R.string.encryption_information_verify) { _, _ ->
|
||||
onVerified()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
|
||||
dialogLayout.findViewById<TextView>(R.id.encrypted_device_info_device_name)?.let {
|
||||
it.text = cryptoDeviceInfo.displayName()
|
||||
}
|
||||
|
||||
dialogLayout.findViewById<TextView>(R.id.encrypted_device_info_device_id)?.let {
|
||||
it.text = cryptoDeviceInfo.deviceId
|
||||
}
|
||||
|
||||
dialogLayout.findViewById<TextView>(R.id.encrypted_device_info_device_key)?.let {
|
||||
it.text = cryptoDeviceInfo.getFingerprintHumanReadable()
|
||||
}
|
||||
|
||||
builder.show()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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.ui.list
|
||||
|
||||
import android.view.View
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
||||
|
||||
/**
|
||||
* A generic button list item.
|
||||
*/
|
||||
@EpoxyModelClass(layout = R.layout.item_generic_button)
|
||||
abstract class GenericButtonItem : VectorEpoxyModel<GenericButtonItem.Holder>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
var text: String? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var itemClickAction: View.OnClickListener? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@ColorInt
|
||||
var textColor: Int? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
@DrawableRes
|
||||
var iconRes: Int? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
holder.button.text = text
|
||||
val textColor = textColor ?: ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_primary)
|
||||
holder.button.setTextColor(textColor)
|
||||
if (iconRes != null) {
|
||||
holder.button.setIconResource(iconRes!!)
|
||||
} else {
|
||||
holder.button.icon = null
|
||||
}
|
||||
|
||||
itemClickAction?.let { holder.view.setOnClickListener(it) }
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val button by bind<MaterialButton>(R.id.itemGenericItemButton)
|
||||
}
|
||||
}
|
|
@ -165,7 +165,9 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||
} else {
|
||||
otherUserShield.setImageResource(R.drawable.ic_shield_warning)
|
||||
}
|
||||
otherUserNameText.text = getString(R.string.complete_security)
|
||||
otherUserNameText.text = getString(
|
||||
if (state.selfVerificationMode) R.string.crosssigning_verify_this_session else R.string.crosssigning_verify_session
|
||||
)
|
||||
otherUserShield.isVisible = true
|
||||
} else {
|
||||
avatarRenderer.render(matrixItem, otherUserAvatarImageView)
|
||||
|
@ -241,7 +243,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||
}
|
||||
|
||||
when (state.qrTransactionState) {
|
||||
is VerificationTxState.QrScannedByOther -> {
|
||||
is VerificationTxState.QrScannedByOther -> {
|
||||
showFragment(VerificationQrScannedByOtherFragment::class, Bundle())
|
||||
return@withState
|
||||
}
|
||||
|
@ -252,19 +254,19 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
|||
})
|
||||
return@withState
|
||||
}
|
||||
is VerificationTxState.Verified -> {
|
||||
is VerificationTxState.Verified -> {
|
||||
showFragment(VerificationConclusionFragment::class, Bundle().apply {
|
||||
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe))
|
||||
})
|
||||
return@withState
|
||||
}
|
||||
is VerificationTxState.Cancelled -> {
|
||||
is VerificationTxState.Cancelled -> {
|
||||
showFragment(VerificationConclusionFragment::class, Bundle().apply {
|
||||
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(false, state.qrTransactionState.cancelCode.value, state.isMe))
|
||||
})
|
||||
return@withState
|
||||
}
|
||||
else -> Unit
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
// At this point there is no SAS transaction for this request
|
||||
|
|
|
@ -185,8 +185,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
// We need to ask
|
||||
promptSecurityEvent(
|
||||
session,
|
||||
R.string.complete_security,
|
||||
R.string.crosssigning_verify_this_session
|
||||
R.string.crosssigning_verify_this_session,
|
||||
R.string.confirm_your_identity
|
||||
) {
|
||||
it.navigator.waitSessionVerification(it)
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ class DeviceTrustInfoEpoxyController @Inject constructor(private val stringProvi
|
|||
|
||||
bottomSheetVerificationActionItem {
|
||||
id("verify")
|
||||
title(stringProvider.getString(R.string.verification_verify_device_manually))
|
||||
title(stringProvider.getString(R.string.cross_signing_verify_by_emoji))
|
||||
titleColor(colorProvider.getColor(R.color.riotx_accent))
|
||||
iconRes(R.drawable.ic_arrow_right)
|
||||
iconColor(colorProvider.getColor(R.color.riotx_accent))
|
||||
|
|
|
@ -59,7 +59,7 @@ class CrossSigningEpoxyController @Inject constructor(
|
|||
if (!data.isUploadingKeys) {
|
||||
bottomSheetVerificationActionItem {
|
||||
id("verify")
|
||||
title(stringProvider.getString(R.string.complete_security))
|
||||
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))
|
||||
|
@ -76,7 +76,7 @@ class CrossSigningEpoxyController @Inject constructor(
|
|||
}
|
||||
bottomSheetVerificationActionItem {
|
||||
id("verify")
|
||||
title(stringProvider.getString(R.string.complete_security))
|
||||
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))
|
||||
|
|
|
@ -20,15 +20,17 @@ import android.graphics.Typeface
|
|||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
import im.vector.riotx.core.utils.DimensionConverter
|
||||
import me.gujun.android.span.span
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
@ -53,22 +55,31 @@ abstract class DeviceItem : VectorEpoxyModel<DeviceItem.Holder>() {
|
|||
var detailedMode = false
|
||||
|
||||
@EpoxyAttribute
|
||||
var trusted : Boolean? = null
|
||||
var trusted: DeviceTrustLevel? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var legacyMode: Boolean = false
|
||||
|
||||
@EpoxyAttribute
|
||||
var trustedSession: Boolean = false
|
||||
|
||||
@EpoxyAttribute
|
||||
var colorProvider: ColorProvider? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var dimensionConverter: DimensionConverter? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
holder.root.setOnClickListener { itemClickAction?.invoke() }
|
||||
|
||||
if (trusted != null) {
|
||||
holder.trustIcon.setImageDrawable(
|
||||
ContextCompat.getDrawable(
|
||||
holder.view.context,
|
||||
if (trusted!!) R.drawable.ic_shield_trusted else R.drawable.ic_shield_warning
|
||||
)
|
||||
)
|
||||
holder.trustIcon.isInvisible = false
|
||||
} else {
|
||||
holder.trustIcon.isInvisible = true
|
||||
}
|
||||
val shield = TrustUtils.shieldForTrust(
|
||||
currentDevice,
|
||||
trustedSession,
|
||||
legacyMode,
|
||||
trusted
|
||||
)
|
||||
|
||||
holder.trustIcon.setImageResource(shield)
|
||||
|
||||
val detailedModeLabels = listOf(
|
||||
holder.displayNameLabelText,
|
||||
|
@ -103,7 +114,28 @@ abstract class DeviceItem : VectorEpoxyModel<DeviceItem.Holder>() {
|
|||
it.setTypeface(null, if (currentDevice) Typeface.BOLD else Typeface.NORMAL)
|
||||
}
|
||||
} else {
|
||||
holder.summaryLabelText.text = deviceInfo.displayName ?: deviceInfo.deviceId ?: ""
|
||||
holder.summaryLabelText.text =
|
||||
span {
|
||||
+(deviceInfo.displayName ?: deviceInfo.deviceId ?: "")
|
||||
apply {
|
||||
// Add additional info if current session is not trusted
|
||||
if (!trustedSession) {
|
||||
+"\n"
|
||||
span {
|
||||
text = "${deviceInfo.deviceId}"
|
||||
apply {
|
||||
colorProvider?.getColorFromAttribute(R.attr.riotx_text_secondary)?.let {
|
||||
textColor = it
|
||||
}
|
||||
dimensionConverter?.spToPx(12)?.let {
|
||||
textSize = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
holder.summaryLabelText.isVisible = true
|
||||
detailedModeLabels.map {
|
||||
it.isVisible = false
|
||||
|
|
|
@ -37,7 +37,10 @@ import im.vector.riotx.core.platform.VectorViewModel
|
|||
|
||||
data class DeviceVerificationInfoBottomSheetViewState(
|
||||
val cryptoDeviceInfo: Async<CryptoDeviceInfo?> = Uninitialized,
|
||||
val deviceInfo: Async<DeviceInfo> = Uninitialized
|
||||
val deviceInfo: Async<DeviceInfo> = Uninitialized,
|
||||
val hasAccountCrossSigning: Boolean = false,
|
||||
val accountCrossSigningIsTrusted: Boolean = false,
|
||||
val isMine : Boolean = false
|
||||
) : MvRxState
|
||||
|
||||
class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: DeviceVerificationInfoBottomSheetViewState,
|
||||
|
@ -51,13 +54,29 @@ class DeviceVerificationInfoBottomSheetViewModel @AssistedInject constructor(@As
|
|||
}
|
||||
|
||||
init {
|
||||
|
||||
setState {
|
||||
copy(
|
||||
hasAccountCrossSigning = session.cryptoService().crossSigningService().getMyCrossSigningKeys() != null,
|
||||
accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified()
|
||||
)
|
||||
}
|
||||
session.rx().liveCrossSigningInfo(session.myUserId)
|
||||
.execute {
|
||||
copy(
|
||||
hasAccountCrossSigning = it.invoke()?.get() != null,
|
||||
accountCrossSigningIsTrusted = it.invoke()?.get()?.isTrusted() == true
|
||||
)
|
||||
}
|
||||
|
||||
session.rx().liveUserCryptoDevices(session.myUserId)
|
||||
.map { list ->
|
||||
list.firstOrNull { it.deviceId == deviceId }
|
||||
}
|
||||
.execute {
|
||||
copy(
|
||||
cryptoDeviceInfo = it
|
||||
cryptoDeviceInfo = it,
|
||||
isMine = it.invoke()?.deviceId == session.sessionParams.credentials.deviceId
|
||||
)
|
||||
}
|
||||
setState {
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
package im.vector.riotx.features.settings.devices
|
||||
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.dividerItem
|
||||
import im.vector.riotx.core.epoxy.loadingItem
|
||||
|
@ -26,6 +28,7 @@ import im.vector.riotx.core.ui.list.GenericItem
|
|||
import im.vector.riotx.core.ui.list.genericFooterItem
|
||||
import im.vector.riotx.core.ui.list.genericItem
|
||||
import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class DeviceVerificationInfoEpoxyController @Inject constructor(private val stringProvider: StringProvider,
|
||||
|
@ -37,111 +40,251 @@ class DeviceVerificationInfoEpoxyController @Inject constructor(private val stri
|
|||
|
||||
override fun buildModels(data: DeviceVerificationInfoBottomSheetViewState?) {
|
||||
val cryptoDeviceInfo = data?.cryptoDeviceInfo?.invoke()
|
||||
if (cryptoDeviceInfo != null) {
|
||||
if (cryptoDeviceInfo.isVerified) {
|
||||
when {
|
||||
cryptoDeviceInfo != null -> {
|
||||
// It's a E2E capable device
|
||||
handleE2ECapableDevice(data, cryptoDeviceInfo)
|
||||
}
|
||||
data?.deviceInfo?.invoke() != null -> {
|
||||
// It's a non E2E capable device
|
||||
handleNonE2EDevice(data)
|
||||
}
|
||||
else -> {
|
||||
loadingItem {
|
||||
id("loading")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleE2ECapableDevice(data: DeviceVerificationInfoBottomSheetViewState, cryptoDeviceInfo: CryptoDeviceInfo) {
|
||||
val shield = TrustUtils.shieldForTrust(
|
||||
currentDevice = data.isMine,
|
||||
trustMSK = data.accountCrossSigningIsTrusted,
|
||||
legacyMode = !data.hasAccountCrossSigning,
|
||||
deviceTrustLevel = cryptoDeviceInfo.trustLevel
|
||||
)
|
||||
|
||||
if (data.hasAccountCrossSigning) {
|
||||
// Cross Signing is enabled
|
||||
handleE2EWithCrossSigning(data.isMine, data.accountCrossSigningIsTrusted, cryptoDeviceInfo, shield)
|
||||
} else {
|
||||
handleE2EInLegacy(data.isMine, cryptoDeviceInfo, shield)
|
||||
}
|
||||
|
||||
// COMMON ACTIONS (Rename / signout)
|
||||
addGenericDeviceManageActions(data, cryptoDeviceInfo.deviceId)
|
||||
}
|
||||
|
||||
private fun handleE2EWithCrossSigning(isMine: Boolean, currentSessionIsTrusted: Boolean, cryptoDeviceInfo: CryptoDeviceInfo, shield: Int) {
|
||||
Timber.v("handleE2EWithCrossSigning $isMine, $cryptoDeviceInfo, $shield")
|
||||
|
||||
if (isMine) {
|
||||
if (currentSessionIsTrusted) {
|
||||
genericItem {
|
||||
id("trust${cryptoDeviceInfo.deviceId}")
|
||||
style(GenericItem.STYLE.BIG_TEXT)
|
||||
titleIconResourceId(R.drawable.ic_shield_trusted)
|
||||
titleIconResourceId(shield)
|
||||
title(stringProvider.getString(R.string.encryption_information_verified))
|
||||
description(stringProvider.getString(R.string.settings_active_sessions_verified_device_desc))
|
||||
}
|
||||
} else {
|
||||
// You need tomcomplete security
|
||||
genericItem {
|
||||
id("trust${cryptoDeviceInfo.deviceId}")
|
||||
titleIconResourceId(R.drawable.ic_shield_warning)
|
||||
style(GenericItem.STYLE.BIG_TEXT)
|
||||
title(stringProvider.getString(R.string.encryption_information_not_verified))
|
||||
description(stringProvider.getString(R.string.settings_active_sessions_unverified_device_desc))
|
||||
}
|
||||
}
|
||||
|
||||
genericItem {
|
||||
id("info${cryptoDeviceInfo.deviceId}")
|
||||
title(cryptoDeviceInfo.displayName() ?: "")
|
||||
description("(${cryptoDeviceInfo.deviceId})")
|
||||
}
|
||||
|
||||
if (!cryptoDeviceInfo.isVerified) {
|
||||
dividerItem {
|
||||
id("d1")
|
||||
}
|
||||
bottomSheetVerificationActionItem {
|
||||
id("verify")
|
||||
title(stringProvider.getString(R.string.verification_verify_device))
|
||||
titleColor(colorProvider.getColor(R.color.riotx_accent))
|
||||
iconRes(R.drawable.ic_arrow_right)
|
||||
iconColor(colorProvider.getColor(R.color.riotx_accent))
|
||||
listener {
|
||||
callback?.onAction(DevicesAction.VerifyMyDevice(cryptoDeviceInfo.deviceId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cryptoDeviceInfo.deviceId != session.sessionParams.credentials.deviceId) {
|
||||
// Add the delete option
|
||||
dividerItem {
|
||||
id("d2")
|
||||
}
|
||||
bottomSheetVerificationActionItem {
|
||||
id("delete")
|
||||
title(stringProvider.getString(R.string.settings_active_sessions_signout_device))
|
||||
titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
||||
iconRes(R.drawable.ic_arrow_right)
|
||||
iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
||||
listener {
|
||||
callback?.onAction(DevicesAction.Delete(cryptoDeviceInfo.deviceId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dividerItem {
|
||||
id("d3")
|
||||
}
|
||||
bottomSheetVerificationActionItem {
|
||||
id("rename")
|
||||
title(stringProvider.getString(R.string.rename))
|
||||
titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
|
||||
iconRes(R.drawable.ic_arrow_right)
|
||||
iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
|
||||
listener {
|
||||
callback?.onAction(DevicesAction.PromptRename(cryptoDeviceInfo.deviceId))
|
||||
}
|
||||
}
|
||||
} else if (data?.deviceInfo?.invoke() != null) {
|
||||
val info = data.deviceInfo.invoke()
|
||||
genericItem {
|
||||
id("info${info?.deviceId}")
|
||||
title(info?.displayName ?: "")
|
||||
description("(${info?.deviceId})")
|
||||
}
|
||||
|
||||
genericFooterItem {
|
||||
id("infoCrypto${info?.deviceId}")
|
||||
text(stringProvider.getString(R.string.settings_failed_to_get_crypto_device_info))
|
||||
}
|
||||
|
||||
if (info?.deviceId != session.sessionParams.credentials.deviceId) {
|
||||
// Add the delete option
|
||||
dividerItem {
|
||||
id("d2")
|
||||
}
|
||||
bottomSheetVerificationActionItem {
|
||||
id("delete")
|
||||
title(stringProvider.getString(R.string.settings_active_sessions_signout_device))
|
||||
titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
||||
iconRes(R.drawable.ic_arrow_right)
|
||||
iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
||||
listener {
|
||||
callback?.onAction(DevicesAction.Delete(info?.deviceId ?: ""))
|
||||
}
|
||||
titleIconResourceId(shield)
|
||||
title(stringProvider.getString(R.string.crosssigning_verify_this_session))
|
||||
description(stringProvider.getString(R.string.confirm_your_identity))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loadingItem {
|
||||
id("loading")
|
||||
if (!currentSessionIsTrusted) {
|
||||
// we don't know if this session is trusted...
|
||||
// for now we show nothing?
|
||||
} else {
|
||||
// we rely on cross signing status
|
||||
val trust = cryptoDeviceInfo.trustLevel?.isCrossSigningVerified() == true
|
||||
if (trust) {
|
||||
genericItem {
|
||||
id("trust${cryptoDeviceInfo.deviceId}")
|
||||
style(GenericItem.STYLE.BIG_TEXT)
|
||||
titleIconResourceId(shield)
|
||||
title(stringProvider.getString(R.string.encryption_information_verified))
|
||||
description(stringProvider.getString(R.string.settings_active_sessions_verified_device_desc))
|
||||
}
|
||||
} else {
|
||||
genericItem {
|
||||
id("trust${cryptoDeviceInfo.deviceId}")
|
||||
titleIconResourceId(shield)
|
||||
style(GenericItem.STYLE.BIG_TEXT)
|
||||
title(stringProvider.getString(R.string.encryption_information_not_verified))
|
||||
description(stringProvider.getString(R.string.settings_active_sessions_unverified_device_desc))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DEVICE INFO SECTION
|
||||
genericItem {
|
||||
id("info${cryptoDeviceInfo.deviceId}")
|
||||
title(cryptoDeviceInfo.displayName() ?: "")
|
||||
description("(${cryptoDeviceInfo.deviceId})")
|
||||
}
|
||||
|
||||
if (isMine && !currentSessionIsTrusted) {
|
||||
// Add complete security
|
||||
dividerItem {
|
||||
id("completeSecurityDiv")
|
||||
}
|
||||
bottomSheetVerificationActionItem {
|
||||
id("completeSecurity")
|
||||
title(stringProvider.getString(R.string.crosssigning_verify_this_session))
|
||||
titleColor(colorProvider.getColor(R.color.riotx_accent))
|
||||
iconRes(R.drawable.ic_arrow_right)
|
||||
iconColor(colorProvider.getColor(R.color.riotx_accent))
|
||||
listener {
|
||||
callback?.onAction(DevicesAction.CompleteSecurity)
|
||||
}
|
||||
}
|
||||
} else if (!isMine) {
|
||||
if (currentSessionIsTrusted) {
|
||||
// we can propose to verify it
|
||||
val isVerified = cryptoDeviceInfo.trustLevel?.crossSigningVerified.orFalse()
|
||||
if (!isVerified) {
|
||||
addVerifyActions(cryptoDeviceInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleE2EInLegacy(isMine: Boolean, cryptoDeviceInfo: CryptoDeviceInfo, shield: Int) {
|
||||
// ==== Legacy
|
||||
|
||||
// TRUST INFO SECTION
|
||||
if (cryptoDeviceInfo.trustLevel?.isLocallyVerified() == true) {
|
||||
genericItem {
|
||||
id("trust${cryptoDeviceInfo.deviceId}")
|
||||
style(GenericItem.STYLE.BIG_TEXT)
|
||||
titleIconResourceId(shield)
|
||||
title(stringProvider.getString(R.string.encryption_information_verified))
|
||||
description(stringProvider.getString(R.string.settings_active_sessions_verified_device_desc))
|
||||
}
|
||||
} else {
|
||||
genericItem {
|
||||
id("trust${cryptoDeviceInfo.deviceId}")
|
||||
titleIconResourceId(shield)
|
||||
style(GenericItem.STYLE.BIG_TEXT)
|
||||
title(stringProvider.getString(R.string.encryption_information_not_verified))
|
||||
description(stringProvider.getString(R.string.settings_active_sessions_unverified_device_desc))
|
||||
}
|
||||
}
|
||||
|
||||
// DEVICE INFO SECTION
|
||||
genericItem {
|
||||
id("info${cryptoDeviceInfo.deviceId}")
|
||||
title(cryptoDeviceInfo.displayName() ?: "")
|
||||
description("(${cryptoDeviceInfo.deviceId})")
|
||||
}
|
||||
|
||||
// ACTIONS
|
||||
|
||||
if (!isMine) {
|
||||
// if it's not the current device you can trigger a verification
|
||||
dividerItem {
|
||||
id("d1")
|
||||
}
|
||||
bottomSheetVerificationActionItem {
|
||||
id("verify${cryptoDeviceInfo.deviceId}")
|
||||
title(stringProvider.getString(R.string.verification_verify_device))
|
||||
titleColor(colorProvider.getColor(R.color.riotx_accent))
|
||||
iconRes(R.drawable.ic_arrow_right)
|
||||
iconColor(colorProvider.getColor(R.color.riotx_accent))
|
||||
listener {
|
||||
callback?.onAction(DevicesAction.VerifyMyDevice(cryptoDeviceInfo.deviceId))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addVerifyActions(cryptoDeviceInfo: CryptoDeviceInfo) {
|
||||
dividerItem {
|
||||
id("verifyDiv")
|
||||
}
|
||||
bottomSheetVerificationActionItem {
|
||||
id("verify_text")
|
||||
title(stringProvider.getString(R.string.cross_signing_verify_by_text))
|
||||
titleColor(colorProvider.getColor(R.color.riotx_accent))
|
||||
iconRes(R.drawable.ic_arrow_right)
|
||||
iconColor(colorProvider.getColor(R.color.riotx_accent))
|
||||
listener {
|
||||
callback?.onAction(DevicesAction.VerifyMyDeviceManually(cryptoDeviceInfo.deviceId))
|
||||
}
|
||||
}
|
||||
dividerItem {
|
||||
id("verifyDiv2")
|
||||
}
|
||||
bottomSheetVerificationActionItem {
|
||||
id("verify_emoji")
|
||||
title(stringProvider.getString(R.string.cross_signing_verify_by_emoji))
|
||||
titleColor(colorProvider.getColor(R.color.riotx_accent))
|
||||
iconRes(R.drawable.ic_arrow_right)
|
||||
iconColor(colorProvider.getColor(R.color.riotx_accent))
|
||||
listener {
|
||||
callback?.onAction(DevicesAction.VerifyMyDevice(cryptoDeviceInfo.deviceId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addGenericDeviceManageActions(data: DeviceVerificationInfoBottomSheetViewState, deviceId: String) {
|
||||
// Offer delete session if not me
|
||||
if (!data.isMine) {
|
||||
// Add the delete option
|
||||
dividerItem {
|
||||
id("manageD1")
|
||||
}
|
||||
bottomSheetVerificationActionItem {
|
||||
id("delete")
|
||||
title(stringProvider.getString(R.string.settings_active_sessions_signout_device))
|
||||
titleColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
||||
iconRes(R.drawable.ic_arrow_right)
|
||||
iconColor(colorProvider.getColor(R.color.riotx_destructive_accent))
|
||||
listener {
|
||||
callback?.onAction(DevicesAction.Delete(deviceId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always offer rename
|
||||
dividerItem {
|
||||
id("manageD2")
|
||||
}
|
||||
bottomSheetVerificationActionItem {
|
||||
id("rename")
|
||||
title(stringProvider.getString(R.string.rename))
|
||||
titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
|
||||
iconRes(R.drawable.ic_arrow_right)
|
||||
iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary))
|
||||
listener {
|
||||
callback?.onAction(DevicesAction.PromptRename(deviceId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNonE2EDevice(data: DeviceVerificationInfoBottomSheetViewState) {
|
||||
val info = data.deviceInfo.invoke() ?: return
|
||||
genericItem {
|
||||
id("info${info.deviceId}")
|
||||
title(info.displayName ?: "")
|
||||
description("(${info.deviceId})")
|
||||
}
|
||||
|
||||
genericFooterItem {
|
||||
id("infoCrypto${info.deviceId}")
|
||||
text(stringProvider.getString(R.string.settings_failed_to_get_crypto_device_info))
|
||||
}
|
||||
|
||||
info.deviceId?.let { addGenericDeviceManageActions(data, it) }
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.riotx.features.settings.devices
|
||||
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class DevicesAction : VectorViewModelAction {
|
||||
|
@ -26,4 +27,7 @@ sealed class DevicesAction : VectorViewModelAction {
|
|||
|
||||
data class PromptRename(val deviceId: String) : DevicesAction()
|
||||
data class VerifyMyDevice(val deviceId: String) : DevicesAction()
|
||||
data class VerifyMyDeviceManually(val deviceId: String) : DevicesAction()
|
||||
object CompleteSecurity : DevicesAction()
|
||||
data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction()
|
||||
}
|
||||
|
|
|
@ -22,19 +22,24 @@ import com.airbnb.mvrx.Loading
|
|||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.matrix.android.api.extensions.sortByLastSeen
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.epoxy.errorWithRetryItem
|
||||
import im.vector.riotx.core.epoxy.loadingItem
|
||||
import im.vector.riotx.core.error.ErrorFormatter
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.core.ui.list.genericItemHeader
|
||||
import im.vector.riotx.core.utils.DimensionConverter
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
import javax.inject.Inject
|
||||
|
||||
class DevicesController @Inject constructor(private val errorFormatter: ErrorFormatter,
|
||||
private val stringProvider: StringProvider,
|
||||
private val colorProvider: ColorProvider,
|
||||
private val dimensionConverter: DimensionConverter,
|
||||
private val vectorPreferences: VectorPreferences) : EpoxyController() {
|
||||
|
||||
var callback: Callback? = null
|
||||
|
@ -68,30 +73,51 @@ class DevicesController @Inject constructor(private val errorFormatter: ErrorFor
|
|||
listener { callback?.retry() }
|
||||
}
|
||||
is Success ->
|
||||
buildDevicesList(devices(), state.cryptoDevices(), state.myDeviceId)
|
||||
buildDevicesList(devices(), state.cryptoDevices(), state.myDeviceId, !state.hasAccountCrossSigning, state.accountCrossSigningIsTrusted)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDevicesList(devices: List<DeviceInfo>, cryptoDevices: List<CryptoDeviceInfo>?, myDeviceId: String) {
|
||||
// Current device
|
||||
genericItemHeader {
|
||||
id("current")
|
||||
text(stringProvider.getString(R.string.devices_current_device))
|
||||
}
|
||||
|
||||
private fun buildDevicesList(devices: List<DeviceInfo>,
|
||||
cryptoDevices: List<CryptoDeviceInfo>?,
|
||||
myDeviceId: String,
|
||||
legacyMode: Boolean,
|
||||
currentSessionCrossTrusted: Boolean) {
|
||||
devices
|
||||
.filter {
|
||||
.firstOrNull() {
|
||||
it.deviceId == myDeviceId
|
||||
}
|
||||
.forEachIndexed { idx, deviceInfo ->
|
||||
}?.let { deviceInfo ->
|
||||
|
||||
// Current device
|
||||
genericItemHeader {
|
||||
id("current")
|
||||
text(stringProvider.getString(R.string.devices_current_device))
|
||||
}
|
||||
|
||||
deviceItem {
|
||||
id("myDevice$idx")
|
||||
id("myDevice${deviceInfo.deviceId}")
|
||||
legacyMode(legacyMode)
|
||||
trustedSession(currentSessionCrossTrusted)
|
||||
dimensionConverter(dimensionConverter)
|
||||
colorProvider(colorProvider)
|
||||
detailedMode(vectorPreferences.developerMode())
|
||||
deviceInfo(deviceInfo)
|
||||
currentDevice(true)
|
||||
itemClickAction { callback?.onDeviceClicked(deviceInfo) }
|
||||
trusted(true)
|
||||
trusted(DeviceTrustLevel(currentSessionCrossTrusted, true))
|
||||
}
|
||||
|
||||
// // If cross signing enabled and this session not trusted, add short cut to complete security
|
||||
// NEED DESIGN
|
||||
// if (!legacyMode && !currentSessionCrossTrusted) {
|
||||
// genericButtonItem {
|
||||
// id("complete_security")
|
||||
// iconRes(R.drawable.ic_shield_warning)
|
||||
// text(stringProvider.getString(R.string.complete_security))
|
||||
// itemClickAction(DebouncedClickListener(View.OnClickListener { _ ->
|
||||
// callback?.completeSecurity()
|
||||
// }))
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// Other devices
|
||||
|
@ -111,11 +137,15 @@ class DevicesController @Inject constructor(private val errorFormatter: ErrorFor
|
|||
val isCurrentDevice = deviceInfo.deviceId == myDeviceId
|
||||
deviceItem {
|
||||
id("device$idx")
|
||||
legacyMode(legacyMode)
|
||||
trustedSession(currentSessionCrossTrusted)
|
||||
dimensionConverter(dimensionConverter)
|
||||
colorProvider(colorProvider)
|
||||
detailedMode(vectorPreferences.developerMode())
|
||||
deviceInfo(deviceInfo)
|
||||
currentDevice(isCurrentDevice)
|
||||
itemClickAction { callback?.onDeviceClicked(deviceInfo) }
|
||||
trusted(cryptoDevices?.firstOrNull { it.deviceId == deviceInfo.deviceId }?.isVerified)
|
||||
trusted(cryptoDevices?.firstOrNull { it.deviceId == deviceInfo.deviceId }?.trustLevel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package im.vector.riotx.features.settings.devices
|
||||
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.riotx.core.platform.VectorViewEvents
|
||||
|
||||
|
@ -35,4 +37,10 @@ sealed class DevicesViewEvents : VectorViewEvents {
|
|||
val userId: String,
|
||||
val transactionId: String?
|
||||
) : DevicesViewEvents()
|
||||
|
||||
data class SelfVerification(
|
||||
val session: Session
|
||||
) : DevicesViewEvents()
|
||||
|
||||
data class ShowManuallyVerify(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesViewEvents()
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.riotx.features.settings.devices
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
|
@ -28,26 +29,33 @@ 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.extensions.tryThis
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
|
||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
|
||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
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.DeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||
import im.vector.matrix.android.internal.util.awaitCallback
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.features.crypto.verification.SupportedVerificationMethodsProvider
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class DevicesViewState(
|
||||
val myDeviceId: String = "",
|
||||
val devices: Async<List<DeviceInfo>> = Uninitialized,
|
||||
val cryptoDevices: Async<List<CryptoDeviceInfo>> = Uninitialized,
|
||||
// TODO Replace by isLoading boolean
|
||||
val request: Async<Unit> = Uninitialized
|
||||
val request: Async<Unit> = Uninitialized,
|
||||
val hasAccountCrossSigning: Boolean = false,
|
||||
val accountCrossSigningIsTrusted: Boolean = false
|
||||
) : MvRxState
|
||||
|
||||
class DevicesViewModel @AssistedInject constructor(
|
||||
|
@ -75,6 +83,21 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
private var _currentSession: String? = null
|
||||
|
||||
init {
|
||||
|
||||
setState {
|
||||
copy(
|
||||
hasAccountCrossSigning = session.cryptoService().crossSigningService().getMyCrossSigningKeys() != null,
|
||||
accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified()
|
||||
)
|
||||
}
|
||||
session.rx().liveCrossSigningInfo(session.myUserId)
|
||||
.execute {
|
||||
copy(
|
||||
hasAccountCrossSigning = it.invoke()?.get() != null,
|
||||
accountCrossSigningIsTrusted = it.invoke()?.get()?.isTrusted() == true
|
||||
)
|
||||
}
|
||||
|
||||
refreshDevicesList()
|
||||
session.cryptoService().verificationService().addListener(this)
|
||||
|
||||
|
@ -164,25 +187,56 @@ class DevicesViewModel @AssistedInject constructor(
|
|||
|
||||
override fun handle(action: DevicesAction) {
|
||||
return when (action) {
|
||||
is DevicesAction.Retry -> refreshDevicesList()
|
||||
is DevicesAction.Delete -> handleDelete(action)
|
||||
is DevicesAction.Password -> handlePassword(action)
|
||||
is DevicesAction.Rename -> handleRename(action)
|
||||
is DevicesAction.PromptRename -> handlePromptRename(action)
|
||||
is DevicesAction.VerifyMyDevice -> handleVerify(action)
|
||||
is DevicesAction.Retry -> refreshDevicesList()
|
||||
is DevicesAction.Delete -> handleDelete(action)
|
||||
is DevicesAction.Password -> handlePassword(action)
|
||||
is DevicesAction.Rename -> handleRename(action)
|
||||
is DevicesAction.PromptRename -> handlePromptRename(action)
|
||||
is DevicesAction.VerifyMyDevice -> handleInteractiveVerification(action)
|
||||
is DevicesAction.CompleteSecurity -> handleCompleteSecurity()
|
||||
is DevicesAction.MarkAsManuallyVerified -> handleVerifyManually(action)
|
||||
is DevicesAction.VerifyMyDeviceManually -> handleShowDeviceCryptoInfo(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleVerify(action: DevicesAction.VerifyMyDevice) {
|
||||
private fun handleInteractiveVerification(action: DevicesAction.VerifyMyDevice) {
|
||||
val txID = session.cryptoService()
|
||||
.verificationService()
|
||||
.requestKeyVerification(supportedVerificationMethodsProvider.provide(), session.myUserId, listOf(action.deviceId))
|
||||
.beginKeyVerification(VerificationMethod.SAS, session.myUserId, action.deviceId, null)
|
||||
_viewEvents.post(DevicesViewEvents.ShowVerifyDevice(
|
||||
session.myUserId,
|
||||
txID.transactionId
|
||||
txID
|
||||
))
|
||||
}
|
||||
|
||||
private fun handleShowDeviceCryptoInfo(action: DevicesAction.VerifyMyDeviceManually) = withState { state ->
|
||||
state.cryptoDevices.invoke()
|
||||
?.firstOrNull { it.deviceId == action.deviceId }
|
||||
?.let {
|
||||
_viewEvents.post(DevicesViewEvents.ShowManuallyVerify(it))
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleVerifyManually(action: DevicesAction.MarkAsManuallyVerified) = withState { state ->
|
||||
viewModelScope.launch {
|
||||
if (state.hasAccountCrossSigning) {
|
||||
awaitCallback<Unit> {
|
||||
tryThis { session.cryptoService().crossSigningService().trustDevice(action.cryptoDeviceInfo.deviceId, it) }
|
||||
}
|
||||
} else {
|
||||
// legacy
|
||||
session.cryptoService().setDeviceVerification(
|
||||
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
||||
action.cryptoDeviceInfo.userId,
|
||||
action.cryptoDeviceInfo.deviceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCompleteSecurity() {
|
||||
_viewEvents.post(DevicesViewEvents.SelfVerification(session))
|
||||
}
|
||||
|
||||
private fun handlePromptRename(action: DevicesAction.PromptRename) = withState { state ->
|
||||
val info = state.devices.invoke()?.firstOrNull { it.deviceId == action.deviceId }
|
||||
if (info != null) {
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.devices
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import im.vector.riotx.R
|
||||
|
||||
object TrustUtils {
|
||||
|
||||
@DrawableRes
|
||||
fun shieldForTrust(currentDevice: Boolean, trustMSK: Boolean, legacyMode: Boolean, deviceTrustLevel: DeviceTrustLevel?): Int {
|
||||
return when {
|
||||
currentDevice -> {
|
||||
if (legacyMode) {
|
||||
// In legacy, current session is always trusted
|
||||
R.drawable.ic_shield_trusted
|
||||
} else {
|
||||
// If current session doesn't trust MSK, show red shield for current device
|
||||
R.drawable.ic_shield_trusted.takeIf { trustMSK } ?: R.drawable.ic_shield_warning
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if (legacyMode) {
|
||||
// use local trust
|
||||
R.drawable.ic_shield_trusted.takeIf { deviceTrustLevel?.locallyVerified == true } ?: R.drawable.ic_shield_warning
|
||||
} else {
|
||||
if (trustMSK) {
|
||||
// use cross sign trust, put locally trusted in black
|
||||
R.drawable.ic_shield_trusted.takeIf { deviceTrustLevel?.crossSigningVerified == true }
|
||||
?: R.drawable.ic_shield_black.takeIf { deviceTrustLevel?.locallyVerified == true }
|
||||
?: R.drawable.ic_shield_warning
|
||||
} else {
|
||||
// The current session is untrusted, so displays others in black
|
||||
// as we can't know the cross-signing state
|
||||
R.drawable.ic_shield_black
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ import com.airbnb.mvrx.fragmentViewModel
|
|||
import com.airbnb.mvrx.withState
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.dialogs.ManuallyVerifyDialog
|
||||
import im.vector.riotx.core.dialogs.PromptPasswordDialog
|
||||
import im.vector.riotx.core.extensions.cleanup
|
||||
import im.vector.riotx.core.extensions.configureWith
|
||||
|
@ -73,6 +74,15 @@ class VectorSettingsDevicesFragment @Inject constructor(
|
|||
transactionId = it.transactionId
|
||||
).show(childFragmentManager, "REQPOP")
|
||||
}
|
||||
is DevicesViewEvents.SelfVerification -> {
|
||||
VerificationBottomSheet.forSelfVerification(it.session)
|
||||
.show(childFragmentManager, "REQPOP")
|
||||
}
|
||||
is DevicesViewEvents.ShowManuallyVerify -> {
|
||||
ManuallyVerifyDialog.show(requireActivity(), it.cryptoDeviceInfo) {
|
||||
viewModel.handle(DevicesAction.MarkAsManuallyVerified(it.cryptoDeviceInfo))
|
||||
}
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<LinearLayout
|
||||
android:id="@+id/alerter_texts"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -68,6 +68,7 @@
|
|||
android:textAppearance="@style/AlertTextAppearance.Text"
|
||||
android:visibility="gone"
|
||||
tools:text="Text"
|
||||
android:maxLines="3"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/itemGenericItemButton"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton.Icon"
|
||||
android:textAllCaps="false"
|
||||
tools:icon="@drawable/ic_shield_warning"
|
||||
app:iconGravity="textStart"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Action Name" />
|
||||
|
||||
</LinearLayout>
|
|
@ -1024,7 +1024,7 @@
|
|||
<string name="encryption_never_send_to_unverified_devices_summary">Never send encrypted messages to unverified sessions from this session.</string>
|
||||
<string name="encryption_import_room_keys_success">%1$d/%2$d key(s) imported with success.</string>
|
||||
|
||||
<string name="encryption_information_not_verified">NOT Verified</string>
|
||||
<string name="encryption_information_not_verified">Not Verified</string>
|
||||
<string name="encryption_information_verified">Verified</string>
|
||||
<string name="encryption_information_blocked">Blacklisted</string>
|
||||
|
||||
|
@ -1038,8 +1038,8 @@
|
|||
<string name="encryption_information_unblock">Unblacklist</string>
|
||||
|
||||
<string name="encryption_information_verify_device">Verify session</string>
|
||||
<string name="encryption_information_verify_device_warning">To verify that this session can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this session matches the key below:</string>
|
||||
<string name="encryption_information_verify_device_warning2">If it matches, press the verify button below. If it doesn’t, then someone else is intercepting this session and you should probably blacklist it. In the future this verification process will be more sophisticated.</string>
|
||||
<string name="encryption_information_verify_device_warning">Confirm by comparing the following with the User Settings in your other session:</string>
|
||||
<string name="encryption_information_verify_device_warning2">"If they don't match, the security of your communication may be compromised."</string>
|
||||
<string name="encryption_information_verify_key_match">I verify that the keys match</string>
|
||||
|
||||
<string name="e2e_enabling_on_app_update">Riot now supports end-to-end encryption but you need to log in again to enable it.\n\nYou can do it now or later from the application settings.</string>
|
||||
|
@ -2110,7 +2110,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
|
|||
<string name="settings_active_sessions_list">Active Sessions</string>
|
||||
<string name="settings_active_sessions_show_all">Show All Sessions</string>
|
||||
<string name="settings_active_sessions_manage">Manage Sessions</string>
|
||||
<string name="settings_active_sessions_signout_device">Sign out this session</string>
|
||||
<string name="settings_active_sessions_signout_device">Sign out of this session</string>
|
||||
|
||||
<string name="settings_failed_to_get_crypto_device_info">No cryptographic information available</string>
|
||||
|
||||
|
@ -2122,7 +2122,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
|
|||
<item quantity="other">%d active sessions</item>
|
||||
</plurals>
|
||||
|
||||
<string name="crosssigning_verify_this_session">Verify this session</string>
|
||||
<string name="crosssigning_verify_this_session">Verify this login</string>
|
||||
<string name="crosssigning_other_user_not_trust">Other users may not trust it</string>
|
||||
<string name="complete_security">Complete Security</string>
|
||||
|
||||
|
|
|
@ -19,6 +19,12 @@
|
|||
<string name="enter_secret_storage_input_key">Select your Recovery Key, or input it manually by typing it or pasting from your clipboard</string>
|
||||
<string name="keys_backup_recovery_key_error_decrypt">Backup could not be decrypted with this Recovery Key: please verify that you entered the correct Recovery Key.</string>
|
||||
<string name="failed_to_access_secure_storage">Failed to access secure storage</string>
|
||||
|
||||
<string name="cross_signing_verify_by_text">Manually Verify by Text</string>
|
||||
<string name="crosssigning_verify_session">Verify login</string>
|
||||
<string name="cross_signing_verify_by_emoji">Interactively Verify by Emoji</string>
|
||||
<string name="confirm_your_identity">Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.</string>
|
||||
<string name="mark_as_verified">Mark as Trusted</string>
|
||||
<!-- END Strings added by Valere -->
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue