Merge pull request #916 from vector-im/debug_qr

Negotiate E2E by default for DMs (#907)
This commit is contained in:
Benoit Marty 2020-01-29 18:02:43 +01:00 committed by GitHub
commit c4649a5824
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 427 additions and 331 deletions

View file

@ -3,6 +3,7 @@ Changes in RiotX 0.14.0 (2020-XX-XX)
Features ✨:
- Enable encryption in unencrypted rooms, from the room settings (#212)
- Negotiate E2E by default for DMs (#907)
Improvements 🙌:
- Sharing things to RiotX: sort list by recent room first (#771)

View file

@ -37,8 +37,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Assert.*
import java.util.*
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import java.util.Arrays
import java.util.HashMap
import java.util.concurrent.CountDownLatch
class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
@ -57,7 +61,7 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
var roomId: String? = null
val lock1 = CountDownLatch(1)
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, object : TestMatrixCallback<String>(lock1) {
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), object : TestMatrixCallback<String>(lock1) {
override fun onSuccess(data: String) {
roomId = data
super.onSuccess(data)

View file

@ -19,7 +19,14 @@ package im.vector.matrix.android.internal.crypto.verification
import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.sas.*
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.SasMode
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.common.CommonTestHelper
@ -30,12 +37,17 @@ import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
import im.vector.matrix.android.internal.crypto.model.rest.toValue
import org.junit.Assert.*
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import java.util.*
import java.util.ArrayList
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@ -97,7 +109,8 @@ class SASTest : InstrumentedTest {
override fun transactionUpdated(tx: VerificationTransaction) {
if (tx.transactionId == txID) {
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnCancelled) {
val immutableState = (tx as SASDefaultVerificationTransaction).state
if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
cancelLatch.countDown()
}
}
@ -110,15 +123,17 @@ class SASTest : InstrumentedTest {
aliceSasTx.cancel(CancelCode.User)
mTestHelper.await(cancelLatch)
assertEquals("Should be cancelled on alice side",
VerificationTxState.Cancelled, aliceSasTx.state)
assertEquals("Should be cancelled on bob side",
VerificationTxState.OnCancelled, bobSasTx.state)
assertTrue("Should be cancelled on alice side", aliceSasTx.state is VerificationTxState.Cancelled)
assertTrue("Should be cancelled on bob side", bobSasTx.state is VerificationTxState.Cancelled)
assertEquals("Should be User cancelled on alice side",
CancelCode.User, aliceSasTx.cancelledReason)
assertEquals("Should be User cancelled on bob side",
CancelCode.User, aliceSasTx.cancelledReason)
val aliceCancelState = aliceSasTx.state as VerificationTxState.Cancelled
val bobCancelState = bobSasTx.state as VerificationTxState.Cancelled
assertTrue("Should be cancelled by me on alice side", aliceCancelState.byMe)
assertFalse("Should be cancelled by other on bob side", bobCancelState.byMe)
assertEquals("Should be User cancelled on alice side", CancelCode.User, aliceCancelState.cancelCode)
assertEquals("Should be User cancelled on bob side", CancelCode.User, bobCancelState.cancelCode)
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
@ -128,6 +143,7 @@ class SASTest : InstrumentedTest {
@Test
fun test_key_agreement_protocols_must_include_curve25519() {
fail("Not passing for the moment")
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
@ -136,15 +152,15 @@ class SASTest : InstrumentedTest {
val tid = "00000000"
// Bob should receive a cancel
var cancelReason: String? = null
var cancelReason: CancelCode? = null
val cancelLatch = CountDownLatch(1)
val bobListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
override fun transactionUpdated(tx: VerificationTransaction) {
if (tx.transactionId == tid && tx.cancelledReason != null) {
cancelReason = tx.cancelledReason?.humanReadable
if (tx.transactionId == tid && tx.state is VerificationTxState.Cancelled) {
cancelReason = (tx.state as VerificationTxState.Cancelled).cancelCode
cancelLatch.countDown()
}
}
@ -185,13 +201,14 @@ class SASTest : InstrumentedTest {
mTestHelper.await(cancelLatch)
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReason)
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
cryptoTestData.close()
}
@Test
fun test_key_agreement_macs_Must_include_hmac_sha256() {
fail("Not passing for the moment")
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
@ -229,6 +246,7 @@ class SASTest : InstrumentedTest {
@Test
fun test_key_agreement_short_code_include_decimal() {
fail("Not passing for the moment")
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
@ -315,7 +333,7 @@ class SASTest : InstrumentedTest {
}
override fun transactionUpdated(tx: VerificationTransaction) {
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnCancelled) {
if ((tx as SASDefaultVerificationTransaction).state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) {
aliceCancelledLatch.countDown()
}
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.extensions
fun Boolean?.orTrue() = this ?: true
fun Boolean?.orFalse() = this ?: false

View file

@ -20,13 +20,13 @@ interface VerificationTransaction {
var state: VerificationTxState
val cancelledReason: CancelCode?
val transactionId: String
val otherUserId: String
var otherDeviceId: String?
// TODO Not used. Remove?
val isIncoming: Boolean
/**
* User wants to cancel the transaction
*/

View file

@ -16,36 +16,36 @@
package im.vector.matrix.android.api.session.crypto.sas
enum class VerificationTxState {
None,
// I have started a verification request
SendingStart,
Started,
// Other user/device sent me a request
OnStarted,
// I have accepted a request started by the other user/device
SendingAccept,
Accepted,
// My request has been accepted by the other user/device
OnAccepted,
// I have sent my public key
SendingKey,
KeySent,
// The other user/device has sent me his public key
OnKeyReceived,
// Short code is ready to be displayed
ShortCodeReady,
// I have compared the code and manually said that they match
ShortCodeAccepted,
sealed class VerificationTxState {
// Uninitialized state
object None : VerificationTxState()
SendingMac,
MacSent,
Verifying,
Verified,
// Specific for SAS
abstract class VerificationSasTxState : VerificationTxState()
// Global: The verification has been cancelled (by me or other), see cancelReason for details
// When I do the cancel
Cancelled,
// When the other user do a cancel
OnCancelled
object SendingStart : VerificationSasTxState()
object Started : VerificationSasTxState()
object OnStarted : VerificationSasTxState()
object SendingAccept : VerificationSasTxState()
object Accepted : VerificationSasTxState()
object OnAccepted : VerificationSasTxState()
object SendingKey : VerificationSasTxState()
object KeySent : VerificationSasTxState()
object OnKeyReceived : VerificationSasTxState()
object ShortCodeReady : VerificationSasTxState()
object ShortCodeAccepted : VerificationSasTxState()
object SendingMac : VerificationSasTxState()
object MacSent : VerificationSasTxState()
object Verifying : VerificationSasTxState()
// Specific for QR code
// TODO Add code for the confirmation step for the user who has been scanned
// Terminal states
abstract class TerminalTxState : VerificationTxState()
object Verified : TerminalTxState()
// Cancelled by me or by other
data class Cancelled(val cancelCode: CancelCode, val byMe: Boolean) : TerminalTxState()
}

View file

@ -35,95 +35,111 @@ import timber.log.Timber
* Parameter to create a room, with facilities functions to configure it
*/
@JsonClass(generateAdapter = true)
class CreateRoomParams {
data class CreateRoomParams(
/**
* A public visibility indicates that the room will be shown in the published room list.
* A private visibility will hide the room from the published room list.
* Rooms default to private visibility if this key is not included.
* NB: This should not be confused with join_rules which also uses the word public. One of: ["public", "private"]
*/
@Json(name = "visibility")
val visibility: RoomDirectoryVisibility? = null,
/**
* A public visibility indicates that the room will be shown in the published room list.
* A private visibility will hide the room from the published room list.
* Rooms default to private visibility if this key is not included.
* NB: This should not be confused with join_rules which also uses the word public. One of: ["public", "private"]
*/
var visibility: RoomDirectoryVisibility? = null
/**
* The desired room alias local part. If this is included, a room alias will be created and mapped to the newly created room.
* The alias will belong on the same homeserver which created the room.
* For example, if this was set to "foo" and sent to the homeserver "example.com" the complete room alias would be #foo:example.com.
*/
@Json(name = "room_alias_name")
val roomAliasName: String? = null,
/**
* The desired room alias local part. If this is included, a room alias will be created and mapped to the newly created room.
* The alias will belong on the same homeserver which created the room.
* For example, if this was set to "foo" and sent to the homeserver "example.com" the complete room alias would be #foo:example.com.
*/
@Json(name = "room_alias_name")
var roomAliasName: String? = null
/**
* If this is included, an m.room.name event will be sent into the room to indicate the name of the room.
* See Room Events for more information on m.room.name.
*/
@Json(name = "name")
val name: String? = null,
/**
* If this is included, an m.room.name event will be sent into the room to indicate the name of the room.
* See Room Events for more information on m.room.name.
*/
var name: String? = null
/**
* If this is included, an m.room.topic event will be sent into the room to indicate the topic for the room.
* See Room Events for more information on m.room.topic.
*/
@Json(name = "topic")
val topic: String? = null,
/**
* If this is included, an m.room.topic event will be sent into the room to indicate the topic for the room.
* See Room Events for more information on m.room.topic.
*/
var topic: String? = null
/**
* A list of user IDs to invite to the room.
* This will tell the server to invite everyone in the list to the newly created room.
*/
@Json(name = "invite")
val invitedUserIds: List<String>? = null,
/**
* A list of user IDs to invite to the room.
* This will tell the server to invite everyone in the list to the newly created room.
*/
@Json(name = "invite")
var invitedUserIds: MutableList<String>? = null
/**
* A list of objects representing third party IDs to invite into the room.
*/
@Json(name = "invite_3pid")
val invite3pids: List<Invite3Pid>? = null,
/**
* A list of objects representing third party IDs to invite into the room.
*/
@Json(name = "invite_3pid")
var invite3pids: MutableList<Invite3Pid>? = null
/**
* Extra keys to be added to the content of the m.room.create.
* The server will clobber the following keys: creator.
* Future versions of the specification may allow the server to clobber other keys.
*/
@Json(name = "creation_content")
val creationContent: Any? = null,
/**
* Extra keys to be added to the content of the m.room.create.
* The server will clobber the following keys: creator.
* Future versions of the specification may allow the server to clobber other keys.
*/
@Json(name = "creation_content")
var creationContent: Any? = null
/**
* A list of state events to set in the new room.
* This allows the user to override the default state events set in the new room.
* The expected format of the state events are an object with type, state_key and content keys set.
* Takes precedence over events set by presets, but gets overridden by name and topic keys.
*/
@Json(name = "initial_state")
val initialStates: List<Event>? = null,
/**
* A list of state events to set in the new room.
* This allows the user to override the default state events set in the new room.
* The expected format of the state events are an object with type, state_key and content keys set.
* Takes precedence over events set by presets, but gets overridden by name and topic keys.
*/
@Json(name = "initial_state")
var initialStates: MutableList<Event>? = null
/**
* Convenience parameter for setting various default state events based on a preset. Must be either:
* private_chat => join_rules is set to invite. history_visibility is set to shared.
* trusted_private_chat => join_rules is set to invite. history_visibility is set to shared. All invitees are given the same power level as the
* room creator.
* public_chat: => join_rules is set to public. history_visibility is set to shared.
*/
@Json(name = "preset")
val preset: CreateRoomPreset? = null,
/**
* Convenience parameter for setting various default state events based on a preset. Must be either:
* private_chat => join_rules is set to invite. history_visibility is set to shared.
* trusted_private_chat => join_rules is set to invite. history_visibility is set to shared. All invitees are given the same power level as the
* room creator.
* public_chat: => join_rules is set to public. history_visibility is set to shared.
*/
var preset: CreateRoomPreset? = null
/**
* This flag makes the server set the is_direct flag on the m.room.member events sent to the users in invite and invite_3pid.
* See Direct Messaging for more information.
*/
@Json(name = "is_direct")
val isDirect: Boolean? = null,
/**
* The power level content to override in the default power level event
*/
@Json(name = "power_level_content_override")
val powerLevelContentOverride: PowerLevelsContent? = null
) {
/**
* This flag makes the server set the is_direct flag on the m.room.member events sent to the users in invite and invite_3pid.
* See Direct Messaging for more information.
* Set to true means that if cross-signing is enabled and we can get keys for every invited users,
* the encryption will be enabled on the created room
*/
@Json(name = "is_direct")
var isDirect: Boolean? = null
@Transient
internal var enableEncryptionIfInvitedUsersSupportIt: Boolean = false
private set
/**
* The power level content to override in the default power level event
*/
@Json(name = "power_level_content_override")
var powerLevelContentOverride: PowerLevelsContent? = null
fun enableEncryptionIfInvitedUsersSupportIt(): CreateRoomParams {
enableEncryptionIfInvitedUsersSupportIt = true
return this
}
/**
* Add the crypto algorithm to the room creation parameters.
*
* @param algorithm the algorithm
*/
fun enableEncryptionWithAlgorithm(algorithm: String) {
if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
fun enableEncryptionWithAlgorithm(algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM): CreateRoomParams {
return if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
val contentMap = mapOf("algorithm" to algorithm)
val algoEvent = Event(
@ -132,13 +148,12 @@ class CreateRoomParams {
content = contentMap.toContent()
)
if (null == initialStates) {
initialStates = mutableListOf(algoEvent)
} else {
initialStates!!.add(algoEvent)
}
copy(
initialStates = initialStates.orEmpty().filter { it.type != EventType.STATE_ROOM_ENCRYPTION } + algoEvent
)
} else {
Timber.e("Unsupported algorithm: $algorithm")
this
}
}
@ -147,9 +162,10 @@ class CreateRoomParams {
*
* @param historyVisibility the expected history visibility, set null to remove any existing value.
*/
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?) {
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?): CreateRoomParams {
// Remove the existing value if any.
initialStates?.removeAll { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY }
val newInitialStates = initialStates
?.filter { it.type != EventType.STATE_ROOM_HISTORY_VISIBILITY }
if (historyVisibility != null) {
val contentMap = mapOf("history_visibility" to historyVisibility)
@ -159,20 +175,24 @@ class CreateRoomParams {
stateKey = "",
content = contentMap.toContent())
if (null == initialStates) {
initialStates = mutableListOf(historyVisibilityEvent)
} else {
initialStates!!.add(historyVisibilityEvent)
}
return copy(
initialStates = newInitialStates.orEmpty() + historyVisibilityEvent
)
} else {
return copy(
initialStates = newInitialStates
)
}
}
/**
* Mark as a direct message room.
*/
fun setDirectMessage() {
preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT
isDirect = true
fun setDirectMessage(): CreateRoomParams {
return copy(
preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT,
isDirect = true
)
}
/**
@ -215,28 +235,26 @@ class CreateRoomParams {
*/
fun addParticipantIds(hsConfig: HomeServerConnectionConfig,
userId: String,
ids: List<String>) {
for (id in ids) {
if (Patterns.EMAIL_ADDRESS.matcher(id).matches() && hsConfig.identityServerUri != null) {
if (null == invite3pids) {
invite3pids = ArrayList()
}
val pid = Invite3Pid(idServer = hsConfig.identityServerUri.host!!,
medium = ThreePidMedium.EMAIL,
address = id)
invite3pids!!.add(pid)
} else if (isUserId(id)) {
// do not invite oneself
if (userId != id) {
if (null == invitedUserIds) {
invitedUserIds = ArrayList()
}
invitedUserIds!!.add(id)
}
}
// TODO add phonenumbers when it will be available
}
ids: List<String>): CreateRoomParams {
return copy(
invite3pids = (invite3pids.orEmpty() + ids
.takeIf { hsConfig.identityServerUri != null }
?.filter { id -> Patterns.EMAIL_ADDRESS.matcher(id).matches() }
?.map { id ->
Invite3Pid(
idServer = hsConfig.identityServerUri!!.host!!,
medium = ThreePidMedium.EMAIL,
address = id
)
}
.orEmpty())
.distinct(),
invitedUserIds = (invitedUserIds.orEmpty() + ids
.filter { id -> isUserId(id) }
// do not invite oneself
.filter { id -> id != userId })
.distinct()
)
// TODO add phonenumbers when it will be available
}
}

View file

@ -27,7 +27,6 @@ import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import okhttp3.internal.toImmutableList
import timber.log.Timber
import javax.inject.Inject
@ -235,7 +234,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
dispatchDeviceChange(userIds.toImmutableList())
dispatchDeviceChange(userIds)
return usersDevicesInfoMap
}
@ -294,7 +293,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
*
* @param downloadUsers the user ids list
*/
private suspend fun doKeyDownloadForUsers(downloadUsers: MutableList<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
private suspend fun doKeyDownloadForUsers(downloadUsers: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers")
// get the user ids which did not already trigger a keys download
val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }

View file

@ -52,22 +52,27 @@ internal class DefaultIncomingSASDefaultVerificationTransaction(
override val uxState: IncomingSasVerificationTransaction.UxState
get() {
return when (state) {
VerificationTxState.OnStarted -> IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT
VerificationTxState.SendingAccept,
VerificationTxState.Accepted,
VerificationTxState.OnKeyReceived,
VerificationTxState.SendingKey,
VerificationTxState.KeySent -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT
VerificationTxState.ShortCodeReady -> IncomingSasVerificationTransaction.UxState.SHOW_SAS
VerificationTxState.ShortCodeAccepted,
VerificationTxState.SendingMac,
VerificationTxState.MacSent,
VerificationTxState.Verifying -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION
VerificationTxState.Verified -> IncomingSasVerificationTransaction.UxState.VERIFIED
VerificationTxState.Cancelled -> IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME
VerificationTxState.OnCancelled -> IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER
else -> IncomingSasVerificationTransaction.UxState.UNKNOWN
return when (val immutableState = state) {
is VerificationTxState.OnStarted -> IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT
is VerificationTxState.SendingAccept,
is VerificationTxState.Accepted,
is VerificationTxState.OnKeyReceived,
is VerificationTxState.SendingKey,
is VerificationTxState.KeySent -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT
is VerificationTxState.ShortCodeReady -> IncomingSasVerificationTransaction.UxState.SHOW_SAS
is VerificationTxState.ShortCodeAccepted,
is VerificationTxState.SendingMac,
is VerificationTxState.MacSent,
is VerificationTxState.Verifying -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION
is VerificationTxState.Verified -> IncomingSasVerificationTransaction.UxState.VERIFIED
is VerificationTxState.Cancelled -> {
if (immutableState.byMe) {
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME
} else {
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER
}
}
else -> IncomingSasVerificationTransaction.UxState.UNKNOWN
}
}

View file

@ -49,23 +49,28 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
override val uxState: OutgoingSasVerificationTransaction.UxState
get() {
return when (state) {
VerificationTxState.None -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_START
VerificationTxState.SendingStart,
VerificationTxState.Started,
VerificationTxState.OnAccepted,
VerificationTxState.SendingKey,
VerificationTxState.KeySent,
VerificationTxState.OnKeyReceived -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT
VerificationTxState.ShortCodeReady -> OutgoingSasVerificationTransaction.UxState.SHOW_SAS
VerificationTxState.ShortCodeAccepted,
VerificationTxState.SendingMac,
VerificationTxState.MacSent,
VerificationTxState.Verifying -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION
VerificationTxState.Verified -> OutgoingSasVerificationTransaction.UxState.VERIFIED
VerificationTxState.OnCancelled -> OutgoingSasVerificationTransaction.UxState.CANCELLED_BY_ME
VerificationTxState.Cancelled -> OutgoingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER
else -> OutgoingSasVerificationTransaction.UxState.UNKNOWN
return when (val immutableState = state) {
is VerificationTxState.None -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_START
is VerificationTxState.SendingStart,
is VerificationTxState.Started,
is VerificationTxState.OnAccepted,
is VerificationTxState.SendingKey,
is VerificationTxState.KeySent,
is VerificationTxState.OnKeyReceived -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT
is VerificationTxState.ShortCodeReady -> OutgoingSasVerificationTransaction.UxState.SHOW_SAS
is VerificationTxState.ShortCodeAccepted,
is VerificationTxState.SendingMac,
is VerificationTxState.MacSent,
is VerificationTxState.Verifying -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION
is VerificationTxState.Verified -> OutgoingSasVerificationTransaction.UxState.VERIFIED
is VerificationTxState.Cancelled -> {
if (immutableState.byMe) {
OutgoingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER
} else {
OutgoingSasVerificationTransaction.UxState.CANCELLED_BY_ME
}
}
else -> OutgoingSasVerificationTransaction.UxState.UNKNOWN
}
}

View file

@ -518,8 +518,7 @@ internal class DefaultVerificationService @Inject constructor(
}
if (existingTransaction is SASDefaultVerificationTransaction) {
existingTransaction.cancelledReason = safeValueOf(cancelReq.code)
existingTransaction.state = VerificationTxState.OnCancelled
existingTransaction.state = VerificationTxState.Cancelled(safeValueOf(cancelReq.code), false)
}
}
@ -1075,19 +1074,7 @@ internal class DefaultVerificationService @Inject constructor(
override fun transactionUpdated(tx: VerificationTransaction) {
dispatchTxUpdated(tx)
if (tx is SASDefaultVerificationTransaction
&& (tx.state == VerificationTxState.Cancelled
|| tx.state == VerificationTxState.OnCancelled
|| tx.state == VerificationTxState.Verified)
) {
// remove
this.removeTransaction(tx.otherUserId, tx.transactionId)
}
if (tx is QrCodeVerificationTransaction
&& (tx.state == VerificationTxState.Cancelled
|| tx.state == VerificationTxState.OnCancelled
|| tx.state == VerificationTxState.Verified)
) {
if (tx.state is VerificationTxState.TerminalTxState) {
// remove
this.removeTransaction(tx.otherUserId, tx.transactionId)
}

View file

@ -33,7 +33,6 @@ import im.vector.matrix.android.internal.util.withoutPrefix
import org.matrix.olm.OlmSAS
import org.matrix.olm.OlmUtility
import timber.log.Timber
import kotlin.properties.Delegates
/**
* Represents an ongoing short code interactive key verification between two devices.
@ -71,23 +70,22 @@ internal abstract class SASDefaultVerificationTransaction(
}
}
override var state by Delegates.observable(VerificationTxState.None) { _, _, new ->
// println("$property has changed from $old to $new")
listeners.forEach {
try {
it.transactionUpdated(this)
} catch (e: Throwable) {
Timber.e(e, "## Error while notifying listeners")
override var state: VerificationTxState = VerificationTxState.None
set(newState) {
field = newState
listeners.forEach {
try {
it.transactionUpdated(this)
} catch (e: Throwable) {
Timber.e(e, "## Error while notifying listeners")
}
}
if (newState is VerificationTxState.TerminalTxState) {
releaseSAS()
}
}
if (new == VerificationTxState.Cancelled
|| new == VerificationTxState.OnCancelled
|| new == VerificationTxState.Verified) {
releaseSAS()
}
}
override var cancelledReason: CancelCode? = null
private var olmSas: OlmSAS? = null
@ -341,8 +339,7 @@ internal abstract class SASDefaultVerificationTransaction(
}
override fun cancel(code: CancelCode) {
cancelledReason = code
state = VerificationTxState.Cancelled
state = VerificationTxState.Cancelled(code, true)
transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code)
}

View file

@ -30,7 +30,6 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfo
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
import im.vector.matrix.android.internal.util.withoutPrefix
import timber.log.Timber
import kotlin.properties.Delegates
internal class DefaultQrCodeVerificationTransaction(
private val setDeviceVerificationAction: SetDeviceVerificationAction,
@ -46,20 +45,21 @@ internal class DefaultQrCodeVerificationTransaction(
override val isIncoming: Boolean
) : DefaultVerificationTransaction(transactionId, otherUserId, otherDeviceId, isIncoming), QrCodeVerificationTransaction {
override var cancelledReason: CancelCode? = null
override val qrCodeText: String?
get() = qrCodeData?.toUrl()
override var state by Delegates.observable(VerificationTxState.None) { _, _, _ ->
listeners.forEach {
try {
it.transactionUpdated(this)
} catch (e: Throwable) {
Timber.e(e, "## Error while notifying listeners")
override var state: VerificationTxState = VerificationTxState.None
set(newState) {
field = newState
listeners.forEach {
try {
it.transactionUpdated(this)
} catch (e: Throwable) {
Timber.e(e, "## Error while notifying listeners")
}
}
}
}
override fun userHasScannedOtherQrCode(otherQrCodeText: String) {
val otherQrCodeData = otherQrCodeText.toQrCodeData() ?: run {
@ -181,8 +181,7 @@ internal class DefaultQrCodeVerificationTransaction(
}
override fun cancel(code: CancelCode) {
cancelledReason = code
state = VerificationTxState.Cancelled
state = VerificationTxState.Cancelled(code, true)
transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code)
}

View file

@ -34,7 +34,13 @@ import im.vector.matrix.android.internal.session.room.timeline.EventContextRespo
import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse
import im.vector.matrix.android.internal.session.room.typing.TypingBody
import retrofit2.Call
import retrofit2.http.*
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query
internal interface RoomAPI {
@ -60,9 +66,12 @@ internal interface RoomAPI {
/**
* Create a room.
* Ref: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-createroom
* Set all the timeouts to 1 minute, because if the server takes time to answer, we will not execute the
* create direct chat request if any
*
* @param param the creation room parameter
*/
@Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "createRoom")
fun createRoom(@Body param: CreateRoomParams): Call<CreateRoomResponse>

View file

@ -17,9 +17,12 @@
package im.vector.matrix.android.internal.session.room.create
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.extensions.orTrue
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
import im.vector.matrix.android.internal.crypto.DeviceListManager
import im.vector.matrix.android.internal.database.awaitNotEmptyResult
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomEntityFields
@ -49,12 +52,41 @@ internal class DefaultCreateRoomTask @Inject constructor(
private val readMarkersTask: SetReadMarkersTask,
@SessionDatabase
private val realmConfiguration: RealmConfiguration,
private val crossSigningService: CrossSigningService,
private val deviceListManager: DeviceListManager,
private val eventBus: EventBus
) : CreateRoomTask {
override suspend fun execute(params: CreateRoomParams): String {
val createRoomParams = params
.takeIf { it.enableEncryptionIfInvitedUsersSupportIt }
?.takeIf { crossSigningService.isCrossSigningEnabled() }
?.takeIf { it.invite3pids.isNullOrEmpty() }
?.invitedUserIds
?.let { userIds ->
val keys = deviceListManager.downloadKeys(userIds, forceDownload = false)
userIds.any { userId ->
if (keys.map[userId].isNullOrEmpty()) {
// A user has no device, so do not enable encryption
true
} else {
// Check that every user's device have at least one key
keys.map[userId]?.values?.any { it.keys.isNullOrEmpty() } ?: true
}
}
}
.orTrue()
.let { cannotEnableEncryption ->
if (!cannotEnableEncryption) {
params.enableEncryptionWithAlgorithm()
} else {
params
}
}
val createRoomResponse = executeRequest<CreateRoomResponse>(eventBus) {
apiCall = roomAPI.createRoom(params)
apiCall = roomAPI.createRoom(createRoomParams)
}
val roomId = createRoomResponse.roomId!!
// Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before)
@ -66,8 +98,8 @@ internal class DefaultCreateRoomTask @Inject constructor(
} catch (exception: TimeoutCancellationException) {
throw CreateRoomFailure.CreatedWithTimeout
}
if (params.isDirect()) {
handleDirectChatCreation(params, roomId)
if (createRoomParams.isDirect()) {
handleDirectChatCreation(createRoomParams, roomId)
}
setReadMarkers(roomId)
return roomId

View file

@ -16,8 +16,6 @@
package im.vector.matrix.android.internal.task
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.delay

View file

@ -189,7 +189,7 @@ class MainActivity : VectorBaseActivity() {
// The homeserver has invalidated the token, with a soft logout
SoftLogoutActivity.newIntent(this)
args.isUserLoggedOut ->
// the homeserver has invalidated the token (password changed, device deleted, other security reason
// the homeserver has invalidated the token (password changed, device deleted, other security reasons)
SignedOutActivity.newIntent(this)
sessionHolder.hasActiveSession() ->
// We have a session.

View file

@ -92,13 +92,12 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
}
private fun createRoomAndInviteSelectedUsers() = withState { currentState ->
val isDirect = currentState.selectedUsers.size == 1
val roomParams = CreateRoomParams().apply {
invitedUserIds = ArrayList(currentState.selectedUsers.map { it.userId })
if (isDirect) {
setDirectMessage()
}
}
val roomParams = CreateRoomParams(
invitedUserIds = currentState.selectedUsers.map { it.userId }
)
.setDirectMessage()
.enableEncryptionIfInvitedUsersSupportIt()
session.rx()
.createRoom(roomParams)
.execute {

View file

@ -18,8 +18,8 @@ package im.vector.riotx.features.crypto.verification
import android.content.Context
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseActivity
@ -54,7 +54,7 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
if (!tx.isToDeviceTransport()) return
// TODO maybe check also if
when (tx.state) {
VerificationTxState.OnStarted -> {
is VerificationTxState.OnStarted -> {
// Add a notification for every incoming request
val name = session?.getUser(tx.otherUserId)?.displayName
?: tx.otherUserId
@ -92,13 +92,11 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
}
PopupAlertManager.postVectorAlert(alert)
}
VerificationTxState.Cancelled,
VerificationTxState.OnCancelled,
VerificationTxState.Verified -> {
is VerificationTxState.TerminalTxState -> {
// cancel related notification
PopupAlertManager.cancelAlert("kvr_${tx.transactionId}")
}
else -> Unit
else -> Unit
}
}
@ -134,7 +132,7 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context
pr.requestInfo?.fromDevice ?: "",
pr.transactionId ?: "",
pr.roomId ?: ""
)
)
}
colorInt = ThemeUtils.getColor(context, R.attr.vctr_notice_secondary)
// 5mn expiration

View file

@ -110,21 +110,21 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
// Did the request result in a SAS transaction?
if (it.sasTransactionState != null) {
when (it.sasTransactionState) {
VerificationTxState.None,
VerificationTxState.SendingStart,
VerificationTxState.Started,
VerificationTxState.OnStarted,
VerificationTxState.SendingAccept,
VerificationTxState.Accepted,
VerificationTxState.OnAccepted,
VerificationTxState.SendingKey,
VerificationTxState.KeySent,
VerificationTxState.OnKeyReceived,
VerificationTxState.ShortCodeReady,
VerificationTxState.ShortCodeAccepted,
VerificationTxState.SendingMac,
VerificationTxState.MacSent,
VerificationTxState.Verifying -> {
is VerificationTxState.None,
is VerificationTxState.SendingStart,
is VerificationTxState.Started,
is VerificationTxState.OnStarted,
is VerificationTxState.SendingAccept,
is VerificationTxState.Accepted,
is VerificationTxState.OnAccepted,
is VerificationTxState.SendingKey,
is VerificationTxState.KeySent,
is VerificationTxState.OnKeyReceived,
is VerificationTxState.ShortCodeReady,
is VerificationTxState.ShortCodeAccepted,
is VerificationTxState.SendingMac,
is VerificationTxState.MacSent,
is VerificationTxState.Verifying -> {
showFragment(VerificationEmojiCodeFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationArgs(
it.otherUserMxItem?.id ?: "",
@ -133,13 +133,14 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
it.pendingRequest.invoke()?.transactionId ?: it.transactionId))
})
}
VerificationTxState.Verified,
VerificationTxState.Cancelled,
VerificationTxState.OnCancelled -> {
is VerificationTxState.Verified -> {
showFragment(VerificationConclusionFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(
it.sasTransactionState == VerificationTxState.Verified,
it.cancelCode?.value))
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null))
})
}
is VerificationTxState.Cancelled -> {
showFragment(VerificationConclusionFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(false, it.sasTransactionState.cancelCode.value))
})
}
}
@ -148,17 +149,19 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
}
when (it.qrTransactionState) {
VerificationTxState.Verified,
VerificationTxState.Cancelled,
VerificationTxState.OnCancelled -> {
is VerificationTxState.Verified -> {
showFragment(VerificationConclusionFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(
it.qrTransactionState == VerificationTxState.Verified,
it.cancelCode?.value))
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null))
})
return@withState
}
else -> Unit
is VerificationTxState.Cancelled -> {
showFragment(VerificationConclusionFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(false, it.qrTransactionState.cancelCode.value))
})
return@withState
}
else -> Unit
}
// At this point there is no SAS transaction for this request

View file

@ -30,7 +30,6 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.QrCodeVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
@ -55,8 +54,7 @@ data class VerificationBottomSheetViewState(
val pendingLocalId: String? = null,
val sasTransactionState: VerificationTxState? = null,
val qrTransactionState: VerificationTxState? = null,
val transactionId: String? = null,
val cancelCode: CancelCode? = null
val transactionId: String? = null
) : MvRxState
class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted initialState: VerificationBottomSheetViewState,
@ -129,10 +127,12 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
pendingRequest = Loading()
)
}
val roomParams = CreateRoomParams().apply {
invitedUserIds = listOf(otherUserId).toMutableList()
setDirectMessage()
}
val roomParams = CreateRoomParams(
invitedUserIds = listOf(otherUserId)
)
.setDirectMessage()
.enableEncryptionIfInvitedUsersSupportIt()
session.createRoom(roomParams, object : MatrixCallback<String> {
override fun onSuccess(data: String) {
setState {
@ -217,19 +217,17 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(@Assisted ini
// A SAS tx has been started following this request
setState {
copy(
sasTransactionState = tx.state,
cancelCode = tx.cancelledReason
sasTransactionState = tx.state
)
}
}
}
is QrCodeVerificationTransaction -> {
if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) {
// A SAS tx has been started following this request
// A QR tx has been started following this request
setState {
copy(
qrTransactionState = tx.state,
cancelCode = tx.cancelledReason
qrTransactionState = tx.state
)
}
}

View file

@ -15,15 +15,23 @@
*/
package im.vector.riotx.features.crypto.verification.emoji
import com.airbnb.mvrx.*
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.core.di.HasScreenInjector
@ -63,16 +71,16 @@ class VerificationEmojiCodeViewModel @AssistedInject constructor(
private fun refreshStateFromTx(sasTx: SasVerificationTransaction?) {
when (sasTx?.state) {
VerificationTxState.None,
VerificationTxState.SendingStart,
VerificationTxState.Started,
VerificationTxState.OnStarted,
VerificationTxState.SendingAccept,
VerificationTxState.Accepted,
VerificationTxState.OnAccepted,
VerificationTxState.SendingKey,
VerificationTxState.KeySent,
VerificationTxState.OnKeyReceived -> {
is VerificationTxState.None,
is VerificationTxState.SendingStart,
is VerificationTxState.Started,
is VerificationTxState.OnStarted,
is VerificationTxState.SendingAccept,
is VerificationTxState.Accepted,
is VerificationTxState.OnAccepted,
is VerificationTxState.SendingKey,
is VerificationTxState.KeySent,
is VerificationTxState.OnKeyReceived -> {
setState {
copy(
isWaitingFromOther = false,
@ -86,7 +94,7 @@ class VerificationEmojiCodeViewModel @AssistedInject constructor(
)
}
}
VerificationTxState.ShortCodeReady -> {
is VerificationTxState.ShortCodeReady -> {
setState {
copy(
isWaitingFromOther = false,
@ -98,17 +106,16 @@ class VerificationEmojiCodeViewModel @AssistedInject constructor(
)
}
}
VerificationTxState.ShortCodeAccepted,
VerificationTxState.SendingMac,
VerificationTxState.MacSent,
VerificationTxState.Verifying,
VerificationTxState.Verified -> {
is VerificationTxState.ShortCodeAccepted,
is VerificationTxState.SendingMac,
is VerificationTxState.MacSent,
is VerificationTxState.Verifying,
is VerificationTxState.Verified -> {
setState {
copy(isWaitingFromOther = true)
}
}
VerificationTxState.Cancelled,
VerificationTxState.OnCancelled -> {
is VerificationTxState.Cancelled -> {
// The fragment should not be rendered in this state,
// it should have been replaced by a conclusion fragment
setState {

View file

@ -81,15 +81,13 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
copy(asyncCreateRoomRequest = Loading())
}
val createRoomParams = CreateRoomParams().apply {
name = state.roomName.takeIf { it.isNotBlank() }
// Directory visibility
visibility = if (state.isInRoomDirectory) RoomDirectoryVisibility.PUBLIC else RoomDirectoryVisibility.PRIVATE
// Public room
preset = if (state.isPublic) CreateRoomPreset.PRESET_PUBLIC_CHAT else CreateRoomPreset.PRESET_PRIVATE_CHAT
}
val createRoomParams = CreateRoomParams(
name = state.roomName.takeIf { it.isNotBlank() },
// Directory visibility
visibility = if (state.isInRoomDirectory) RoomDirectoryVisibility.PUBLIC else RoomDirectoryVisibility.PRIVATE,
// Public room
preset = if (state.isPublic) CreateRoomPreset.PRESET_PUBLIC_CHAT else CreateRoomPreset.PRESET_PRIVATE_CHAT
)
session.createRoom(createRoomParams, object : MatrixCallback<String> {
override fun onSuccess(data: String) {