self verification basics
This commit is contained in:
parent
5b3e3a7019
commit
d302fdc655
@ -2287,6 +2287,7 @@
|
|||||||
<string name="verification_verify_user">Verify %s</string>
|
<string name="verification_verify_user">Verify %s</string>
|
||||||
<string name="verification_verified_user">Verified %s</string>
|
<string name="verification_verified_user">Verified %s</string>
|
||||||
<string name="verification_request_waiting_for">Waiting for %s…</string>
|
<string name="verification_request_waiting_for">Waiting for %s…</string>
|
||||||
|
<string name="verification_request_waiting_for_recovery">Verifying from Secure Key or Phrase…</string>
|
||||||
<string name="room_profile_not_encrypted_subtitle">Messages in this room are not end-to-end encrypted.</string>
|
<string name="room_profile_not_encrypted_subtitle">Messages in this room are not end-to-end encrypted.</string>
|
||||||
<string name="direct_room_profile_not_encrypted_subtitle">Messages here are not end-to-end encrypted.</string>
|
<string name="direct_room_profile_not_encrypted_subtitle">Messages here are not end-to-end encrypted.</string>
|
||||||
<string name="room_profile_encrypted_subtitle">Messages in this room are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them.</string>
|
<string name="room_profile_encrypted_subtitle">Messages in this room are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them.</string>
|
||||||
@ -2396,6 +2397,8 @@
|
|||||||
<string name="crosssigning_cannot_verify_this_session">Unable to verify this device</string>
|
<string name="crosssigning_cannot_verify_this_session">Unable to verify this device</string>
|
||||||
<string name="crosssigning_cannot_verify_this_session_desc">You won’t be able to access encrypted message history. Reset your Secure Message Backup and verification keys to start fresh.</string>
|
<string name="crosssigning_cannot_verify_this_session_desc">You won’t be able to access encrypted message history. Reset your Secure Message Backup and verification keys to start fresh.</string>
|
||||||
|
|
||||||
|
<string name="verification_verify_with_another_device">Verify with another device</string>
|
||||||
|
<string name="verification_verify_identity">Verify your identity to access encrypted messages and prove your identity to others.</string>
|
||||||
<string name="verification_open_other_to_verify">Use an existing session to verify this one, granting it access to encrypted messages.</string>
|
<string name="verification_open_other_to_verify">Use an existing session to verify this one, granting it access to encrypted messages.</string>
|
||||||
|
|
||||||
<string name="verification_profile_verify">Verify</string>
|
<string name="verification_profile_verify">Verify</string>
|
||||||
|
@ -63,7 +63,7 @@ import javax.inject.Inject
|
|||||||
@SessionScope
|
@SessionScope
|
||||||
internal class DefaultVerificationService @Inject constructor(
|
internal class DefaultVerificationService @Inject constructor(
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
@DeviceId private val deviceId: String?,
|
@DeviceId private val myDeviceId: String?,
|
||||||
private val cryptoStore: IMXCryptoStore,
|
private val cryptoStore: IMXCryptoStore,
|
||||||
// private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
|
// private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
|
||||||
// private val secretShareManager: SecretShareManager,
|
// private val secretShareManager: SecretShareManager,
|
||||||
@ -582,7 +582,7 @@ internal class DefaultVerificationService @Inject constructor(
|
|||||||
} else if (userId > otherUserId) {
|
} else if (userId > otherUserId) {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return otherDeviceId < deviceId ?: ""
|
return otherDeviceId < myDeviceId ?: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1299,7 +1299,9 @@ internal class DefaultVerificationService @Inject constructor(
|
|||||||
override suspend fun requestDeviceVerification(methods: List<VerificationMethod>, otherUserId: String, otherDeviceId: String?): PendingVerificationRequest {
|
override suspend fun requestDeviceVerification(methods: List<VerificationMethod>, otherUserId: String, otherDeviceId: String?): PendingVerificationRequest {
|
||||||
// TODO refactor this with the DM one
|
// TODO refactor this with the DM one
|
||||||
|
|
||||||
val targetDevices = otherDeviceId?.let { listOf(it) } ?: cryptoStore.getUserDevices(otherUserId)
|
val targetDevices = otherDeviceId?.let { listOf(it) }
|
||||||
|
?: cryptoStore.getUserDevices(otherUserId)
|
||||||
|
?.filter { it.key != myDeviceId }
|
||||||
?.values?.map { it.deviceId }.orEmpty()
|
?.values?.map { it.deviceId }.orEmpty()
|
||||||
|
|
||||||
Timber.i("## Requesting verification to user: $otherUserId with device list $targetDevices")
|
Timber.i("## Requesting verification to user: $otherUserId with device list $targetDevices")
|
||||||
|
@ -1354,7 +1354,14 @@ internal class VerificationActor @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val methodValues = if (verificationTrustBackend.getMyTrustedMasterKeyBase64() != null) {
|
// XXX We should probably throw here if you try to verify someone else from an untrusted session
|
||||||
|
val shouldShowQROption = if (msg.otherUserId == myUserId) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
// It's verifying someone else, I should trust my key before doing it?
|
||||||
|
verificationTrustBackend.getUserMasterKeyBase64(myUserId) != null
|
||||||
|
}
|
||||||
|
val methodValues = if (shouldShowQROption) {
|
||||||
// Add reciprocate method if application declares it can scan or show QR codes
|
// Add reciprocate method if application declares it can scan or show QR codes
|
||||||
// Not sure if it ok to do that (?)
|
// Not sure if it ok to do that (?)
|
||||||
val reciprocateMethod = msg.methods
|
val reciprocateMethod = msg.methods
|
||||||
@ -1452,8 +1459,11 @@ internal class VerificationActor @AssistedInject constructor(
|
|||||||
cancelRequest(matchingRequest, CancelCode.UnexpectedMessage)
|
cancelRequest(matchingRequest, CancelCode.UnexpectedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// for room verification
|
// for room verification (user)
|
||||||
if (msg.fromUser == myUserId && msg.readyInfo.fromDevice != myDevice) {
|
// TODO if room and incoming I should check that right?
|
||||||
|
// actually it will not reach that point? handleReadyByAnotherOfMySessionReceived would be called instead? and
|
||||||
|
// the actor never sees event send by me in rooms
|
||||||
|
if (matchingRequest.otherUserId != myUserId && msg.fromUser == myUserId && msg.readyInfo.fromDevice != myDevice) {
|
||||||
// it's a ready from another of my devices, so we should just
|
// it's a ready from another of my devices, so we should just
|
||||||
// ignore following messages related to that request
|
// ignore following messages related to that request
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
|
@ -16,11 +16,11 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.api.session.crypto.verification
|
package org.matrix.android.sdk.api.session.crypto.verification
|
||||||
|
|
||||||
sealed class VerificationEvent(val transactionId: String) {
|
sealed class VerificationEvent(val transactionId: String, val otherUserId: String) {
|
||||||
data class RequestAdded(val request: PendingVerificationRequest) : VerificationEvent(request.transactionId)
|
data class RequestAdded(val request: PendingVerificationRequest) : VerificationEvent(request.transactionId, request.otherUserId)
|
||||||
data class RequestUpdated(val request: PendingVerificationRequest) : VerificationEvent(request.transactionId)
|
data class RequestUpdated(val request: PendingVerificationRequest) : VerificationEvent(request.transactionId, request.otherUserId)
|
||||||
data class TransactionAdded(val transaction: VerificationTransaction) : VerificationEvent(transaction.transactionId)
|
data class TransactionAdded(val transaction: VerificationTransaction) : VerificationEvent(transaction.transactionId, transaction.otherUserId)
|
||||||
data class TransactionUpdated(val transaction: VerificationTransaction) : VerificationEvent(transaction.transactionId)
|
data class TransactionUpdated(val transaction: VerificationTransaction) : VerificationEvent(transaction.transactionId, transaction.otherUserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun VerificationEvent.getRequest(): PendingVerificationRequest? {
|
fun VerificationEvent.getRequest(): PendingVerificationRequest? {
|
||||||
|
@ -58,7 +58,7 @@ interface VerificationService {
|
|||||||
* If no specific device should be verified, but we would like to request
|
* If no specific device should be verified, but we would like to request
|
||||||
* verification from all our devices, use [requestSelfKeyVerification] instead.
|
* verification from all our devices, use [requestSelfKeyVerification] instead.
|
||||||
*/
|
*/
|
||||||
suspend fun requestDeviceVerification(methods: List<VerificationMethod>, otherUserId: String, otherDeviceId: String?): PendingVerificationRequest?
|
suspend fun requestDeviceVerification(methods: List<VerificationMethod>, otherUserId: String, otherDeviceId: String?): PendingVerificationRequest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request key verification with another user via room events (instead of the to-device API).
|
* Request key verification with another user via room events (instead of the to-device API).
|
||||||
|
@ -100,6 +100,7 @@ internal class FakeCryptoStoreForVerification(private val mode: StoreMode) {
|
|||||||
val aliceMxId = "alice@example.com"
|
val aliceMxId = "alice@example.com"
|
||||||
val bobMxId = "bob@example.com"
|
val bobMxId = "bob@example.com"
|
||||||
val bobDeviceId = "MKRJDSLYGA"
|
val bobDeviceId = "MKRJDSLYGA"
|
||||||
|
val bobDeviceId2 = "RRIWTEKZEI"
|
||||||
|
|
||||||
val aliceDevice1Id = "MGDAADVDMG"
|
val aliceDevice1Id = "MGDAADVDMG"
|
||||||
|
|
||||||
@ -147,6 +148,22 @@ internal class FakeCryptoStoreForVerification(private val mode: StoreMode) {
|
|||||||
unsigned = UnsignedDeviceInfo(deviceDisplayName = "Element Ios")
|
unsigned = UnsignedDeviceInfo(deviceDisplayName = "Element Ios")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val aBobDevice2 = CryptoDeviceInfo(
|
||||||
|
deviceId = bobDeviceId2,
|
||||||
|
userId = bobMxId,
|
||||||
|
algorithms = MXCryptoAlgorithms.supportedAlgorithms(),
|
||||||
|
keys = mapOf(
|
||||||
|
"curve25519:$bobDeviceId" to "mE4WKAcyRRv7Gk1IDIVm0lZNzb8g9YL2eRQZUHmkkCI",
|
||||||
|
"ed25519:$bobDeviceId" to "yB/9LITHTqrvdXWDR2k6Qw/MDLUBWABlP9v2eYuqHPE",
|
||||||
|
),
|
||||||
|
signatures = mapOf(
|
||||||
|
bobMxId to mapOf(
|
||||||
|
"ed25519:$bobDeviceId" to "zAJqsmOSzkx8EWXcrynCsWtbgWZifN7A6DLyEBs+ZPPLnNuPN5Jwzc1Rg+oZWZaRPvOPcSL0cgcxRegSBU0NBA",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
unsigned = UnsignedDeviceInfo(deviceDisplayName = "Element Android")
|
||||||
|
)
|
||||||
|
|
||||||
private val aliceMSKBase = CryptoCrossSigningKey(
|
private val aliceMSKBase = CryptoCrossSigningKey(
|
||||||
userId = aliceMxId,
|
userId = aliceMxId,
|
||||||
usages = listOf(KeyUsage.MASTER.value),
|
usages = listOf(KeyUsage.MASTER.value),
|
||||||
|
@ -22,6 +22,7 @@ import io.mockk.mockk
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.channels.SendChannel
|
import kotlinx.coroutines.channels.SendChannel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady
|
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady
|
||||||
@ -53,8 +54,8 @@ internal class VerificationActorHelper {
|
|||||||
var aliceChannel: SendChannel<VerificationIntent>? = null
|
var aliceChannel: SendChannel<VerificationIntent>? = null
|
||||||
|
|
||||||
fun setUpActors(): TestData {
|
fun setUpActors(): TestData {
|
||||||
val aliceTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.aliceMxId) { bobChannel }
|
val aliceTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.aliceMxId) { listOf(bobChannel) }
|
||||||
val bobTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.bobMxId) { aliceChannel }
|
val bobTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.bobMxId) { listOf(aliceChannel) }
|
||||||
|
|
||||||
val fakeAliceStore = FakeCryptoStoreForVerification(StoreMode.Alice)
|
val fakeAliceStore = FakeCryptoStoreForVerification(StoreMode.Alice)
|
||||||
val aliceActor = fakeActor(
|
val aliceActor = fakeActor(
|
||||||
@ -82,7 +83,53 @@ internal class VerificationActorHelper {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mockTransportTo(fromUser: String, otherChannel: (() -> SendChannel<VerificationIntent>?)): VerificationTransportLayer {
|
fun setupMultipleSessions() {
|
||||||
|
val aliceTargetChannels = mutableListOf<Channel<VerificationIntent>>()
|
||||||
|
val aliceTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.aliceMxId) { aliceTargetChannels }
|
||||||
|
val bobTargetChannels = mutableListOf<Channel<VerificationIntent>>()
|
||||||
|
val bobTransportLayer = mockTransportTo(FakeCryptoStoreForVerification.bobMxId) { bobTargetChannels }
|
||||||
|
val bob2TargetChannels = mutableListOf<Channel<VerificationIntent>>()
|
||||||
|
val bob2TransportLayer = mockTransportTo(FakeCryptoStoreForVerification.bobMxId) { bob2TargetChannels }
|
||||||
|
|
||||||
|
val fakeAliceStore = FakeCryptoStoreForVerification(StoreMode.Alice)
|
||||||
|
val aliceActor = fakeActor(
|
||||||
|
actorAScope,
|
||||||
|
FakeCryptoStoreForVerification.aliceMxId,
|
||||||
|
fakeAliceStore.instance,
|
||||||
|
aliceTransportLayer,
|
||||||
|
)
|
||||||
|
|
||||||
|
val fakeBobStore1 = FakeCryptoStoreForVerification(StoreMode.Bob)
|
||||||
|
val bobActor = fakeActor(
|
||||||
|
actorBScope,
|
||||||
|
FakeCryptoStoreForVerification.bobMxId,
|
||||||
|
fakeBobStore1.instance,
|
||||||
|
bobTransportLayer
|
||||||
|
)
|
||||||
|
|
||||||
|
val actorCScope = CoroutineScope(SupervisorJob())
|
||||||
|
val fakeBobStore2 = FakeCryptoStoreForVerification(StoreMode.Bob)
|
||||||
|
every { fakeBobStore2.instance.getMyDeviceId() } returns FakeCryptoStoreForVerification.bobDeviceId2
|
||||||
|
every { fakeBobStore2.instance.getMyDevice() } returns FakeCryptoStoreForVerification.aBobDevice2
|
||||||
|
|
||||||
|
val bobActor2 = fakeActor(
|
||||||
|
actorCScope,
|
||||||
|
FakeCryptoStoreForVerification.bobMxId,
|
||||||
|
fakeBobStore2.instance,
|
||||||
|
bobTransportLayer
|
||||||
|
)
|
||||||
|
|
||||||
|
aliceTargetChannels.add(bobActor.channel)
|
||||||
|
aliceTargetChannels.add(bobActor2.channel)
|
||||||
|
|
||||||
|
bobTargetChannels.add(aliceActor.channel)
|
||||||
|
bobTargetChannels.add(bobActor2.channel)
|
||||||
|
|
||||||
|
bob2TargetChannels.add(aliceActor.channel)
|
||||||
|
bob2TargetChannels.add(bobActor.channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mockTransportTo(fromUser: String, otherChannel: (() -> List<SendChannel<VerificationIntent>?>)): VerificationTransportLayer {
|
||||||
return mockk<VerificationTransportLayer> {
|
return mockk<VerificationTransportLayer> {
|
||||||
coEvery { sendToOther(any(), any(), any()) } answers {
|
coEvery { sendToOther(any(), any(), any()) } answers {
|
||||||
val request = firstArg<KotlinVerificationRequest>()
|
val request = firstArg<KotlinVerificationRequest>()
|
||||||
@ -93,7 +140,8 @@ internal class VerificationActorHelper {
|
|||||||
when (type) {
|
when (type) {
|
||||||
EventType.KEY_VERIFICATION_READY -> {
|
EventType.KEY_VERIFICATION_READY -> {
|
||||||
val readyContent = info.asValidObject()
|
val readyContent = info.asValidObject()
|
||||||
otherChannel()?.send(
|
otherChannel().onEach {
|
||||||
|
it?.send(
|
||||||
VerificationIntent.OnReadyReceived(
|
VerificationIntent.OnReadyReceived(
|
||||||
transactionId = request.requestId,
|
transactionId = request.requestId,
|
||||||
fromUser = fromUser,
|
fromUser = fromUser,
|
||||||
@ -102,9 +150,11 @@ internal class VerificationActorHelper {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
EventType.KEY_VERIFICATION_START -> {
|
EventType.KEY_VERIFICATION_START -> {
|
||||||
val startContent = info.asValidObject()
|
val startContent = info.asValidObject()
|
||||||
otherChannel()?.send(
|
otherChannel().onEach {
|
||||||
|
it?.send(
|
||||||
VerificationIntent.OnStartReceived(
|
VerificationIntent.OnStartReceived(
|
||||||
fromUser = fromUser,
|
fromUser = fromUser,
|
||||||
viaRoom = request.roomId,
|
viaRoom = request.roomId,
|
||||||
@ -112,9 +162,11 @@ internal class VerificationActorHelper {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
EventType.KEY_VERIFICATION_ACCEPT -> {
|
EventType.KEY_VERIFICATION_ACCEPT -> {
|
||||||
val content = info.asValidObject()
|
val content = info.asValidObject()
|
||||||
otherChannel()?.send(
|
otherChannel().onEach {
|
||||||
|
it?.send(
|
||||||
VerificationIntent.OnAcceptReceived(
|
VerificationIntent.OnAcceptReceived(
|
||||||
fromUser = fromUser,
|
fromUser = fromUser,
|
||||||
viaRoom = request.roomId,
|
viaRoom = request.roomId,
|
||||||
@ -122,9 +174,11 @@ internal class VerificationActorHelper {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
EventType.KEY_VERIFICATION_KEY -> {
|
EventType.KEY_VERIFICATION_KEY -> {
|
||||||
val content = info.asValidObject()
|
val content = info.asValidObject()
|
||||||
otherChannel()?.send(
|
otherChannel().onEach {
|
||||||
|
it?.send(
|
||||||
VerificationIntent.OnKeyReceived(
|
VerificationIntent.OnKeyReceived(
|
||||||
fromUser = fromUser,
|
fromUser = fromUser,
|
||||||
viaRoom = request.roomId,
|
viaRoom = request.roomId,
|
||||||
@ -132,9 +186,11 @@ internal class VerificationActorHelper {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
EventType.KEY_VERIFICATION_MAC -> {
|
EventType.KEY_VERIFICATION_MAC -> {
|
||||||
val content = info.asValidObject()
|
val content = info.asValidObject()
|
||||||
otherChannel()?.send(
|
otherChannel().onEach {
|
||||||
|
it?.send(
|
||||||
VerificationIntent.OnMacReceived(
|
VerificationIntent.OnMacReceived(
|
||||||
fromUser = fromUser,
|
fromUser = fromUser,
|
||||||
viaRoom = request.roomId,
|
viaRoom = request.roomId,
|
||||||
@ -142,9 +198,11 @@ internal class VerificationActorHelper {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
EventType.KEY_VERIFICATION_DONE -> {
|
EventType.KEY_VERIFICATION_DONE -> {
|
||||||
val content = info.asValidObject()
|
val content = info.asValidObject()
|
||||||
otherChannel()?.send(
|
otherChannel().onEach {
|
||||||
|
it?.send(
|
||||||
VerificationIntent.OnDoneReceived(
|
VerificationIntent.OnDoneReceived(
|
||||||
fromUser = fromUser,
|
fromUser = fromUser,
|
||||||
viaRoom = request.roomId,
|
viaRoom = request.roomId,
|
||||||
@ -155,6 +213,7 @@ internal class VerificationActorHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
coEvery { sendInRoom(any(), any(), any(), any()) } answers {
|
coEvery { sendInRoom(any(), any(), any(), any()) } answers {
|
||||||
val type = secondArg<String>()
|
val type = secondArg<String>()
|
||||||
val roomId = thirdArg<String>()
|
val roomId = thirdArg<String>()
|
||||||
@ -167,7 +226,8 @@ internal class VerificationActorHelper {
|
|||||||
val requestContent = content.toModel<MessageVerificationRequestContent>()?.copy(
|
val requestContent = content.toModel<MessageVerificationRequestContent>()?.copy(
|
||||||
transactionId = fakeEventId
|
transactionId = fakeEventId
|
||||||
)?.asValidObject()
|
)?.asValidObject()
|
||||||
otherChannel()?.send(
|
otherChannel().onEach {
|
||||||
|
it?.send(
|
||||||
VerificationIntent.OnVerificationRequestReceived(
|
VerificationIntent.OnVerificationRequestReceived(
|
||||||
requestContent!!,
|
requestContent!!,
|
||||||
senderId = FakeCryptoStoreForVerification.aliceMxId,
|
senderId = FakeCryptoStoreForVerification.aliceMxId,
|
||||||
@ -176,10 +236,12 @@ internal class VerificationActorHelper {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
EventType.KEY_VERIFICATION_READY -> {
|
EventType.KEY_VERIFICATION_READY -> {
|
||||||
val readyContent = content.toModel<MessageVerificationReadyContent>()
|
val readyContent = content.toModel<MessageVerificationReadyContent>()
|
||||||
?.asValidObject()
|
?.asValidObject()
|
||||||
otherChannel()?.send(
|
otherChannel().onEach {
|
||||||
|
it?.send(
|
||||||
VerificationIntent.OnReadyReceived(
|
VerificationIntent.OnReadyReceived(
|
||||||
transactionId = readyContent!!.transactionId,
|
transactionId = readyContent!!.transactionId,
|
||||||
fromUser = fromUser,
|
fromUser = fromUser,
|
||||||
@ -190,6 +252,7 @@ internal class VerificationActorHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fakeEventId
|
fakeEventId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import im.vector.app.features.createdirect.CreateDirectRoomViewModel
|
|||||||
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel
|
import im.vector.app.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel
|
||||||
import im.vector.app.features.crypto.quads.SharedSecureStorageViewModel
|
import im.vector.app.features.crypto.quads.SharedSecureStorageViewModel
|
||||||
import im.vector.app.features.crypto.recover.BootstrapSharedViewModel
|
import im.vector.app.features.crypto.recover.BootstrapSharedViewModel
|
||||||
|
import im.vector.app.features.crypto.verification.self.SelfVerificationViewModel
|
||||||
import im.vector.app.features.crypto.verification.user.UserVerificationViewModel
|
import im.vector.app.features.crypto.verification.user.UserVerificationViewModel
|
||||||
import im.vector.app.features.devtools.RoomDevToolViewModel
|
import im.vector.app.features.devtools.RoomDevToolViewModel
|
||||||
import im.vector.app.features.discovery.DiscoverySettingsViewModel
|
import im.vector.app.features.discovery.DiscoverySettingsViewModel
|
||||||
@ -594,6 +595,11 @@ interface MavericksViewModelModule {
|
|||||||
@MavericksViewModelKey(UserVerificationViewModel::class)
|
@MavericksViewModelKey(UserVerificationViewModel::class)
|
||||||
fun userVerificationBottomSheetViewModelFactory(factory: UserVerificationViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
fun userVerificationBottomSheetViewModelFactory(factory: UserVerificationViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@MavericksViewModelKey(SelfVerificationViewModel::class)
|
||||||
|
fun selfVerificationBottomSheetViewModelFactory(factory: SelfVerificationViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@MavericksViewModelKey(CreatePollViewModel::class)
|
@MavericksViewModelKey(CreatePollViewModel::class)
|
||||||
|
@ -187,8 +187,9 @@ class IncomingVerificationRequestHandler @Inject constructor(
|
|||||||
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
|
(weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let {
|
||||||
val roomId = pr.roomId
|
val roomId = pr.roomId
|
||||||
if (roomId.isNullOrBlank()) {
|
if (roomId.isNullOrBlank()) {
|
||||||
// TODO
|
if (pr.otherUserId == session?.myUserId) {
|
||||||
// it.navigator.waitSessionVerification(it)
|
it.navigator.showIncomingSelfVerification(it, pr.transactionId)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
it.navigator.openRoom(
|
it.navigator.openRoom(
|
||||||
context = it,
|
context = it,
|
||||||
|
@ -21,6 +21,7 @@ import im.vector.app.core.platform.VectorViewModelAction
|
|||||||
// TODO Remove otherUserId and transactionId when it's not necessary. Should be known by the ViewModel, no?
|
// TODO Remove otherUserId and transactionId when it's not necessary. Should be known by the ViewModel, no?
|
||||||
sealed class VerificationAction : VectorViewModelAction {
|
sealed class VerificationAction : VectorViewModelAction {
|
||||||
object RequestVerificationByDM : VerificationAction()
|
object RequestVerificationByDM : VerificationAction()
|
||||||
|
object RequestSelfVerification : VerificationAction()
|
||||||
object StartSASVerification : VerificationAction()
|
object StartSASVerification : VerificationAction()
|
||||||
data class RemoteQrCodeScanned(val otherUserId: String, val transactionId: String, val scannedData: String) : VerificationAction()
|
data class RemoteQrCodeScanned(val otherUserId: String, val transactionId: String, val scannedData: String) : VerificationAction()
|
||||||
object OtherUserScannedSuccessfully : VerificationAction()
|
object OtherUserScannedSuccessfully : VerificationAction()
|
||||||
@ -28,6 +29,7 @@ sealed class VerificationAction : VectorViewModelAction {
|
|||||||
object SASMatchAction : VerificationAction()
|
object SASMatchAction : VerificationAction()
|
||||||
object SASDoNotMatchAction : VerificationAction()
|
object SASDoNotMatchAction : VerificationAction()
|
||||||
data class GotItConclusion(val verified: Boolean) : VerificationAction()
|
data class GotItConclusion(val verified: Boolean) : VerificationAction()
|
||||||
|
object FailedToGetKeysFrom4S : VerificationAction()
|
||||||
object SkipVerification : VerificationAction()
|
object SkipVerification : VerificationAction()
|
||||||
object VerifyFromPassphrase : VerificationAction()
|
object VerifyFromPassphrase : VerificationAction()
|
||||||
object ReadyPendingVerification : VerificationAction()
|
object ReadyPendingVerification : VerificationAction()
|
||||||
|
@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.crypto.verification.self
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.commitTransaction
|
||||||
|
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||||
|
import im.vector.app.core.extensions.toMvRxBundle
|
||||||
|
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
|
||||||
|
import im.vector.app.databinding.BottomSheetVerificationBinding
|
||||||
|
import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
|
||||||
|
import im.vector.app.features.crypto.verification.VerificationAction
|
||||||
|
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewEvents
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
class SelfVerificationBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetVerificationBinding>() {
|
||||||
|
|
||||||
|
override val showExpanded = true
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Args(
|
||||||
|
// when verifying a new session from an existing safe one
|
||||||
|
val targetDevice: String? = null,
|
||||||
|
// when we started from an incoming request
|
||||||
|
val transactionId: String? = null,
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
private val viewModel by fragmentViewModel(SelfVerificationViewModel::class)
|
||||||
|
|
||||||
|
override fun getBinding(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?
|
||||||
|
) = BottomSheetVerificationBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
showFragment(SelfVerificationFragment::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val secretStartForActivityResult = registerStartForActivityResult { activityResult ->
|
||||||
|
if (activityResult.resultCode == Activity.RESULT_OK) {
|
||||||
|
val result = activityResult.data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)
|
||||||
|
val reset = activityResult.data?.getBooleanExtra(SharedSecureStorageActivity.EXTRA_DATA_RESET, false) ?: false
|
||||||
|
if (result != null) {
|
||||||
|
viewModel.handle(VerificationAction.GotResultFromSsss(result, SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS))
|
||||||
|
} else if (reset) {
|
||||||
|
// all have been reset, so we are verified?
|
||||||
|
viewModel.handle(VerificationAction.SecuredStorageHasBeenReset)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
viewModel.handle(VerificationAction.CancelledFromSsss)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
viewModel.observeViewEvents { event ->
|
||||||
|
when (event) {
|
||||||
|
VerificationBottomSheetViewEvents.AccessSecretStore -> {
|
||||||
|
secretStartForActivityResult.launch(
|
||||||
|
SharedSecureStorageActivity.newReadIntent(
|
||||||
|
requireContext(),
|
||||||
|
null, // use default key
|
||||||
|
listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME),
|
||||||
|
SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
VerificationBottomSheetViewEvents.Dismiss -> {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
VerificationBottomSheetViewEvents.GoToSettings -> {
|
||||||
|
// nop for user verificaiton
|
||||||
|
}
|
||||||
|
is VerificationBottomSheetViewEvents.ModalError -> {
|
||||||
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(getString(R.string.dialog_title_error))
|
||||||
|
.setMessage(event.errorMessage)
|
||||||
|
.setCancelable(false)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// viewModel.observeViewEvents {
|
||||||
|
// when (it) {
|
||||||
|
// is VerificationBottomSheetViewEvents.Dismiss -> dismiss()
|
||||||
|
// is VerificationBottomSheetViewEvents.AccessSecretStore -> {
|
||||||
|
// secretStartForActivityResult.launch(
|
||||||
|
// SharedSecureStorageActivity.newReadIntent(
|
||||||
|
// requireContext(),
|
||||||
|
// null, // use default key
|
||||||
|
// listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME),
|
||||||
|
// SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// is VerificationBottomSheetViewEvents.ModalError -> {
|
||||||
|
// MaterialAlertDialogBuilder(requireContext())
|
||||||
|
// .setTitle(getString(R.string.dialog_title_error))
|
||||||
|
// .setMessage(it.errorMessage)
|
||||||
|
// .setCancelable(false)
|
||||||
|
// .setPositiveButton(R.string.ok, null)
|
||||||
|
// .show()
|
||||||
|
// Unit
|
||||||
|
// }
|
||||||
|
// VerificationBottomSheetViewEvents.GoToSettings -> {
|
||||||
|
// dismiss()
|
||||||
|
// (activity as? VectorBaseActivity<*>)?.let { activity ->
|
||||||
|
// activity.navigator.openSettings(activity, VectorSettingsActivity.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
|
// avatarRenderer.render(state.otherUserMxItem, views.otherUserAvatarImageView)
|
||||||
|
views.otherUserShield.isVisible = false
|
||||||
|
if (state.isThisSessionVerified) {
|
||||||
|
views.otherUserAvatarImageView.setImageResource(
|
||||||
|
R.drawable.ic_shield_trusted
|
||||||
|
)
|
||||||
|
views.otherUserNameText.text = getString(R.string.verification_profile_verify)
|
||||||
|
} else {
|
||||||
|
views.otherUserAvatarImageView.setImageResource(
|
||||||
|
R.drawable.ic_shield_black
|
||||||
|
)
|
||||||
|
views.otherUserNameText.text = getString(R.string.crosssigning_verify_this_session)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showFragment(fragmentClass: KClass<out Fragment>, argsParcelable: Parcelable? = null) {
|
||||||
|
if (childFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) {
|
||||||
|
childFragmentManager.commitTransaction {
|
||||||
|
replace(
|
||||||
|
R.id.bottomSheetFragmentContainer,
|
||||||
|
fragmentClass.java,
|
||||||
|
argsParcelable?.toMvRxBundle(),
|
||||||
|
fragmentClass.simpleName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun verifyOwnUntrustedDevice(): SelfVerificationBottomSheet {
|
||||||
|
return SelfVerificationBottomSheet().apply {
|
||||||
|
setArguments(
|
||||||
|
Args()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forTransaction(transactionId: String): SelfVerificationBottomSheet {
|
||||||
|
return SelfVerificationBottomSheet().apply {
|
||||||
|
setArguments(
|
||||||
|
Args(transactionId = transactionId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,314 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.crypto.verification.self
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.bottomSheetDividerItem
|
||||||
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.features.crypto.verification.epoxy.bottomSheetSelfWaitItem
|
||||||
|
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
|
||||||
|
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
|
||||||
|
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem
|
||||||
|
import im.vector.app.features.crypto.verification.user.BaseEpoxyVerificationController
|
||||||
|
import im.vector.app.features.crypto.verification.user.VerificationTransactionData
|
||||||
|
import im.vector.app.features.crypto.verification.user.bottomDone
|
||||||
|
import im.vector.app.features.crypto.verification.user.gotIt
|
||||||
|
import im.vector.app.features.crypto.verification.user.renderAcceptDeclineRequest
|
||||||
|
import im.vector.app.features.crypto.verification.user.renderCancel
|
||||||
|
import im.vector.app.features.crypto.verification.user.renderSasTransaction
|
||||||
|
import im.vector.app.features.crypto.verification.user.renderStartTransactionOptions
|
||||||
|
import im.vector.app.features.crypto.verification.user.verifiedSuccessTile
|
||||||
|
import im.vector.app.features.html.EventHtmlRenderer
|
||||||
|
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SelfVerificationController @Inject constructor(
|
||||||
|
stringProvider: StringProvider,
|
||||||
|
colorProvider: ColorProvider,
|
||||||
|
eventHtmlRenderer: EventHtmlRenderer,
|
||||||
|
) : BaseEpoxyVerificationController(stringProvider, colorProvider, eventHtmlRenderer) {
|
||||||
|
|
||||||
|
interface InteractionListener : BaseEpoxyVerificationController.InteractionListener {
|
||||||
|
fun onClickRecoverFromPassphrase()
|
||||||
|
fun onClickSkip()
|
||||||
|
fun onClickResetSecurity()
|
||||||
|
fun onDoneFrom4S()
|
||||||
|
fun keysNotIn4S()
|
||||||
|
}
|
||||||
|
|
||||||
|
var state: SelfVerificationViewState? = null
|
||||||
|
|
||||||
|
fun update(state: SelfVerificationViewState) {
|
||||||
|
Timber.w("VALR controller updated $state")
|
||||||
|
this.state = state
|
||||||
|
requestModelBuild()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun buildModels() {
|
||||||
|
val state = this.state ?: return
|
||||||
|
when (state.pendingRequest) {
|
||||||
|
Uninitialized -> {
|
||||||
|
renderBaseNoActiveRequest(state)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
renderRequest(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderBaseNoActiveRequest(state: SelfVerificationViewState) {
|
||||||
|
if (state.verifyingFrom4SAction !is Uninitialized) {
|
||||||
|
render4SCheckState(state)
|
||||||
|
} else {
|
||||||
|
renderNoRequestStarted(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderRequest(state: SelfVerificationViewState) {
|
||||||
|
val host = this
|
||||||
|
when (state.pendingRequest) {
|
||||||
|
Uninitialized -> {
|
||||||
|
// let's add option to start one
|
||||||
|
val styledText = stringProvider.getString(R.string.verify_new_session_notice)
|
||||||
|
|
||||||
|
bottomSheetVerificationNoticeItem {
|
||||||
|
id("notice")
|
||||||
|
notice(styledText.toEpoxyCharSequence())
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetDividerItem {
|
||||||
|
id("sep")
|
||||||
|
}
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("start")
|
||||||
|
title(host.stringProvider.getString(R.string.start_verification))
|
||||||
|
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||||
|
// subTitle(host.stringProvider.getString(R.string.verification_request_start_notice))
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
listener { host.listener?.onClickOnVerificationStart() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Loading -> {
|
||||||
|
bottomSheetSelfWaitItem {
|
||||||
|
id("waiting")
|
||||||
|
}
|
||||||
|
// bottomSheetVerificationWaitingItem {
|
||||||
|
// id("waiting_pr_loading")
|
||||||
|
// // title(host.stringProvider.getString(R.string.verification_request_waiting_for, state.otherUserMxItem.getBestName()))
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
val pendingRequest = state.pendingRequest.invoke()
|
||||||
|
when (pendingRequest.state) {
|
||||||
|
EVerificationState.WaitingForReady -> {
|
||||||
|
bottomSheetSelfWaitItem {
|
||||||
|
id("waiting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EVerificationState.Requested -> {
|
||||||
|
// add accept buttons?
|
||||||
|
renderAcceptDeclineRequest()
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("not me")
|
||||||
|
title(host.stringProvider.getString(R.string.verify_new_session_was_not_me))
|
||||||
|
titleColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
listener {
|
||||||
|
TODO()
|
||||||
|
// host.listener?.wasNotMe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EVerificationState.Ready -> {
|
||||||
|
// add start options
|
||||||
|
renderStartTransactionOptions(pendingRequest, true)
|
||||||
|
}
|
||||||
|
EVerificationState.Started,
|
||||||
|
EVerificationState.WeStarted -> {
|
||||||
|
// nothing to do, in this case the active transaction is shown
|
||||||
|
renderActiveTransaction(state)
|
||||||
|
}
|
||||||
|
EVerificationState.WaitingForDone,
|
||||||
|
EVerificationState.Done -> {
|
||||||
|
verifiedSuccessTile()
|
||||||
|
bottomDone {
|
||||||
|
listener?.onDone(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EVerificationState.Cancelled -> {
|
||||||
|
renderCancel(pendingRequest.cancelConclusion ?: CancelCode.User)
|
||||||
|
}
|
||||||
|
EVerificationState.HandledByOtherSession -> {
|
||||||
|
// we should dismiss
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Fail -> {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderNoRequestStarted(state: SelfVerificationViewState) {
|
||||||
|
val host = this
|
||||||
|
bottomSheetVerificationNoticeItem {
|
||||||
|
id("notice")
|
||||||
|
notice(host.stringProvider.getString(R.string.verification_verify_identity).toEpoxyCharSequence())
|
||||||
|
}
|
||||||
|
bottomSheetDividerItem {
|
||||||
|
id("notice_div")
|
||||||
|
}
|
||||||
|
// Option to verify with another device
|
||||||
|
if (state.hasAnyOtherSession) {
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("start")
|
||||||
|
title(host.stringProvider.getString(R.string.verification_verify_with_another_device))
|
||||||
|
titleColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
// subTitle(host.stringProvider.getString(R.string.verification_request_start_notice))
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
listener { (host.listener as? InteractionListener)?.onClickOnVerificationStart() }
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetDividerItem {
|
||||||
|
id("start_div")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.quadSContainsSecrets) {
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("passphrase")
|
||||||
|
title(host.stringProvider.getString(R.string.verification_cannot_access_other_session))
|
||||||
|
titleColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
subTitle(host.stringProvider.getString(R.string.verification_use_passphrase))
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
listener { (host.listener as? InteractionListener)?.onClickRecoverFromPassphrase() }
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetDividerItem {
|
||||||
|
id("start_div")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// option to reset all
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("reset")
|
||||||
|
title(host.stringProvider.getString(R.string.bad_passphrase_key_reset_all_action))
|
||||||
|
titleColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
subTitle(host.stringProvider.getString(R.string.secure_backup_reset_all_no_other_devices))
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
listener { (host.listener as? InteractionListener)?.onClickResetSecurity() }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.isVerificationRequired) {
|
||||||
|
bottomSheetDividerItem {
|
||||||
|
id("reset_div")
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("skip")
|
||||||
|
title(host.stringProvider.getString(R.string.action_skip))
|
||||||
|
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorError))
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorError))
|
||||||
|
listener { (host.listener as? InteractionListener)?.onClickSkip() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun render4SCheckState(state: SelfVerificationViewState) {
|
||||||
|
val host = this
|
||||||
|
when (val action = state.verifyingFrom4SAction) {
|
||||||
|
is Fail -> {
|
||||||
|
}
|
||||||
|
is Loading -> {
|
||||||
|
bottomSheetVerificationWaitingItem {
|
||||||
|
id("waiting")
|
||||||
|
title(host.stringProvider.getString(R.string.verification_request_waiting_for_recovery))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
val invoke = action.invoke()
|
||||||
|
if (invoke) {
|
||||||
|
verifiedSuccessTile()
|
||||||
|
bottomDone { (host.listener as? InteractionListener)?.onDoneFrom4S() }
|
||||||
|
} else {
|
||||||
|
bottomSheetVerificationNoticeItem {
|
||||||
|
id("notice_4s_failed'")
|
||||||
|
notice(
|
||||||
|
host.stringProvider.getString(
|
||||||
|
R.string.error_failed_to_import_keys
|
||||||
|
)
|
||||||
|
.toEpoxyCharSequence()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
gotIt { (host.listener as? InteractionListener)?.keysNotIn4S() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// nop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderActiveTransaction(state: SelfVerificationViewState) {
|
||||||
|
val transaction = state.startedTransaction
|
||||||
|
val host = this
|
||||||
|
when (transaction) {
|
||||||
|
is Loading -> {
|
||||||
|
// Loading => We are starting a transaction
|
||||||
|
bottomSheetVerificationWaitingItem {
|
||||||
|
id("waiting")
|
||||||
|
title(host.stringProvider.getString(R.string.please_wait))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Success -> {
|
||||||
|
// Success => There is an active transaction
|
||||||
|
renderTransaction(state, transaction = transaction.invoke())
|
||||||
|
}
|
||||||
|
is Fail -> {
|
||||||
|
// todo
|
||||||
|
}
|
||||||
|
is Uninitialized -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderTransaction(state: SelfVerificationViewState, transaction: VerificationTransactionData) {
|
||||||
|
when (transaction) {
|
||||||
|
is VerificationTransactionData.QrTransactionData -> {
|
||||||
|
// TODO
|
||||||
|
// renderQrTransaction(transaction, state.otherUserMxItem)
|
||||||
|
}
|
||||||
|
is VerificationTransactionData.SasTransactionData -> {
|
||||||
|
renderSasTransaction(transaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.crypto.verification.self
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.airbnb.mvrx.parentFragmentViewModel
|
||||||
|
import com.airbnb.mvrx.withState
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.cleanup
|
||||||
|
import im.vector.app.core.extensions.configureWith
|
||||||
|
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||||
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
|
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||||
|
import im.vector.app.core.utils.checkPermissions
|
||||||
|
import im.vector.app.core.utils.onPermissionDeniedDialog
|
||||||
|
import im.vector.app.core.utils.registerForPermissionsResult
|
||||||
|
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
|
||||||
|
import im.vector.app.features.crypto.verification.VerificationAction
|
||||||
|
import im.vector.app.features.qrcode.QrCodeScannerActivity
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class SelfVerificationFragment : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(),
|
||||||
|
SelfVerificationController.InteractionListener {
|
||||||
|
|
||||||
|
@Inject lateinit var controller: SelfVerificationController
|
||||||
|
|
||||||
|
private val viewModel by parentFragmentViewModel(SelfVerificationViewModel::class)
|
||||||
|
|
||||||
|
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetVerificationChildFragmentBinding {
|
||||||
|
return BottomSheetVerificationChildFragmentBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
setupRecyclerView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
views.bottomSheetVerificationRecyclerView.cleanup()
|
||||||
|
controller.listener = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRecyclerView() {
|
||||||
|
views.bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true)
|
||||||
|
controller.listener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invalidate() = withState(viewModel) { state ->
|
||||||
|
Timber.w("VALR: invalidate with State: ${state.pendingRequest}")
|
||||||
|
controller.update(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClickRecoverFromPassphrase() {
|
||||||
|
viewModel.handle(VerificationAction.VerifyFromPassphrase)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClickSkip() {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClickResetSecurity() {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDoneFrom4S() {
|
||||||
|
viewModel.handle(VerificationAction.GotItConclusion(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun keysNotIn4S() {
|
||||||
|
viewModel.handle(VerificationAction.FailedToGetKeysFrom4S)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClickOnVerificationStart() {
|
||||||
|
viewModel.handle(VerificationAction.RequestSelfVerification)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDone(b: Boolean) {
|
||||||
|
viewModel.handle(VerificationAction.GotItConclusion(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDoNotMatchButtonTapped() {
|
||||||
|
viewModel.handle(VerificationAction.SASDoNotMatchAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMatchButtonTapped() {
|
||||||
|
viewModel.handle(VerificationAction.SASMatchAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
|
||||||
|
if (allGranted) {
|
||||||
|
doOpenQRCodeScanner()
|
||||||
|
} else if (deniedPermanently) {
|
||||||
|
activity?.onPermissionDeniedDialog(R.string.denied_permission_camera)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun openCamera() {
|
||||||
|
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
|
||||||
|
doOpenQRCodeScanner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doOpenQRCodeScanner() {
|
||||||
|
QrCodeScannerActivity.startForResult(requireActivity(), scanActivityResultLauncher)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val scanActivityResultLauncher = registerStartForActivityResult { activityResult ->
|
||||||
|
if (activityResult.resultCode == Activity.RESULT_OK) {
|
||||||
|
val scannedQrCode = QrCodeScannerActivity.getResultText(activityResult.data)
|
||||||
|
val wasQrCode = QrCodeScannerActivity.getResultIsQrCode(activityResult.data)
|
||||||
|
|
||||||
|
if (wasQrCode && !scannedQrCode.isNullOrBlank()) {
|
||||||
|
onRemoteQrCodeScanned(scannedQrCode)
|
||||||
|
} else {
|
||||||
|
Timber.w("It was not a QR code, or empty result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onRemoteQrCodeScanned(remoteQrCode: String) = withState(viewModel) { state ->
|
||||||
|
viewModel.handle(
|
||||||
|
VerificationAction.RemoteQrCodeScanned(
|
||||||
|
state.pendingRequest.invoke()?.otherUserId.orEmpty(),
|
||||||
|
state.pendingRequest.invoke()?.transactionId.orEmpty(),
|
||||||
|
remoteQrCode
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun doVerifyBySas() {
|
||||||
|
viewModel.handle(VerificationAction.StartSASVerification)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUserDeniesQrCodeScanned() {
|
||||||
|
viewModel.handle(VerificationAction.OtherUserDidNotScanned)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUserConfirmsQrCodeScanned() {
|
||||||
|
viewModel.handle(VerificationAction.OtherUserScannedSuccessfully)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun acceptRequest() {
|
||||||
|
viewModel.handle(VerificationAction.ReadyPendingVerification)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun declineRequest() {
|
||||||
|
viewModel.handle(VerificationAction.CancelPendingVerification)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,428 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.crypto.verification.self
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.MavericksState
|
||||||
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
|
||||||
|
import im.vector.app.features.crypto.verification.VerificationAction
|
||||||
|
import im.vector.app.features.crypto.verification.VerificationBottomSheetViewEvents
|
||||||
|
import im.vector.app.features.crypto.verification.user.VerificationTransactionData
|
||||||
|
import im.vector.app.features.crypto.verification.user.toDataClass
|
||||||
|
import im.vector.app.features.raw.wellknown.getElementWellknown
|
||||||
|
import im.vector.app.features.raw.wellknown.isSecureBackupRequired
|
||||||
|
import im.vector.app.features.session.coroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.matrix.android.sdk.api.Matrix
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupUtils
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.computeRecoveryKey
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.QrCodeVerificationTransaction
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.getRequest
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.getTransaction
|
||||||
|
import org.matrix.android.sdk.api.util.fromBase64
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
data class SelfVerificationViewState(
|
||||||
|
val pendingRequest: Async<PendingVerificationRequest> = Uninitialized,
|
||||||
|
// need something immutable for state to work properly, VerificationTransaction is not
|
||||||
|
val startedTransaction: Async<VerificationTransactionData> = Uninitialized,
|
||||||
|
val verifyingFrom4SAction: Async<Boolean> = Uninitialized,
|
||||||
|
val otherDeviceId: String? = null,
|
||||||
|
val transactionId: String? = null,
|
||||||
|
val currentDeviceCanCrossSign: Boolean = false,
|
||||||
|
val userWantsToCancel: Boolean = false,
|
||||||
|
val hasAnyOtherSession: Boolean = false,
|
||||||
|
val quadSContainsSecrets: Boolean = false,
|
||||||
|
val isVerificationRequired: Boolean = false,
|
||||||
|
val isThisSessionVerified: Boolean = false,
|
||||||
|
) : MavericksState {
|
||||||
|
|
||||||
|
constructor(args: SelfVerificationBottomSheet.Args) : this(
|
||||||
|
transactionId = args.transactionId,
|
||||||
|
otherDeviceId = args.targetDevice,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelfVerificationViewModel @AssistedInject constructor(
|
||||||
|
@Assisted private val initialState: SelfVerificationViewState,
|
||||||
|
private val session: Session,
|
||||||
|
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
|
||||||
|
private val rawService: RawService,
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
private val matrix: Matrix,
|
||||||
|
) :
|
||||||
|
VectorViewModel<SelfVerificationViewState, VerificationAction, VerificationBottomSheetViewEvents>(initialState) {
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory : MavericksAssistedViewModelFactory<SelfVerificationViewModel, SelfVerificationViewState> {
|
||||||
|
override fun create(initialState: SelfVerificationViewState): SelfVerificationViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : MavericksViewModelFactory<SelfVerificationViewModel, SelfVerificationViewState> by hiltMavericksViewModelFactory()
|
||||||
|
|
||||||
|
init {
|
||||||
|
|
||||||
|
if (initialState.transactionId != null) {
|
||||||
|
setState {
|
||||||
|
copy(pendingRequest = Loading())
|
||||||
|
}
|
||||||
|
viewModelScope.launch {
|
||||||
|
session.cryptoService().verificationService().getExistingVerificationRequest(session.myUserId, initialState.transactionId)?.let {
|
||||||
|
setState {
|
||||||
|
copy(pendingRequest = Success(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
observeRequestsAndTransactions()
|
||||||
|
// This is async, but at this point should be in cache
|
||||||
|
// so it's ok to not wait until result
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val wellKnown = rawService.getElementWellknown(session.sessionParams)
|
||||||
|
setState {
|
||||||
|
copy(isVerificationRequired = wellKnown?.isSecureBackupRequired().orFalse())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val hasAnyOtherSession = session.cryptoService()
|
||||||
|
.getCryptoDeviceInfo(session.myUserId)
|
||||||
|
.any {
|
||||||
|
it.deviceId != session.sessionParams.deviceId
|
||||||
|
}
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign(),
|
||||||
|
quadSContainsSecrets = session.sharedSecretStorageService().isRecoverySetup(),
|
||||||
|
hasAnyOtherSession = hasAnyOtherSession
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
val isThisSessionVerified = session.cryptoService().crossSigningService().isCrossSigningVerified()
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
isThisSessionVerified = isThisSessionVerified,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeRequestsAndTransactions() {
|
||||||
|
session.cryptoService().verificationService()
|
||||||
|
.requestEventFlow()
|
||||||
|
.filter {
|
||||||
|
it.otherUserId == session.myUserId
|
||||||
|
}
|
||||||
|
.onEach {
|
||||||
|
it.getRequest()?.let {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
pendingRequest = Success(it),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.getTransaction()?.let {
|
||||||
|
val dClass = it.toDataClass()
|
||||||
|
if (dClass != null) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
startedTransaction = Success(dClass),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
startedTransaction = Fail(IllegalArgumentException("Unsupported Transaction")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handle(action: VerificationAction) {
|
||||||
|
when (action) {
|
||||||
|
VerificationAction.CancelPendingVerification -> {
|
||||||
|
withState { state ->
|
||||||
|
state.pendingRequest.invoke()?.let {
|
||||||
|
viewModelScope.launch {
|
||||||
|
session.cryptoService().verificationService()
|
||||||
|
.cancelVerificationRequest(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VerificationAction.CancelledFromSsss -> {
|
||||||
|
setState {
|
||||||
|
copy(verifyingFrom4SAction = Uninitialized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is VerificationAction.GotItConclusion -> {
|
||||||
|
// just dismiss
|
||||||
|
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
|
||||||
|
}
|
||||||
|
is VerificationAction.GotResultFromSsss -> handleSecretBackFromSSSS(action)
|
||||||
|
VerificationAction.OtherUserDidNotScanned -> {
|
||||||
|
withState { state ->
|
||||||
|
state.startedTransaction.invoke()?.let {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val tx = session.cryptoService().verificationService()
|
||||||
|
.getExistingTransaction(it.otherUserId, it.transactionId)
|
||||||
|
as? QrCodeVerificationTransaction
|
||||||
|
tx?.otherUserDidNotScannedMyQrCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VerificationAction.OtherUserScannedSuccessfully -> {
|
||||||
|
withState { state ->
|
||||||
|
state.startedTransaction.invoke()?.let {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val tx = session.cryptoService().verificationService()
|
||||||
|
.getExistingTransaction(it.otherUserId, it.transactionId)
|
||||||
|
as? QrCodeVerificationTransaction
|
||||||
|
tx?.otherUserScannedMyQrCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VerificationAction.ReadyPendingVerification -> {
|
||||||
|
withState { state ->
|
||||||
|
state.pendingRequest.invoke()?.let {
|
||||||
|
viewModelScope.launch {
|
||||||
|
session.cryptoService().verificationService()
|
||||||
|
.readyPendingVerification(
|
||||||
|
supportedVerificationMethodsProvider.provide(),
|
||||||
|
it.otherUserId, it.transactionId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is VerificationAction.RemoteQrCodeScanned -> {
|
||||||
|
setState {
|
||||||
|
copy(startedTransaction = Loading())
|
||||||
|
}
|
||||||
|
withState { state ->
|
||||||
|
val request = state.pendingRequest.invoke() ?: return@withState
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
session.cryptoService().verificationService()
|
||||||
|
.reciprocateQRVerification(
|
||||||
|
request.otherUserId,
|
||||||
|
request.transactionId,
|
||||||
|
action.scannedData
|
||||||
|
)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.w(failure, "Failed to reciprocated")
|
||||||
|
setState {
|
||||||
|
copy(startedTransaction = Fail(failure))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is VerificationAction.SASDoNotMatchAction -> {
|
||||||
|
withState { state ->
|
||||||
|
viewModelScope.launch {
|
||||||
|
val transaction = session.cryptoService().verificationService()
|
||||||
|
.getExistingTransaction(session.myUserId, state.transactionId.orEmpty())
|
||||||
|
(transaction as? SasVerificationTransaction)?.shortCodeDoesNotMatch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is VerificationAction.SASMatchAction -> {
|
||||||
|
withState { state ->
|
||||||
|
viewModelScope.launch {
|
||||||
|
val transaction = session.cryptoService().verificationService()
|
||||||
|
.getExistingTransaction(session.myUserId, state.transactionId.orEmpty())
|
||||||
|
(transaction as? SasVerificationTransaction)?.userHasVerifiedShortCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VerificationAction.SecuredStorageHasBeenReset -> TODO()
|
||||||
|
VerificationAction.SkipVerification -> TODO()
|
||||||
|
VerificationAction.StartSASVerification -> {
|
||||||
|
withState { state ->
|
||||||
|
val request = state.pendingRequest.invoke() ?: return@withState
|
||||||
|
viewModelScope.launch {
|
||||||
|
session.cryptoService().verificationService()
|
||||||
|
.startKeyVerification(VerificationMethod.SAS, session.myUserId, request.transactionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VerificationAction.VerifyFromPassphrase -> {
|
||||||
|
setState { copy(verifyingFrom4SAction = Loading()) }
|
||||||
|
_viewEvents.post(VerificationBottomSheetViewEvents.AccessSecretStore)
|
||||||
|
}
|
||||||
|
VerificationAction.FailedToGetKeysFrom4S -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
verifyingFrom4SAction = Uninitialized,
|
||||||
|
quadSContainsSecrets = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VerificationAction.RequestSelfVerification -> {
|
||||||
|
handleRequestVerification()
|
||||||
|
}
|
||||||
|
VerificationAction.RequestVerificationByDM -> {
|
||||||
|
// not applicable in self verification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleRequestVerification() {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
pendingRequest = Loading()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var targetDevice: String? = null
|
||||||
|
withState { state ->
|
||||||
|
targetDevice = state.otherDeviceId
|
||||||
|
}
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val request = session.cryptoService().verificationService().requestDeviceVerification(
|
||||||
|
supportedVerificationMethodsProvider.provide(),
|
||||||
|
session.myUserId,
|
||||||
|
targetDevice
|
||||||
|
)
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
pendingRequest = Success(request),
|
||||||
|
transactionId = request.transactionId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
pendingRequest = Loading(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
action.cypherData.fromBase64().inputStream().use { ins ->
|
||||||
|
val res = matrix.secureStorageService().loadSecureSecret<Map<String, String>>(ins, action.alias)
|
||||||
|
val trustResult = session.cryptoService().crossSigningService().checkTrustFromPrivateKeys(
|
||||||
|
res?.get(MASTER_KEY_SSSS_NAME),
|
||||||
|
res?.get(USER_SIGNING_KEY_SSSS_NAME),
|
||||||
|
res?.get(SELF_SIGNING_KEY_SSSS_NAME)
|
||||||
|
)
|
||||||
|
if (trustResult.isVerified()) {
|
||||||
|
// Sign this device and upload the signature
|
||||||
|
try {
|
||||||
|
session.cryptoService().crossSigningService().trustDevice(session.sessionParams.deviceId)
|
||||||
|
} catch (failure: Exception) {
|
||||||
|
Timber.w(failure, "Failed to sign my device after recovery")
|
||||||
|
}
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
verifyingFrom4SAction = Success(true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
tentativeRestoreBackup(res)
|
||||||
|
} else {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
verifyingFrom4SAction = Success(false),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
verifyingFrom4SAction = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_viewEvents.post(
|
||||||
|
VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage ?: stringProvider.getString(R.string.unexpected_error))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tentativeRestoreBackup(res: Map<String, String>?) {
|
||||||
|
// on session scope because will happen after viewmodel is cleared
|
||||||
|
session.coroutineScope.launch {
|
||||||
|
// It's not a good idea to download the full backup, it might take very long
|
||||||
|
// and use a lot of resources
|
||||||
|
// Just check that the key is valid and store it, the backup will be used megolm session per
|
||||||
|
// megolm session when an UISI is encountered
|
||||||
|
try {
|
||||||
|
val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also {
|
||||||
|
Timber.v("## Keybackup secret not restored from SSSS")
|
||||||
|
}
|
||||||
|
|
||||||
|
val version = session.cryptoService().keysBackupService().getCurrentVersion()?.toKeysVersionResult() ?: return@launch
|
||||||
|
|
||||||
|
val recoveryKey = computeRecoveryKey(secret.fromBase64())
|
||||||
|
val backupRecoveryKey = BackupUtils.recoveryKeyFromBase58(recoveryKey)
|
||||||
|
val isValid = backupRecoveryKey
|
||||||
|
?.let { session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(it) }
|
||||||
|
?: false
|
||||||
|
if (isValid) {
|
||||||
|
session.cryptoService().keysBackupService().saveBackupRecoveryKey(backupRecoveryKey, version.version)
|
||||||
|
// session.cryptoService().keysBackupService().trustKeysBackupVersion(version, true)
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
// Just ignore for now
|
||||||
|
Timber.e(failure, "## Failed to restore backup after SSSS recovery")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,13 +26,10 @@ import im.vector.app.R
|
|||||||
import im.vector.app.core.epoxy.bottomSheetDividerItem
|
import im.vector.app.core.epoxy.bottomSheetDividerItem
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.core.ui.list.buttonPositiveDestructiveButtonBarItem
|
|
||||||
import im.vector.app.core.utils.colorizeMatchingText
|
import im.vector.app.core.utils.colorizeMatchingText
|
||||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
|
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
|
||||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem
|
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem
|
||||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationEmojisItem
|
|
||||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
|
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
|
||||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationQrCodeItem
|
|
||||||
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem
|
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem
|
||||||
import im.vector.app.features.displayname.getBestName
|
import im.vector.app.features.displayname.getBestName
|
||||||
import im.vector.app.features.html.EventHtmlRenderer
|
import im.vector.app.features.html.EventHtmlRenderer
|
||||||
@ -40,23 +37,18 @@ import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
|||||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
|
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentation
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.QRCodeVerificationState
|
import org.matrix.android.sdk.api.session.crypto.verification.QRCodeVerificationState
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.SasTransactionState
|
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class UserVerificationController @Inject constructor(
|
abstract class BaseEpoxyVerificationController(
|
||||||
private val stringProvider: StringProvider,
|
val stringProvider: StringProvider,
|
||||||
private val colorProvider: ColorProvider,
|
val colorProvider: ColorProvider,
|
||||||
private val eventHtmlRenderer: EventHtmlRenderer,
|
val eventHtmlRenderer: EventHtmlRenderer,
|
||||||
) : EpoxyController() {
|
) : EpoxyController() {
|
||||||
|
|
||||||
interface InteractionListener {
|
interface InteractionListener {
|
||||||
fun acceptRequest()
|
|
||||||
fun declineRequest()
|
|
||||||
fun onClickOnVerificationStart()
|
fun onClickOnVerificationStart()
|
||||||
fun onDone(b: Boolean)
|
fun onDone(b: Boolean)
|
||||||
fun onDoNotMatchButtonTapped()
|
fun onDoNotMatchButtonTapped()
|
||||||
@ -65,9 +57,23 @@ class UserVerificationController @Inject constructor(
|
|||||||
fun doVerifyBySas()
|
fun doVerifyBySas()
|
||||||
fun onUserDeniesQrCodeScanned()
|
fun onUserDeniesQrCodeScanned()
|
||||||
fun onUserConfirmsQrCodeScanned()
|
fun onUserConfirmsQrCodeScanned()
|
||||||
|
fun acceptRequest()
|
||||||
|
fun declineRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
var listener: InteractionListener? = null
|
var listener: InteractionListener? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserVerificationController @Inject constructor(
|
||||||
|
stringProvider: StringProvider,
|
||||||
|
colorProvider: ColorProvider,
|
||||||
|
eventHtmlRenderer: EventHtmlRenderer,
|
||||||
|
) : BaseEpoxyVerificationController(stringProvider, colorProvider, eventHtmlRenderer) {
|
||||||
|
|
||||||
|
// interface InteractionListener: BaseEpoxyVerificationController.InteractionListener {
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var listener: InteractionListener? = null
|
||||||
|
|
||||||
var state: UserVerificationViewState? = null
|
var state: UserVerificationViewState? = null
|
||||||
|
|
||||||
@ -126,17 +132,11 @@ class UserVerificationController @Inject constructor(
|
|||||||
}
|
}
|
||||||
EVerificationState.Requested -> {
|
EVerificationState.Requested -> {
|
||||||
// add accept buttons?
|
// add accept buttons?
|
||||||
buttonPositiveDestructiveButtonBarItem {
|
renderAcceptDeclineRequest()
|
||||||
id("accept_decline")
|
|
||||||
positiveText(host.stringProvider.getString(R.string.action_accept).toEpoxyCharSequence())
|
|
||||||
destructiveText(host.stringProvider.getString(R.string.action_decline).toEpoxyCharSequence())
|
|
||||||
positiveButtonClickAction { host.listener?.acceptRequest() }
|
|
||||||
destructiveButtonClickAction { host.listener?.declineRequest() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
EVerificationState.Ready -> {
|
EVerificationState.Ready -> {
|
||||||
// add start options
|
// add start options
|
||||||
renderStartTransactionOptions(state, pendingRequest)
|
renderStartTransactionOptions(pendingRequest, false)
|
||||||
}
|
}
|
||||||
EVerificationState.Started,
|
EVerificationState.Started,
|
||||||
EVerificationState.WeStarted -> {
|
EVerificationState.WeStarted -> {
|
||||||
@ -145,21 +145,7 @@ class UserVerificationController @Inject constructor(
|
|||||||
}
|
}
|
||||||
EVerificationState.WaitingForDone,
|
EVerificationState.WaitingForDone,
|
||||||
EVerificationState.Done -> {
|
EVerificationState.Done -> {
|
||||||
bottomSheetVerificationNoticeItem {
|
verifiedSuccessTile()
|
||||||
id("notice")
|
|
||||||
notice(
|
|
||||||
host.stringProvider.getString(
|
|
||||||
R.string.verification_conclusion_ok_notice
|
|
||||||
)
|
|
||||||
.toEpoxyCharSequence()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
bottomSheetVerificationBigImageItem {
|
|
||||||
id("image")
|
|
||||||
roomEncryptionTrustLevel(RoomEncryptionTrustLevel.Trusted)
|
|
||||||
}
|
|
||||||
|
|
||||||
bottomDone()
|
bottomDone()
|
||||||
}
|
}
|
||||||
EVerificationState.Cancelled -> {
|
EVerificationState.Cancelled -> {
|
||||||
@ -167,6 +153,7 @@ class UserVerificationController @Inject constructor(
|
|||||||
}
|
}
|
||||||
EVerificationState.HandledByOtherSession -> {
|
EVerificationState.HandledByOtherSession -> {
|
||||||
// we should dismiss
|
// we should dismiss
|
||||||
|
bottomDone { listener?.onDone(false) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -176,64 +163,64 @@ class UserVerificationController @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderStartTransactionOptions(state: UserVerificationViewState, request: PendingVerificationRequest) {
|
// private fun renderStartTransactionOptions(request: PendingVerificationRequest) {
|
||||||
val scanCodeInstructions = stringProvider.getString(R.string.verification_scan_notice)
|
// val scanCodeInstructions = stringProvider.getString(R.string.verification_scan_notice)
|
||||||
val host = this
|
// val host = this
|
||||||
val scanOtherCodeTitle = stringProvider.getString(R.string.verification_scan_their_code)
|
// val scanOtherCodeTitle = stringProvider.getString(R.string.verification_scan_their_code)
|
||||||
val compareEmojiSubtitle = stringProvider.getString(R.string.verification_scan_emoji_subtitle)
|
// val compareEmojiSubtitle = stringProvider.getString(R.string.verification_scan_emoji_subtitle)
|
||||||
|
//
|
||||||
bottomSheetVerificationNoticeItem {
|
// bottomSheetVerificationNoticeItem {
|
||||||
id("notice")
|
// id("notice")
|
||||||
notice(scanCodeInstructions.toEpoxyCharSequence())
|
// notice(scanCodeInstructions.toEpoxyCharSequence())
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
if (request.weShouldDisplayQRCode && !request.qrCodeText.isNullOrEmpty()) {
|
// if (request.weShouldDisplayQRCode && !request.qrCodeText.isNullOrEmpty()) {
|
||||||
bottomSheetVerificationQrCodeItem {
|
// bottomSheetVerificationQrCodeItem {
|
||||||
id("qr")
|
// id("qr")
|
||||||
data(request.qrCodeText!!)
|
// data(request.qrCodeText!!)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
bottomSheetDividerItem {
|
// bottomSheetDividerItem {
|
||||||
id("sep0")
|
// id("sep0")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
if (request.weShouldShowScanOption) {
|
// if (request.weShouldShowScanOption) {
|
||||||
bottomSheetVerificationActionItem {
|
// bottomSheetVerificationActionItem {
|
||||||
id("openCamera")
|
// id("openCamera")
|
||||||
title(scanOtherCodeTitle)
|
// title(scanOtherCodeTitle)
|
||||||
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
// titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||||
iconRes(R.drawable.ic_camera)
|
// iconRes(R.drawable.ic_camera)
|
||||||
iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
// iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||||
listener { host.listener?.openCamera() }
|
// listener { host.listener?.openCamera() }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
bottomSheetDividerItem {
|
// bottomSheetDividerItem {
|
||||||
id("sep1")
|
// id("sep1")
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
bottomSheetVerificationActionItem {
|
// bottomSheetVerificationActionItem {
|
||||||
id("openEmoji")
|
// id("openEmoji")
|
||||||
title(host.stringProvider.getString(R.string.verification_scan_emoji_title))
|
// title(host.stringProvider.getString(R.string.verification_scan_emoji_title))
|
||||||
titleColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
// titleColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
subTitle(compareEmojiSubtitle)
|
// subTitle(compareEmojiSubtitle)
|
||||||
iconRes(R.drawable.ic_arrow_right)
|
// iconRes(R.drawable.ic_arrow_right)
|
||||||
iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
// iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
listener { host.listener?.doVerifyBySas() }
|
// listener { host.listener?.doVerifyBySas() }
|
||||||
}
|
// }
|
||||||
} else if (request.isSasSupported) {
|
// } else if (request.isSasSupported) {
|
||||||
bottomSheetVerificationActionItem {
|
// bottomSheetVerificationActionItem {
|
||||||
id("openEmoji")
|
// id("openEmoji")
|
||||||
title(host.stringProvider.getString(R.string.verification_no_scan_emoji_title))
|
// title(host.stringProvider.getString(R.string.verification_no_scan_emoji_title))
|
||||||
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
// titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||||
iconRes(R.drawable.ic_arrow_right)
|
// iconRes(R.drawable.ic_arrow_right)
|
||||||
iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
// iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
listener { host.listener?.doVerifyBySas() }
|
// listener { host.listener?.doVerifyBySas() }
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
// ??? can this happen
|
// // ??? can this happen
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
private fun renderActiveTransaction(state: UserVerificationViewState) {
|
private fun renderActiveTransaction(state: UserVerificationViewState) {
|
||||||
val transaction = state.startedTransaction
|
val transaction = state.startedTransaction
|
||||||
@ -343,118 +330,6 @@ class UserVerificationController @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderSasTransaction(transaction: VerificationTransactionData.SasTransactionData) {
|
|
||||||
val host = this
|
|
||||||
when (val txState = transaction.state) {
|
|
||||||
SasTransactionState.SasShortCodeReady -> {
|
|
||||||
buildEmojiItem(transaction.emojiCodeRepresentation.orEmpty())
|
|
||||||
}
|
|
||||||
is SasTransactionState.SasMacReceived -> {
|
|
||||||
if (!txState.codeConfirmed) {
|
|
||||||
buildEmojiItem(transaction.emojiCodeRepresentation.orEmpty())
|
|
||||||
} else {
|
|
||||||
// waiting
|
|
||||||
bottomSheetVerificationWaitingItem {
|
|
||||||
id("waiting")
|
|
||||||
title(host.stringProvider.getString(R.string.please_wait))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is SasTransactionState.Cancelled,
|
|
||||||
is SasTransactionState.Done -> {
|
|
||||||
// should show request status
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// waiting
|
|
||||||
bottomSheetVerificationWaitingItem {
|
|
||||||
id("waiting")
|
|
||||||
title(host.stringProvider.getString(R.string.please_wait))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderCancel(cancelCode: CancelCode) {
|
|
||||||
val host = this
|
|
||||||
when (cancelCode) {
|
|
||||||
CancelCode.QrCodeInvalid -> {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
CancelCode.MismatchedUser,
|
|
||||||
CancelCode.MismatchedSas,
|
|
||||||
CancelCode.MismatchedCommitment,
|
|
||||||
CancelCode.MismatchedKeys -> {
|
|
||||||
bottomSheetVerificationNoticeItem {
|
|
||||||
id("notice")
|
|
||||||
notice(host.stringProvider.getString(R.string.verification_conclusion_not_secure).toEpoxyCharSequence())
|
|
||||||
}
|
|
||||||
|
|
||||||
bottomSheetVerificationBigImageItem {
|
|
||||||
id("image")
|
|
||||||
roomEncryptionTrustLevel(RoomEncryptionTrustLevel.Warning)
|
|
||||||
}
|
|
||||||
|
|
||||||
bottomSheetVerificationNoticeItem {
|
|
||||||
id("warning_notice")
|
|
||||||
notice(host.eventHtmlRenderer.render(host.stringProvider.getString(R.string.verification_conclusion_compromised)).toEpoxyCharSequence())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
bottomSheetVerificationNoticeItem {
|
|
||||||
id("notice_cancelled")
|
|
||||||
notice(host.stringProvider.getString(R.string.verify_cancelled_notice).toEpoxyCharSequence())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildEmojiItem(emoji: List<EmojiRepresentation>) {
|
|
||||||
val host = this
|
|
||||||
bottomSheetVerificationNoticeItem {
|
|
||||||
id("notice")
|
|
||||||
notice(host.stringProvider.getString(R.string.verification_emoji_notice).toEpoxyCharSequence())
|
|
||||||
}
|
|
||||||
|
|
||||||
bottomSheetVerificationEmojisItem {
|
|
||||||
id("emojis")
|
|
||||||
emojiRepresentation0(emoji[0])
|
|
||||||
emojiRepresentation1(emoji[1])
|
|
||||||
emojiRepresentation2(emoji[2])
|
|
||||||
emojiRepresentation3(emoji[3])
|
|
||||||
emojiRepresentation4(emoji[4])
|
|
||||||
emojiRepresentation5(emoji[5])
|
|
||||||
emojiRepresentation6(emoji[6])
|
|
||||||
}
|
|
||||||
|
|
||||||
buildSasCodeActions()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildSasCodeActions() {
|
|
||||||
val host = this
|
|
||||||
bottomSheetDividerItem {
|
|
||||||
id("sepsas0")
|
|
||||||
}
|
|
||||||
bottomSheetVerificationActionItem {
|
|
||||||
id("ko")
|
|
||||||
title(host.stringProvider.getString(R.string.verification_sas_do_not_match))
|
|
||||||
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorError))
|
|
||||||
iconRes(R.drawable.ic_check_off)
|
|
||||||
iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorError))
|
|
||||||
listener { host.listener?.onDoNotMatchButtonTapped() }
|
|
||||||
}
|
|
||||||
bottomSheetDividerItem {
|
|
||||||
id("sepsas1")
|
|
||||||
}
|
|
||||||
bottomSheetVerificationActionItem {
|
|
||||||
id("ok")
|
|
||||||
title(host.stringProvider.getString(R.string.verification_sas_match))
|
|
||||||
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
|
||||||
iconRes(R.drawable.ic_check_on)
|
|
||||||
iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
|
||||||
listener { host.listener?.onMatchButtonTapped() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bottomDone() {
|
private fun bottomDone() {
|
||||||
val host = this
|
val host = this
|
||||||
bottomSheetDividerItem {
|
bottomSheetDividerItem {
|
||||||
|
@ -41,7 +41,7 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class UserVerificationFragment : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(),
|
class UserVerificationFragment : VectorBaseFragment<BottomSheetVerificationChildFragmentBinding>(),
|
||||||
UserVerificationController.InteractionListener {
|
BaseEpoxyVerificationController.InteractionListener {
|
||||||
|
|
||||||
@Inject lateinit var controller: UserVerificationController
|
@Inject lateinit var controller: UserVerificationController
|
||||||
|
|
||||||
|
@ -59,8 +59,8 @@ import timber.log.Timber
|
|||||||
|
|
||||||
data class UserVerificationViewState(
|
data class UserVerificationViewState(
|
||||||
val pendingRequest: Async<PendingVerificationRequest> = Uninitialized,
|
val pendingRequest: Async<PendingVerificationRequest> = Uninitialized,
|
||||||
val startedTransaction: Async<VerificationTransactionData> = Uninitialized,
|
|
||||||
// need something immutable for state to work properly, VerificationTransaction is not
|
// need something immutable for state to work properly, VerificationTransaction is not
|
||||||
|
val startedTransaction: Async<VerificationTransactionData> = Uninitialized,
|
||||||
val otherUserMxItem: MatrixItem,
|
val otherUserMxItem: MatrixItem,
|
||||||
val otherUserId: String,
|
val otherUserId: String,
|
||||||
val otherDeviceId: String? = null,
|
val otherDeviceId: String? = null,
|
||||||
@ -103,7 +103,7 @@ sealed class VerificationTransactionData(
|
|||||||
) : VerificationTransactionData(transactionId, otherUserId)
|
) : VerificationTransactionData(transactionId, otherUserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun VerificationTransaction.toDataClass(): VerificationTransactionData? {
|
fun VerificationTransaction.toDataClass(): VerificationTransactionData? {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is SasVerificationTransaction -> {
|
is SasVerificationTransaction -> {
|
||||||
VerificationTransactionData.SasTransactionData(
|
VerificationTransactionData.SasTransactionData(
|
||||||
@ -255,7 +255,6 @@ class UserVerificationViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VerificationAction.CancelledFromSsss -> TODO()
|
|
||||||
is VerificationAction.GotItConclusion -> {
|
is VerificationAction.GotItConclusion -> {
|
||||||
// just dismiss
|
// just dismiss
|
||||||
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
|
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
|
||||||
@ -369,7 +368,6 @@ class UserVerificationViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VerificationAction.SkipVerification -> TODO()
|
|
||||||
is VerificationAction.StartSASVerification -> {
|
is VerificationAction.StartSASVerification -> {
|
||||||
withState { state ->
|
withState { state ->
|
||||||
val request = state.pendingRequest.invoke() ?: return@withState
|
val request = state.pendingRequest.invoke() ?: return@withState
|
||||||
@ -379,8 +377,14 @@ class UserVerificationViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VerificationAction.VerifyFromPassphrase -> TODO()
|
VerificationAction.CancelledFromSsss,
|
||||||
VerificationAction.SecuredStorageHasBeenReset -> TODO()
|
VerificationAction.SkipVerification,
|
||||||
|
VerificationAction.VerifyFromPassphrase,
|
||||||
|
VerificationAction.SecuredStorageHasBeenReset,
|
||||||
|
VerificationAction.FailedToGetKeysFrom4S -> {
|
||||||
|
// Not applicable for user verification
|
||||||
|
}
|
||||||
|
VerificationAction.RequestSelfVerification -> TODO()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,290 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.crypto.verification.user
|
||||||
|
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.epoxy.ClickListener
|
||||||
|
import im.vector.app.core.epoxy.bottomSheetDividerItem
|
||||||
|
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
|
||||||
|
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationBigImageItem
|
||||||
|
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationEmojisItem
|
||||||
|
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
|
||||||
|
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationQrCodeItem
|
||||||
|
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationWaitingItem
|
||||||
|
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentation
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.SasTransactionState
|
||||||
|
|
||||||
|
fun BaseEpoxyVerificationController.verifiedSuccessTile() {
|
||||||
|
val host = this
|
||||||
|
bottomSheetVerificationNoticeItem {
|
||||||
|
id("notice_done")
|
||||||
|
notice(
|
||||||
|
host.stringProvider.getString(
|
||||||
|
R.string.verification_conclusion_ok_notice
|
||||||
|
)
|
||||||
|
.toEpoxyCharSequence()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
bottomSheetVerificationBigImageItem {
|
||||||
|
id("image")
|
||||||
|
roomEncryptionTrustLevel(RoomEncryptionTrustLevel.Trusted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseEpoxyVerificationController.bottomDone(listener: ClickListener) {
|
||||||
|
val host = this
|
||||||
|
bottomSheetDividerItem {
|
||||||
|
id("sep_done")
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("done")
|
||||||
|
title(host.stringProvider.getString(R.string.done))
|
||||||
|
titleColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
// listener { host.listener?.onDone(true) }
|
||||||
|
listener(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseEpoxyVerificationController.gotIt(listener: ClickListener) {
|
||||||
|
val host = this
|
||||||
|
bottomSheetDividerItem {
|
||||||
|
id("sep_gotit")
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("gotit")
|
||||||
|
title(host.stringProvider.getString(R.string.action_got_it))
|
||||||
|
titleColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
listener(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseEpoxyVerificationController.renderStartTransactionOptions(request: PendingVerificationRequest, isMe: Boolean) {
|
||||||
|
val scanCodeInstructions: String
|
||||||
|
val scanOtherCodeTitle: String
|
||||||
|
val compareEmojiSubtitle: String
|
||||||
|
if (isMe) {
|
||||||
|
scanCodeInstructions = stringProvider.getString(R.string.verification_scan_self_notice)
|
||||||
|
scanOtherCodeTitle = stringProvider.getString(R.string.verification_scan_with_this_device)
|
||||||
|
compareEmojiSubtitle = stringProvider.getString(R.string.verification_scan_self_emoji_subtitle)
|
||||||
|
} else {
|
||||||
|
scanCodeInstructions = stringProvider.getString(R.string.verification_scan_notice)
|
||||||
|
scanOtherCodeTitle = stringProvider.getString(R.string.verification_scan_their_code)
|
||||||
|
compareEmojiSubtitle = stringProvider.getString(R.string.verification_scan_emoji_subtitle)
|
||||||
|
}
|
||||||
|
val host = this
|
||||||
|
|
||||||
|
bottomSheetVerificationNoticeItem {
|
||||||
|
id("notice")
|
||||||
|
notice(scanCodeInstructions.toEpoxyCharSequence())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.weShouldDisplayQRCode && !request.qrCodeText.isNullOrEmpty()) {
|
||||||
|
bottomSheetVerificationQrCodeItem {
|
||||||
|
id("qr")
|
||||||
|
data(request.qrCodeText!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetDividerItem {
|
||||||
|
id("sep0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.weShouldShowScanOption) {
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("openCamera")
|
||||||
|
title(scanOtherCodeTitle)
|
||||||
|
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||||
|
iconRes(R.drawable.ic_camera)
|
||||||
|
iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||||
|
listener { host.listener?.openCamera() }
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetDividerItem {
|
||||||
|
id("sep1")
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("openEmoji")
|
||||||
|
title(host.stringProvider.getString(R.string.verification_scan_emoji_title))
|
||||||
|
titleColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
subTitle(compareEmojiSubtitle)
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
listener { host.listener?.doVerifyBySas() }
|
||||||
|
}
|
||||||
|
} else if (request.isSasSupported) {
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("openEmoji")
|
||||||
|
title(host.stringProvider.getString(R.string.verification_no_scan_emoji_title))
|
||||||
|
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
|
||||||
|
listener { host.listener?.doVerifyBySas() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ??? can this happen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseEpoxyVerificationController.renderAcceptDeclineRequest() {
|
||||||
|
val host = this
|
||||||
|
bottomSheetDividerItem {
|
||||||
|
id("sep_accept_Decline")
|
||||||
|
}
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("accept_pr")
|
||||||
|
title(host.stringProvider.getString(R.string.action_accept))
|
||||||
|
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||||
|
// subTitle(host.stringProvider.getString(R.string.verification_request_start_notice))
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||||
|
listener { host.listener?.acceptRequest() }
|
||||||
|
}
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("decline_pr")
|
||||||
|
title(host.stringProvider.getString(R.string.action_decline))
|
||||||
|
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorError))
|
||||||
|
// subTitle(host.stringProvider.getString(R.string.verification_request_start_notice))
|
||||||
|
iconRes(R.drawable.ic_arrow_right)
|
||||||
|
iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorError))
|
||||||
|
listener { host.listener?.declineRequest() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseEpoxyVerificationController.renderCancel(cancelCode: CancelCode) {
|
||||||
|
val host = this
|
||||||
|
when (cancelCode) {
|
||||||
|
CancelCode.QrCodeInvalid -> {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
CancelCode.MismatchedUser,
|
||||||
|
CancelCode.MismatchedSas,
|
||||||
|
CancelCode.MismatchedCommitment,
|
||||||
|
CancelCode.MismatchedKeys -> {
|
||||||
|
bottomSheetVerificationNoticeItem {
|
||||||
|
id("notice")
|
||||||
|
notice(host.stringProvider.getString(R.string.verification_conclusion_not_secure).toEpoxyCharSequence())
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetVerificationBigImageItem {
|
||||||
|
id("image")
|
||||||
|
roomEncryptionTrustLevel(RoomEncryptionTrustLevel.Warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetVerificationNoticeItem {
|
||||||
|
id("warning_notice")
|
||||||
|
notice(host.eventHtmlRenderer.render(host.stringProvider.getString(R.string.verification_conclusion_compromised)).toEpoxyCharSequence())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
bottomSheetVerificationNoticeItem {
|
||||||
|
id("notice_cancelled")
|
||||||
|
notice(host.stringProvider.getString(R.string.verify_cancelled_notice).toEpoxyCharSequence())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseEpoxyVerificationController.buildEmojiItem(emoji: List<EmojiRepresentation>) {
|
||||||
|
val host = this
|
||||||
|
bottomSheetVerificationNoticeItem {
|
||||||
|
id("notice")
|
||||||
|
notice(host.stringProvider.getString(R.string.verification_emoji_notice).toEpoxyCharSequence())
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetVerificationEmojisItem {
|
||||||
|
id("emojis")
|
||||||
|
emojiRepresentation0(emoji[0])
|
||||||
|
emojiRepresentation1(emoji[1])
|
||||||
|
emojiRepresentation2(emoji[2])
|
||||||
|
emojiRepresentation3(emoji[3])
|
||||||
|
emojiRepresentation4(emoji[4])
|
||||||
|
emojiRepresentation5(emoji[5])
|
||||||
|
emojiRepresentation6(emoji[6])
|
||||||
|
}
|
||||||
|
|
||||||
|
buildSasCodeActions()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseEpoxyVerificationController.buildSasCodeActions() {
|
||||||
|
val host = this
|
||||||
|
bottomSheetDividerItem {
|
||||||
|
id("sepsas0")
|
||||||
|
}
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("ko")
|
||||||
|
title(host.stringProvider.getString(R.string.verification_sas_do_not_match))
|
||||||
|
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorError))
|
||||||
|
iconRes(R.drawable.ic_check_off)
|
||||||
|
iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorError))
|
||||||
|
listener { host.listener?.onDoNotMatchButtonTapped() }
|
||||||
|
}
|
||||||
|
bottomSheetDividerItem {
|
||||||
|
id("sepsas1")
|
||||||
|
}
|
||||||
|
bottomSheetVerificationActionItem {
|
||||||
|
id("ok")
|
||||||
|
title(host.stringProvider.getString(R.string.verification_sas_match))
|
||||||
|
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||||
|
iconRes(R.drawable.ic_check_on)
|
||||||
|
iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||||
|
listener { host.listener?.onMatchButtonTapped() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseEpoxyVerificationController.renderSasTransaction(transaction: VerificationTransactionData.SasTransactionData) {
|
||||||
|
val host = this
|
||||||
|
when (val txState = transaction.state) {
|
||||||
|
SasTransactionState.SasShortCodeReady -> {
|
||||||
|
buildEmojiItem(transaction.emojiCodeRepresentation.orEmpty())
|
||||||
|
}
|
||||||
|
is SasTransactionState.SasMacReceived -> {
|
||||||
|
if (!txState.codeConfirmed) {
|
||||||
|
buildEmojiItem(transaction.emojiCodeRepresentation.orEmpty())
|
||||||
|
} else {
|
||||||
|
// waiting
|
||||||
|
bottomSheetVerificationWaitingItem {
|
||||||
|
id("waiting")
|
||||||
|
title(host.stringProvider.getString(R.string.please_wait))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is SasTransactionState.Cancelled,
|
||||||
|
is SasTransactionState.Done -> {
|
||||||
|
// should show request status
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// waiting
|
||||||
|
bottomSheetVerificationWaitingItem {
|
||||||
|
id("waiting")
|
||||||
|
title(host.stringProvider.getString(R.string.please_wait))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VerificationEpoxyExt
|
@ -64,7 +64,6 @@ import im.vector.app.features.home.room.list.home.release.ReleaseNotesActivity
|
|||||||
import im.vector.app.features.matrixto.MatrixToBottomSheet
|
import im.vector.app.features.matrixto.MatrixToBottomSheet
|
||||||
import im.vector.app.features.matrixto.OriginOfMatrixTo
|
import im.vector.app.features.matrixto.OriginOfMatrixTo
|
||||||
import im.vector.app.features.navigation.Navigator
|
import im.vector.app.features.navigation.Navigator
|
||||||
import im.vector.app.features.navigation.SettingsActivityPayload
|
|
||||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||||
import im.vector.app.features.onboarding.AuthenticationDescription
|
import im.vector.app.features.onboarding.AuthenticationDescription
|
||||||
import im.vector.app.features.permalink.NavigationInterceptor
|
import im.vector.app.features.permalink.NavigationInterceptor
|
||||||
@ -77,6 +76,7 @@ import im.vector.app.features.popup.PopupAlertManager
|
|||||||
import im.vector.app.features.popup.VerificationVectorAlert
|
import im.vector.app.features.popup.VerificationVectorAlert
|
||||||
import im.vector.app.features.rageshake.ReportType
|
import im.vector.app.features.rageshake.ReportType
|
||||||
import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler
|
import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler
|
||||||
|
import im.vector.app.features.session.coroutineScope
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import im.vector.app.features.settings.VectorSettingsActivity
|
import im.vector.app.features.settings.VectorSettingsActivity
|
||||||
import im.vector.app.features.spaces.SpaceCreationActivity
|
import im.vector.app.features.spaces.SpaceCreationActivity
|
||||||
@ -87,9 +87,11 @@ import im.vector.app.features.spaces.share.ShareSpaceBottomSheet
|
|||||||
import im.vector.app.features.themes.ThemeUtils
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
import im.vector.app.features.usercode.UserCodeActivity
|
import im.vector.app.features.usercode.UserCodeActivity
|
||||||
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
|
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
||||||
import org.matrix.android.sdk.api.session.sync.InitialSyncStrategy
|
import org.matrix.android.sdk.api.session.sync.InitialSyncStrategy
|
||||||
@ -452,8 +454,16 @@ class HomeActivity :
|
|||||||
R.string.crosssigning_verify_this_session,
|
R.string.crosssigning_verify_this_session,
|
||||||
R.string.confirm_your_identity
|
R.string.confirm_your_identity
|
||||||
) {
|
) {
|
||||||
TODO()
|
// check first if it's not an outdated request?
|
||||||
// it.navigator.waitSessionVerification(it)
|
activeSessionHolder.getSafeActiveSession()?.let { session ->
|
||||||
|
session.coroutineScope.launch {
|
||||||
|
if (!session.cryptoService().crossSigningService().isCrossSigningVerified()) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
it.navigator.requestSelfSessionVerification(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,11 +474,11 @@ class HomeActivity :
|
|||||||
R.string.crosssigning_verify_this_session,
|
R.string.crosssigning_verify_this_session,
|
||||||
R.string.confirm_your_identity
|
R.string.confirm_your_identity
|
||||||
) {
|
) {
|
||||||
navigator.openSettings(this, SettingsActivityPayload.SecurityPrivacy)
|
// navigator.openSettings(this, SettingsActivityPayload.SecurityPrivacy)
|
||||||
// if (event.waitForIncomingRequest) {
|
// if (event.waitForIncomingRequest) {
|
||||||
// //it.navigator.waitSessionVerification(it)
|
// //it.navigator.waitSessionVerification(it)
|
||||||
// } else {
|
// } else {
|
||||||
// it.navigator.requestSelfSessionVerification(it)
|
it.navigator.requestSelfSessionVerification(it)
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ sealed interface HomeActivityViewEvents : VectorViewEvents {
|
|||||||
data class AskPasswordToInitCrossSigning(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
|
data class AskPasswordToInitCrossSigning(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents
|
||||||
data class CurrentSessionNotVerified(
|
data class CurrentSessionNotVerified(
|
||||||
val userItem: MatrixItem.UserItem,
|
val userItem: MatrixItem.UserItem,
|
||||||
val waitForIncomingRequest: Boolean = true,
|
// val waitForIncomingRequest: Boolean = true,
|
||||||
) : HomeActivityViewEvents
|
) : HomeActivityViewEvents
|
||||||
data class CurrentSessionCannotBeVerified(
|
data class CurrentSessionCannotBeVerified(
|
||||||
val userItem: MatrixItem.UserItem,
|
val userItem: MatrixItem.UserItem,
|
||||||
|
@ -396,8 +396,6 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||||||
_viewEvents.post(
|
_viewEvents.post(
|
||||||
HomeActivityViewEvents.CurrentSessionNotVerified(
|
HomeActivityViewEvents.CurrentSessionNotVerified(
|
||||||
session.getUserOrDefault(session.myUserId).toMatrixItem(),
|
session.getUserOrDefault(session.myUserId).toMatrixItem(),
|
||||||
// Always send request instead of waiting for an incoming as per recent EW changes
|
|
||||||
false
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -52,6 +52,7 @@ import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity
|
|||||||
import im.vector.app.features.crypto.recover.BootstrapBottomSheet
|
import im.vector.app.features.crypto.recover.BootstrapBottomSheet
|
||||||
import im.vector.app.features.crypto.recover.SetupMode
|
import im.vector.app.features.crypto.recover.SetupMode
|
||||||
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
|
import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider
|
||||||
|
import im.vector.app.features.crypto.verification.self.SelfVerificationBottomSheet
|
||||||
import im.vector.app.features.devtools.RoomDevToolActivity
|
import im.vector.app.features.devtools.RoomDevToolActivity
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
import im.vector.app.features.home.room.detail.RoomDetailActivity
|
||||||
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
|
import im.vector.app.features.home.room.detail.arguments.TimelineArgs
|
||||||
@ -104,6 +105,7 @@ import im.vector.app.features.terms.ReviewTermsActivity
|
|||||||
import im.vector.app.features.widgets.WidgetActivity
|
import im.vector.app.features.widgets.WidgetActivity
|
||||||
import im.vector.app.features.widgets.WidgetArgsBuilder
|
import im.vector.app.features.widgets.WidgetArgsBuilder
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
@ -266,12 +268,13 @@ class DefaultNavigator @Inject constructor(
|
|||||||
override fun requestSelfSessionVerification(context: Context) {
|
override fun requestSelfSessionVerification(context: Context) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
val session = sessionHolder.getSafeActiveSession() ?: return@launch
|
val session = sessionHolder.getSafeActiveSession() ?: return@launch
|
||||||
val otherSessions = session.cryptoService()
|
// val otherSessions = session.cryptoService()
|
||||||
.getCryptoDeviceInfoList(session.myUserId)
|
// .getCryptoDeviceInfoList(session.myUserId)
|
||||||
.filter { it.deviceId != session.sessionParams.deviceId }
|
// .filter { it.deviceId != session.sessionParams.deviceId }
|
||||||
.map { it.deviceId }
|
// .map { it.deviceId }
|
||||||
if (context is AppCompatActivity) {
|
if (context is AppCompatActivity) {
|
||||||
TODO()
|
SelfVerificationBottomSheet.verifyOwnUntrustedDevice()
|
||||||
|
.show(context.supportFragmentManager, "VERIF")
|
||||||
// if (otherSessions.isNotEmpty()) {
|
// if (otherSessions.isNotEmpty()) {
|
||||||
// val pr = session.cryptoService().verificationService().requestSelfKeyVerification(
|
// val pr = session.cryptoService().verificationService().requestSelfKeyVerification(
|
||||||
// supportedVerificationMethodsProvider.provide())
|
// supportedVerificationMethodsProvider.provide())
|
||||||
@ -285,11 +288,13 @@ class DefaultNavigator @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// override fun waitSessionVerification(fragmentActivity: FragmentActivity) {
|
override fun showIncomingSelfVerification(fragmentActivity: FragmentActivity, transactionId: String) {
|
||||||
// val session = sessionHolder.getSafeActiveSession() ?: return
|
// val session = sessionHolder.getSafeActiveSession() ?: return
|
||||||
// VerificationBottomSheet.forSelfVerification(session)
|
coroutineScope.launch(Dispatchers.Main) {
|
||||||
// .show(fragmentActivity.supportFragmentManager, VerificationBottomSheet.WAITING_SELF_VERIF_TAG)
|
SelfVerificationBottomSheet.forTransaction(transactionId)
|
||||||
// }
|
.show(fragmentActivity.supportFragmentManager, "SELF_VERIF_TAG")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun upgradeSessionSecurity(fragmentActivity: FragmentActivity, initCrossSigningOnly: Boolean) {
|
override fun upgradeSessionSecurity(fragmentActivity: FragmentActivity, initCrossSigningOnly: Boolean) {
|
||||||
BootstrapBottomSheet.show(
|
BootstrapBottomSheet.show(
|
||||||
|
@ -83,7 +83,7 @@ interface Navigator {
|
|||||||
|
|
||||||
fun requestSelfSessionVerification(context: Context)
|
fun requestSelfSessionVerification(context: Context)
|
||||||
|
|
||||||
// fun waitSessionVerification(fragmentActivity: FragmentActivity)
|
fun showIncomingSelfVerification(fragmentActivity: FragmentActivity, transactionId: String)
|
||||||
|
|
||||||
fun upgradeSessionSecurity(fragmentActivity: FragmentActivity, initCrossSigningOnly: Boolean)
|
fun upgradeSessionSecurity(fragmentActivity: FragmentActivity, initCrossSigningOnly: Boolean)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user