From 26b4b6e1942e6bfb57bcccfc1973223352121d63 Mon Sep 17 00:00:00 2001 From: Valere Date: Sun, 1 Dec 2019 12:44:29 +0100 Subject: [PATCH 001/217] Support verification using room transport --- CHANGES.md | 1 + .../crypto/sas/SasVerificationService.kt | 23 ++ .../crypto/sas/SasVerificationTransaction.kt | 2 +- .../api/session/events/model/EventType.kt | 1 + .../model/message/MessageRelationContent.kt | 26 ++ .../session/room/model/message/MessageType.kt | 1 + .../MessageVerificationAcceptContent.kt | 76 ++++ .../MessageVerificationCancelContent.kt | 58 +++ .../message/MessageVerificationDoneContent.kt | 28 ++ .../message/MessageVerificationKeyContent.kt | 61 ++++ .../message/MessageVerificationMacContent.kt | 57 +++ .../MessageVerificationRequestContent.kt | 35 ++ .../MessageVerificationStartContent.kt | 61 ++++ .../android/internal/crypto/CryptoModule.kt | 3 + .../internal/crypto/DefaultCryptoService.kt | 4 + .../model/rest/KeyVerificationAccept.kt | 38 +- .../model/rest/KeyVerificationCancel.kt | 25 +- .../crypto/model/rest/KeyVerificationKey.kt | 24 +- .../crypto/model/rest/KeyVerificationMac.kt | 43 +-- .../model/rest/KeyVerificationRequest.kt | 48 +++ .../crypto/model/rest/KeyVerificationStart.kt | 26 +- .../internal/crypto/tasks/EncryptEventTask.kt | 80 ++++ .../crypto/tasks/RequestVerificationDMTask.kt | 88 +++++ .../tasks/SendVerificationMessageTask.kt | 101 ++++++ ...aultIncomingSASVerificationTransaction.kt} | 66 ++-- ... DefaultOutgoingSASVerificationRequest.kt} | 75 ++-- .../DefaultSasVerificationService.kt | 342 ++++++++++++++---- .../SASVerificationTransaction.kt | 80 ++-- .../crypto/verification/SasTransport.kt | 53 +++ .../verification/SasTransportRoomMessage.kt | 129 +++++++ .../verification/SasTransportToDevice.kt | 115 ++++++ .../crypto/verification/VerifInfoAccept.kt | 57 +++ .../crypto/verification/VerifInfoCancel.kt | 30 ++ .../crypto/verification/VerifInfoKey.kt | 32 ++ .../crypto/verification/VerifInfoMac.kt | 38 ++ .../crypto/verification/VerifInfoStart.kt | 49 +++ .../crypto/verification/VerificationInfo.kt | 25 ++ .../VerificationMessageLiveObserver.kt | 113 ++++++ .../verification/VerificationTransaction.kt | 3 +- .../internal/network/RetrofitExtensions.kt | 2 +- .../android/internal/session/SessionModule.kt | 5 + .../session/room/send/DefaultSendService.kt | 3 +- .../room/send/LocalEchoEventFactory.kt | 18 + .../session/room/send/LocalEchoUpdater.kt | 3 +- .../src/main/res/values/strings_RiotX.xml | 3 + .../im/vector/riotx/core/di/FragmentModule.kt | 5 + .../vector/riotx/features/command/Command.kt | 4 +- .../riotx/features/command/CommandParser.kt | 11 + .../riotx/features/command/ParsedCommand.kt | 2 + .../home/room/detail/RoomDetailViewModel.kt | 20 +- .../timeline/factory/TimelineItemFactory.kt | 10 + .../helper/TimelineDisplayableEvents.kt | 8 +- .../features/settings/VectorPreferences.kt | 3 +- .../settings/VectorSettingsLabsFragment.kt | 9 +- vector/src/main/res/values/strings.xml | 1 + vector/src/main/res/values/strings_riotX.xml | 2 + .../src/main/res/xml/vector_settings_labs.xml | 1 - 57 files changed, 1939 insertions(+), 288 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageRelationContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationAcceptContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationCancelContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationDoneContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationKeyContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationMacContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationRequestContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/EncryptEventTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RequestVerificationDMTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendVerificationMessageTask.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/{IncomingSASVerificationTransaction.kt => DefaultIncomingSASVerificationTransaction.kt} (79%) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/{OutgoingSASVerificationRequest.kt => DefaultOutgoingSASVerificationRequest.kt} (79%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoAccept.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoCancel.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoKey.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoMac.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoStart.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt diff --git a/CHANGES.md b/CHANGES.md index 21cf052d45..d90237b31b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -33,6 +33,7 @@ Changes in RiotX 0.9.0 (2019-12-05) Features ✨: - Account creation. It's now possible to create account on any homeserver with RiotX (#34) - Iteration of the login flow (#613) + - [SDK] MSC2241 / verification in DMs (#707) Improvements 🙌: - Send mention Pills from composer diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt index 88c0787b4d..902baae06f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt @@ -16,19 +16,42 @@ package im.vector.matrix.android.api.session.crypto.sas +import im.vector.matrix.android.api.MatrixCallback + +/** + * https://matrix.org/docs/spec/client_server/r0.5.0#key-verification-framework + * + * Verifying keys manually by reading out the Ed25519 key is not very user friendly, and can lead to errors. + * SAS verification is a user-friendly key verification process. + * SAS verification is intended to be a highly interactive process for users, + * and as such exposes verification methods which are easier for users to use. + */ interface SasVerificationService { + fun addListener(listener: SasVerificationListener) fun removeListener(listener: SasVerificationListener) + /** + * Mark this device as verified manually + */ fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) fun getExistingTransaction(otherUser: String, tid: String): SasVerificationTransaction? + /** + * Shortcut for KeyVerificationStart.VERIF_METHOD_SAS + * @see beginKeyVerification + */ fun beginKeyVerificationSAS(userId: String, deviceID: String): String? + /** + * Request a key verification from another user using toDevice events. + */ fun beginKeyVerification(method: String, userId: String, deviceID: String): String? + fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback?) + // fun transactionUpdated(tx: SasVerificationTransaction) interface SasVerificationListener { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt index d24ccadb55..9610daf294 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationTransaction.kt @@ -17,7 +17,7 @@ package im.vector.matrix.android.api.session.crypto.sas interface SasVerificationTransaction { - val state: SasVerificationTxState + var state: SasVerificationTxState val cancelledReason: CancelCode? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt index 38c24fa89b..a8c6aed44e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt @@ -72,6 +72,7 @@ object EventType { const val KEY_VERIFICATION_KEY = "m.key.verification.key" const val KEY_VERIFICATION_MAC = "m.key.verification.mac" const val KEY_VERIFICATION_CANCEL = "m.key.verification.cancel" + const val KEY_VERIFICATION_DONE = "m.key.verification.done" // Relation Events const val REACTION = "m.reaction" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageRelationContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageRelationContent.kt new file mode 100644 index 0000000000..ec773916fd --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageRelationContent.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2019 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.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent + +@JsonClass(generateAdapter = true) +internal data class MessageRelationContent( + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageType.kt index 8cef40f21a..d4e6d5ea71 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageType.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageType.kt @@ -26,6 +26,7 @@ object MessageType { const val MSGTYPE_VIDEO = "m.video" const val MSGTYPE_LOCATION = "m.location" const val MSGTYPE_FILE = "m.file" + const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request" const val FORMAT_MATRIX_HTML = "org.matrix.custom.html" // Add, in local, a fake message type in order to StickerMessage can inherit Message class // Because sticker isn't a message type but a event type without msgtype field diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationAcceptContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationAcceptContent.kt new file mode 100644 index 0000000000..bda4b9f0ac --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationAcceptContent.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2019 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.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.events.model.RelationType +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent +import im.vector.matrix.android.internal.crypto.verification.AcceptVerifInfoFactory +import im.vector.matrix.android.internal.crypto.verification.VerifInfoAccept +import timber.log.Timber + +@JsonClass(generateAdapter = true) +internal data class MessageVerificationAcceptContent( + @Json(name = "hash") override val hash: String?, + @Json(name = "key_agreement_protocol") override val keyAgreementProtocol: String?, + @Json(name = "message_authentication_code") override val messageAuthenticationCode: String?, + @Json(name = "short_authentication_string") override val shortAuthenticationStrings: List?, + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?, + @Json(name = "commitment") override var commitment: String? = null +) : VerifInfoAccept { + + override val transactionID: String? + get() = relatesTo?.eventId + + override fun isValid(): Boolean { + if (transactionID.isNullOrBlank() + || keyAgreementProtocol.isNullOrBlank() + || hash.isNullOrBlank() + || commitment.isNullOrBlank() + || messageAuthenticationCode.isNullOrBlank() + || shortAuthenticationStrings.isNullOrEmpty()) { + Timber.e("## received invalid verification request") + return false + } + return true + } + + override fun toEventContent() = this.toContent() + + companion object : AcceptVerifInfoFactory { + + override fun create(tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List): VerifInfoAccept { + return MessageVerificationAcceptContent( + hash, + keyAgreementProtocol, + messageAuthenticationCode, + shortAuthenticationStrings, + RelationDefaultContent( + RelationType.REFERENCE, + tid + ), + commitment + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationCancelContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationCancelContent.kt new file mode 100644 index 0000000000..08fc3cbdbb --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationCancelContent.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2019 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.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.crypto.sas.CancelCode +import im.vector.matrix.android.api.session.events.model.RelationType +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent +import im.vector.matrix.android.internal.crypto.verification.VerifInfoCancel + +@JsonClass(generateAdapter = true) +internal data class MessageVerificationCancelContent( + @Json(name = "code") override val code: String? = null, + @Json(name = "reason") override val reason: String? = null, + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? + +) : VerifInfoCancel { + + override val transactionID: String? + get() = relatesTo?.eventId + + override fun toEventContent() = this.toContent() + + override fun isValid(): Boolean { + if (transactionID.isNullOrBlank() || code.isNullOrBlank()) { + return false + } + return true + } + + companion object { + fun create(transactionId: String, reason: CancelCode): MessageVerificationCancelContent { + return MessageVerificationCancelContent( + reason.value, + reason.humanReadable, + RelationDefaultContent( + RelationType.REFERENCE, + transactionId + ) + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationDoneContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationDoneContent.kt new file mode 100644 index 0000000000..965fcb79bb --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationDoneContent.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2019 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.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent +import im.vector.matrix.android.internal.crypto.verification.VerificationInfo + +@JsonClass(generateAdapter = true) +internal data class MessageVerificationDoneContent( + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? +) : VerificationInfo { + override fun isValid() = true +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationKeyContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationKeyContent.kt new file mode 100644 index 0000000000..0b93e3299a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationKeyContent.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2019 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.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.events.model.RelationType +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent +import im.vector.matrix.android.internal.crypto.verification.VerifInfoKey +import im.vector.matrix.android.internal.crypto.verification.KeyVerifInfoFactory +import timber.log.Timber + +@JsonClass(generateAdapter = true) +internal data class MessageVerificationKeyContent( + /** + * The device’s ephemeral public key, as an unpadded base64 string + */ + @Json(name = "key") override val key: String? = null, + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? +) : VerifInfoKey { + + override val transactionID: String? + get() = relatesTo?.eventId + + override fun isValid(): Boolean { + if (transactionID.isNullOrBlank() || key.isNullOrBlank()) { + Timber.e("## received invalid verification request") + return false + } + return true + } + + override fun toEventContent() = this.toContent() + + companion object : KeyVerifInfoFactory { + + override fun create(tid: String, pubKey: String): VerifInfoKey { + return MessageVerificationKeyContent( + pubKey, + RelationDefaultContent( + RelationType.REFERENCE, + tid + ) + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationMacContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationMacContent.kt new file mode 100644 index 0000000000..92ea4bca52 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationMacContent.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2019 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.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.events.model.RelationType +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent +import im.vector.matrix.android.internal.crypto.verification.VerifInfoMac +import im.vector.matrix.android.internal.crypto.verification.VerifInfoMacFactory + +@JsonClass(generateAdapter = true) +internal data class MessageVerificationMacContent( + @Json(name = "mac") override val mac: Map? = null, + @Json(name = "keys") override val keys: String? = null, + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? +) : VerifInfoMac { + + override val transactionID: String? + get() = relatesTo?.eventId + + override fun toEventContent() = this.toContent() + + override fun isValid(): Boolean { + if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) { + return false + } + return true + } + + companion object : VerifInfoMacFactory { + override fun create(tid: String, mac: Map, keys: String): VerifInfoMac { + return MessageVerificationMacContent( + mac, + keys, + RelationDefaultContent( + RelationType.REFERENCE, + tid + ) + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationRequestContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationRequestContent.kt new file mode 100644 index 0000000000..afefa39847 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationRequestContent.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2019 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.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.events.model.Content +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent + +@JsonClass(generateAdapter = true) +class MessageVerificationRequestContent( + @Json(name = "msgtype") override val type: String = MessageType.MSGTYPE_VERIFICATION_REQUEST, + @Json(name = "body") override val body: String, + @Json(name = "from_device") val fromDevice: String, + @Json(name = "methods") val methods: List, + @Json(name = "to") val to: String, + // @Json(name = "timestamp") val timestamp: Int, + @Json(name = "format") val format: String? = null, + @Json(name = "formatted_body") val formattedBody: String? = null, + @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, + @Json(name = "m.new_content") override val newContent: Content? = null +) : MessageContent diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt new file mode 100644 index 0000000000..f6ec00ffb2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2019 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.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.crypto.sas.SasMode +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart +import im.vector.matrix.android.internal.crypto.verification.SASVerificationTransaction +import im.vector.matrix.android.internal.crypto.verification.VerifInfoStart +import im.vector.matrix.android.internal.util.JsonCanonicalizer +import timber.log.Timber + +@JsonClass(generateAdapter = true) +data class MessageVerificationStartContent( + @Json(name = "from_device") override val fromDevice: String?, + @Json(name = "hashes") override val hashes: List?, + @Json(name = "key_agreement_protocols") override val keyAgreementProtocols: List?, + @Json(name = "message_authentication_codes") override val messageAuthenticationCodes: List?, + @Json(name = "short_authentication_string") override val shortAuthenticationStrings: List?, + @Json(name = "method") override val method: String?, + @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? +) : VerifInfoStart { + + override fun toCanonicalJson(): String? { + return JsonCanonicalizer.getCanonicalJson(MessageVerificationStartContent::class.java, this) + } + + override val transactionID: String? + get() = relatesTo?.eventId + + override fun isValid(): Boolean { + if ( + (transactionID.isNullOrBlank() || fromDevice.isNullOrBlank() || method != KeyVerificationStart.VERIF_METHOD_SAS || keyAgreementProtocols.isNullOrEmpty() || hashes.isNullOrEmpty()) + || !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty() + || (!messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256) && !messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256_LONGKDF)) + || shortAuthenticationStrings.isNullOrEmpty() + || !shortAuthenticationStrings.contains(SasMode.DECIMAL)) { + Timber.e("## received invalid verification request") + return false + } + return true + } + + override fun toEventContent() = this.toContent() +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt index a12f6e40ce..4243c6a464 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt @@ -177,6 +177,9 @@ internal abstract class CryptoModule { @Binds abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask + @Binds + abstract fun bindEncryptEventTask(encryptEventTask: DefaultEncryptEventTask): EncryptEventTask + @Binds abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice) : ClaimOneTimeKeysForUsersDeviceTask diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index c50b9e2e10..58ab8dda32 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -136,6 +136,10 @@ internal class DefaultCryptoService @Inject constructor( private val cryptoCoroutineScope: CoroutineScope ) : CryptoService { + init { + sasVerificationService.cryptoService = this + } + private val uiHandler = Handler(Looper.getMainLooper()) // MXEncrypting instance for each room. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationAccept.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationAccept.kt index 7be6f2042c..20d7682cb9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationAccept.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationAccept.kt @@ -17,13 +17,15 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.crypto.verification.VerifInfoAccept +import im.vector.matrix.android.internal.crypto.verification.AcceptVerifInfoFactory import timber.log.Timber /** * Sent by Bob to accept a verification from a previously sent m.key.verification.start message. */ @JsonClass(generateAdapter = true) -data class KeyVerificationAccept( +internal data class KeyVerificationAccept( /** * string to identify the transaction. @@ -31,39 +33,41 @@ data class KeyVerificationAccept( * Alice’s device should record this ID and use it in future messages in this transaction. */ @Json(name = "transaction_id") - var transactionID: String? = null, + override var transactionID: String? = null, /** * The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device */ @Json(name = "key_agreement_protocol") - var keyAgreementProtocol: String? = null, + override var keyAgreementProtocol: String? = null, /** * The hash algorithm that Bob’s device has selected to use, out of the list proposed by Alice’s device */ - var hash: String? = null, + @Json(name = "hash") + override var hash: String? = null, /** * The message authentication code that Bob’s device has selected to use, out of the list proposed by Alice’s device */ @Json(name = "message_authentication_code") - var messageAuthenticationCode: String? = null, + override var messageAuthenticationCode: String? = null, /** * An array of short authentication string methods that Bob’s client (and Bob) understands. Must be a subset of the list proposed by Alice’s device */ @Json(name = "short_authentication_string") - var shortAuthenticationStrings: List? = null, + override var shortAuthenticationStrings: List? = null, /** * The hash (encoded as unpadded base64) of the concatenation of the device’s ephemeral public key (QB, encoded as unpadded base64) * and the canonical JSON representation of the m.key.verification.start message. */ - var commitment: String? = null -) : SendToDeviceObject { + @Json(name = "commitment") + override var commitment: String? = null +) : SendToDeviceObject, VerifInfoAccept { - fun isValid(): Boolean { + override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || keyAgreementProtocol.isNullOrBlank() || hash.isNullOrBlank() @@ -76,13 +80,15 @@ data class KeyVerificationAccept( return true } - companion object { - fun create(tid: String, - keyAgreementProtocol: String, - hash: String, - commitment: String, - messageAuthenticationCode: String, - shortAuthenticationStrings: List): KeyVerificationAccept { + override fun toSendToDeviceObject() = this + + companion object : AcceptVerifInfoFactory { + override fun create(tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List): VerifInfoAccept { return KeyVerificationAccept().apply { this.transactionID = tid this.keyAgreementProtocol = keyAgreementProtocol diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationCancel.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationCancel.kt index b5c45e9566..7ffffbbfa1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationCancel.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationCancel.kt @@ -18,40 +18,43 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.crypto.sas.CancelCode +import im.vector.matrix.android.internal.crypto.verification.VerifInfoCancel /** * To device event sent by either party to cancel a key verification. */ @JsonClass(generateAdapter = true) -data class KeyVerificationCancel( +internal data class KeyVerificationCancel( /** * the transaction ID of the verification to cancel */ @Json(name = "transaction_id") - var transactionID: String? = null, + override val transactionID: String? = null, /** * machine-readable reason for cancelling, see #CancelCode */ - var code: String? = null, + override var code: String? = null, /** * human-readable reason for cancelling. This should only be used if the receiving client does not understand the code given. */ - var reason: String? = null -) : SendToDeviceObject { + override var reason: String? = null +) : SendToDeviceObject, VerifInfoCancel { companion object { fun create(tid: String, cancelCode: CancelCode): KeyVerificationCancel { - return KeyVerificationCancel().apply { - this.transactionID = tid - this.code = cancelCode.value - this.reason = cancelCode.humanReadable - } + return KeyVerificationCancel( + tid, + cancelCode.value, + cancelCode.humanReadable + ) } } - fun isValid(): Boolean { + override fun toSendToDeviceObject() = this + + override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || code.isNullOrBlank()) { return false } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationKey.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationKey.kt index 4c6243fee3..458c12743f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationKey.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationKey.kt @@ -17,37 +17,33 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.crypto.verification.KeyVerifInfoFactory +import im.vector.matrix.android.internal.crypto.verification.VerifInfoKey /** * Sent by both devices to send their ephemeral Curve25519 public key to the other device. */ @JsonClass(generateAdapter = true) -data class KeyVerificationKey( +internal data class KeyVerificationKey( /** * the ID of the transaction that the message is part of */ - @Json(name = "transaction_id") - @JvmField - var transactionID: String? = null, + @Json(name = "transaction_id") override var transactionID: String? = null, /** * The device’s ephemeral public key, as an unpadded base64 string */ - @JvmField - var key: String? = null + @Json(name = "key") override val key: String? = null -) : SendToDeviceObject { +) : SendToDeviceObject, VerifInfoKey { - companion object { - fun create(tid: String, key: String): KeyVerificationKey { - return KeyVerificationKey().apply { - this.transactionID = tid - this.key = key - } + companion object : KeyVerifInfoFactory { + override fun create(tid: String, pubKey: String): KeyVerificationKey { + return KeyVerificationKey(tid, pubKey) } } - fun isValid(): Boolean { + override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || key.isNullOrBlank()) { return false } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationMac.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationMac.kt index 8732e366d2..d2c147e145 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationMac.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationMac.kt @@ -17,49 +17,32 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.crypto.verification.VerifInfoMac +import im.vector.matrix.android.internal.crypto.verification.VerifInfoMacFactory /** * Sent by both devices to send the MAC of their device key to the other device. */ @JsonClass(generateAdapter = true) -data class KeyVerificationMac( - /** - * the ID of the transaction that the message is part of - */ - @Json(name = "transaction_id") - var transactionID: String? = null, +internal data class KeyVerificationMac( + @Json(name = "transaction_id") override val transactionID: String? = null, + @Json(name = "mac") override val mac: Map? = null, + @Json(name = "key") override val keys: String? = null - /** - * A map of key ID to the MAC of the key, as an unpadded base64 string, calculated using the MAC key - */ - @JvmField - var mac: Map? = null, +) : SendToDeviceObject, VerifInfoMac { - /** - * The MAC of the comma-separated, sorted list of key IDs given in the mac property, - * as an unpadded base64 string, calculated using the MAC key. - * For example, if the mac property gives MACs for the keys ed25519:ABCDEFG and ed25519:HIJKLMN, then this property will - * give the MAC of the string “ed25519:ABCDEFG,ed25519:HIJKLMN”. - */ - @JvmField - var keys: String? = null - -) : SendToDeviceObject { - - fun isValid(): Boolean { + override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) { return false } return true } - companion object { - fun create(tid: String, mac: Map, keys: String): KeyVerificationMac { - return KeyVerificationMac().apply { - this.transactionID = tid - this.mac = mac - this.keys = keys - } + override fun toSendToDeviceObject(): SendToDeviceObject? = this + + companion object : VerifInfoMacFactory { + override fun create(tid: String, mac: Map, keys: String): VerifInfoMac { + return KeyVerificationMac(tid, mac, keys) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt new file mode 100644 index 0000000000..14954a17cd --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2019 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.internal.crypto.model.rest + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.crypto.verification.VerificationInfo + +/** + * Requests a key verification with another user's devices. + */ +@JsonClass(generateAdapter = true) +data class KeyVerificationRequest( + + @Json(name = "from_device") + val fromDevice: String, + /** The verification methods supported by the sender. */ + val methods: List = listOf(KeyVerificationStart.VERIF_METHOD_SAS), + /** + * The POSIX timestamp in milliseconds for when the request was made. + * If the request is in the future by more than 5 minutes or more than 10 minutes in the past, + * the message should be ignored by the receiver. + */ + val timestamp: Int, + + @Json(name = "transaction_id") + var transactionID: String? = null + +) : SendToDeviceObject, VerificationInfo { + + override fun isValid(): Boolean { + // TODO + return true + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt index 081b19161a..f7cc10a12b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt @@ -19,21 +19,27 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.crypto.sas.SasMode import im.vector.matrix.android.internal.crypto.verification.SASVerificationTransaction +import im.vector.matrix.android.internal.crypto.verification.VerifInfoStart +import im.vector.matrix.android.internal.util.JsonCanonicalizer import timber.log.Timber /** * Sent by Alice to initiate an interactive key verification. */ @JsonClass(generateAdapter = true) -class KeyVerificationStart : SendToDeviceObject { +class KeyVerificationStart : SendToDeviceObject, VerifInfoStart { + + override fun toCanonicalJson(): String? { + return JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, this) + } /** * Alice’s device ID */ @Json(name = "from_device") - var fromDevice: String? = null + override var fromDevice: String? = null - var method: String? = null + override var method: String? = null /** * String to identify the transaction. @@ -41,7 +47,7 @@ class KeyVerificationStart : SendToDeviceObject { * Alice’s device should record this ID and use it in future messages in this transaction. */ @Json(name = "transaction_id") - var transactionID: String? = null + override var transactionID: String? = null /** * An array of key agreement protocols that Alice’s client understands. @@ -49,13 +55,13 @@ class KeyVerificationStart : SendToDeviceObject { * Other methods may be defined in the future */ @Json(name = "key_agreement_protocols") - var keyAgreementProtocols: List? = null + override var keyAgreementProtocols: List? = null /** * An array of hashes that Alice’s client understands. * Must include “sha256”. Other methods may be defined in the future. */ - var hashes: List? = null + override var hashes: List? = null /** * An array of message authentication codes that Alice’s client understands. @@ -63,7 +69,7 @@ class KeyVerificationStart : SendToDeviceObject { * Other methods may be defined in the future. */ @Json(name = "message_authentication_codes") - var messageAuthenticationCodes: List? = null + override var messageAuthenticationCodes: List? = null /** * An array of short authentication string methods that Alice’s client (and Alice) understands. @@ -72,13 +78,13 @@ class KeyVerificationStart : SendToDeviceObject { * Other methods may be defined in the future */ @Json(name = "short_authentication_string") - var shortAuthenticationStrings: List? = null + override var shortAuthenticationStrings: List? = null companion object { const val VERIF_METHOD_SAS = "m.sas.v1" } - fun isValid(): Boolean { + override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || fromDevice.isNullOrBlank() || method != VERIF_METHOD_SAS @@ -95,4 +101,6 @@ class KeyVerificationStart : SendToDeviceObject { } return true } + + override fun toSendToDeviceObject() = this } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/EncryptEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/EncryptEventTask.kt new file mode 100644 index 0000000000..951bc6385a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/EncryptEventTask.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2019 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.internal.crypto.tasks + +import im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult +import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater +import im.vector.matrix.android.internal.task.Task +import im.vector.matrix.android.internal.util.awaitCallback +import javax.inject.Inject + +internal interface EncryptEventTask : Task { + data class Params(val roomId: String, + val event: Event, + /**Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to)*/ + val keepKeys: List? = null, + val crypto: CryptoService + ) +} + +internal class DefaultEncryptEventTask @Inject constructor( +// private val crypto: CryptoService + private val localEchoUpdater: LocalEchoUpdater +) : EncryptEventTask { + override suspend fun execute(params: EncryptEventTask.Params): Event { + if (!params.crypto.isRoomEncrypted(params.roomId)) return params.event + val localEvent = params.event + if (localEvent.eventId == null) { + throw IllegalArgumentException() + } + + localEchoUpdater.updateSendState(localEvent.eventId, SendState.ENCRYPTING) + + val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf() + params.keepKeys?.forEach { + localMutableContent.remove(it) + } + +// try { + awaitCallback { + params.crypto.encryptEventContent(localMutableContent, localEvent.type, params.roomId, it) + }.let { result -> + val modifiedContent = HashMap(result.eventContent) + params.keepKeys?.forEach { toKeep -> + localEvent.content?.get(toKeep)?.let { + // put it back in the encrypted thing + modifiedContent[toKeep] = it + } + } + val safeResult = result.copy(eventContent = modifiedContent) + return localEvent.copy( + type = safeResult.eventType, + content = safeResult.eventContent + ) + } +// } catch (throwable: Throwable) { +// val sendState = when (throwable) { +// is Failure.CryptoError -> SendState.FAILED_UNKNOWN_DEVICES +// else -> SendState.UNDELIVERED +// } +// localEchoUpdater.updateSendState(localEvent.eventId, sendState) +// throw throwable +// } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RequestVerificationDMTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RequestVerificationDMTask.kt new file mode 100644 index 0000000000..57d225a193 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RequestVerificationDMTask.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2019 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.internal.crypto.tasks + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory +import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater +import im.vector.matrix.android.internal.session.room.send.SendResponse +import im.vector.matrix.android.internal.task.Task +import javax.inject.Inject + +internal interface RequestVerificationDMTask : Task { + data class Params( + val roomId: String, + val from: String, + val methods: List, + val to: String, + val cryptoService: CryptoService + ) +} + +internal class DefaultRequestVerificationDMTask @Inject constructor( + private val localEchoUpdater: LocalEchoUpdater, + private val localEchoEventFactory: LocalEchoEventFactory, + private val encryptEventTask: DefaultEncryptEventTask, + private val monarchy: Monarchy, + private val roomAPI: RoomAPI) + : RequestVerificationDMTask { + + override suspend fun execute(params: RequestVerificationDMTask.Params): SendResponse { + val event = createRequestEvent(params) + val localID = event.eventId!! + + try { + localEchoUpdater.updateSendState(localID, SendState.SENDING) + val executeRequest = executeRequest { + apiCall = roomAPI.send( + localID, + roomId = params.roomId, + content = event.content, + eventType = event.type // message or room.encrypted + ) + } + localEchoUpdater.updateSendState(localID, SendState.SENT) + return executeRequest + } catch (e: Throwable) { + localEchoUpdater.updateSendState(localID, SendState.UNDELIVERED) + throw e + } + } + + private suspend fun createRequestEvent(params: RequestVerificationDMTask.Params): Event { + val event = localEchoEventFactory.createVerificationRequest(params.roomId, params.from, params.to, params.methods).also { + localEchoEventFactory.saveLocalEcho(monarchy, it) + } + if (params.cryptoService.isRoomEncrypted(params.roomId)) { + try { + return encryptEventTask.execute(EncryptEventTask.Params( + params.roomId, + event, + listOf("m.relates_to"), + params.cryptoService + )) + } catch (throwable: Throwable) { + // We said it's ok to send verification request in clear + } + } + return event + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendVerificationMessageTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendVerificationMessageTask.kt new file mode 100644 index 0000000000..b850a1a1e6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/SendVerificationMessageTask.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2019 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.internal.crypto.tasks + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.events.model.Content +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.LocalEcho +import im.vector.matrix.android.api.session.events.model.UnsignedData +import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory +import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater +import im.vector.matrix.android.internal.session.room.send.SendResponse +import im.vector.matrix.android.internal.task.Task +import javax.inject.Inject + +internal interface SendVerificationMessageTask : Task { + data class Params( + val type: String, + val roomId: String, + val content: Content, + val cryptoService: CryptoService? + ) +} + +internal class DefaultSendVerificationMessageTask @Inject constructor( + private val localEchoUpdater: LocalEchoUpdater, + private val localEchoEventFactory: LocalEchoEventFactory, + private val encryptEventTask: DefaultEncryptEventTask, + private val monarchy: Monarchy, + @UserId private val userId: String, + private val roomAPI: RoomAPI) : SendVerificationMessageTask { + + override suspend fun execute(params: SendVerificationMessageTask.Params): SendResponse { + val event = createRequestEvent(params) + val localID = event.eventId!! + + try { + localEchoUpdater.updateSendState(localID, SendState.SENDING) + val executeRequest = executeRequest { + apiCall = roomAPI.send( + localID, + roomId = params.roomId, + content = event.content, + eventType = event.type + ) + } + localEchoUpdater.updateSendState(localID, SendState.SENT) + return executeRequest + } catch (e: Throwable) { + localEchoUpdater.updateSendState(localID, SendState.UNDELIVERED) + throw e + } + } + + private suspend fun createRequestEvent(params: SendVerificationMessageTask.Params): Event { + val localID = LocalEcho.createLocalEchoId() + val event = Event( + roomId = params.roomId, + originServerTs = System.currentTimeMillis(), + senderId = userId, + eventId = localID, + type = params.type, + content = params.content, + unsignedData = UnsignedData(age = null, transactionId = localID) + ).also { + localEchoEventFactory.saveLocalEcho(monarchy, it) + } + + if (params.cryptoService?.isRoomEncrypted(params.roomId) == true) { + try { + return encryptEventTask.execute(EncryptEventTask.Params( + params.roomId, + event, + listOf("m.relates_to"), + params.cryptoService + )) + } catch (throwable: Throwable) { + // We said it's ok to send verification request in clear + } + } + return event + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/IncomingSASVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt similarity index 79% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/IncomingSASVerificationTransaction.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt index 6ed5be4881..aac2b49f57 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/IncomingSASVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.crypto.verification import android.util.Base64 +import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction @@ -23,33 +24,20 @@ import im.vector.matrix.android.api.session.crypto.sas.SasMode import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore -import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.util.JsonCanonicalizer import timber.log.Timber -internal class IncomingSASVerificationTransaction( - private val sasVerificationService: DefaultSasVerificationService, - private val setDeviceVerificationAction: SetDeviceVerificationAction, - private val credentials: Credentials, +internal class DefaultIncomingSASVerificationTransaction( + setDeviceVerificationAction: SetDeviceVerificationAction, + override val credentials: Credentials, private val cryptoStore: IMXCryptoStore, - private val sendToDeviceTask: SendToDeviceTask, - private val taskExecutor: TaskExecutor, deviceFingerprint: String, transactionId: String, - otherUserID: String) - : SASVerificationTransaction( - sasVerificationService, + otherUserID: String +) : SASVerificationTransaction( setDeviceVerificationAction, credentials, cryptoStore, - sendToDeviceTask, - taskExecutor, deviceFingerprint, transactionId, otherUserID, @@ -78,10 +66,10 @@ internal class IncomingSASVerificationTransaction( } } - override fun onVerificationStart(startReq: KeyVerificationStart) { - Timber.v("## SAS received verification request from state $state") + override fun onVerificationStart(startReq: VerifInfoStart) { + Timber.v("## SAS I: received verification request from state $state") if (state != SasVerificationTxState.None) { - Timber.e("## received verification request from invalid state") + Timber.e("## SAS I: received verification request from invalid state") // should I cancel?? throw IllegalStateException("Interactive Key verification already started") } @@ -92,7 +80,7 @@ internal class IncomingSASVerificationTransaction( override fun performAccept() { if (state != SasVerificationTxState.OnStarted) { - Timber.e("## Cannot perform accept from state $state") + Timber.e("## SAS Cannot perform accept from state $state") return } @@ -109,7 +97,7 @@ internal class IncomingSASVerificationTransaction( if (listOf(agreedProtocol, agreedHash, agreedMac).any { it.isNullOrBlank() } || agreedShortCode.isNullOrEmpty()) { // Failed to find agreement - Timber.e("## Failed to find agreement ") + Timber.e("## SAS Failed to find agreement ") cancel(CancelCode.UnknownMethod) return } @@ -118,15 +106,15 @@ internal class IncomingSASVerificationTransaction( val mxDeviceInfo = cryptoStore.getUserDevice(deviceId = otherDeviceId!!, userId = otherUserId) if (mxDeviceInfo?.fingerprint() == null) { - Timber.e("## Failed to find device key ") + Timber.e("## SAS Failed to find device key ") // TODO force download keys!! // would be probably better to download the keys // for now I cancel cancel(CancelCode.User) } else { - // val otherKey = info.identityKey() + // val otherKey = info.identityKey() // need to jump back to correct thread - val accept = KeyVerificationAccept.create( + val accept = transport.createAccept( tid = transactionId, keyAgreementProtocol = agreedProtocol!!, hash = agreedHash!!, @@ -138,13 +126,13 @@ internal class IncomingSASVerificationTransaction( } } - private fun doAccept(accept: KeyVerificationAccept) { + private fun doAccept(accept: VerifInfoAccept) { this.accepted = accept - Timber.v("## SAS accept request id:$transactionId") + Timber.v("## SAS incoming accept request id:$transactionId") // The hash commitment is the hash (using the selected hash algorithm) of the unpadded base64 representation of QB, // concatenated with the canonical JSON representation of the content of the m.key.verification.start message - val concat = getSAS().publicKey + JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, startReq!!) + val concat = getSAS().publicKey + startReq!!.toCanonicalJson() accept.commitment = hashUsingAgreedHashMethod(concat) ?: "" // we need to send this to other device now state = SasVerificationTxState.SendingAccept @@ -156,15 +144,15 @@ internal class IncomingSASVerificationTransaction( } } - override fun onVerificationAccept(accept: KeyVerificationAccept) { + override fun onVerificationAccept(accept: VerifInfoAccept) { Timber.v("## SAS invalid message for incoming request id:$transactionId") cancel(CancelCode.UnexpectedMessage) } - override fun onKeyVerificationKey(userId: String, vKey: KeyVerificationKey) { + override fun onKeyVerificationKey(userId: String, vKey: VerifInfoKey) { Timber.v("## SAS received key for request id:$transactionId") if (state != SasVerificationTxState.SendingAccept && state != SasVerificationTxState.Accepted) { - Timber.e("## received key from invalid state $state") + Timber.e("## SAS received key from invalid state $state") cancel(CancelCode.UnexpectedMessage) return } @@ -175,7 +163,7 @@ internal class IncomingSASVerificationTransaction( // sending Bob’s public key QB val pubKey = getSAS().publicKey - val keyToDevice = KeyVerificationKey.create(transactionId, pubKey) + val keyToDevice = transport.createKey(transactionId, pubKey) // we need to send this to other device now state = SasVerificationTxState.SendingKey this.sendToOther(EventType.KEY_VERIFICATION_KEY, keyToDevice, SasVerificationTxState.KeySent, CancelCode.User) { @@ -206,14 +194,16 @@ internal class IncomingSASVerificationTransaction( // emoji: generate six bytes by using HKDF. shortCodeBytes = getSAS().generateShortCode(sasInfo, 6) - Timber.e("************ BOB CODE ${getDecimalCodeRepresentation(shortCodeBytes!!)}") - Timber.e("************ BOB EMOJI CODE ${getShortCodeRepresentation(SasMode.EMOJI)}") + if (BuildConfig.LOG_PRIVATE_DATA) { + Timber.v("************ BOB CODE ${getDecimalCodeRepresentation(shortCodeBytes!!)}") + Timber.v("************ BOB EMOJI CODE ${getShortCodeRepresentation(SasMode.EMOJI)}") + } state = SasVerificationTxState.ShortCodeReady } - override fun onKeyVerificationMac(vKey: KeyVerificationMac) { - Timber.v("## SAS received mac for request id:$transactionId") + override fun onKeyVerificationMac(vKey: VerifInfoMac) { + Timber.v("## SAS I: received mac for request id:$transactionId") // Check for state? if (state != SasVerificationTxState.SendingKey && state != SasVerificationTxState.KeySent @@ -221,7 +211,7 @@ internal class IncomingSASVerificationTransaction( && state != SasVerificationTxState.ShortCodeAccepted && state != SasVerificationTxState.SendingMac && state != SasVerificationTxState.MacSent) { - Timber.e("## received key from invalid state $state") + Timber.e("## SAS I: received key from invalid state $state") cancel(CancelCode.UnexpectedMessage) return } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/OutgoingSASVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt similarity index 79% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/OutgoingSASVerificationRequest.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt index cade637cce..4362e897c8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/OutgoingSASVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt @@ -21,34 +21,22 @@ import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRe import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore -import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.util.JsonCanonicalizer import timber.log.Timber -internal class OutgoingSASVerificationRequest( - private val sasVerificationService: DefaultSasVerificationService, - private val setDeviceVerificationAction: SetDeviceVerificationAction, - private val credentials: Credentials, - private val cryptoStore: IMXCryptoStore, - private val sendToDeviceTask: SendToDeviceTask, - private val taskExecutor: TaskExecutor, +internal class DefaultOutgoingSASVerificationRequest( + setDeviceVerificationAction: SetDeviceVerificationAction, + credentials: Credentials, + cryptoStore: IMXCryptoStore, deviceFingerprint: String, transactionId: String, otherUserId: String, - otherDeviceId: String) - : SASVerificationTransaction( - sasVerificationService, + otherDeviceId: String +) : SASVerificationTransaction( setDeviceVerificationAction, credentials, cryptoStore, - sendToDeviceTask, - taskExecutor, deviceFingerprint, transactionId, otherUserId, @@ -78,14 +66,14 @@ internal class OutgoingSASVerificationRequest( } } - override fun onVerificationStart(startReq: KeyVerificationStart) { - Timber.e("## onVerificationStart - unexpected id:$transactionId") + override fun onVerificationStart(startReq: VerifInfoStart) { + Timber.e("## SAS O: onVerificationStart - unexpected id:$transactionId") cancel(CancelCode.UnexpectedMessage) } fun start() { if (state != SasVerificationTxState.None) { - Timber.e("## start verification from invalid state") + Timber.e("## SAS O: start verification from invalid state") // should I cancel?? throw IllegalStateException("Interactive Key verification already started") } @@ -111,10 +99,33 @@ internal class OutgoingSASVerificationRequest( ) } - override fun onVerificationAccept(accept: KeyVerificationAccept) { - Timber.v("## onVerificationAccept id:$transactionId") +// fun request() { +// if (state != SasVerificationTxState.None) { +// Timber.e("## start verification from invalid state") +// // should I cancel?? +// throw IllegalStateException("Interactive Key verification already started") +// } +// +// val requestMessage = KeyVerificationRequest( +// fromDevice = session.sessionParams.credentials.deviceId ?: "", +// methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS), +// timestamp = System.currentTimeMillis().toInt(), +// transactionID = transactionId +// ) +// +// sendToOther( +// EventType.KEY_VERIFICATION_REQUEST, +// requestMessage, +// SasVerificationTxState.None, +// CancelCode.User, +// null +// ) +// } + + override fun onVerificationAccept(accept: VerifInfoAccept) { + Timber.v("## SAS O: onVerificationAccept id:$transactionId") if (state != SasVerificationTxState.Started) { - Timber.e("## received accept request from invalid state $state") + Timber.e("## SAS O: received accept request from invalid state $state") cancel(CancelCode.UnexpectedMessage) return } @@ -123,7 +134,7 @@ internal class OutgoingSASVerificationRequest( || !KNOWN_HASHES.contains(accept.hash) || !KNOWN_MACS.contains(accept.messageAuthenticationCode) || accept.shortAuthenticationStrings!!.intersect(KNOWN_SHORT_CODES).isEmpty()) { - Timber.e("## received accept request from invalid state") + Timber.e("## SAS O: received accept request from invalid state") cancel(CancelCode.UnknownMethod) return } @@ -137,7 +148,7 @@ internal class OutgoingSASVerificationRequest( // and replies with a to_device message with type set to “m.key.verification.key”, sending Alice’s public key QA val pubKey = getSAS().publicKey - val keyToDevice = KeyVerificationKey.create(transactionId, pubKey) + val keyToDevice = transport.createKey(transactionId, pubKey) // we need to send this to other device now state = SasVerificationTxState.SendingKey sendToOther(EventType.KEY_VERIFICATION_KEY, keyToDevice, SasVerificationTxState.KeySent, CancelCode.User) { @@ -148,8 +159,8 @@ internal class OutgoingSASVerificationRequest( } } - override fun onKeyVerificationKey(userId: String, vKey: KeyVerificationKey) { - Timber.v("## onKeyVerificationKey id:$transactionId") + override fun onKeyVerificationKey(userId: String, vKey: VerifInfoKey) { + Timber.v("## SAS O: onKeyVerificationKey id:$transactionId") if (state != SasVerificationTxState.SendingKey && state != SasVerificationTxState.KeySent) { Timber.e("## received key from invalid state $state") cancel(CancelCode.UnexpectedMessage) @@ -163,7 +174,7 @@ internal class OutgoingSASVerificationRequest( // in Bob’s m.key.verification.key and the content of Alice’s m.key.verification.start message. // check commitment - val concat = vKey.key + JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, startReq!!) + val concat = vKey.key + startReq!!.toCanonicalJson() val otherCommitment = hashUsingAgreedHashMethod(concat) ?: "" if (accepted!!.commitment.equals(otherCommitment)) { @@ -190,14 +201,14 @@ internal class OutgoingSASVerificationRequest( } } - override fun onKeyVerificationMac(vKey: KeyVerificationMac) { - Timber.v("## onKeyVerificationMac id:$transactionId") + override fun onKeyVerificationMac(vKey: VerifInfoMac) { + Timber.v("## SAS O: onKeyVerificationMac id:$transactionId") if (state != SasVerificationTxState.OnKeyReceived && state != SasVerificationTxState.ShortCodeReady && state != SasVerificationTxState.ShortCodeAccepted && state != SasVerificationTxState.SendingMac && state != SasVerificationTxState.MacSent) { - Timber.e("## received key from invalid state $state") + Timber.e("## SAS O: received key from invalid state $state") cancel(CancelCode.UnexpectedMessage) return } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index e0cd47e0e0..6552dca7ab 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -21,6 +21,7 @@ import android.os.Looper import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState @@ -28,6 +29,7 @@ import im.vector.matrix.android.api.session.crypto.sas.safeValueOf import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction @@ -35,24 +37,23 @@ import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.rest.* import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore +import im.vector.matrix.android.internal.crypto.tasks.DefaultRequestVerificationDMTask +import im.vector.matrix.android.internal.crypto.tasks.RequestVerificationDMTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.session.SessionScope +import im.vector.matrix.android.internal.session.room.send.SendResponse +import im.vector.matrix.android.internal.task.TaskConstraints import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import timber.log.Timber -import java.lang.Exception -import java.util.UUID +import java.util.* import javax.inject.Inject +import kotlin.collections.ArrayList import kotlin.collections.HashMap - -/** - * Manages all current verifications transactions with short codes. - * Short codes interactive verification is a more user friendly way of verifying devices - * that is still maintaining a good level of security (alternative to the 43-character strings compare method). - */ +import kotlin.collections.set @SessionScope internal class DefaultSasVerificationService @Inject constructor(private val credentials: Credentials, @@ -61,12 +62,18 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre private val deviceListManager: DeviceListManager, private val setDeviceVerificationAction: SetDeviceVerificationAction, private val sendToDeviceTask: SendToDeviceTask, + private val requestVerificationDMTask: DefaultRequestVerificationDMTask, private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val sasTransportRoomMessageFactory: SasTransportRoomMessageFactory, + private val sasToDeviceTransportFactory: SasToDeviceTransportFactory, private val taskExecutor: TaskExecutor) : VerificationTransaction.Listener, SasVerificationService { private val uiHandler = Handler(Looper.getMainLooper()) + // Cannot be injected in constructor as it creates a dependency cycle + lateinit var cryptoService: CryptoService + // map [sender : [transaction]] private val txMap = HashMap>() @@ -96,6 +103,39 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre } } + fun onRoomEvent(event: Event) { + GlobalScope.launch(coroutineDispatchers.crypto) { + when (event.getClearType()) { + EventType.KEY_VERIFICATION_START -> { + onRoomStartRequestReceived(event) + } + EventType.KEY_VERIFICATION_CANCEL -> { + onRoomCancelReceived(event) + } + EventType.KEY_VERIFICATION_ACCEPT -> { + onRoomAcceptReceived(event) + } + EventType.KEY_VERIFICATION_KEY -> { + onRoomKeyRequestReceived(event) + } + EventType.KEY_VERIFICATION_MAC -> { + onRoomMacReceived(event) + } + EventType.KEY_VERIFICATION_DONE -> { + // TODO? + } + EventType.MESSAGE -> { + if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel()?.type) { + onRoomRequestReceived(event) + } + } + else -> { + // ignore + } + } + } + } + private var listeners = ArrayList() override fun addListener(listener: SasVerificationService.SasVerificationListener) { @@ -150,15 +190,64 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre } } + fun onRoomRequestReceived(event: Event) { + // TODO + Timber.v("## SAS Verification request from ${event.senderId} in room ${event.roomId}") + } + + private suspend fun onRoomStartRequestReceived(event: Event) { + val startReq = event.getClearContent().toModel() + ?.copy( + // relates_to is in clear in encrypted payload + relatesTo = event.content.toModel()?.relatesTo + ) + + val otherUserId = event.senderId + if (startReq?.isValid()?.not() == true) { + Timber.e("## received invalid verification request") + if (startReq.transactionID != null) { + sasTransportRoomMessageFactory.createTransport(event.roomId + ?: "", cryptoService).cancelTransaction( + startReq.transactionID ?: "", + otherUserId!!, + startReq.fromDevice ?: event.getSenderKey()!!, + CancelCode.UnknownMethod + ) + } + return + } + + handleStart(otherUserId, startReq as VerifInfoStart) { + it.transport = sasTransportRoomMessageFactory.createTransport(event.roomId + ?: "", cryptoService) + }?.let { + sasTransportRoomMessageFactory.createTransport(event.roomId + ?: "", cryptoService).cancelTransaction( + startReq.transactionID ?: "", + otherUserId!!, + startReq.fromDevice ?: event.getSenderKey()!!, + it + ) + } + } + private suspend fun onStartRequestReceived(event: Event) { + Timber.e("## SAS received Start request ${event.eventId}") val startReq = event.getClearContent().toModel()!! + Timber.v("## SAS received Start request $startReq") val otherUserId = event.senderId if (!startReq.isValid()) { - Timber.e("## received invalid verification request") + Timber.e("## SAS received invalid verification request") if (startReq.transactionID != null) { - cancelTransaction( - startReq.transactionID!!, +// cancelTransaction( +// startReq.transactionID!!, +// otherUserId!!, +// startReq.fromDevice ?: event.getSenderKey()!!, +// CancelCode.UnknownMethod +// ) + sasToDeviceTransportFactory.createTransport(null).cancelTransaction( + startReq.transactionID ?: "", otherUserId!!, startReq.fromDevice ?: event.getSenderKey()!!, CancelCode.UnknownMethod @@ -167,8 +256,22 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre return } // Download device keys prior to everything + handleStart(otherUserId, startReq) { + it.transport = sasToDeviceTransportFactory.createTransport(it) + }?.let { + sasToDeviceTransportFactory.createTransport(null).cancelTransaction( + startReq.transactionID ?: "", + otherUserId!!, + startReq.fromDevice ?: event.getSenderKey()!!, + it + ) + } + } + + private suspend fun handleStart(otherUserId: String?, startReq: VerifInfoStart, txConfigure: (SASVerificationTransaction) -> Unit): CancelCode? { + Timber.d("## SAS onStartRequestReceived ${startReq.transactionID!!}") if (checkKeysAreDownloaded(otherUserId!!, startReq) != null) { - Timber.v("## SAS onStartRequestReceived ${startReq.transactionID!!}") + Timber.v("## SAS onStartRequestReceived $startReq") val tid = startReq.transactionID!! val existing = getExistingTransaction(otherUserId, tid) val existingTxs = getExistingTransactionsForUser(otherUserId) @@ -176,43 +279,46 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre // should cancel both! Timber.v("## SAS onStartRequestReceived - Request exist with same if ${startReq.transactionID!!}") existing.cancel(CancelCode.UnexpectedMessage) - cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) + return CancelCode.UnexpectedMessage + // cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) } else if (existingTxs?.isEmpty() == false) { Timber.v("## SAS onStartRequestReceived - There is already a transaction with this user ${startReq.transactionID!!}") // Multiple keyshares between two devices: any two devices may only have at most one key verification in flight at a time. existingTxs.forEach { it.cancel(CancelCode.UnexpectedMessage) } - cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) + return CancelCode.UnexpectedMessage + // cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) } else { // Ok we can create if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) { Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}") - val tx = IncomingSASVerificationTransaction( - this, + val tx = DefaultIncomingSASVerificationTransaction( +// this, setDeviceVerificationAction, credentials, cryptoStore, - sendToDeviceTask, - taskExecutor, myDeviceInfoHolder.get().myDevice.fingerprint()!!, startReq.transactionID!!, - otherUserId) + otherUserId).also { txConfigure(it) } addTransaction(tx) - tx.acceptToDeviceEvent(otherUserId, startReq) + tx.acceptVerificationEvent(otherUserId, startReq) } else { Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}") - cancelTransaction(tid, otherUserId, startReq.fromDevice - ?: event.getSenderKey()!!, CancelCode.UnknownMethod) + return CancelCode.UnknownMethod + // cancelTransaction(tid, otherUserId, startReq.fromDevice +// ?: event.getSenderKey()!!, CancelCode.UnknownMethod) } } } else { - cancelTransaction(startReq.transactionID!!, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) + return CancelCode.UnexpectedMessage +// cancelTransaction(startReq.transactionID!!, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage) } + return null } private suspend fun checkKeysAreDownloaded(otherUserId: String, - startReq: KeyVerificationStart): MXUsersDevicesMap? { + startReq: VerifInfoStart): MXUsersDevicesMap? { return try { val keys = deviceListManager.downloadKeys(listOf(otherUserId), true) val deviceIds = keys.getUserDeviceIds(otherUserId) ?: return null @@ -222,17 +328,36 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre } } - private suspend fun onCancelReceived(event: Event) { + private fun onRoomCancelReceived(event: Event) { + val cancelReq = event.getClearContent().toModel() + ?.copy( + // relates_to is in clear in encrypted payload + relatesTo = event.content.toModel()?.relatesTo + ) + if (cancelReq == null || cancelReq.isValid().not()) { + // ignore + Timber.e("## SAS Received invalid key request") + // TODO should we cancel? + return + } + handleOnCancel(event.senderId!!, cancelReq) + } + + private fun onCancelReceived(event: Event) { Timber.v("## SAS onCancelReceived") val cancelReq = event.getClearContent().toModel()!! if (!cancelReq.isValid()) { // ignore - Timber.e("## Received invalid accept request") + Timber.e("## SAS Received invalid accept request") return } val otherUserId = event.senderId!! + handleOnCancel(otherUserId, cancelReq) + } + + private fun handleOnCancel(otherUserId: String, cancelReq: VerifInfoCancel) { Timber.v("## SAS onCancelReceived otherUser:$otherUserId reason:${cancelReq.reason}") val existing = getExistingTransaction(otherUserId, cancelReq.transactionID!!) if (existing == null) { @@ -245,65 +370,119 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre } } - private suspend fun onAcceptReceived(event: Event) { - val acceptReq = event.getClearContent().toModel()!! + private fun onRoomAcceptReceived(event: Event) { + Timber.d("## SAS Received Accept via DM $event") + val accept = event.getClearContent().toModel() + ?.copy( + // relates_to is in clear in encrypted payload + relatesTo = event.content.toModel()?.relatesTo + ) + ?: return + handleAccept(accept, event.senderId!!) + } + private fun onAcceptReceived(event: Event) { + Timber.d("## SAS Received Accept $event") + val acceptReq = event.getClearContent().toModel() ?: return + handleAccept(acceptReq, event.senderId!!) + } + + private fun handleAccept(acceptReq: VerifInfoAccept, senderId: String) { if (!acceptReq.isValid()) { // ignore - Timber.e("## Received invalid accept request") + Timber.e("## SAS Received invalid accept request") return } - val otherUserId = event.senderId!! + val otherUserId = senderId val existing = getExistingTransaction(otherUserId, acceptReq.transactionID!!) if (existing == null) { - Timber.e("## Received invalid accept request") + Timber.e("## SAS Received invalid accept request") return } if (existing is SASVerificationTransaction) { - existing.acceptToDeviceEvent(otherUserId, acceptReq) + existing.acceptVerificationEvent(otherUserId, acceptReq) } else { // not other types now } } - private suspend fun onKeyReceived(event: Event) { + private fun onRoomKeyRequestReceived(event: Event) { + val keyReq = event.getClearContent().toModel() + ?.copy( + // relates_to is in clear in encrypted payload + relatesTo = event.content.toModel()?.relatesTo + ) + if (keyReq == null || keyReq.isValid().not()) { + // ignore + Timber.e("## SAS Received invalid key request") + // TODO should we cancel? + return + } + handleKeyReceived(event, keyReq) + } + + private fun onKeyReceived(event: Event) { val keyReq = event.getClearContent().toModel()!! if (!keyReq.isValid()) { // ignore - Timber.e("## Received invalid key request") + Timber.e("## SAS Received invalid key request") return } + handleKeyReceived(event, keyReq) + } + + private fun handleKeyReceived(event: Event, keyReq: VerifInfoKey) { + Timber.d("## SAS Received Key from ${event.senderId} with info $keyReq") val otherUserId = event.senderId!! val existing = getExistingTransaction(otherUserId, keyReq.transactionID!!) if (existing == null) { - Timber.e("## Received invalid accept request") + Timber.e("## SAS Received invalid accept request") return } if (existing is SASVerificationTransaction) { - existing.acceptToDeviceEvent(otherUserId, keyReq) + existing.acceptVerificationEvent(otherUserId, keyReq) } else { // not other types now } } - private suspend fun onMacReceived(event: Event) { - val macReq = event.getClearContent().toModel()!! - - if (!macReq.isValid()) { + private fun onRoomMacReceived(event: Event) { + val macReq = event.getClearContent().toModel() + ?.copy( + // relates_to is in clear in encrypted payload + relatesTo = event.content.toModel()?.relatesTo + ) + if (macReq == null || macReq.isValid().not() || event.senderId == null) { // ignore - Timber.e("## Received invalid key request") + Timber.e("## SAS Received invalid mac request") + // TODO should we cancel? return } - val otherUserId = event.senderId!! - val existing = getExistingTransaction(otherUserId, macReq.transactionID!!) + handleMacReceived(event.senderId, macReq) + } + + private fun onMacReceived(event: Event) { + val macReq = event.getClearContent().toModel()!! + + if (!macReq.isValid() || event.senderId == null) { + // ignore + Timber.e("## SAS Received invalid mac request") + return + } + handleMacReceived(event.senderId, macReq) + } + + private fun handleMacReceived(senderId: String, macReq: VerifInfoMac) { + Timber.v("## SAS Received $macReq") + val existing = getExistingTransaction(senderId, macReq.transactionID!!) if (existing == null) { - Timber.e("## Received invalid accept request") + Timber.e("## SAS Received invalid accept request") return } if (existing is SASVerificationTransaction) { - existing.acceptToDeviceEvent(otherUserId, macReq) + existing.acceptVerificationEvent(senderId, macReq) } else { // not other types known for now } @@ -346,13 +525,10 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre val txID = createUniqueIDForTransaction(userId, deviceID) // should check if already one (and cancel it) if (KeyVerificationStart.VERIF_METHOD_SAS == method) { - val tx = OutgoingSASVerificationRequest( - this, + val tx = DefaultOutgoingSASVerificationRequest( setDeviceVerificationAction, credentials, cryptoStore, - sendToDeviceTask, - taskExecutor, myDeviceInfoHolder.get().myDevice.fingerprint()!!, txID, userId, @@ -366,6 +542,30 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre } } + override fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback?) { + requestVerificationDMTask.configureWith( + RequestVerificationDMTask.Params( + roomId = roomId, + from = credentials.deviceId ?: "", + methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS), + to = userId, + cryptoService = cryptoService + ) + ) { + this.callback = object : MatrixCallback { + override fun onSuccess(data: SendResponse) { + callback?.onSuccess(data.eventId) + } + + override fun onFailure(failure: Throwable) { + callback?.onFailure(failure) + } + } + constraints = TaskConstraints(true) + retryCount = 3 + }.executeBy(taskExecutor) + } + /** * This string must be unique for the pair of users performing verification for the duration that the transaction is valid */ @@ -390,24 +590,28 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre this.removeTransaction(tx.otherUserId, tx.transactionId) } } - - fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) { - val cancelMessage = KeyVerificationCancel.create(transactionId, code) - val contentMap = MXUsersDevicesMap() - contentMap.setObject(userId, userDevice, cancelMessage) - - sendToDeviceTask - .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) { - this.callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { - Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}") - } - - override fun onFailure(failure: Throwable) { - Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.") - } - } - } - .executeBy(taskExecutor) - } +// +// fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode, roomId: String? = null) { +// val cancelMessage = KeyVerificationCancel.create(transactionId, code) +// val contentMap = MXUsersDevicesMap() +// contentMap.setObject(userId, userDevice, cancelMessage) +// +// if (roomId != null) { +// +// } else { +// sendToDeviceTask +// .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) { +// this.callback = object : MatrixCallback { +// override fun onSuccess(data: Unit) { +// Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}") +// } +// +// override fun onFailure(failure: Throwable) { +// Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.") +// } +// } +// } +// .executeBy(taskExecutor) +// } +// } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt index 589103d38a..443443afad 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.crypto.verification import android.os.Build -import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation @@ -26,13 +25,8 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXKey -import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap -import im.vector.matrix.android.internal.crypto.model.rest.* import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore -import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.extensions.toUnsignedInt -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.task.configureWith import org.matrix.olm.OlmSAS import org.matrix.olm.OlmUtility import timber.log.Timber @@ -42,12 +36,9 @@ import kotlin.properties.Delegates * Represents an ongoing short code interactive key verification between two devices. */ internal abstract class SASVerificationTransaction( - private val sasVerificationService: DefaultSasVerificationService, private val setDeviceVerificationAction: SetDeviceVerificationAction, - private val credentials: Credentials, + open val credentials: Credentials, private val cryptoStore: IMXCryptoStore, - private val sendToDeviceTask: SendToDeviceTask, - private val taskExecutor: TaskExecutor, private val deviceFingerprint: String, transactionId: String, otherUserId: String, @@ -55,6 +46,8 @@ internal abstract class SASVerificationTransaction( isIncoming: Boolean) : VerificationTransaction(transactionId, otherUserId, otherDevice, isIncoming) { + lateinit var transport: SasTransport + companion object { const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256" const val SAS_MAC_SHA256 = "hkdf-hmac-sha256" @@ -95,13 +88,13 @@ internal abstract class SASVerificationTransaction( private var olmSas: OlmSAS? = null - var startReq: KeyVerificationStart? = null - var accepted: KeyVerificationAccept? = null + var startReq: VerifInfoStart? = null + var accepted: VerifInfoAccept? = null var otherKey: String? = null var shortCodeBytes: ByteArray? = null - var myMac: KeyVerificationMac? = null - var theirMac: KeyVerificationMac? = null + var myMac: VerifInfoMac? = null + var theirMac: VerifInfoMac? = null fun getSAS(): OlmSAS { if (olmSas == null) olmSas = OlmSAS() @@ -160,7 +153,7 @@ internal abstract class SASVerificationTransaction( return } - val macMsg = KeyVerificationMac.create(transactionId, mapOf(keyId to macString), keyStrings) + val macMsg = transport.createMac(transactionId, mapOf(keyId to macString), keyStrings) myMac = macMsg state = SasVerificationTxState.SendingMac sendToOther(EventType.KEY_VERIFICATION_MAC, macMsg, SasVerificationTxState.MacSent, CancelCode.User) { @@ -176,25 +169,25 @@ internal abstract class SASVerificationTransaction( } // if not wait for it } - override fun acceptToDeviceEvent(senderId: String, event: SendToDeviceObject) { - when (event) { - is KeyVerificationStart -> onVerificationStart(event) - is KeyVerificationAccept -> onVerificationAccept(event) - is KeyVerificationKey -> onKeyVerificationKey(senderId, event) - is KeyVerificationMac -> onKeyVerificationMac(event) - else -> { + override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) { + when (info) { + is VerifInfoStart -> onVerificationStart(info) + is VerifInfoAccept -> onVerificationAccept(info) + is VerifInfoKey -> onKeyVerificationKey(senderId, info) + is VerifInfoMac -> onKeyVerificationMac(info) + else -> { // nop } } } - abstract fun onVerificationStart(startReq: KeyVerificationStart) + abstract fun onVerificationStart(startReq: VerifInfoStart) - abstract fun onVerificationAccept(accept: KeyVerificationAccept) + abstract fun onVerificationAccept(accept: VerifInfoAccept) - abstract fun onKeyVerificationKey(userId: String, vKey: KeyVerificationKey) + abstract fun onKeyVerificationKey(userId: String, vKey: VerifInfoKey) - abstract fun onKeyVerificationMac(vKey: KeyVerificationMac) + abstract fun onKeyVerificationMac(vKey: VerifInfoMac) protected fun verifyMacs() { Timber.v("## SAS verifying macs for id:$transactionId") @@ -245,7 +238,7 @@ internal abstract class SASVerificationTransaction( // if none of the keys could be verified, then error because the app // should be informed about that if (verifiedDevices.isEmpty()) { - Timber.e("Verification: No devices verified") + Timber.e("## SAS Verification: No devices verified") cancel(CancelCode.MismatchedKeys) return } @@ -254,6 +247,7 @@ internal abstract class SASVerificationTransaction( verifiedDevices.forEach { setDeviceVerified(it, otherUserId) } + transport.done(transactionId) state = SasVerificationTxState.Verified } @@ -270,41 +264,15 @@ internal abstract class SASVerificationTransaction( override fun cancel(code: CancelCode) { cancelledReason = code state = SasVerificationTxState.Cancelled - sasVerificationService.cancelTransaction( - transactionId, - otherUserId, - otherDeviceId ?: "", - code) + transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code) } protected fun sendToOther(type: String, - keyToDevice: Any, + keyToDevice: VerificationInfo, nextState: SasVerificationTxState, onErrorReason: CancelCode, onDone: (() -> Unit)?) { - val contentMap = MXUsersDevicesMap() - contentMap.setObject(otherUserId, otherDeviceId, keyToDevice) - - sendToDeviceTask - .configureWith(SendToDeviceTask.Params(type, contentMap, transactionId)) { - this.callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { - Timber.v("## SAS verification [$transactionId] toDevice type '$type' success.") - if (onDone != null) { - onDone() - } else { - state = nextState - } - } - - override fun onFailure(failure: Throwable) { - Timber.e("## SAS verification [$transactionId] failed to send toDevice in state : $state") - - cancel(onErrorReason) - } - } - } - .executeBy(taskExecutor) + transport.sendToOther(type, keyToDevice, nextState, onErrorReason, onDone) } fun getShortCodeRepresentation(shortAuthenticationStringMode: String): String? { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt new file mode 100644 index 0000000000..23ebd89f7a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2019 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.internal.crypto.verification + +import im.vector.matrix.android.api.session.crypto.sas.CancelCode +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState + +/** + * SAS verification can be performed using toDevice events or via DM. + * This class abstracts the concept of transport for SAS + */ +internal interface SasTransport { + + /** + * Sends a message + */ + fun sendToOther(type: String, + verificationInfo: VerificationInfo, + nextState: SasVerificationTxState, + onErrorReason: CancelCode, + onDone: (() -> Unit)?) + + fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) + + fun done(transactionId: String) + /** + * Creates an accept message suitable for this transport + */ + fun createAccept(tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List): VerifInfoAccept + + fun createKey(tid: String, + pubKey: String): VerifInfoKey + + fun createMac(tid: String, mac: Map, keys: String): VerifInfoMac +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt new file mode 100644 index 0000000000..1173914ba1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt @@ -0,0 +1,129 @@ +/* + * Copyright 2019 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.internal.crypto.verification + +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.crypto.sas.CancelCode +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.RelationType +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.message.* +import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent +import im.vector.matrix.android.internal.crypto.tasks.DefaultSendVerificationMessageTask +import im.vector.matrix.android.internal.crypto.tasks.SendVerificationMessageTask +import im.vector.matrix.android.internal.session.room.send.SendResponse +import im.vector.matrix.android.internal.task.TaskConstraints +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith +import timber.log.Timber +import javax.inject.Inject + +internal class SasTransportRoomMessage constructor( + private val roomId: String, + private val cryptoService: CryptoService, +// private val tx: SASVerificationTransaction?, + private val sendVerificationMessageTask: SendVerificationMessageTask, + private val taskExecutor: TaskExecutor +) : SasTransport { + + override fun sendToOther(type: String, verificationInfo: VerificationInfo, nextState: SasVerificationTxState, onErrorReason: CancelCode, onDone: (() -> Unit)?) { + Timber.d("## SAS sending msg type $type") + Timber.v("## SAS sending msg info $verificationInfo") + sendVerificationMessageTask.configureWith( + SendVerificationMessageTask.Params( + type, + roomId, + verificationInfo.toEventContent()!!, + cryptoService + ) + ) { + constraints = TaskConstraints(true) + retryCount = 3 + } + .executeBy(taskExecutor) + } + + override fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) { + Timber.d("## SAS canceling transaction $transactionId for reason $code") + sendVerificationMessageTask.configureWith( + SendVerificationMessageTask.Params( + EventType.KEY_VERIFICATION_CANCEL, + roomId, + MessageVerificationCancelContent.create(transactionId, code).toContent(), + cryptoService + ) + ) { + constraints = TaskConstraints(true) + retryCount = 3 + callback = object : MatrixCallback { + override fun onSuccess(data: SendResponse) { + Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}") + } + + override fun onFailure(failure: Throwable) { + Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.") + } + } + } + .executeBy(taskExecutor) + } + + override fun done(transactionId: String) { + sendVerificationMessageTask.configureWith( + SendVerificationMessageTask.Params( + EventType.KEY_VERIFICATION_DONE, + roomId, + MessageVerificationDoneContent( + relatesTo = RelationDefaultContent( + RelationType.REFERENCE, + transactionId + ) + ).toContent(), + cryptoService + ) + ) { + constraints = TaskConstraints(true) + retryCount = 3 + } + .executeBy(taskExecutor) + } + + override fun createAccept(tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List) + : VerifInfoAccept = MessageVerificationAcceptContent.create(tid, keyAgreementProtocol, hash, commitment, messageAuthenticationCode, shortAuthenticationStrings) + + override fun createKey(tid: String, pubKey: String): VerifInfoKey = MessageVerificationKeyContent.create(tid, pubKey) + + override fun createMac(tid: String, mac: Map, keys: String) = MessageVerificationMacContent.create(tid, mac, keys) +} + +internal class SasTransportRoomMessageFactory @Inject constructor( + private val sendVerificationMessageTask: DefaultSendVerificationMessageTask, + private val taskExecutor: TaskExecutor) { + + fun createTransport(roomId: String, + cryptoService: CryptoService +// tx: SASVerificationTransaction? + ): SasTransportRoomMessage { + return SasTransportRoomMessage(roomId, cryptoService, /*tx,*/ sendVerificationMessageTask, taskExecutor) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt new file mode 100644 index 0000000000..4f36a5218e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt @@ -0,0 +1,115 @@ +/* + * Copyright 2019 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.internal.crypto.verification + +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.crypto.sas.CancelCode +import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap +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.KeyVerificationKey +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac +import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith +import timber.log.Timber +import javax.inject.Inject + +internal class SasTransportToDevice( + private var tx: SASVerificationTransaction?, + private var sendToDeviceTask: SendToDeviceTask, + private var taskExecutor: TaskExecutor +) : SasTransport { + + override fun sendToOther(type: String, verificationInfo: VerificationInfo, nextState: SasVerificationTxState, onErrorReason: CancelCode, onDone: (() -> Unit)?) { + Timber.d("## SAS sending msg type $type") + Timber.v("## SAS sending msg info $verificationInfo") + val tx = tx ?: return + val contentMap = MXUsersDevicesMap() + val toSendToDeviceObject = verificationInfo.toSendToDeviceObject() + ?: return Unit.also { tx.cancel() } + + contentMap.setObject(tx.otherUserId, tx.otherDeviceId, toSendToDeviceObject) + + sendToDeviceTask + .configureWith(SendToDeviceTask.Params(type, contentMap, tx.transactionId)) { + this.callback = object : MatrixCallback { + override fun onSuccess(data: Unit) { + Timber.v("## SAS verification [$tx.transactionId] toDevice type '$type' success.") + if (onDone != null) { + onDone() + } else { + tx.state = nextState + } + } + + override fun onFailure(failure: Throwable) { + Timber.e("## SAS verification [$tx.transactionId] failed to send toDevice in state : $tx.state") + + tx.cancel(onErrorReason) + } + } + } + .executeBy(taskExecutor) + } + + override fun done(transactionId: String) { + // To device do not do anything here + } + + override fun cancelTransaction(transactionId: String, userId: String, userDevice: String, code: CancelCode) { + Timber.d("## SAS canceling transaction $transactionId for reason $code") + val cancelMessage = KeyVerificationCancel.create(transactionId, code) + val contentMap = MXUsersDevicesMap() + contentMap.setObject(userId, userDevice, cancelMessage) + sendToDeviceTask + .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) { + this.callback = object : MatrixCallback { + override fun onSuccess(data: Unit) { + Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}") + } + + override fun onFailure(failure: Throwable) { + Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.") + } + } + } + .executeBy(taskExecutor) + } + + override fun createAccept(tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List) + : VerifInfoAccept = KeyVerificationAccept.create(tid, keyAgreementProtocol, hash, commitment, messageAuthenticationCode, shortAuthenticationStrings) + + override fun createKey(tid: String, pubKey: String): VerifInfoKey = KeyVerificationKey.create(tid, pubKey) + + override fun createMac(tid: String, mac: Map, keys: String) = KeyVerificationMac.create(tid, mac, keys) +} + +internal class SasToDeviceTransportFactory @Inject constructor( + private val sendToDeviceTask: SendToDeviceTask, + private val taskExecutor: TaskExecutor) { + + fun createTransport(tx: SASVerificationTransaction?): SasTransportToDevice { + return SasTransportToDevice(tx, sendToDeviceTask, taskExecutor) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoAccept.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoAccept.kt new file mode 100644 index 0000000000..c0677439f4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoAccept.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2019 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.internal.crypto.verification + +internal interface VerifInfoAccept : VerificationInfo { + + val transactionID: String? + + /** + * The key agreement protocol that Bob’s device has selected to use, out of the list proposed by Alice’s device + */ + val keyAgreementProtocol: String? + + /** + * The hash algorithm that Bob’s device has selected to use, out of the list proposed by Alice’s device + */ + val hash: String? + + /** + * The message authentication code that Bob’s device has selected to use, out of the list proposed by Alice’s device + */ + val messageAuthenticationCode: String? + + /** + * An array of short authentication string methods that Bob’s client (and Bob) understands. Must be a subset of the list proposed by Alice’s device + */ + val shortAuthenticationStrings: List? + + /** + * The hash (encoded as unpadded base64) of the concatenation of the device’s ephemeral public key (QB, encoded as unpadded base64) + * and the canonical JSON representation of the m.key.verification.start message. + */ + var commitment: String? +} + +internal interface AcceptVerifInfoFactory { + + fun create(tid: String, + keyAgreementProtocol: String, + hash: String, + commitment: String, + messageAuthenticationCode: String, + shortAuthenticationStrings: List): VerifInfoAccept +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoCancel.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoCancel.kt new file mode 100644 index 0000000000..94c52f61ea --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoCancel.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2019 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.internal.crypto.verification + +interface VerifInfoCancel : VerificationInfo { + + val transactionID: String? + /** + * machine-readable reason for cancelling, see #CancelCode + */ + val code: String? + + /** + * human-readable reason for cancelling. This should only be used if the receiving client does not understand the code given. + */ + val reason: String? +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoKey.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoKey.kt new file mode 100644 index 0000000000..69ae917938 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoKey.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2019 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.internal.crypto.verification + +/** + * Sent by both devices to send their ephemeral Curve25519 public key to the other device. + */ +internal interface VerifInfoKey : VerificationInfo { + + val transactionID: String? + /** + * The device’s ephemeral public key, as an unpadded base64 string + */ + val key: String? +} + +internal interface KeyVerifInfoFactory { + fun create(tid: String, pubKey: String): VerifInfoKey +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoMac.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoMac.kt new file mode 100644 index 0000000000..14da21a398 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoMac.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2019 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.internal.crypto.verification + +internal interface VerifInfoMac : VerificationInfo { + + val transactionID: String? + + /** + * A map of key ID to the MAC of the key, as an unpadded base64 string, calculated using the MAC key + */ + val mac: Map? + + /** + * The MAC of the comma-separated, sorted list of key IDs given in the mac property, + * as an unpadded base64 string, calculated using the MAC key. + * For example, if the mac property gives MACs for the keys ed25519:ABCDEFG and ed25519:HIJKLMN, then this property will + * give the MAC of the string “ed25519:ABCDEFG,ed25519:HIJKLMN”. + */ + val keys: String? +} + +internal interface VerifInfoMacFactory { + fun create(tid: String, mac: Map, keys: String) : VerifInfoMac +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoStart.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoStart.kt new file mode 100644 index 0000000000..380022def7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoStart.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2019 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.internal.crypto.verification + +interface VerifInfoStart : VerificationInfo { + + val method: String? + val fromDevice: String? + + val transactionID: String? + + val keyAgreementProtocols: List? + + /** + * An array of hashes that Alice’s client understands. + * Must include “sha256”. Other methods may be defined in the future. + */ + val hashes: List? + + /** + * An array of message authentication codes that Alice’s client understands. + * Must include “hkdf-hmac-sha256”. + * Other methods may be defined in the future. + */ + val messageAuthenticationCodes: List? + + /** + * An array of short authentication string methods that Alice’s client (and Alice) understands. + * Must include “decimal”. + * This document also describes the “emoji” method. + * Other methods may be defined in the future + */ + val shortAuthenticationStrings: List? + + fun toCanonicalJson(): String? +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt new file mode 100644 index 0000000000..5fe5c62edd --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2019 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.internal.crypto.verification + +import im.vector.matrix.android.api.session.events.model.Content +import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceObject + +interface VerificationInfo { + fun toEventContent(): Content? = null + fun toSendToDeviceObject(): SendToDeviceObject? = null + fun isValid() : Boolean +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt new file mode 100644 index 0000000000..2886d78d8c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2019 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.internal.crypto.verification + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.crypto.CryptoService +import im.vector.matrix.android.api.session.crypto.MXCryptoError +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageType +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult +import im.vector.matrix.android.internal.database.RealmLiveEntityObserver +import im.vector.matrix.android.internal.database.mapper.asDomain +import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.query.types +import im.vector.matrix.android.internal.di.SessionDatabase +import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.task.TaskExecutor +import io.realm.OrderedCollectionChangeSet +import io.realm.RealmConfiguration +import io.realm.RealmResults +import timber.log.Timber +import java.util.* +import javax.inject.Inject + +internal class VerificationMessageLiveObserver @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, + @UserId private val userId: String, + private val cryptoService: CryptoService, + private val sasVerificationService: DefaultSasVerificationService, + private val taskExecutor: TaskExecutor) : + RealmLiveEntityObserver(realmConfiguration) { + + override val query = Monarchy.Query { + EventEntity.types(it, listOf( + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_KEY, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_DONE, + EventType.MESSAGE, + EventType.ENCRYPTED) + ) + } + + override fun onChange(results: RealmResults, changeSet: OrderedCollectionChangeSet) { + // TODO do that in a task + // TODO how to ignore when it's an initial sync? + val events = changeSet.insertions + .asSequence() + .mapNotNull { results[it]?.asDomain() } + .filterNot { + // ignore mines ^^ + it.senderId == userId + } + .toList() + + events.forEach { event -> + Timber.d("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}") + Timber.v("## SAS Verification live observer: received msgId: $event") + + // decrypt if needed? + + if (event.isEncrypted() && event.mxDecryptionResult == null) { + // TODO use a global event decryptor? attache to session and that listen to new sessionId? + // for now decrypt sync + try { + val result = cryptoService.decryptEvent(event, event.roomId + UUID.randomUUID().toString()) + event.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } catch (e: MXCryptoError) { + Timber.e("## SAS Failed to decrypt event: ${event.eventId}") + } + } + Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}") + when (event.getClearType()) { + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_KEY, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_DONE -> { + sasVerificationService.onRoomEvent(event) + } + EventType.MESSAGE -> { + if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel()?.type) { + // TODO If the request is in the future by more than 5 minutes or more than 10 minutes in the past, + // the message should be ignored by the receiver. + sasVerificationService.onRoomRequestReceived(event) + } + } + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransaction.kt index be3f4c7885..d6cc5e3279 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransaction.kt @@ -17,7 +17,6 @@ package im.vector.matrix.android.internal.crypto.verification import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTransaction -import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceObject /** * Generic interactive key verification transaction @@ -42,7 +41,7 @@ internal abstract class VerificationTransaction( listeners.remove(listener) } - abstract fun acceptToDeviceEvent(senderId: String, event: SendToDeviceObject) + abstract fun acceptVerificationEvent(senderId: String, info: VerificationInfo) abstract fun cancel(code: CancelCode) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt index fa0b9a1f1c..a89e21b04a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt @@ -104,7 +104,7 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure { return Failure.ServerError(matrixError, httpCode) } - } catch (ex: JsonDataException) { + } catch (ex: Exception) { // This is not a MatrixError Timber.w("The error returned by the server is not a MatrixError") } catch (ex: JsonEncodingException) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 0e88894969..b7d121998c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.InitialSyncProgressService import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService import im.vector.matrix.android.api.session.securestorage.SecureStorageService +import im.vector.matrix.android.internal.crypto.verification.VerificationMessageLiveObserver import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory import im.vector.matrix.android.internal.di.* @@ -164,6 +165,10 @@ internal abstract class SessionModule { @IntoSet abstract fun bindRoomCreateEventLiveObserver(roomCreateEventLiveObserver: RoomCreateEventLiveObserver): LiveEntityObserver + @Binds + @IntoSet + abstract fun bindVerificationEventObserver(verificationMessageLiveObserver: VerificationMessageLiveObserver): LiveEntityObserver + @Binds abstract fun bindInitialSyncProgressService(initialSyncProgressService: DefaultInitialSyncProgressService): InitialSyncProgressService diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt index 8fad03b588..acbe385ff6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -157,7 +157,8 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private override fun deleteFailedEcho(localEcho: TimelineEvent) { monarchy.writeAsync { realm -> - TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId ?: "").findFirst()?.let { + TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId + ?: "").findFirst()?.let { it.deleteFromRealm() } EventEntity.where(realm, eventId = localEcho.root.eventId ?: "").findFirst()?.let { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index 0fed1ca6f5..a225146d83 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -286,6 +286,24 @@ internal class LocalEchoEventFactory @Inject constructor( ) } + fun createVerificationRequest(roomId: String, fromDevice: String, to: String, methods: List): Event { + val localID = LocalEcho.createLocalEchoId() + return Event( + roomId = roomId, + originServerTs = dummyOriginServerTs(), + senderId = userId, + eventId = localID, + type = EventType.MESSAGE, + content = MessageVerificationRequestContent( + body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId), + fromDevice = fromDevice, + to = to, + methods = methods + ).toContent(), + unsignedData = UnsignedData(age = null, transactionId = localID) + ) + } + private fun dummyOriginServerTs(): Long { return System.currentTimeMillis() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt index 4c45ba0a4d..d6d924ab46 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt @@ -20,7 +20,6 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.util.awaitTransaction import timber.log.Timber import javax.inject.Inject @@ -28,7 +27,7 @@ internal class LocalEchoUpdater @Inject constructor(private val monarchy: Monarc suspend fun updateSendState(eventId: String, sendState: SendState) { Timber.v("Update local state of $eventId to ${sendState.name}") - monarchy.awaitTransaction { realm -> + monarchy.writeAsync { realm -> val sendingEventEntity = EventEntity.where(realm, eventId).findFirst() if (sendingEventEntity != null) { if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) { diff --git a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml index a22533c6d1..4fe6009268 100644 --- a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml +++ b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml @@ -17,4 +17,7 @@ %1$s withdrew %2$s\'s invitation. Reason: %3$s There is no network connection right now + + %s is requesting to verify your key, but your client does not support in-chat key verification. You will need to use legacy key verification to verify keys. + \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index 442c5f6f96..94d7a39379 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -207,6 +207,11 @@ interface FragmentModule { @FragmentKey(VectorSettingsNotificationPreferenceFragment::class) fun bindVectorSettingsNotificationPreferenceFragment(fragment: VectorSettingsNotificationPreferenceFragment): Fragment + @Binds + @IntoMap + @FragmentKey(VectorSettingsLabsFragment::class) + fun bindVectorSettingsLabsFragment(fragment: VectorSettingsLabsFragment): Fragment + @Binds @IntoMap @FragmentKey(VectorSettingsPreferencesFragment::class) diff --git a/vector/src/main/java/im/vector/riotx/features/command/Command.kt b/vector/src/main/java/im/vector/riotx/features/command/Command.kt index 8b72ffa4a6..fbc3ddebe5 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/Command.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/Command.kt @@ -38,7 +38,9 @@ enum class Command(val command: String, val parameters: String, @StringRes val d CHANGE_DISPLAY_NAME("/nick", "", R.string.command_description_nick), MARKDOWN("/markdown", "", R.string.command_description_markdown), CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token), - SPOILER("/spoiler", "", R.string.command_description_spoiler); + SHRUG("/shrug", "", R.string.command_description_shrug), + // TODO temporary command + VERIFY_USER("/verify", "", R.string.command_description_spoiler); val length get() = command.length + 1 diff --git a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt index 359f2c1f13..23e30e1d3c 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt @@ -244,6 +244,17 @@ object CommandParser { ParsedCommand.SendSpoiler(message) } + Command.SHRUG.command -> { + val message = textMessage.subSequence(Command.SHRUG.command.length, textMessage.length).trim() + + ParsedCommand.SendShrug(message) + } + + Command.VERIFY_USER.command -> { + val message = textMessage.substring(Command.VERIFY_USER.command.length).trim() + + ParsedCommand.VerifyUser(message) + } else -> { // Unknown command ParsedCommand.ErrorUnknownSlashCommand(slashCommand) diff --git a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt index dd7c0c7e86..b16f68c7b9 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt @@ -46,4 +46,6 @@ sealed class ParsedCommand { class SetMarkdown(val enable: Boolean) : ParsedCommand() object ClearScalarToken : ParsedCommand() class SendSpoiler(val message: String) : ParsedCommand() + class SendShrug(val message: CharSequence) : ParsedCommand() + class VerifyUser(val userId: String) : ParsedCommand() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index e7a18753cd..efdfd53234 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -50,7 +50,6 @@ import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap -import im.vector.riotx.BuildConfig import im.vector.riotx.R import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.platform.VectorViewModel @@ -377,6 +376,25 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) popDraft() } + is ParsedCommand.SendShrug -> { + val sequence: CharSequence = buildString { + append("¯\\_(ツ)_/¯") + .apply { + if (slashCommandResult.message.isNotEmpty()) { + append(" ") + append(slashCommandResult.message) + } + } + } + room.sendTextMessage(sequence) + _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + popDraft() + } + is ParsedCommand.VerifyUser -> { + session.getSasVerificationService().requestKeyVerificationInDMs(slashCommandResult.userId, room.roomId, null) + _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) + popDraft() + } is ParsedCommand.ChangeTopic -> { handleChangeTopicSlashCommand(slashCommandResult) popDraft() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 5b6dec9900..4c36b55fef 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -64,6 +64,16 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me encryptedItemFactory.create(event, nextEvent, highlight, callback) } } + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_DONE, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_KEY, + EventType.KEY_VERIFICATION_MAC -> { + // These events are filtered from timeline in normal case + // Only visible in developer mode + defaultItemFactory.create(event, highlight, readMarkerVisible, callback) + } // Unhandled event types (yet) EventType.STATE_ROOM_THIRD_PARTY_INVITE -> defaultItemFactory.create(event, highlight, callback) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 1cd851f8c8..033ff68433 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -42,7 +42,13 @@ object TimelineDisplayableEvents { val DEBUG_DISPLAYABLE_TYPES = DISPLAYABLE_TYPES + listOf( EventType.REDACTION, - EventType.REACTION + EventType.REACTION, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_DONE, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_KEY ) } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt index dd99488465..ee8c0530d9 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt @@ -23,6 +23,7 @@ import android.net.Uri import android.provider.MediaStore import androidx.core.content.edit import androidx.preference.PreferenceManager +import im.vector.riotx.BuildConfig import im.vector.riotx.R import im.vector.riotx.features.homeserver.ServerUrlsRepository import im.vector.riotx.features.themes.ThemeUtils @@ -256,7 +257,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { } fun labAllowedExtendedLogging(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_LABS_ALLOW_EXTENDED_LOGS, false) + return defaultPrefs.getBoolean(SETTINGS_LABS_ALLOW_EXTENDED_LOGS, BuildConfig.DEBUG) } /** diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt index 37dfd02c43..cd201900fb 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt @@ -17,14 +17,21 @@ package im.vector.riotx.features.settings import im.vector.riotx.R +import im.vector.riotx.core.preference.VectorSwitchPreference +import javax.inject.Inject -class VectorSettingsLabsFragment : VectorSettingsBaseFragment() { +class VectorSettingsLabsFragment @Inject constructor(val vectorPreferences: VectorPreferences) : VectorSettingsBaseFragment() { override var titleRes = R.string.room_settings_labs_pref_title override val preferenceXmlRes = R.xml.vector_settings_labs override fun bindPref() { // Lab + + findPreference(VectorPreferences.SETTINGS_LABS_ALLOW_EXTENDED_LOGS)?.let { + it.isChecked = vectorPreferences.labAllowedExtendedLogging() + } + // val useCryptoPref = findPreference(VectorPreferences.SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY) as SwitchPreference // val cryptoIsEnabledPref = findPreference(VectorPreferences.SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 2e4d04354b..10ca8e728e 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1695,6 +1695,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Name or ID (#example:matrix.org) Enable swipe to reply in timeline + Enable verification other DM Link copied to clipboard diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index f259a34e44..f8d65fab15 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -12,6 +12,8 @@ "Leave the room" "%1$s made no changes" Sends the given message as a spoiler + Request to verify the given userID + Prepends ¯\\_(ツ)_/¯ to a plain-text message Spoiler Type keywords to find a reaction. diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index e9e5e27198..1a64f75de5 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -45,7 +45,6 @@ android:key="SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY" android:title="@string/labs_swipe_to_reply_in_timeline" /> - Date: Tue, 3 Dec 2019 17:43:49 +0100 Subject: [PATCH 002/217] Code review --- .../MessageVerificationAcceptContent.kt | 10 ++--- .../MessageVerificationCancelContent.kt | 4 +- .../message/MessageVerificationKeyContent.kt | 10 ++--- .../message/MessageVerificationMacContent.kt | 10 ++--- .../MessageVerificationRequestContent.kt | 4 +- .../MessageVerificationStartContent.kt | 6 +-- .../model/rest/KeyVerificationAccept.kt | 10 ++--- .../model/rest/KeyVerificationCancel.kt | 4 +- .../crypto/model/rest/KeyVerificationKey.kt | 8 ++-- .../crypto/model/rest/KeyVerificationMac.kt | 10 ++--- .../model/rest/KeyVerificationRequest.kt | 2 +- .../crypto/model/rest/KeyVerificationStart.kt | 4 +- .../crypto/tasks/RequestVerificationDMTask.kt | 5 +-- ...faultIncomingSASVerificationTransaction.kt | 10 ++--- .../DefaultOutgoingSASVerificationRequest.kt | 8 ++-- .../DefaultSasVerificationService.kt | 45 +++++++++---------- .../SASVerificationTransaction.kt | 26 +++++------ .../crypto/verification/SasTransport.kt | 6 +-- .../verification/SasTransportRoomMessage.kt | 6 +-- .../verification/SasTransportToDevice.kt | 6 +-- .../crypto/verification/VerificationInfo.kt | 2 +- ...nfoAccept.kt => VerificationInfoAccept.kt} | 6 +-- ...nfoCancel.kt => VerificationInfoCancel.kt} | 4 +- ...VerifInfoKey.kt => VerificationInfoKey.kt} | 6 +-- ...VerifInfoMac.kt => VerificationInfoMac.kt} | 6 +-- ...fInfoStart.kt => VerificationInfoStart.kt} | 2 +- .../VerificationMessageLiveObserver.kt | 15 ++++--- .../android/internal/session/SessionModule.kt | 2 +- .../session/room/send/DefaultSendService.kt | 13 +++--- .../room/send/LocalEchoEventFactory.kt | 4 +- .../session/room/send/LocalEchoUpdater.kt | 2 +- .../vector/riotx/features/command/Command.kt | 2 +- .../riotx/features/command/CommandParser.kt | 6 +-- .../home/room/detail/RoomDetailViewModel.kt | 12 +++-- .../settings/VectorSettingsLabsFragment.kt | 4 +- .../src/main/res/xml/vector_settings_labs.xml | 2 +- 36 files changed, 140 insertions(+), 142 deletions(-) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/{VerifInfoAccept.kt => VerificationInfoAccept.kt} (90%) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/{VerifInfoCancel.kt => VerificationInfoCancel.kt} (87%) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/{VerifInfoKey.kt => VerificationInfoKey.kt} (83%) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/{VerifInfoMac.kt => VerificationInfoMac.kt} (90%) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/{VerifInfoStart.kt => VerificationInfoStart.kt} (96%) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationAcceptContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationAcceptContent.kt index bda4b9f0ac..66914374df 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationAcceptContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationAcceptContent.kt @@ -20,8 +20,8 @@ import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent -import im.vector.matrix.android.internal.crypto.verification.AcceptVerifInfoFactory -import im.vector.matrix.android.internal.crypto.verification.VerifInfoAccept +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAcceptFactory +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAccept import timber.log.Timber @JsonClass(generateAdapter = true) @@ -32,7 +32,7 @@ internal data class MessageVerificationAcceptContent( @Json(name = "short_authentication_string") override val shortAuthenticationStrings: List?, @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?, @Json(name = "commitment") override var commitment: String? = null -) : VerifInfoAccept { +) : VerificationInfoAccept { override val transactionID: String? get() = relatesTo?.eventId @@ -52,14 +52,14 @@ internal data class MessageVerificationAcceptContent( override fun toEventContent() = this.toContent() - companion object : AcceptVerifInfoFactory { + companion object : VerificationInfoAcceptFactory { override fun create(tid: String, keyAgreementProtocol: String, hash: String, commitment: String, messageAuthenticationCode: String, - shortAuthenticationStrings: List): VerifInfoAccept { + shortAuthenticationStrings: List): VerificationInfoAccept { return MessageVerificationAcceptContent( hash, keyAgreementProtocol, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationCancelContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationCancelContent.kt index 08fc3cbdbb..2070845f46 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationCancelContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationCancelContent.kt @@ -21,7 +21,7 @@ import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent -import im.vector.matrix.android.internal.crypto.verification.VerifInfoCancel +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoCancel @JsonClass(generateAdapter = true) internal data class MessageVerificationCancelContent( @@ -29,7 +29,7 @@ internal data class MessageVerificationCancelContent( @Json(name = "reason") override val reason: String? = null, @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? -) : VerifInfoCancel { +) : VerificationInfoCancel { override val transactionID: String? get() = relatesTo?.eventId diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationKeyContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationKeyContent.kt index 0b93e3299a..2dacb19871 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationKeyContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationKeyContent.kt @@ -20,8 +20,8 @@ import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent -import im.vector.matrix.android.internal.crypto.verification.VerifInfoKey -import im.vector.matrix.android.internal.crypto.verification.KeyVerifInfoFactory +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKey +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKeyFactory import timber.log.Timber @JsonClass(generateAdapter = true) @@ -31,7 +31,7 @@ internal data class MessageVerificationKeyContent( */ @Json(name = "key") override val key: String? = null, @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? -) : VerifInfoKey { +) : VerificationInfoKey { override val transactionID: String? get() = relatesTo?.eventId @@ -46,9 +46,9 @@ internal data class MessageVerificationKeyContent( override fun toEventContent() = this.toContent() - companion object : KeyVerifInfoFactory { + companion object : VerificationInfoKeyFactory { - override fun create(tid: String, pubKey: String): VerifInfoKey { + override fun create(tid: String, pubKey: String): VerificationInfoKey { return MessageVerificationKeyContent( pubKey, RelationDefaultContent( diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationMacContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationMacContent.kt index 92ea4bca52..f08625791f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationMacContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationMacContent.kt @@ -20,15 +20,15 @@ import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent -import im.vector.matrix.android.internal.crypto.verification.VerifInfoMac -import im.vector.matrix.android.internal.crypto.verification.VerifInfoMacFactory +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoMac +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoMacFactory @JsonClass(generateAdapter = true) internal data class MessageVerificationMacContent( @Json(name = "mac") override val mac: Map? = null, @Json(name = "keys") override val keys: String? = null, @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? -) : VerifInfoMac { +) : VerificationInfoMac { override val transactionID: String? get() = relatesTo?.eventId @@ -42,8 +42,8 @@ internal data class MessageVerificationMacContent( return true } - companion object : VerifInfoMacFactory { - override fun create(tid: String, mac: Map, keys: String): VerifInfoMac { + companion object : VerificationInfoMacFactory { + override fun create(tid: String, mac: Map, keys: String): VerificationInfoMac { return MessageVerificationMacContent( mac, keys, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationRequestContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationRequestContent.kt index afefa39847..897eb9dbbf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationRequestContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationRequestContent.kt @@ -21,12 +21,12 @@ import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent @JsonClass(generateAdapter = true) -class MessageVerificationRequestContent( +internal data class MessageVerificationRequestContent( @Json(name = "msgtype") override val type: String = MessageType.MSGTYPE_VERIFICATION_REQUEST, @Json(name = "body") override val body: String, @Json(name = "from_device") val fromDevice: String, @Json(name = "methods") val methods: List, - @Json(name = "to") val to: String, + @Json(name = "to") val toUserId: String, // @Json(name = "timestamp") val timestamp: Int, @Json(name = "format") val format: String? = null, @Json(name = "formatted_body") val formattedBody: String? = null, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt index f6ec00ffb2..7d77a34e27 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt @@ -22,12 +22,12 @@ import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart import im.vector.matrix.android.internal.crypto.verification.SASVerificationTransaction -import im.vector.matrix.android.internal.crypto.verification.VerifInfoStart +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart import im.vector.matrix.android.internal.util.JsonCanonicalizer import timber.log.Timber @JsonClass(generateAdapter = true) -data class MessageVerificationStartContent( +internal data class MessageVerificationStartContent( @Json(name = "from_device") override val fromDevice: String?, @Json(name = "hashes") override val hashes: List?, @Json(name = "key_agreement_protocols") override val keyAgreementProtocols: List?, @@ -35,7 +35,7 @@ data class MessageVerificationStartContent( @Json(name = "short_authentication_string") override val shortAuthenticationStrings: List?, @Json(name = "method") override val method: String?, @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? -) : VerifInfoStart { +) : VerificationInfoStart { override fun toCanonicalJson(): String? { return JsonCanonicalizer.getCanonicalJson(MessageVerificationStartContent::class.java, this) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationAccept.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationAccept.kt index 20d7682cb9..ef30986124 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationAccept.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationAccept.kt @@ -17,8 +17,8 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import im.vector.matrix.android.internal.crypto.verification.VerifInfoAccept -import im.vector.matrix.android.internal.crypto.verification.AcceptVerifInfoFactory +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAccept +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAcceptFactory import timber.log.Timber /** @@ -65,7 +65,7 @@ internal data class KeyVerificationAccept( */ @Json(name = "commitment") override var commitment: String? = null -) : SendToDeviceObject, VerifInfoAccept { +) : SendToDeviceObject, VerificationInfoAccept { override fun isValid(): Boolean { if (transactionID.isNullOrBlank() @@ -82,13 +82,13 @@ internal data class KeyVerificationAccept( override fun toSendToDeviceObject() = this - companion object : AcceptVerifInfoFactory { + companion object : VerificationInfoAcceptFactory { override fun create(tid: String, keyAgreementProtocol: String, hash: String, commitment: String, messageAuthenticationCode: String, - shortAuthenticationStrings: List): VerifInfoAccept { + shortAuthenticationStrings: List): VerificationInfoAccept { return KeyVerificationAccept().apply { this.transactionID = tid this.keyAgreementProtocol = keyAgreementProtocol diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationCancel.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationCancel.kt index 7ffffbbfa1..818bffc942 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationCancel.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationCancel.kt @@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.crypto.sas.CancelCode -import im.vector.matrix.android.internal.crypto.verification.VerifInfoCancel +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoCancel /** * To device event sent by either party to cancel a key verification. @@ -40,7 +40,7 @@ internal data class KeyVerificationCancel( * human-readable reason for cancelling. This should only be used if the receiving client does not understand the code given. */ override var reason: String? = null -) : SendToDeviceObject, VerifInfoCancel { +) : SendToDeviceObject, VerificationInfoCancel { companion object { fun create(tid: String, cancelCode: CancelCode): KeyVerificationCancel { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationKey.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationKey.kt index 458c12743f..bf1482ac9f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationKey.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationKey.kt @@ -17,8 +17,8 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import im.vector.matrix.android.internal.crypto.verification.KeyVerifInfoFactory -import im.vector.matrix.android.internal.crypto.verification.VerifInfoKey +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKeyFactory +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKey /** * Sent by both devices to send their ephemeral Curve25519 public key to the other device. @@ -35,9 +35,9 @@ internal data class KeyVerificationKey( */ @Json(name = "key") override val key: String? = null -) : SendToDeviceObject, VerifInfoKey { +) : SendToDeviceObject, VerificationInfoKey { - companion object : KeyVerifInfoFactory { + companion object : VerificationInfoKeyFactory { override fun create(tid: String, pubKey: String): KeyVerificationKey { return KeyVerificationKey(tid, pubKey) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationMac.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationMac.kt index d2c147e145..6bb1ae6644 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationMac.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationMac.kt @@ -17,8 +17,8 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import im.vector.matrix.android.internal.crypto.verification.VerifInfoMac -import im.vector.matrix.android.internal.crypto.verification.VerifInfoMacFactory +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoMac +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoMacFactory /** * Sent by both devices to send the MAC of their device key to the other device. @@ -29,7 +29,7 @@ internal data class KeyVerificationMac( @Json(name = "mac") override val mac: Map? = null, @Json(name = "key") override val keys: String? = null -) : SendToDeviceObject, VerifInfoMac { +) : SendToDeviceObject, VerificationInfoMac { override fun isValid(): Boolean { if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) { @@ -40,8 +40,8 @@ internal data class KeyVerificationMac( override fun toSendToDeviceObject(): SendToDeviceObject? = this - companion object : VerifInfoMacFactory { - override fun create(tid: String, mac: Map, keys: String): VerifInfoMac { + companion object : VerificationInfoMacFactory { + override fun create(tid: String, mac: Map, keys: String): VerificationInfoMac { return KeyVerificationMac(tid, mac, keys) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt index 14954a17cd..51eed84412 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationRequest.kt @@ -23,7 +23,7 @@ import im.vector.matrix.android.internal.crypto.verification.VerificationInfo * Requests a key verification with another user's devices. */ @JsonClass(generateAdapter = true) -data class KeyVerificationRequest( +internal data class KeyVerificationRequest( @Json(name = "from_device") val fromDevice: String, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt index f7cc10a12b..47c2a62d92 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt @@ -19,7 +19,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.crypto.sas.SasMode import im.vector.matrix.android.internal.crypto.verification.SASVerificationTransaction -import im.vector.matrix.android.internal.crypto.verification.VerifInfoStart +import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart import im.vector.matrix.android.internal.util.JsonCanonicalizer import timber.log.Timber @@ -27,7 +27,7 @@ import timber.log.Timber * Sent by Alice to initiate an interactive key verification. */ @JsonClass(generateAdapter = true) -class KeyVerificationStart : SendToDeviceObject, VerifInfoStart { +class KeyVerificationStart : SendToDeviceObject, VerificationInfoStart { override fun toCanonicalJson(): String? { return JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, this) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RequestVerificationDMTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RequestVerificationDMTask.kt index 57d225a193..ae8a40f296 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RequestVerificationDMTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RequestVerificationDMTask.kt @@ -68,9 +68,8 @@ internal class DefaultRequestVerificationDMTask @Inject constructor( } private suspend fun createRequestEvent(params: RequestVerificationDMTask.Params): Event { - val event = localEchoEventFactory.createVerificationRequest(params.roomId, params.from, params.to, params.methods).also { - localEchoEventFactory.saveLocalEcho(monarchy, it) - } + val event = localEchoEventFactory.createVerificationRequest(params.roomId, params.from, params.to, params.methods) + .also { localEchoEventFactory.saveLocalEcho(monarchy, it) } if (params.cryptoService.isRoomEncrypted(params.roomId)) { try { return encryptEventTask.execute(EncryptEventTask.Params( diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt index aac2b49f57..5eff26a5bb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt @@ -66,7 +66,7 @@ internal class DefaultIncomingSASVerificationTransaction( } } - override fun onVerificationStart(startReq: VerifInfoStart) { + override fun onVerificationStart(startReq: VerificationInfoStart) { Timber.v("## SAS I: received verification request from state $state") if (state != SasVerificationTxState.None) { Timber.e("## SAS I: received verification request from invalid state") @@ -126,7 +126,7 @@ internal class DefaultIncomingSASVerificationTransaction( } } - private fun doAccept(accept: VerifInfoAccept) { + private fun doAccept(accept: VerificationInfoAccept) { this.accepted = accept Timber.v("## SAS incoming accept request id:$transactionId") @@ -144,12 +144,12 @@ internal class DefaultIncomingSASVerificationTransaction( } } - override fun onVerificationAccept(accept: VerifInfoAccept) { + override fun onVerificationAccept(accept: VerificationInfoAccept) { Timber.v("## SAS invalid message for incoming request id:$transactionId") cancel(CancelCode.UnexpectedMessage) } - override fun onKeyVerificationKey(userId: String, vKey: VerifInfoKey) { + override fun onKeyVerificationKey(userId: String, vKey: VerificationInfoKey) { Timber.v("## SAS received key for request id:$transactionId") if (state != SasVerificationTxState.SendingAccept && state != SasVerificationTxState.Accepted) { Timber.e("## SAS received key from invalid state $state") @@ -202,7 +202,7 @@ internal class DefaultIncomingSASVerificationTransaction( state = SasVerificationTxState.ShortCodeReady } - override fun onKeyVerificationMac(vKey: VerifInfoMac) { + override fun onKeyVerificationMac(vKey: VerificationInfoMac) { Timber.v("## SAS I: received mac for request id:$transactionId") // Check for state? if (state != SasVerificationTxState.SendingKey diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt index 4362e897c8..5827ecf4b8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt @@ -66,7 +66,7 @@ internal class DefaultOutgoingSASVerificationRequest( } } - override fun onVerificationStart(startReq: VerifInfoStart) { + override fun onVerificationStart(startReq: VerificationInfoStart) { Timber.e("## SAS O: onVerificationStart - unexpected id:$transactionId") cancel(CancelCode.UnexpectedMessage) } @@ -122,7 +122,7 @@ internal class DefaultOutgoingSASVerificationRequest( // ) // } - override fun onVerificationAccept(accept: VerifInfoAccept) { + override fun onVerificationAccept(accept: VerificationInfoAccept) { Timber.v("## SAS O: onVerificationAccept id:$transactionId") if (state != SasVerificationTxState.Started) { Timber.e("## SAS O: received accept request from invalid state $state") @@ -159,7 +159,7 @@ internal class DefaultOutgoingSASVerificationRequest( } } - override fun onKeyVerificationKey(userId: String, vKey: VerifInfoKey) { + override fun onKeyVerificationKey(userId: String, vKey: VerificationInfoKey) { Timber.v("## SAS O: onKeyVerificationKey id:$transactionId") if (state != SasVerificationTxState.SendingKey && state != SasVerificationTxState.KeySent) { Timber.e("## received key from invalid state $state") @@ -201,7 +201,7 @@ internal class DefaultOutgoingSASVerificationRequest( } } - override fun onKeyVerificationMac(vKey: VerifInfoMac) { + override fun onKeyVerificationMac(vKey: VerificationInfoMac) { Timber.v("## SAS O: onKeyVerificationMac id:$transactionId") if (state != SasVerificationTxState.OnKeyReceived && state != SasVerificationTxState.ShortCodeReady diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index 6552dca7ab..1d15a996dd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -39,7 +39,6 @@ import im.vector.matrix.android.internal.crypto.model.rest.* import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.tasks.DefaultRequestVerificationDMTask import im.vector.matrix.android.internal.crypto.tasks.RequestVerificationDMTask -import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.task.TaskConstraints @@ -56,18 +55,18 @@ import kotlin.collections.HashMap import kotlin.collections.set @SessionScope -internal class DefaultSasVerificationService @Inject constructor(private val credentials: Credentials, - private val cryptoStore: IMXCryptoStore, - private val myDeviceInfoHolder: Lazy, - private val deviceListManager: DeviceListManager, - private val setDeviceVerificationAction: SetDeviceVerificationAction, - private val sendToDeviceTask: SendToDeviceTask, - private val requestVerificationDMTask: DefaultRequestVerificationDMTask, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val sasTransportRoomMessageFactory: SasTransportRoomMessageFactory, - private val sasToDeviceTransportFactory: SasToDeviceTransportFactory, - private val taskExecutor: TaskExecutor) - : VerificationTransaction.Listener, SasVerificationService { +internal class DefaultSasVerificationService @Inject constructor( + private val credentials: Credentials, + private val cryptoStore: IMXCryptoStore, + private val myDeviceInfoHolder: Lazy, + private val deviceListManager: DeviceListManager, + private val setDeviceVerificationAction: SetDeviceVerificationAction, + private val requestVerificationDMTask: DefaultRequestVerificationDMTask, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val sasTransportRoomMessageFactory: SasTransportRoomMessageFactory, + private val sasTransportToDeviceFactory: SasTransportToDeviceFactory, + private val taskExecutor: TaskExecutor +) : VerificationTransaction.Listener, SasVerificationService { private val uiHandler = Handler(Looper.getMainLooper()) @@ -217,7 +216,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre return } - handleStart(otherUserId, startReq as VerifInfoStart) { + handleStart(otherUserId, startReq as VerificationInfoStart) { it.transport = sasTransportRoomMessageFactory.createTransport(event.roomId ?: "", cryptoService) }?.let { @@ -246,7 +245,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre // startReq.fromDevice ?: event.getSenderKey()!!, // CancelCode.UnknownMethod // ) - sasToDeviceTransportFactory.createTransport(null).cancelTransaction( + sasTransportToDeviceFactory.createTransport(null).cancelTransaction( startReq.transactionID ?: "", otherUserId!!, startReq.fromDevice ?: event.getSenderKey()!!, @@ -257,9 +256,9 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre } // Download device keys prior to everything handleStart(otherUserId, startReq) { - it.transport = sasToDeviceTransportFactory.createTransport(it) + it.transport = sasTransportToDeviceFactory.createTransport(it) }?.let { - sasToDeviceTransportFactory.createTransport(null).cancelTransaction( + sasTransportToDeviceFactory.createTransport(null).cancelTransaction( startReq.transactionID ?: "", otherUserId!!, startReq.fromDevice ?: event.getSenderKey()!!, @@ -268,7 +267,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre } } - private suspend fun handleStart(otherUserId: String?, startReq: VerifInfoStart, txConfigure: (SASVerificationTransaction) -> Unit): CancelCode? { + private suspend fun handleStart(otherUserId: String?, startReq: VerificationInfoStart, txConfigure: (SASVerificationTransaction) -> Unit): CancelCode? { Timber.d("## SAS onStartRequestReceived ${startReq.transactionID!!}") if (checkKeysAreDownloaded(otherUserId!!, startReq) != null) { Timber.v("## SAS onStartRequestReceived $startReq") @@ -318,7 +317,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre } private suspend fun checkKeysAreDownloaded(otherUserId: String, - startReq: VerifInfoStart): MXUsersDevicesMap? { + startReq: VerificationInfoStart): MXUsersDevicesMap? { return try { val keys = deviceListManager.downloadKeys(listOf(otherUserId), true) val deviceIds = keys.getUserDeviceIds(otherUserId) ?: return null @@ -357,7 +356,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre handleOnCancel(otherUserId, cancelReq) } - private fun handleOnCancel(otherUserId: String, cancelReq: VerifInfoCancel) { + private fun handleOnCancel(otherUserId: String, cancelReq: VerificationInfoCancel) { Timber.v("## SAS onCancelReceived otherUser:$otherUserId reason:${cancelReq.reason}") val existing = getExistingTransaction(otherUserId, cancelReq.transactionID!!) if (existing == null) { @@ -387,7 +386,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre handleAccept(acceptReq, event.senderId!!) } - private fun handleAccept(acceptReq: VerifInfoAccept, senderId: String) { + private fun handleAccept(acceptReq: VerificationInfoAccept, senderId: String) { if (!acceptReq.isValid()) { // ignore Timber.e("## SAS Received invalid accept request") @@ -433,7 +432,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre handleKeyReceived(event, keyReq) } - private fun handleKeyReceived(event: Event, keyReq: VerifInfoKey) { + private fun handleKeyReceived(event: Event, keyReq: VerificationInfoKey) { Timber.d("## SAS Received Key from ${event.senderId} with info $keyReq") val otherUserId = event.senderId!! val existing = getExistingTransaction(otherUserId, keyReq.transactionID!!) @@ -474,7 +473,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre handleMacReceived(event.senderId, macReq) } - private fun handleMacReceived(senderId: String, macReq: VerifInfoMac) { + private fun handleMacReceived(senderId: String, macReq: VerificationInfoMac) { Timber.v("## SAS Received $macReq") val existing = getExistingTransaction(senderId, macReq.transactionID!!) if (existing == null) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt index 443443afad..31d6fd4b5c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASVerificationTransaction.kt @@ -88,13 +88,13 @@ internal abstract class SASVerificationTransaction( private var olmSas: OlmSAS? = null - var startReq: VerifInfoStart? = null - var accepted: VerifInfoAccept? = null + var startReq: VerificationInfoStart? = null + var accepted: VerificationInfoAccept? = null var otherKey: String? = null var shortCodeBytes: ByteArray? = null - var myMac: VerifInfoMac? = null - var theirMac: VerifInfoMac? = null + var myMac: VerificationInfoMac? = null + var theirMac: VerificationInfoMac? = null fun getSAS(): OlmSAS { if (olmSas == null) olmSas = OlmSAS() @@ -171,23 +171,23 @@ internal abstract class SASVerificationTransaction( override fun acceptVerificationEvent(senderId: String, info: VerificationInfo) { when (info) { - is VerifInfoStart -> onVerificationStart(info) - is VerifInfoAccept -> onVerificationAccept(info) - is VerifInfoKey -> onKeyVerificationKey(senderId, info) - is VerifInfoMac -> onKeyVerificationMac(info) - else -> { + is VerificationInfoStart -> onVerificationStart(info) + is VerificationInfoAccept -> onVerificationAccept(info) + is VerificationInfoKey -> onKeyVerificationKey(senderId, info) + is VerificationInfoMac -> onKeyVerificationMac(info) + else -> { // nop } } } - abstract fun onVerificationStart(startReq: VerifInfoStart) + abstract fun onVerificationStart(startReq: VerificationInfoStart) - abstract fun onVerificationAccept(accept: VerifInfoAccept) + abstract fun onVerificationAccept(accept: VerificationInfoAccept) - abstract fun onKeyVerificationKey(userId: String, vKey: VerifInfoKey) + abstract fun onKeyVerificationKey(userId: String, vKey: VerificationInfoKey) - abstract fun onKeyVerificationMac(vKey: VerifInfoMac) + abstract fun onKeyVerificationMac(vKey: VerificationInfoMac) protected fun verifyMacs() { Timber.v("## SAS verifying macs for id:$transactionId") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt index 23ebd89f7a..31a89335b7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt @@ -44,10 +44,10 @@ internal interface SasTransport { hash: String, commitment: String, messageAuthenticationCode: String, - shortAuthenticationStrings: List): VerifInfoAccept + shortAuthenticationStrings: List): VerificationInfoAccept fun createKey(tid: String, - pubKey: String): VerifInfoKey + pubKey: String): VerificationInfoKey - fun createMac(tid: String, mac: Map, keys: String): VerifInfoMac + fun createMac(tid: String, mac: Map, keys: String): VerificationInfoMac } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt index 1173914ba1..fda411af39 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt @@ -33,7 +33,7 @@ import im.vector.matrix.android.internal.task.configureWith import timber.log.Timber import javax.inject.Inject -internal class SasTransportRoomMessage constructor( +internal class SasTransportRoomMessage( private val roomId: String, private val cryptoService: CryptoService, // private val tx: SASVerificationTransaction?, @@ -109,9 +109,9 @@ internal class SasTransportRoomMessage constructor( commitment: String, messageAuthenticationCode: String, shortAuthenticationStrings: List) - : VerifInfoAccept = MessageVerificationAcceptContent.create(tid, keyAgreementProtocol, hash, commitment, messageAuthenticationCode, shortAuthenticationStrings) + : VerificationInfoAccept = MessageVerificationAcceptContent.create(tid, keyAgreementProtocol, hash, commitment, messageAuthenticationCode, shortAuthenticationStrings) - override fun createKey(tid: String, pubKey: String): VerifInfoKey = MessageVerificationKeyContent.create(tid, pubKey) + override fun createKey(tid: String, pubKey: String): VerificationInfoKey = MessageVerificationKeyContent.create(tid, pubKey) override fun createMac(tid: String, mac: Map, keys: String) = MessageVerificationMacContent.create(tid, mac, keys) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt index 4f36a5218e..0e1459a920 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt @@ -98,14 +98,14 @@ internal class SasTransportToDevice( commitment: String, messageAuthenticationCode: String, shortAuthenticationStrings: List) - : VerifInfoAccept = KeyVerificationAccept.create(tid, keyAgreementProtocol, hash, commitment, messageAuthenticationCode, shortAuthenticationStrings) + : VerificationInfoAccept = KeyVerificationAccept.create(tid, keyAgreementProtocol, hash, commitment, messageAuthenticationCode, shortAuthenticationStrings) - override fun createKey(tid: String, pubKey: String): VerifInfoKey = KeyVerificationKey.create(tid, pubKey) + override fun createKey(tid: String, pubKey: String): VerificationInfoKey = KeyVerificationKey.create(tid, pubKey) override fun createMac(tid: String, mac: Map, keys: String) = KeyVerificationMac.create(tid, mac, keys) } -internal class SasToDeviceTransportFactory @Inject constructor( +internal class SasTransportToDeviceFactory @Inject constructor( private val sendToDeviceTask: SendToDeviceTask, private val taskExecutor: TaskExecutor) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt index 5fe5c62edd..44a65aa926 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfo.kt @@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.crypto.verification import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceObject -interface VerificationInfo { +internal interface VerificationInfo { fun toEventContent(): Content? = null fun toSendToDeviceObject(): SendToDeviceObject? = null fun isValid() : Boolean diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoAccept.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoAccept.kt similarity index 90% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoAccept.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoAccept.kt index c0677439f4..7f639c3bc7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoAccept.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoAccept.kt @@ -15,7 +15,7 @@ */ package im.vector.matrix.android.internal.crypto.verification -internal interface VerifInfoAccept : VerificationInfo { +internal interface VerificationInfoAccept : VerificationInfo { val transactionID: String? @@ -46,12 +46,12 @@ internal interface VerifInfoAccept : VerificationInfo { var commitment: String? } -internal interface AcceptVerifInfoFactory { +internal interface VerificationInfoAcceptFactory { fun create(tid: String, keyAgreementProtocol: String, hash: String, commitment: String, messageAuthenticationCode: String, - shortAuthenticationStrings: List): VerifInfoAccept + shortAuthenticationStrings: List): VerificationInfoAccept } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoCancel.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoCancel.kt similarity index 87% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoCancel.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoCancel.kt index 94c52f61ea..2970358a15 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoCancel.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoCancel.kt @@ -15,11 +15,11 @@ */ package im.vector.matrix.android.internal.crypto.verification -interface VerifInfoCancel : VerificationInfo { +internal interface VerificationInfoCancel : VerificationInfo { val transactionID: String? /** - * machine-readable reason for cancelling, see #CancelCode + * machine-readable reason for cancelling, see [CancelCode] */ val code: String? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoKey.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoKey.kt similarity index 83% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoKey.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoKey.kt index 69ae917938..e5deb63b56 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoKey.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoKey.kt @@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.crypto.verification /** * Sent by both devices to send their ephemeral Curve25519 public key to the other device. */ -internal interface VerifInfoKey : VerificationInfo { +internal interface VerificationInfoKey : VerificationInfo { val transactionID: String? /** @@ -27,6 +27,6 @@ internal interface VerifInfoKey : VerificationInfo { val key: String? } -internal interface KeyVerifInfoFactory { - fun create(tid: String, pubKey: String): VerifInfoKey +internal interface VerificationInfoKeyFactory { + fun create(tid: String, pubKey: String): VerificationInfoKey } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoMac.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoMac.kt similarity index 90% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoMac.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoMac.kt index 14da21a398..01fc865250 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoMac.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoMac.kt @@ -15,7 +15,7 @@ */ package im.vector.matrix.android.internal.crypto.verification -internal interface VerifInfoMac : VerificationInfo { +internal interface VerificationInfoMac : VerificationInfo { val transactionID: String? @@ -33,6 +33,6 @@ internal interface VerifInfoMac : VerificationInfo { val keys: String? } -internal interface VerifInfoMacFactory { - fun create(tid: String, mac: Map, keys: String) : VerifInfoMac +internal interface VerificationInfoMacFactory { + fun create(tid: String, mac: Map, keys: String) : VerificationInfoMac } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoStart.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoStart.kt similarity index 96% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoStart.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoStart.kt index 380022def7..f1cbd3f9f1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerifInfoStart.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoStart.kt @@ -15,7 +15,7 @@ */ package im.vector.matrix.android.internal.crypto.verification -interface VerifInfoStart : VerificationInfo { +internal interface VerificationInfoStart : VerificationInfo { val method: String? val fromDevice: String? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt index 2886d78d8c..f75a11cdcb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt @@ -37,12 +37,13 @@ import timber.log.Timber import java.util.* import javax.inject.Inject -internal class VerificationMessageLiveObserver @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, - @UserId private val userId: String, - private val cryptoService: CryptoService, - private val sasVerificationService: DefaultSasVerificationService, - private val taskExecutor: TaskExecutor) : - RealmLiveEntityObserver(realmConfiguration) { +internal class VerificationMessageLiveObserver @Inject constructor( + @SessionDatabase realmConfiguration: RealmConfiguration, + @UserId private val userId: String, + private val cryptoService: CryptoService, + private val sasVerificationService: DefaultSasVerificationService, + private val taskExecutor: TaskExecutor +) : RealmLiveEntityObserver(realmConfiguration) { override val query = Monarchy.Query { EventEntity.types(it, listOf( @@ -70,7 +71,7 @@ internal class VerificationMessageLiveObserver @Inject constructor(@SessionDatab .toList() events.forEach { event -> - Timber.d("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}") + Timber.d("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}") Timber.v("## SAS Verification live observer: received msgId: $event") // decrypt if needed? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index b7d121998c..883fd37745 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -167,7 +167,7 @@ internal abstract class SessionModule { @Binds @IntoSet - abstract fun bindVerificationEventObserver(verificationMessageLiveObserver: VerificationMessageLiveObserver): LiveEntityObserver + abstract fun bindVerificationMessageLiveObserver(verificationMessageLiveObserver: VerificationMessageLiveObserver): LiveEntityObserver @Binds abstract fun bindInitialSyncProgressService(initialSyncProgressService: DefaultInitialSyncProgressService): InitialSyncProgressService diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt index acbe385ff6..0e6c93590b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/DefaultSendService.kt @@ -157,13 +157,12 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private override fun deleteFailedEcho(localEcho: TimelineEvent) { monarchy.writeAsync { realm -> - TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId - ?: "").findFirst()?.let { - it.deleteFromRealm() - } - EventEntity.where(realm, eventId = localEcho.root.eventId ?: "").findFirst()?.let { - it.deleteFromRealm() - } + TimelineEventEntity.where(realm, roomId = roomId, eventId = localEcho.root.eventId ?: "") + .findFirst() + ?.let { it.deleteFromRealm() } + EventEntity.where(realm, eventId = localEcho.root.eventId ?: "") + .findFirst() + ?.let { it.deleteFromRealm() } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index a225146d83..45172fb213 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -286,7 +286,7 @@ internal class LocalEchoEventFactory @Inject constructor( ) } - fun createVerificationRequest(roomId: String, fromDevice: String, to: String, methods: List): Event { + fun createVerificationRequest(roomId: String, fromDevice: String, toUserId: String, methods: List): Event { val localID = LocalEcho.createLocalEchoId() return Event( roomId = roomId, @@ -297,7 +297,7 @@ internal class LocalEchoEventFactory @Inject constructor( content = MessageVerificationRequestContent( body = stringProvider.getString(R.string.key_verification_request_fallback_message, userId), fromDevice = fromDevice, - to = to, + toUserId = toUserId, methods = methods ).toContent(), unsignedData = UnsignedData(age = null, transactionId = localID) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt index d6d924ab46..60d1a217a7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoUpdater.kt @@ -25,7 +25,7 @@ import javax.inject.Inject internal class LocalEchoUpdater @Inject constructor(private val monarchy: Monarchy) { - suspend fun updateSendState(eventId: String, sendState: SendState) { + fun updateSendState(eventId: String, sendState: SendState) { Timber.v("Update local state of $eventId to ${sendState.name}") monarchy.writeAsync { realm -> val sendingEventEntity = EventEntity.where(realm, eventId).findFirst() diff --git a/vector/src/main/java/im/vector/riotx/features/command/Command.kt b/vector/src/main/java/im/vector/riotx/features/command/Command.kt index fbc3ddebe5..3f636775c0 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/Command.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/Command.kt @@ -40,7 +40,7 @@ enum class Command(val command: String, val parameters: String, @StringRes val d CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token), SHRUG("/shrug", "", R.string.command_description_shrug), // TODO temporary command - VERIFY_USER("/verify", "", R.string.command_description_spoiler); + VERIFY_USER("/verify", "", R.string.command_description_verify); val length get() = command.length + 1 diff --git a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt index 23e30e1d3c..dcdb7ad8a2 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt @@ -244,13 +244,13 @@ object CommandParser { ParsedCommand.SendSpoiler(message) } - Command.SHRUG.command -> { - val message = textMessage.subSequence(Command.SHRUG.command.length, textMessage.length).trim() + Command.SHRUG.command -> { + val message = textMessage.substring(Command.SHRUG.command.length).trim() ParsedCommand.SendShrug(message) } - Command.VERIFY_USER.command -> { + Command.VERIFY_USER.command -> { val message = textMessage.substring(Command.VERIFY_USER.command.length).trim() ParsedCommand.VerifyUser(message) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index efdfd53234..c1d3f4ce4a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -377,14 +377,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro popDraft() } is ParsedCommand.SendShrug -> { - val sequence: CharSequence = buildString { + val sequence = buildString { append("¯\\_(ツ)_/¯") - .apply { - if (slashCommandResult.message.isNotEmpty()) { - append(" ") - append(slashCommandResult.message) - } - } + if (slashCommandResult.message.isNotEmpty()) { + append(" ") + append(slashCommandResult.message) + } } room.sendTextMessage(sequence) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt index cd201900fb..8b1a2dba31 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsLabsFragment.kt @@ -20,7 +20,9 @@ import im.vector.riotx.R import im.vector.riotx.core.preference.VectorSwitchPreference import javax.inject.Inject -class VectorSettingsLabsFragment @Inject constructor(val vectorPreferences: VectorPreferences) : VectorSettingsBaseFragment() { +class VectorSettingsLabsFragment @Inject constructor( + private val vectorPreferences: VectorPreferences +) : VectorSettingsBaseFragment() { override var titleRes = R.string.room_settings_labs_pref_title override val preferenceXmlRes = R.xml.vector_settings_labs diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index 1a64f75de5..530c207749 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -46,7 +46,7 @@ android:title="@string/labs_swipe_to_reply_in_timeline" /> From 36c5566b070281113b0ecf7170540fbc6f4f0ab9 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 3 Dec 2019 18:22:44 +0100 Subject: [PATCH 003/217] cleaning --- .../message/MessageVerificationStartContent.kt | 13 +++++++++---- .../verification/SasTransportRoomMessage.kt | 15 +++++++++++++-- .../crypto/verification/SasTransportToDevice.kt | 14 ++++++++++++-- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt index 7d77a34e27..f928e21a82 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationStartContent.kt @@ -38,7 +38,7 @@ internal data class MessageVerificationStartContent( ) : VerificationInfoStart { override fun toCanonicalJson(): String? { - return JsonCanonicalizer.getCanonicalJson(MessageVerificationStartContent::class.java, this) + return JsonCanonicalizer.getCanonicalJson(MessageVerificationStartContent::class.java, this) } override val transactionID: String? @@ -46,9 +46,14 @@ internal data class MessageVerificationStartContent( override fun isValid(): Boolean { if ( - (transactionID.isNullOrBlank() || fromDevice.isNullOrBlank() || method != KeyVerificationStart.VERIF_METHOD_SAS || keyAgreementProtocols.isNullOrEmpty() || hashes.isNullOrEmpty()) + (transactionID.isNullOrBlank() + || fromDevice.isNullOrBlank() + || method != KeyVerificationStart.VERIF_METHOD_SAS + || keyAgreementProtocols.isNullOrEmpty() + || hashes.isNullOrEmpty()) || !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty() - || (!messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256) && !messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256_LONGKDF)) + || (!messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256) + && !messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256_LONGKDF)) || shortAuthenticationStrings.isNullOrEmpty() || !shortAuthenticationStrings.contains(SasMode.DECIMAL)) { Timber.e("## received invalid verification request") @@ -57,5 +62,5 @@ internal data class MessageVerificationStartContent( return true } - override fun toEventContent() = this.toContent() + override fun toEventContent() = this.toContent() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt index fda411af39..87bb4bc805 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt @@ -41,7 +41,11 @@ internal class SasTransportRoomMessage( private val taskExecutor: TaskExecutor ) : SasTransport { - override fun sendToOther(type: String, verificationInfo: VerificationInfo, nextState: SasVerificationTxState, onErrorReason: CancelCode, onDone: (() -> Unit)?) { + override fun sendToOther(type: String, + verificationInfo: VerificationInfo, + nextState: SasVerificationTxState, + onErrorReason: CancelCode, + onDone: (() -> Unit)?) { Timber.d("## SAS sending msg type $type") Timber.v("## SAS sending msg info $verificationInfo") sendVerificationMessageTask.configureWith( @@ -109,7 +113,14 @@ internal class SasTransportRoomMessage( commitment: String, messageAuthenticationCode: String, shortAuthenticationStrings: List) - : VerificationInfoAccept = MessageVerificationAcceptContent.create(tid, keyAgreementProtocol, hash, commitment, messageAuthenticationCode, shortAuthenticationStrings) + : VerificationInfoAccept = MessageVerificationAcceptContent.create( + tid, + keyAgreementProtocol, + hash, + commitment, + messageAuthenticationCode, + shortAuthenticationStrings + ) override fun createKey(tid: String, pubKey: String): VerificationInfoKey = MessageVerificationKeyContent.create(tid, pubKey) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt index 0e1459a920..96ae47895e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt @@ -36,7 +36,11 @@ internal class SasTransportToDevice( private var taskExecutor: TaskExecutor ) : SasTransport { - override fun sendToOther(type: String, verificationInfo: VerificationInfo, nextState: SasVerificationTxState, onErrorReason: CancelCode, onDone: (() -> Unit)?) { + override fun sendToOther(type: String, + verificationInfo: VerificationInfo, + nextState: SasVerificationTxState, + onErrorReason: CancelCode, + onDone: (() -> Unit)?) { Timber.d("## SAS sending msg type $type") Timber.v("## SAS sending msg info $verificationInfo") val tx = tx ?: return @@ -98,7 +102,13 @@ internal class SasTransportToDevice( commitment: String, messageAuthenticationCode: String, shortAuthenticationStrings: List) - : VerificationInfoAccept = KeyVerificationAccept.create(tid, keyAgreementProtocol, hash, commitment, messageAuthenticationCode, shortAuthenticationStrings) + : VerificationInfoAccept = KeyVerificationAccept.create( + tid, + keyAgreementProtocol, + hash, + commitment, + messageAuthenticationCode, + shortAuthenticationStrings) override fun createKey(tid: String, pubKey: String): VerificationInfoKey = KeyVerificationKey.create(tid, pubKey) From bbd9738452cd6a049fab8566656ab4e008a9be2d Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 3 Dec 2019 18:24:18 +0100 Subject: [PATCH 004/217] Simple strategy to Ignore old verification messages --- .../VerificationMessageLiveObserver.kt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt index f75a11cdcb..9a9cf9a420 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt @@ -70,12 +70,23 @@ internal class VerificationMessageLiveObserver @Inject constructor( } .toList() + // TODO use age also, ignore initial sync or back pagination? + val now = System.currentTimeMillis() + val tooInThePast = now - (10 * 60 * 1000 * 1000) + val tooInTheFuture = System.currentTimeMillis() + (5 * 60 * 1000 * 1000) + events.forEach { event -> Timber.d("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}") Timber.v("## SAS Verification live observer: received msgId: $event") + // If the request is in the future by more than 5 minutes or more than 10 minutes in the past, + // the message should be ignored by the receiver. + val eventOrigin = event.originServerTs ?: -1 + if (eventOrigin < tooInThePast || eventOrigin > tooInTheFuture) { + Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is out of time ^^") + return@forEach + } // decrypt if needed? - if (event.isEncrypted() && event.mxDecryptionResult == null) { // TODO use a global event decryptor? attache to session and that listen to new sessionId? // for now decrypt sync @@ -103,8 +114,6 @@ internal class VerificationMessageLiveObserver @Inject constructor( } EventType.MESSAGE -> { if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel()?.type) { - // TODO If the request is in the future by more than 5 minutes or more than 10 minutes in the past, - // the message should be ignored by the receiver. sasVerificationService.onRoomRequestReceived(event) } } From 2aa9c3ea22d65ff669e0778cd990f8ca62c1ea83 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 4 Dec 2019 18:22:01 +0100 Subject: [PATCH 005/217] Fix / Use transport to start verification --- .../crypto/sas/SasVerificationService.kt | 7 +++++ .../MessageVerificationRequestContent.kt | 2 +- .../DefaultOutgoingSASVerificationRequest.kt | 17 ++++++------ .../DefaultSasVerificationService.kt | 27 +++++++++++++++++-- .../crypto/verification/SasTransport.kt | 10 ++++++- .../verification/SasTransportRoomMessage.kt | 21 +++++++++++++++ .../verification/SasTransportToDevice.kt | 23 +++++++++++++--- .../android/internal/di/MoshiProvider.kt | 1 + 8 files changed, 92 insertions(+), 16 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt index 902baae06f..3c3c43dbd4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/sas/SasVerificationService.kt @@ -52,6 +52,13 @@ interface SasVerificationService { fun requestKeyVerificationInDMs(userId: String, roomId: String, callback: MatrixCallback?) + fun beginKeyVerificationInDMs(method: String, + transactionId: String, + roomId: String, + otherUserId: String, + otherDeviceId: String, + callback: MatrixCallback?): String? + // fun transactionUpdated(tx: SasVerificationTransaction) interface SasVerificationListener { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationRequestContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationRequestContent.kt index 897eb9dbbf..1f6c8158fe 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationRequestContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVerificationRequestContent.kt @@ -21,7 +21,7 @@ import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent @JsonClass(generateAdapter = true) -internal data class MessageVerificationRequestContent( +data class MessageVerificationRequestContent( @Json(name = "msgtype") override val type: String = MessageType.MSGTYPE_VERIFICATION_REQUEST, @Json(name = "body") override val body: String, @Json(name = "from_device") val fromDevice: String, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt index 5827ecf4b8..5eead70c17 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASVerificationRequest.kt @@ -78,14 +78,15 @@ internal class DefaultOutgoingSASVerificationRequest( throw IllegalStateException("Interactive Key verification already started") } - val startMessage = KeyVerificationStart() - startMessage.fromDevice = credentials.deviceId - startMessage.method = KeyVerificationStart.VERIF_METHOD_SAS - startMessage.transactionID = transactionId - startMessage.keyAgreementProtocols = KNOWN_AGREEMENT_PROTOCOLS - startMessage.hashes = KNOWN_HASHES - startMessage.messageAuthenticationCodes = KNOWN_MACS - startMessage.shortAuthenticationStrings = KNOWN_SHORT_CODES + val startMessage = transport.createStart( + credentials.deviceId ?: "", + KeyVerificationStart.VERIF_METHOD_SAS, + transactionId, + KNOWN_AGREEMENT_PROTOCOLS, + KNOWN_HASHES, + KNOWN_MACS, + KNOWN_SHORT_CODES + ) startReq = startMessage state = SasVerificationTxState.SendingStart diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index 1d15a996dd..2546f1f492 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -348,7 +348,7 @@ internal class DefaultSasVerificationService @Inject constructor( if (!cancelReq.isValid()) { // ignore - Timber.e("## SAS Received invalid accept request") + Timber.e("## SAS Received invalid cancel request") return } val otherUserId = event.senderId!! @@ -477,7 +477,7 @@ internal class DefaultSasVerificationService @Inject constructor( Timber.v("## SAS Received $macReq") val existing = getExistingTransaction(senderId, macReq.transactionID!!) if (existing == null) { - Timber.e("## SAS Received invalid accept request") + Timber.e("## SAS Received invalid Mac request") return } if (existing is SASVerificationTransaction) { @@ -532,6 +532,7 @@ internal class DefaultSasVerificationService @Inject constructor( txID, userId, deviceID) + tx.transport = sasTransportToDeviceFactory.createTransport(tx) addTransaction(tx) tx.start() @@ -565,6 +566,28 @@ internal class DefaultSasVerificationService @Inject constructor( }.executeBy(taskExecutor) } + override fun beginKeyVerificationInDMs(method: String, transactionId: String, roomId: String, + otherUserId: String, otherDeviceId: String, + callback: MatrixCallback?): String? { + if (KeyVerificationStart.VERIF_METHOD_SAS == method) { + val tx = DefaultOutgoingSASVerificationRequest( + setDeviceVerificationAction, + credentials, + cryptoStore, + myDeviceInfoHolder.get().myDevice.fingerprint()!!, + transactionId, + otherUserId, + otherDeviceId) + tx.transport = sasTransportRoomMessageFactory.createTransport(roomId, cryptoService) + addTransaction(tx) + + tx.start() + return transactionId + } else { + throw IllegalArgumentException("Unknown verification method") + } + } + /** * This string must be unique for the pair of users performing verification for the duration that the transaction is valid */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt index 31a89335b7..ae5f55b662 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransport.kt @@ -47,7 +47,15 @@ internal interface SasTransport { shortAuthenticationStrings: List): VerificationInfoAccept fun createKey(tid: String, - pubKey: String): VerificationInfoKey + pubKey: String): VerificationInfoKey + + fun createStart(fromDevice: String, + method: String, + transactionID: String, + keyAgreementProtocols: List, + hashes: List, + messageAuthenticationCodes: List, + shortAuthenticationStrings: List) : VerificationInfoStart fun createMac(tid: String, mac: Map, keys: String): VerificationInfoMac } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt index 87bb4bc805..fc131764af 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt @@ -125,6 +125,27 @@ internal class SasTransportRoomMessage( override fun createKey(tid: String, pubKey: String): VerificationInfoKey = MessageVerificationKeyContent.create(tid, pubKey) override fun createMac(tid: String, mac: Map, keys: String) = MessageVerificationMacContent.create(tid, mac, keys) + + override fun createStart(fromDevice: String, + method: String, + transactionID: String, + keyAgreementProtocols: List, + hashes: List, + messageAuthenticationCodes: List, + shortAuthenticationStrings: List): VerificationInfoStart { + return MessageVerificationStartContent( + fromDevice, + hashes, + keyAgreementProtocols, + messageAuthenticationCodes, + shortAuthenticationStrings, + method, + RelationDefaultContent( + type = RelationType.REFERENCE, + eventId = transactionID + ) + ) + } } internal class SasTransportRoomMessageFactory @Inject constructor( diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt index 96ae47895e..f1b472ae75 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt @@ -20,10 +20,7 @@ import im.vector.matrix.android.api.session.crypto.sas.CancelCode import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap -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.KeyVerificationKey -import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationMac +import im.vector.matrix.android.internal.crypto.model.rest.* import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith @@ -113,6 +110,24 @@ internal class SasTransportToDevice( override fun createKey(tid: String, pubKey: String): VerificationInfoKey = KeyVerificationKey.create(tid, pubKey) override fun createMac(tid: String, mac: Map, keys: String) = KeyVerificationMac.create(tid, mac, keys) + + override fun createStart(fromDevice: String, + method: String, + transactionID: String, + keyAgreementProtocols: List, + hashes: List, + messageAuthenticationCodes: List, + shortAuthenticationStrings: List): VerificationInfoStart { + return KeyVerificationStart().apply { + this.fromDevice = fromDevice + this.method = method + this.transactionID = transactionID + this.keyAgreementProtocols = keyAgreementProtocols + this.hashes = hashes + this.messageAuthenticationCodes = messageAuthenticationCodes + this.shortAuthenticationStrings = shortAuthenticationStrings + } + } } internal class SasTransportToDeviceFactory @Inject constructor( diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt index 793be10880..98cf9e234e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt @@ -46,6 +46,7 @@ object MoshiProvider { .registerSubtype(MessageVideoContent::class.java, MessageType.MSGTYPE_VIDEO) .registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION) .registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE) + .registerSubtype(MessageVerificationRequestContent::class.java, MessageType.MSGTYPE_VERIFICATION_REQUEST) ) .add(SerializeNulls.JSON_ADAPTER_FACTORY) .build() From e14602d1dc011752827b775b992111136b55532e Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 5 Dec 2019 09:53:51 +0100 Subject: [PATCH 006/217] fix rebase --- .../home/room/detail/timeline/factory/TimelineItemFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 4c36b55fef..a705576234 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -72,7 +72,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.KEY_VERIFICATION_MAC -> { // These events are filtered from timeline in normal case // Only visible in developer mode - defaultItemFactory.create(event, highlight, readMarkerVisible, callback) + defaultItemFactory.create(event, highlight, callback) } // Unhandled event types (yet) From 3cdd373368a7a732e238a5aa73fe21b8f4a4872e Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 5 Dec 2019 10:08:27 +0100 Subject: [PATCH 007/217] Convert KeyVerificationStart to data class --- .../crypto/model/rest/KeyVerificationStart.kt | 75 +++++-------------- .../DefaultSasVerificationService.kt | 8 +- .../verification/SasTransportToDevice.kt | 17 ++--- .../verification/VerificationInfoStart.kt | 13 ++++ 4 files changed, 40 insertions(+), 73 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt index 47c2a62d92..d1e0ff9049 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt @@ -27,75 +27,36 @@ import timber.log.Timber * Sent by Alice to initiate an interactive key verification. */ @JsonClass(generateAdapter = true) -class KeyVerificationStart : SendToDeviceObject, VerificationInfoStart { +data class KeyVerificationStart( + @Json(name = "from_device") override val fromDevice: String? = null, + override val method: String? = null, + @Json(name = "transaction_id") override val transactionID: String? = null, + @Json(name = "key_agreement_protocols") override val keyAgreementProtocols: List? = null, + @Json(name = "hashes") override val hashes: List? = null, + @Json(name = "message_authentication_codes") override val messageAuthenticationCodes: List? = null, + @Json(name = "short_authentication_string") override val shortAuthenticationStrings: List? = null +) : SendToDeviceObject, VerificationInfoStart { override fun toCanonicalJson(): String? { return JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, this) } - /** - * Alice’s device ID - */ - @Json(name = "from_device") - override var fromDevice: String? = null - - override var method: String? = null - - /** - * String to identify the transaction. - * This string must be unique for the pair of users performing verification for the duration that the transaction is valid. - * Alice’s device should record this ID and use it in future messages in this transaction. - */ - @Json(name = "transaction_id") - override var transactionID: String? = null - - /** - * An array of key agreement protocols that Alice’s client understands. - * Must include “curve25519”. - * Other methods may be defined in the future - */ - @Json(name = "key_agreement_protocols") - override var keyAgreementProtocols: List? = null - - /** - * An array of hashes that Alice’s client understands. - * Must include “sha256”. Other methods may be defined in the future. - */ - override var hashes: List? = null - - /** - * An array of message authentication codes that Alice’s client understands. - * Must include “hkdf-hmac-sha256”. - * Other methods may be defined in the future. - */ - @Json(name = "message_authentication_codes") - override var messageAuthenticationCodes: List? = null - - /** - * An array of short authentication string methods that Alice’s client (and Alice) understands. - * Must include “decimal”. - * This document also describes the “emoji” method. - * Other methods may be defined in the future - */ - @Json(name = "short_authentication_string") - override var shortAuthenticationStrings: List? = null companion object { const val VERIF_METHOD_SAS = "m.sas.v1" } override fun isValid(): Boolean { - if (transactionID.isNullOrBlank() - || fromDevice.isNullOrBlank() - || method != VERIF_METHOD_SAS - || keyAgreementProtocols.isNullOrEmpty() - || hashes.isNullOrEmpty() - || hashes?.contains("sha256") == false + if ((transactionID.isNullOrBlank() + || fromDevice.isNullOrBlank() + || method != VERIF_METHOD_SAS + || keyAgreementProtocols.isNullOrEmpty() + || hashes.isNullOrEmpty()) + || !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty() - || (messageAuthenticationCodes?.contains(SASVerificationTransaction.SAS_MAC_SHA256) == false - && messageAuthenticationCodes?.contains(SASVerificationTransaction.SAS_MAC_SHA256_LONGKDF) == false) - || shortAuthenticationStrings.isNullOrEmpty() - || shortAuthenticationStrings?.contains(SasMode.DECIMAL) == false) { + || (!messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256) + && !messageAuthenticationCodes.contains(SASVerificationTransaction.SAS_MAC_SHA256_LONGKDF)) + || shortAuthenticationStrings.isNullOrEmpty() || !shortAuthenticationStrings.contains(SasMode.DECIMAL)) { Timber.e("## received invalid verification request") return false } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index 2546f1f492..d7e01c2fde 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -239,14 +239,8 @@ internal class DefaultSasVerificationService @Inject constructor( if (!startReq.isValid()) { Timber.e("## SAS received invalid verification request") if (startReq.transactionID != null) { -// cancelTransaction( -// startReq.transactionID!!, -// otherUserId!!, -// startReq.fromDevice ?: event.getSenderKey()!!, -// CancelCode.UnknownMethod -// ) sasTransportToDeviceFactory.createTransport(null).cancelTransaction( - startReq.transactionID ?: "", + startReq.transactionID, otherUserId!!, startReq.fromDevice ?: event.getSenderKey()!!, CancelCode.UnknownMethod diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt index f1b472ae75..bce23de1cd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt @@ -118,15 +118,14 @@ internal class SasTransportToDevice( hashes: List, messageAuthenticationCodes: List, shortAuthenticationStrings: List): VerificationInfoStart { - return KeyVerificationStart().apply { - this.fromDevice = fromDevice - this.method = method - this.transactionID = transactionID - this.keyAgreementProtocols = keyAgreementProtocols - this.hashes = hashes - this.messageAuthenticationCodes = messageAuthenticationCodes - this.shortAuthenticationStrings = shortAuthenticationStrings - } + return KeyVerificationStart( + fromDevice, + method, + transactionID, + keyAgreementProtocols, + hashes, + messageAuthenticationCodes, + shortAuthenticationStrings) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoStart.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoStart.kt index f1cbd3f9f1..2248a239fb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoStart.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationInfoStart.kt @@ -18,10 +18,23 @@ package im.vector.matrix.android.internal.crypto.verification internal interface VerificationInfoStart : VerificationInfo { val method: String? + /** + * Alice’s device ID + */ val fromDevice: String? + /** + * String to identify the transaction. + * This string must be unique for the pair of users performing verification for the duration that the transaction is valid. + * Alice’s device should record this ID and use it in future messages in this transaction. + */ val transactionID: String? + /** + * An array of key agreement protocols that Alice’s client understands. + * Must include “curve25519”. + * Other methods may be defined in the future + */ val keyAgreementProtocols: List? /** From c462d15bcf2c4c63737fce5c824037f82ff201f6 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 5 Dec 2019 11:20:53 +0100 Subject: [PATCH 008/217] rebase --- .../android/internal/crypto/model/rest/KeyVerificationStart.kt | 1 - .../vector/matrix/android/internal/network/RetrofitExtensions.kt | 1 - vector/src/main/java/im/vector/riotx/features/command/Command.kt | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt index d1e0ff9049..e8c0334539 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeyVerificationStart.kt @@ -41,7 +41,6 @@ data class KeyVerificationStart( return JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, this) } - companion object { const val VERIF_METHOD_SAS = "m.sas.v1" } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt index a89e21b04a..0bce924abc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt @@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.network -import com.squareup.moshi.JsonDataException import com.squareup.moshi.JsonEncodingException import im.vector.matrix.android.api.failure.ConsentNotGivenError import im.vector.matrix.android.api.failure.Failure diff --git a/vector/src/main/java/im/vector/riotx/features/command/Command.kt b/vector/src/main/java/im/vector/riotx/features/command/Command.kt index 3f636775c0..776e8385ad 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/Command.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/Command.kt @@ -38,6 +38,7 @@ enum class Command(val command: String, val parameters: String, @StringRes val d CHANGE_DISPLAY_NAME("/nick", "", R.string.command_description_nick), MARKDOWN("/markdown", "", R.string.command_description_markdown), CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token), + SPOILER("/spoiler", "", R.string.command_description_spoiler), SHRUG("/shrug", "", R.string.command_description_shrug), // TODO temporary command VERIFY_USER("/verify", "", R.string.command_description_verify); From 73f0132d5dd073ae7d1b307ba0859439b41cf0ad Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 10 Dec 2019 16:37:54 +0100 Subject: [PATCH 009/217] FIx / room transport was not updating state --- .../DefaultSasVerificationService.kt | 10 ++++----- .../verification/SasTransportRoomMessage.kt | 22 +++++++++++++++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index d7e01c2fde..d54ae3a22a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -206,7 +206,7 @@ internal class DefaultSasVerificationService @Inject constructor( Timber.e("## received invalid verification request") if (startReq.transactionID != null) { sasTransportRoomMessageFactory.createTransport(event.roomId - ?: "", cryptoService).cancelTransaction( + ?: "", cryptoService, null).cancelTransaction( startReq.transactionID ?: "", otherUserId!!, startReq.fromDevice ?: event.getSenderKey()!!, @@ -218,10 +218,10 @@ internal class DefaultSasVerificationService @Inject constructor( handleStart(otherUserId, startReq as VerificationInfoStart) { it.transport = sasTransportRoomMessageFactory.createTransport(event.roomId - ?: "", cryptoService) + ?: "", cryptoService, it) }?.let { sasTransportRoomMessageFactory.createTransport(event.roomId - ?: "", cryptoService).cancelTransaction( + ?: "", cryptoService, null).cancelTransaction( startReq.transactionID ?: "", otherUserId!!, startReq.fromDevice ?: event.getSenderKey()!!, @@ -431,7 +431,7 @@ internal class DefaultSasVerificationService @Inject constructor( val otherUserId = event.senderId!! val existing = getExistingTransaction(otherUserId, keyReq.transactionID!!) if (existing == null) { - Timber.e("## SAS Received invalid accept request") + Timber.e("## SAS Received invalid key request") return } if (existing is SASVerificationTransaction) { @@ -572,7 +572,7 @@ internal class DefaultSasVerificationService @Inject constructor( transactionId, otherUserId, otherDeviceId) - tx.transport = sasTransportRoomMessageFactory.createTransport(roomId, cryptoService) + tx.transport = sasTransportRoomMessageFactory.createTransport(roomId, cryptoService, tx) addTransaction(tx) tx.start() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt index fc131764af..8e956fd60e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt @@ -36,7 +36,7 @@ import javax.inject.Inject internal class SasTransportRoomMessage( private val roomId: String, private val cryptoService: CryptoService, -// private val tx: SASVerificationTransaction?, + private val tx: SASVerificationTransaction?, private val sendVerificationMessageTask: SendVerificationMessageTask, private val taskExecutor: TaskExecutor ) : SasTransport { @@ -57,6 +57,20 @@ internal class SasTransportRoomMessage( ) ) { constraints = TaskConstraints(true) + callback = object : MatrixCallback { + override fun onSuccess(data: SendResponse) { + if (onDone != null) { + onDone() + } else { + tx?.state = nextState + } + } + + override fun onFailure(failure: Throwable) { + Timber.e("## SAS verification [${tx?.transactionId}] failed to send toDevice in state : ${tx?.state}") + tx?.cancel(onErrorReason) + } + } retryCount = 3 } .executeBy(taskExecutor) @@ -153,9 +167,9 @@ internal class SasTransportRoomMessageFactory @Inject constructor( private val taskExecutor: TaskExecutor) { fun createTransport(roomId: String, - cryptoService: CryptoService -// tx: SASVerificationTransaction? + cryptoService: CryptoService, + tx: SASVerificationTransaction? ): SasTransportRoomMessage { - return SasTransportRoomMessage(roomId, cryptoService, /*tx,*/ sendVerificationMessageTask, taskExecutor) + return SasTransportRoomMessage(roomId, cryptoService, tx, sendVerificationMessageTask, taskExecutor) } } From 8305ce67dd1136c382e914e261cbc512376a408a Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 11 Dec 2019 14:44:31 +0100 Subject: [PATCH 010/217] Aggregate Event References for DM verifications --- .../android/api/session/events/model/Event.kt | 8 + .../api/session/events/model/UnsignedData.kt | 14 ++ .../room/model/EventAnnotationsSummary.kt | 3 +- .../room/model/ReferencesAggregatedContent.kt | 31 ++++ .../room/model/ReferencesAggregatedSummary.kt | 30 ++++ .../model/message/MessageRelationContent.kt | 2 +- .../verification/SasTransportRoomMessage.kt | 7 + .../verification/SasTransportToDevice.kt | 1 - .../VerificationMessageLiveObserver.kt | 21 ++- .../mapper/EventAnnotationsSummaryMapper.kt | 18 +++ .../internal/database/mapper/EventMapper.kt | 2 + .../model/EventAnnotationsSummaryEntity.kt | 3 +- .../internal/database/model/EventEntity.kt | 2 + .../ReferencesAggregatedSummaryEntity.kt | 31 ++++ .../database/model/SessionRealmModule.kt | 1 + ...eferencesAggregatedSummaryEntityQueries.kt | 22 +++ .../android/internal/di/MatrixModule.kt | 9 +- .../room/EventRelationsAggregationTask.kt | 141 +++++++++++++++--- .../room/EventRelationsAggregationUpdater.kt | 6 + .../internal/session/sync/RoomSyncHandler.kt | 20 ++- .../android/internal/task/TaskExecutor.kt | 1 + .../android/internal/task/TaskThread.kt | 3 +- .../util/MatrixCoroutineDispatchers.kt | 3 +- 23 files changed, 336 insertions(+), 43 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/ReferencesAggregatedContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/ReferencesAggregatedSummary.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ReferencesAggregatedSummaryEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReferencesAggregatedSummaryEntityQueries.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt index bc6885eddc..ab39dea178 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt @@ -85,6 +85,14 @@ data class Event( @Transient var sendState: SendState = SendState.UNKNOWN + /** + The `age` value transcoded in a timestamp based on the device clock when the SDK received + the event from the home server. + Unlike `age`, this value is static. + */ + @Transient + var ageLocalTs: Long? = null + /** * Check if event is a state event. * @return true if event is state event. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt index 57eaa7dc76..b179cb7a31 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/UnsignedData.kt @@ -21,9 +21,23 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class UnsignedData( + /** + * The time in milliseconds that has elapsed since the event was sent. + * This field is generated by the local homeserver, and may be incorrect if the local time on at least one of the two servers + * is out of sync, which can cause the age to either be negative or greater than it actually is. + */ @Json(name = "age") val age: Long?, + /** + * Optional. The event that redacted this event, if any. + */ @Json(name = "redacted_because") val redactedEvent: Event? = null, + /** + * The client-supplied transaction ID, if the client being given the event is the same one which sent it. + */ @Json(name = "transaction_id") val transactionId: String? = null, + /** + * Optional. The previous content for this event. If there is no previous content, this key will be missing. + */ @Json(name = "prev_content") val prevContent: Map? = null, @Json(name = "m.relations") val relations: AggregatedRelations? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/EventAnnotationsSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/EventAnnotationsSummary.kt index 0d403be2f4..28edfcfe04 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/EventAnnotationsSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/EventAnnotationsSummary.kt @@ -18,5 +18,6 @@ package im.vector.matrix.android.api.session.room.model data class EventAnnotationsSummary( var eventId: String, var reactionsSummary: List, - var editSummary: EditAggregatedSummary? + var editSummary: EditAggregatedSummary?, + var referencesAggregatedSummary: ReferencesAggregatedSummary? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/ReferencesAggregatedContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/ReferencesAggregatedContent.kt new file mode 100644 index 0000000000..ae6e52a091 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/ReferencesAggregatedContent.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2019 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.session.room.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Contains an aggregated summary info of the references. + * Put pre-computed info that you want to access quickly without having + * to go through all references events + */ +@JsonClass(generateAdapter = true) +data class ReferencesAggregatedContent( + // Verification status info for m.key.verification.request msgType events + @Json(name = "verif_sum") val verificationSummary: String + // Add more fields for future summary info. +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/ReferencesAggregatedSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/ReferencesAggregatedSummary.kt new file mode 100644 index 0000000000..fce166c37a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/ReferencesAggregatedSummary.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2019 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.session.room.model + +import im.vector.matrix.android.api.session.events.model.Content + +/** + * Events can relates to other events, this object keeps a summary + * of all events that are referencing the 'eventId' event via the RelationType.REFERENCE + */ +class ReferencesAggregatedSummary( + val eventId: String, + val content: Content?, + val sourceEvents: List, + val localEchos: List +) + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageRelationContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageRelationContent.kt index ec773916fd..f65215e2bf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageRelationContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageRelationContent.kt @@ -21,6 +21,6 @@ import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent @JsonClass(generateAdapter = true) -internal data class MessageRelationContent( +data class MessageRelationContent( @Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt index 8e956fd60e..e40e8be31f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportRoomMessage.kt @@ -29,6 +29,7 @@ import im.vector.matrix.android.internal.crypto.tasks.SendVerificationMessageTas import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.task.TaskConstraints import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.TaskThread import im.vector.matrix.android.internal.task.configureWith import timber.log.Timber import javax.inject.Inject @@ -56,6 +57,8 @@ internal class SasTransportRoomMessage( cryptoService ) ) { + callbackThread = TaskThread.DM_VERIF + executionThread = TaskThread.DM_VERIF constraints = TaskConstraints(true) callback = object : MatrixCallback { override fun onSuccess(data: SendResponse) { @@ -86,6 +89,8 @@ internal class SasTransportRoomMessage( cryptoService ) ) { + callbackThread = TaskThread.DM_VERIF + executionThread = TaskThread.DM_VERIF constraints = TaskConstraints(true) retryCount = 3 callback = object : MatrixCallback { @@ -115,6 +120,8 @@ internal class SasTransportRoomMessage( cryptoService ) ) { + callbackThread = TaskThread.DM_VERIF + executionThread = TaskThread.DM_VERIF constraints = TaskConstraints(true) retryCount = 3 } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt index bce23de1cd..85e9099972 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SasTransportToDevice.kt @@ -61,7 +61,6 @@ internal class SasTransportToDevice( override fun onFailure(failure: Throwable) { Timber.e("## SAS verification [$tx.transactionId] failed to send toDevice in state : $tx.state") - tx.cancel(onErrorReason) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt index 9a9cf9a420..e7a38e8908 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationMessageLiveObserver.kt @@ -70,10 +70,12 @@ internal class VerificationMessageLiveObserver @Inject constructor( } .toList() - // TODO use age also, ignore initial sync or back pagination? + // TODO ignore initial sync or back pagination? + val now = System.currentTimeMillis() - val tooInThePast = now - (10 * 60 * 1000 * 1000) - val tooInTheFuture = System.currentTimeMillis() + (5 * 60 * 1000 * 1000) + val tooInThePast = now - (10 * 60 * 1000) + val fiveMinInMs = 5 * 60 * 1000 + val tooInTheFuture = System.currentTimeMillis() + fiveMinInMs events.forEach { event -> Timber.d("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}") @@ -81,11 +83,18 @@ internal class VerificationMessageLiveObserver @Inject constructor( // If the request is in the future by more than 5 minutes or more than 10 minutes in the past, // the message should be ignored by the receiver. - val eventOrigin = event.originServerTs ?: -1 - if (eventOrigin < tooInThePast || eventOrigin > tooInTheFuture) { - Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is out of time ^^") + val ageLocalTs = event.ageLocalTs + if (ageLocalTs != null && (now - ageLocalTs) > fiveMinInMs) { + Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is too old (age: ${(now - ageLocalTs)})") return@forEach + } else { + val eventOrigin = event.originServerTs ?: -1 + if (eventOrigin < tooInThePast || eventOrigin > tooInTheFuture) { + Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is too old (ts: $eventOrigin") + return@forEach + } } + // decrypt if needed? if (event.isEncrypted() && event.mxDecryptionResult == null) { // TODO use a global event decryptor? attache to session and that listen to new sessionId? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventAnnotationsSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventAnnotationsSummaryMapper.kt index 701d35926a..ccdb8fb91f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventAnnotationsSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventAnnotationsSummaryMapper.kt @@ -19,9 +19,11 @@ package im.vector.matrix.android.internal.database.mapper import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary +import im.vector.matrix.android.api.session.room.model.ReferencesAggregatedSummary import im.vector.matrix.android.internal.database.model.EditAggregatedSummaryEntity import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntity +import im.vector.matrix.android.internal.database.model.ReferencesAggregatedSummaryEntity import io.realm.RealmList internal object EventAnnotationsSummaryMapper { @@ -45,6 +47,14 @@ internal object EventAnnotationsSummaryMapper { it.sourceLocalEchoEvents.toList(), it.lastEditTs ) + }, + referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let { + ReferencesAggregatedSummary( + it.eventId, + ContentMapper.map(it.content), + it.sourceEvents.toList(), + it.sourceLocalEcho.toList() + ) } ) } @@ -75,6 +85,14 @@ internal object EventAnnotationsSummaryMapper { }) } } + eventAnnotationsSummaryEntity.referencesSummaryEntity = annotationsSummary.referencesAggregatedSummary?.let { + ReferencesAggregatedSummaryEntity( + it.eventId, + ContentMapper.map(it.content), + RealmList().apply { addAll(it.sourceEvents) }, + RealmList().apply { addAll(it.localEchos) } + ) + } return eventAnnotationsSummaryEntity } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt index ed5f04ef75..faac57e486 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt @@ -43,6 +43,7 @@ internal object EventMapper { eventEntity.redacts = event.redacts eventEntity.age = event.unsignedData?.age ?: event.originServerTs eventEntity.unsignedData = uds + eventEntity.ageLocalTs = event.ageLocalTs return eventEntity } @@ -70,6 +71,7 @@ internal object EventMapper { unsignedData = ud, redacts = eventEntity.redacts ).also { + it.ageLocalTs = eventEntity.ageLocalTs it.sendState = eventEntity.sendState eventEntity.decryptionResultJson?.let { json -> try { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventAnnotationsSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventAnnotationsSummaryEntity.kt index 523d94b770..1a4f72f0b1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventAnnotationsSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventAnnotationsSummaryEntity.kt @@ -24,7 +24,8 @@ internal open class EventAnnotationsSummaryEntity( var eventId: String = "", var roomId: String? = null, var reactionsSummary: RealmList = RealmList(), - var editSummary: EditAggregatedSummaryEntity? = null + var editSummary: EditAggregatedSummaryEntity? = null, + var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt index 4def7aec5d..9e3ed6a93b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt @@ -59,6 +59,8 @@ internal open class EventEntity(@Index var eventId: String = "", sendStateStr = value.name } + var ageLocalTs: Long? = null + companion object @LinkingObjects("untimelinedStateEvents") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ReferencesAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ReferencesAggregatedSummaryEntity.kt new file mode 100644 index 0000000000..1c3ea70e52 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/ReferencesAggregatedSummaryEntity.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2019 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.internal.database.model + +import io.realm.RealmList +import io.realm.RealmObject + +internal open class ReferencesAggregatedSummaryEntity( + var eventId: String = "", + var content: String? = null, + // The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk) + var sourceEvents: RealmList = RealmList(), + // List of transaction ids for local echos + var sourceLocalEcho: RealmList = RealmList() +) : RealmObject() { + + companion object +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt index 6059d3faf7..4a93819027 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt @@ -38,6 +38,7 @@ import io.realm.annotations.RealmModule IgnoredUserEntity::class, BreadcrumbsEntity::class, EventAnnotationsSummaryEntity::class, + ReferencesAggregatedSummaryEntity::class, ReactionAggregatedSummaryEntity::class, EditAggregatedSummaryEntity::class, PushRulesEntity::class, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReferencesAggregatedSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReferencesAggregatedSummaryEntityQueries.kt new file mode 100644 index 0000000000..9c7547b5e1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReferencesAggregatedSummaryEntityQueries.kt @@ -0,0 +1,22 @@ +package im.vector.matrix.android.internal.database.query + +import im.vector.matrix.android.internal.database.model.ReferencesAggregatedSummaryEntity +import im.vector.matrix.android.internal.database.model.ReferencesAggregatedSummaryEntityFields +import io.realm.Realm +import io.realm.RealmQuery +import io.realm.kotlin.where + +internal fun ReferencesAggregatedSummaryEntity.Companion.where(realm: Realm, eventId: String): RealmQuery { + val query = realm.where() + query.equalTo(ReferencesAggregatedSummaryEntityFields.EVENT_ID, eventId) + return query +} + +internal fun ReferencesAggregatedSummaryEntity.Companion.create(realm: Realm, txID: String): ReferencesAggregatedSummaryEntity { + return realm.createObject(ReferencesAggregatedSummaryEntity::class.java).apply { + this.eventId = txID + } +} + + + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt index c17864b82b..1cf0964e50 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt @@ -36,10 +36,11 @@ internal object MatrixModule { @MatrixScope fun providesMatrixCoroutineDispatchers(): MatrixCoroutineDispatchers { return MatrixCoroutineDispatchers(io = Dispatchers.IO, - computation = Dispatchers.Default, - main = Dispatchers.Main, - crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher(), - sync = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + computation = Dispatchers.Default, + main = Dispatchers.Main, + crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher(), + sync = Executors.newSingleThreadExecutor().asCoroutineDispatcher(), + dmVerif = Executors.newSingleThreadExecutor().asCoroutineDispatcher() ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt index 3d7c5df5fc..5c38fcf797 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt @@ -19,7 +19,9 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.events.model.* +import im.vector.matrix.android.api.session.room.model.ReferencesAggregatedContent import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageRelationContent import im.vector.matrix.android.api.session.room.model.relation.ReactionContent import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent @@ -42,6 +44,14 @@ internal interface EventRelationsAggregationTask : Task { + EventType.REACTION -> { // we got a reaction!! Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}") handleReaction(event, roomId, realm, userId, isLocalEcho) } - EventType.MESSAGE -> { + EventType.MESSAGE -> { if (event.unsignedData?.relations?.annotations != null) { Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}") handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm) @@ -99,33 +109,49 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor( } } - EventType.ENCRYPTED -> { + EventType.KEY_VERIFICATION_DONE, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_KEY -> { + Timber.v("## SAS REF in room $roomId for event ${event.eventId}") + event.content.toModel()?.relatesTo?.let { + if (it.type == RelationType.REFERENCE && it.eventId != null) { + handleVerification(realm, event, roomId, isLocalEcho, it.eventId, userId) + } + } + } + + EventType.ENCRYPTED -> { // Relation type is in clear val encryptedEventContent = event.content.toModel() if (encryptedEventContent?.relatesTo?.type == RelationType.REPLACE) { // we need to decrypt if needed - if (event.mxDecryptionResult == null) { - try { - val result = cryptoService.decryptEvent(event, event.roomId) - event.mxDecryptionResult = OlmDecryptionResult( - payload = result.clearEvent, - senderKey = result.senderCurve25519Key, - keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, - forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain - ) - } catch (e: MXCryptoError) { - Timber.w("Failed to decrypt e2e replace") - // TODO -> we should keep track of this and retry, or aggregation will be broken - } - } + decryptIfNeeded(event) event.getClearContent().toModel()?.let { Timber.v("###REPLACE in room $roomId for event ${event.eventId}") // A replace! handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) } + } else if (encryptedEventContent?.relatesTo?.type == RelationType.REFERENCE) { + decryptIfNeeded(event) + when (event.getClearType()) { + EventType.KEY_VERIFICATION_DONE, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_KEY -> { + Timber.v("## SAS REF in room $roomId for event ${event.eventId}") + encryptedEventContent.relatesTo.eventId?.let { + handleVerification(realm, event, roomId, isLocalEcho, it, userId) + } + } + } } } - EventType.REDACTION -> { + EventType.REDACTION -> { val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } ?: return@forEach when (eventToPrune.type) { @@ -145,7 +171,7 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor( } } } - else -> Timber.v("UnHandled event ${event.eventId}") + else -> Timber.v("UnHandled event ${event.eventId}") } } catch (t: Throwable) { Timber.e(t, "## Should not happen ") @@ -153,6 +179,23 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor( } } + private fun decryptIfNeeded(event: Event) { + if (event.mxDecryptionResult == null) { + try { + val result = cryptoService.decryptEvent(event, event.roomId ?: "") + event.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } catch (e: MXCryptoError) { + Timber.w("Failed to decrypt e2e replace") + // TODO -> we should keep track of this and retry, or aggregation will be broken + } + } + } + private fun handleReplace(realm: Realm, event: Event, content: MessageContent, roomId: String, isLocalEcho: Boolean, relatedEventId: String? = null) { val eventId = event.eventId ?: return val targetEventId = relatedEventId ?: content.relatesTo?.eventId ?: return @@ -228,7 +271,8 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor( val eventSummary = EventAnnotationsSummaryEntity.create(realm, roomId, eventId) val sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java) sum.key = it.key - sum.firstTimestamp = event.originServerTs ?: 0 // TODO how to maintain order? + sum.firstTimestamp = event.originServerTs + ?: 0 // TODO how to maintain order? sum.count = it.count eventSummary.reactionsSummary.add(sum) } else { @@ -374,4 +418,61 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor( Timber.e("## Cannot find summary for key $reactionKey") } } + + private fun handleVerification(realm: Realm, event: Event, roomId: String, isLocalEcho: Boolean, relatedEventId: String, userId: String) { + val eventSummary = EventAnnotationsSummaryEntity.where(realm, relatedEventId).findFirst() + ?: EventAnnotationsSummaryEntity.create(realm, roomId, relatedEventId).apply { this.roomId = roomId } + + val verifSummary = eventSummary.referencesSummaryEntity + ?: ReferencesAggregatedSummaryEntity.create(realm, relatedEventId).also { + eventSummary.referencesSummaryEntity = it + } + + val txId = event.unsignedData?.transactionId + + if (!isLocalEcho && verifSummary.sourceLocalEcho.contains(txId)) { + // ok it has already been handled + } else { + ContentMapper.map(verifSummary.content)?.toModel() + var data = ContentMapper.map(verifSummary.content)?.toModel() + ?: ReferencesAggregatedContent(VerificationState.REQUEST.name) + // TODO ignore invalid messages? e.g a START after a CANCEL? + // i.e. never change state if already canceled/done + val newState = when (event.getClearType()) { + EventType.KEY_VERIFICATION_START -> { + VerificationState.WAITING + } + EventType.KEY_VERIFICATION_ACCEPT -> { + VerificationState.WAITING + } + EventType.KEY_VERIFICATION_KEY -> { + VerificationState.WAITING + } + EventType.KEY_VERIFICATION_MAC -> { + VerificationState.WAITING + } + EventType.KEY_VERIFICATION_CANCEL -> { + if (event.senderId == userId) { + VerificationState.CANCELED_BY_ME + } else VerificationState.CANCELED_BY_OTHER + } + EventType.KEY_VERIFICATION_DONE -> { + VerificationState.DONE + } + else -> VerificationState.REQUEST + } + + data = data.copy(verificationSummary = newState.name) + verifSummary.content = ContentMapper.map(data.toContent()) + } + + if (isLocalEcho) { + verifSummary.sourceLocalEcho.add(event.eventId) + } else { + if (verifSummary.sourceLocalEcho.contains(txId)) { + verifSummary.sourceLocalEcho.remove(txId) + } + verifSummary.sourceEvents.add(event.eventId) + } + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt index aadf1bfccf..916430877e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt @@ -48,6 +48,12 @@ internal class EventRelationsAggregationUpdater @Inject constructor(@SessionData EventType.MESSAGE, EventType.REDACTION, EventType.REACTION, + EventType.KEY_VERIFICATION_DONE, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_KEY, EventType.ENCRYPTED) ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index 4a003eb7d9..649de30339 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -93,14 +93,15 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch // PRIVATE METHODS ***************************************************************************** private fun handleRoomSync(realm: Realm, handlingStrategy: HandlingStrategy, isInitialSync: Boolean, reporter: DefaultInitialSyncProgressService?) { + val syncLocalTimeStampMillis = System.currentTimeMillis() val rooms = when (handlingStrategy) { is HandlingStrategy.JOINED -> handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_joined_rooms, 0.6f) { - handleJoinedRoom(realm, it.key, it.value, isInitialSync) + handleJoinedRoom(realm, it.key, it.value, isInitialSync, syncLocalTimeStampMillis) } is HandlingStrategy.INVITED -> handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_invited_rooms, 0.1f) { - handleInvitedRoom(realm, it.key, it.value) + handleInvitedRoom(realm, it.key, it.value, syncLocalTimeStampMillis) } is HandlingStrategy.LEFT -> { @@ -115,7 +116,8 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch private fun handleJoinedRoom(realm: Realm, roomId: String, roomSync: RoomSync, - isInitialSync: Boolean): RoomEntity { + isInitialSync: Boolean, + syncLocalTimestampMillis: Long): RoomEntity { Timber.v("Handle join sync for room $roomId") if (roomSync.ephemeral != null && roomSync.ephemeral.events.isNotEmpty()) { @@ -154,7 +156,8 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch roomEntity, roomSync.timeline.events, roomSync.timeline.prevToken, - roomSync.timeline.limited + roomSync.timeline.limited, + syncLocalTimestampMillis ) roomEntity.addOrUpdate(chunkEntity) } @@ -170,12 +173,13 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch private fun handleInvitedRoom(realm: Realm, roomId: String, - roomSync: InvitedRoomSync): RoomEntity { + roomSync: InvitedRoomSync, + syncLocalTimestampMillis: Long): RoomEntity { Timber.v("Handle invited sync for room $roomId") val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId) roomEntity.membership = Membership.INVITE if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) { - val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events) + val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events, syncLocalTimestampMillis = syncLocalTimestampMillis) roomEntity.addOrUpdate(chunkEntity) } val hasRoomMember = roomSync.inviteState?.events?.firstOrNull { @@ -200,7 +204,8 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch roomEntity: RoomEntity, eventList: List, prevToken: String? = null, - isLimited: Boolean = true): ChunkEntity { + isLimited: Boolean = true, + syncLocalTimestampMillis: Long): ChunkEntity { val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomEntity.roomId) var stateIndexOffset = 0 val chunkEntity = if (!isLimited && lastChunk != null) { @@ -216,6 +221,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch val eventIds = ArrayList(eventList.size) for (event in eventList) { + event.ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } event.eventId?.also { eventIds.add(it) } chunkEntity.add(roomEntity.roomId, event, PaginationDirection.FORWARDS, stateIndexOffset) // Give info to crypto module diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt index d5392779d1..fefd21b2cf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt @@ -86,5 +86,6 @@ internal class TaskExecutor @Inject constructor(private val coroutineDispatchers TaskThread.CALLER -> EmptyCoroutineContext TaskThread.CRYPTO -> coroutineDispatchers.crypto TaskThread.SYNC -> coroutineDispatchers.sync + TaskThread.DM_VERIF -> coroutineDispatchers.dmVerif } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskThread.kt index 16ed93662c..4c24f30506 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskThread.kt @@ -22,5 +22,6 @@ internal enum class TaskThread { IO, CALLER, CRYPTO, - SYNC + SYNC, + DM_VERIF } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/MatrixCoroutineDispatchers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/MatrixCoroutineDispatchers.kt index 23201c084e..ce43c5ea77 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/MatrixCoroutineDispatchers.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/MatrixCoroutineDispatchers.kt @@ -23,5 +23,6 @@ internal data class MatrixCoroutineDispatchers( val computation: CoroutineDispatcher, val main: CoroutineDispatcher, val crypto: CoroutineDispatcher, - val sync: CoroutineDispatcher + val sync: CoroutineDispatcher, + val dmVerif: CoroutineDispatcher ) From 02f03e6b2330b12ef1e9f9e173de57fad20e206c Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 11 Dec 2019 16:00:53 +0100 Subject: [PATCH 011/217] Fix test compilation --- .../vector/matrix/android/SingleThreadCoroutineDispatcher.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/SingleThreadCoroutineDispatcher.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/SingleThreadCoroutineDispatcher.kt index e63123f3b3..fb1faa92d4 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/SingleThreadCoroutineDispatcher.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/SingleThreadCoroutineDispatcher.kt @@ -18,5 +18,8 @@ package im.vector.matrix.android import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.Executors -internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main) +internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main, + Executors.newSingleThreadExecutor().asCoroutineDispatcher()) From 0776a301ea5f1f80e9736038ebbc04960d95565a Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 11 Dec 2019 16:48:33 +0100 Subject: [PATCH 012/217] Incoming DM verification handling in timeline --- .../room/model/ReferencesAggregatedSummary.kt | 1 - ...eferencesAggregatedSummaryEntityQueries.kt | 3 - .../home/room/detail/RoomDetailAction.kt | 3 + .../home/room/detail/RoomDetailFragment.kt | 4 + .../home/room/detail/RoomDetailViewModel.kt | 18 ++ .../timeline/TimelineEventController.kt | 4 + .../action/MessageActionsViewModel.kt | 7 +- .../timeline/factory/EncryptionItemFactory.kt | 7 +- .../timeline/factory/MessageItemFactory.kt | 69 ++++++- .../timeline/factory/TimelineItemFactory.kt | 9 +- .../factory/VerificationItemFactory.kt | 116 ++++++++++++ .../timeline/format/NoticeEventFormatter.kt | 6 + .../helper/MessageInformationDataFactory.kt | 16 +- .../timeline/item/AbsBaseMessageItem.kt | 142 ++++++++++++++ .../detail/timeline/item/AbsMessageItem.kt | 98 ++-------- .../timeline/item/MessageInformationData.kt | 10 +- .../item/VerificationRequestConclusionItem.kt | 77 ++++++++ .../timeline/item/VerificationRequestItem.kt | 177 ++++++++++++++++++ ...button_destructive_background_selector.xml | 5 + ...button_destructive_text_color_selector.xml | 5 + .../button_positive_background_selector.xml | 5 + .../button_positive_text_color_selector.xml | 5 + .../src/main/res/drawable/ic_shield_black.xml | 14 ++ .../main/res/drawable/ic_shield_trusted.xml | 18 ++ .../res/drawable/rounded_rect_shape_8.xml | 11 ++ .../layout/item_timeline_event_base_state.xml | 95 ++++++++++ ..._timeline_event_verification_done_stub.xml | 36 ++++ .../item_timeline_event_verification_stub.xml | 68 +++++++ vector/src/main/res/values/colors_riot.xml | 3 + vector/src/main/res/values/colors_riotx.xml | 11 ++ vector/src/main/res/values/strings_riotX.xml | 10 + vector/src/main/res/values/styles_riot.xml | 17 ++ 32 files changed, 963 insertions(+), 107 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/VerificationItemFactory.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestConclusionItem.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt create mode 100644 vector/src/main/res/color/button_destructive_background_selector.xml create mode 100644 vector/src/main/res/color/button_destructive_text_color_selector.xml create mode 100644 vector/src/main/res/color/button_positive_background_selector.xml create mode 100644 vector/src/main/res/color/button_positive_text_color_selector.xml create mode 100644 vector/src/main/res/drawable/ic_shield_black.xml create mode 100644 vector/src/main/res/drawable/ic_shield_trusted.xml create mode 100644 vector/src/main/res/drawable/rounded_rect_shape_8.xml create mode 100644 vector/src/main/res/layout/item_timeline_event_base_state.xml create mode 100644 vector/src/main/res/layout/item_timeline_event_verification_done_stub.xml create mode 100644 vector/src/main/res/layout/item_timeline_event_verification_stub.xml diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/ReferencesAggregatedSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/ReferencesAggregatedSummary.kt index fce166c37a..ca9d81cba1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/ReferencesAggregatedSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/ReferencesAggregatedSummary.kt @@ -27,4 +27,3 @@ class ReferencesAggregatedSummary( val sourceEvents: List, val localEchos: List ) - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReferencesAggregatedSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReferencesAggregatedSummaryEntityQueries.kt index 9c7547b5e1..88f127066d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReferencesAggregatedSummaryEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/query/ReferencesAggregatedSummaryEntityQueries.kt @@ -17,6 +17,3 @@ internal fun ReferencesAggregatedSummaryEntity.Companion.create(realm: Realm, tx this.eventId = txID } } - - - diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt index c1743ae3fc..5d00b09204 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt @@ -63,4 +63,7 @@ sealed class RoomDetailAction : VectorViewModelAction { object ClearSendQueue : RoomDetailAction() object ResendAll : RoomDetailAction() + + data class AcceptVerificationRequest(val transactionId: String, val otherUserId: String, val otherdDeviceId: String) : RoomDetailAction() + data class DeclineVerificationRequest(val transactionId: String) : RoomDetailAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 80f54a9c1f..9cc8eabe58 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -1024,6 +1024,10 @@ class RoomDetailFragment @Inject constructor( .show(requireActivity().supportFragmentManager, "DISPLAY_EDITS") } + override fun onTimelineItemAction(itemAction: RoomDetailAction) { + roomDetailViewModel.handle(itemAction) + } + override fun onRoomCreateLinkClicked(url: String) { permalinkHandler.launch(requireContext(), url, object : NavigateToRoomInterceptor { override fun navToRoom(roomId: String, eventId: String?): Boolean { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index c1d3f4ce4a..3ce27be63a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -48,6 +48,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent +import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap import im.vector.riotx.R @@ -177,6 +178,8 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages() is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages() + is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) + is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) } } @@ -786,6 +789,21 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro }) } + private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) { + session.getSasVerificationService().beginKeyVerificationInDMs( + KeyVerificationStart.VERIF_METHOD_SAS, + action.transactionId, + room.roomId, + action.otherUserId, + action.otherdDeviceId, + null + ) + } + + private fun handleDeclineVerification(action: RoomDetailAction.DeclineVerificationRequest) { + Timber.e("TODO implement $action") + } + private fun observeSyncState() { session.rx() .liveSyncState() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt index 576b9fa0ba..fe1a681480 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt @@ -31,6 +31,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.core.epoxy.LoadingItem_ import im.vector.riotx.core.extensions.localDateTime +import im.vector.riotx.features.home.room.detail.RoomDetailAction import im.vector.riotx.features.home.room.detail.RoomDetailViewState import im.vector.riotx.features.home.room.detail.UnreadState import im.vector.riotx.features.home.room.detail.timeline.factory.MergedHeaderItemFactory @@ -62,6 +63,9 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec fun onFileMessageClicked(eventId: String, messageFileContent: MessageFileContent) fun onAudioMessageClicked(messageAudioContent: MessageAudioContent) fun onEditedDecorationClicked(informationData: MessageInformationData) + + // TODO move all callbacks to this? + fun onTimelineItemAction(itemAction: RoomDetailAction) } interface ReactionPillCallback { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 102412948b..e8047c2b06 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -23,10 +23,7 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.isTextMessage import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.message.MessageContent -import im.vector.matrix.android.api.session.room.model.message.MessageImageContent -import im.vector.matrix.android.api.session.room.model.message.MessageTextContent -import im.vector.matrix.android.api.session.room.model.message.MessageType +import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent @@ -172,6 +169,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { eventHtmlRenderer.get().render(messageContent.formattedBody ?: messageContent.body) + } else if (messageContent is MessageVerificationRequestContent) { + stringProvider.getString(R.string.verification_request) } else { messageContent?.body } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt index 3f234fcd3e..29b01120d1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt @@ -17,6 +17,7 @@ package im.vector.riotx.features.home.room.detail.timeline.factory import android.view.View +import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel @@ -34,7 +35,8 @@ import javax.inject.Inject class EncryptionItemFactory @Inject constructor(private val stringProvider: StringProvider, private val avatarRenderer: AvatarRenderer, - private val avatarSizeProvider: AvatarSizeProvider) { + private val avatarSizeProvider: AvatarSizeProvider, + private val session: Session) { fun create(event: TimelineEvent, highlight: Boolean, @@ -46,7 +48,8 @@ class EncryptionItemFactory @Inject constructor(private val stringProvider: Stri sendState = event.root.sendState, avatarUrl = event.senderAvatar, memberName = event.getDisambiguatedDisplayName(), - showInformation = false + showInformation = false, + sentByMe = event.root.senderId == session.myUserId ) val attributes = NoticeItem.Attributes( avatarRenderer = avatarRenderer, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 9c96f17022..93d5ab3789 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -24,6 +24,7 @@ import android.text.style.ClickableSpan import android.text.style.ForegroundColorSpan import android.view.View import dagger.Lazy +import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.* @@ -64,7 +65,8 @@ class MessageItemFactory @Inject constructor( private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder, private val defaultItemFactory: DefaultItemFactory, private val noticeItemFactory: NoticeItemFactory, - private val avatarSizeProvider: AvatarSizeProvider) { + private val avatarSizeProvider: AvatarSizeProvider, + private val session: Session) { fun create(event: TimelineEvent, nextEvent: TimelineEvent?, @@ -97,14 +99,15 @@ class MessageItemFactory @Inject constructor( // val all = event.root.toContent() // val ev = all.toModel() return when (messageContent) { - is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes) - is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageFileContent -> buildFileMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, callback, attributes) - else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback) + is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes) + is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageFileContent -> buildFileMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) + else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback) } } @@ -128,6 +131,51 @@ class MessageItemFactory @Inject constructor( })) } + private fun buildVerificationRequestMessageItem(messageContent: MessageVerificationRequestContent, + @Suppress("UNUSED_PARAMETER") + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes): VerificationRequestItem? { + // If this request is not sent by me or sent to me, we should ignore it in timeline + val myUserId = session.myUserId + if (informationData.senderId != myUserId && messageContent.toUserId != myUserId) { + return null + } + + val otherUserId = if (informationData.sentByMe) messageContent.toUserId else informationData.senderId + val otherUserName = if (informationData.sentByMe) session.getUser(messageContent.toUserId)?.displayName + else informationData.memberName + return VerificationRequestItem_() + .attributes( + VerificationRequestItem.Attributes( + otherUserId, + otherUserName.toString(), + messageContent.fromDevice, + informationData.eventId, + informationData, + attributes.avatarRenderer, + attributes.colorProvider, + attributes.itemLongClickListener, + attributes.itemClickListener, + attributes.reactionPillCallback, + attributes.readReceiptsCallback, + attributes.emojiTypeFace + ) + ) + .callback(callback) +// .izLocalFile(messageContent.getFileUrl().isLocalFile()) +// .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) +// .filename(messageContent.body) +// .iconRes(R.drawable.filetype_audio) +// .clickListener( +// DebouncedClickListener(View.OnClickListener { +// callback?.onAudioMessageClicked(messageContent) +// })) + } + private fun buildFileMessageItem(messageContent: MessageFileContent, informationData: MessageInformationData, highlight: Boolean, @@ -193,7 +241,8 @@ class MessageItemFactory @Inject constructor( val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize() val thumbnailData = ImageContentRenderer.Data( filename = messageContent.body, - url = messageContent.videoInfo?.thumbnailFile?.url ?: messageContent.videoInfo?.thumbnailUrl, + url = messageContent.videoInfo?.thumbnailFile?.url + ?: messageContent.videoInfo?.thumbnailUrl, elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(), height = messageContent.videoInfo?.height, maxHeight = maxHeight, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index a705576234..3b0c2c2bb7 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -28,7 +28,8 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me private val encryptedItemFactory: EncryptedItemFactory, private val noticeItemFactory: NoticeItemFactory, private val defaultItemFactory: DefaultItemFactory, - private val roomCreateItemFactory: RoomCreateItemFactory) { + private val roomCreateItemFactory: RoomCreateItemFactory, + private val verificationConclusionItemFactory: VerificationItemFactory) { fun create(event: TimelineEvent, nextEvent: TimelineEvent?, @@ -66,13 +67,15 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me } EventType.KEY_VERIFICATION_ACCEPT, EventType.KEY_VERIFICATION_START, - EventType.KEY_VERIFICATION_DONE, EventType.KEY_VERIFICATION_CANCEL, EventType.KEY_VERIFICATION_KEY, EventType.KEY_VERIFICATION_MAC -> { // These events are filtered from timeline in normal case // Only visible in developer mode - defaultItemFactory.create(event, highlight, callback) + noticeItemFactory.create(event, highlight, callback) + } + EventType.KEY_VERIFICATION_DONE -> { + verificationConclusionItemFactory.create(event, highlight, callback) } // Unhandled event types (yet) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/VerificationItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/VerificationItemFactory.kt new file mode 100644 index 0000000000..56284b6777 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/VerificationItemFactory.kt @@ -0,0 +1,116 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.riotx.features.home.room.detail.timeline.factory + +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.RelationType +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.message.MessageRelationContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent +import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.internal.session.room.VerificationState +import im.vector.riotx.core.epoxy.VectorEpoxyModel +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.resources.UserPreferencesProvider +import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController +import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider +import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory +import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory +import im.vector.riotx.features.home.room.detail.timeline.item.VerificationRequestConclusionItem +import im.vector.riotx.features.home.room.detail.timeline.item.VerificationRequestConclusionItem_ +import javax.inject.Inject + +/** + * Can creates verification conclusion items + * Notice that not all KEY_VERIFICATION_DONE will be displayed in timeline, + * several checks are made to see if this conclusion is attached to a known request + */ +class VerificationItemFactory @Inject constructor( + private val colorProvider: ColorProvider, + private val messageInformationDataFactory: MessageInformationDataFactory, + private val messageItemAttributesFactory: MessageItemAttributesFactory, + private val avatarSizeProvider: AvatarSizeProvider, + private val noticeItemFactory: NoticeItemFactory, + private val userPreferencesProvider: UserPreferencesProvider, + private val session: Session +) { + + fun create(event: TimelineEvent, + highlight: Boolean, + callback: TimelineEventController.Callback? + ): VectorEpoxyModel<*>? { + if (event.root.eventId == null) return null + + val relContent: MessageRelationContent = event.root.content.toModel() + ?: event.root.getClearContent().toModel() + ?: return ignoredConclusion(event, highlight, callback) + + if (relContent.relatesTo?.type != RelationType.REFERENCE) return ignoredConclusion(event, highlight, callback) + val refEventId = relContent.relatesTo?.eventId + ?: return ignoredConclusion(event, highlight, callback) + + // If we cannot find the referenced request we do not display the done event + val refEvent = session.getRoom(event.root.roomId ?: "")?.getTimeLineEvent(refEventId) + ?: return ignoredConclusion(event, highlight, callback) + + // If it's not a request ignore this event + if (refEvent.root.getClearContent().toModel() == null) return ignoredConclusion(event, highlight, callback) + + // Is the request referenced is actually really completed? + val referenceInformationData = messageInformationDataFactory.create(refEvent, null) + if (referenceInformationData.referencesInfoData?.verificationStatus != VerificationState.DONE) return ignoredConclusion(event, highlight, callback) + + val informationData = messageInformationDataFactory.create(event, null) + val attributes = messageItemAttributesFactory.create(null, informationData, callback) + + when (event.root.getClearType()) { + EventType.KEY_VERIFICATION_DONE -> { + // We only tale the one sent by me + if (informationData.sentByMe) { + // We only display the done sent by the other user, the done send by me is ignored + return ignoredConclusion(event, highlight, callback) + } + return VerificationRequestConclusionItem_() + .attributes( + VerificationRequestConclusionItem.Attributes( + toUserId = informationData.senderId, + toUserName = informationData.memberName.toString(), + informationData = informationData, + avatarRenderer = attributes.avatarRenderer, + colorProvider = colorProvider, + emojiTypeFace = attributes.emojiTypeFace, + itemClickListener = attributes.itemClickListener, + itemLongClickListener = attributes.itemLongClickListener, + reactionPillCallback = attributes.reactionPillCallback, + readReceiptsCallback = attributes.readReceiptsCallback + ) + ) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) + } + } + return null + } + + private fun ignoredConclusion(event: TimelineEvent, + highlight: Boolean, + callback: TimelineEventController.Callback? + ): VectorEpoxyModel<*>? { + if (userPreferencesProvider.shouldShowHiddenEvents()) return noticeItemFactory.create(event, highlight, callback) + return null + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 75100e6c03..f5253a9a28 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -44,6 +44,12 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.MESSAGE, EventType.REACTION, + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_CANCEL, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_MAC, + EventType.KEY_VERIFICATION_DONE, + EventType.KEY_VERIFICATION_KEY, EventType.REDACTION -> formatDebug(timelineEvent.root) else -> { Timber.v("Type $type not handled by this formatter") diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 784a180d00..5e29c8db67 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -20,15 +20,19 @@ package im.vector.riotx.features.home.room.detail.timeline.helper import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.ReferencesAggregatedContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited +import im.vector.matrix.android.internal.session.room.VerificationState +import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.core.extensions.localDateTime import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.features.home.getColorFromUserId -import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotx.features.home.room.detail.timeline.item.ReactionInfoData import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData +import im.vector.riotx.features.home.room.detail.timeline.item.ReferencesInfoData import me.gujun.android.span.span import javax.inject.Inject @@ -86,7 +90,15 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses .map { ReadReceiptData(it.user.userId, it.user.avatarUrl, it.user.displayName, it.originServerTs) } - .toList() + .toList(), + referencesInfoData = event.annotations?.referencesAggregatedSummary?.let { referencesAggregatedSummary -> + val stateStr = referencesAggregatedSummary.content.toModel()?.verificationSummary + ReferencesInfoData( + VerificationState.values().firstOrNull { stateStr == it.name } + ?: VerificationState.REQUEST + ) + }, + sentByMe = event.root.senderId == session.myUserId ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt new file mode 100644 index 0000000000..6d99bb2650 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt @@ -0,0 +1,142 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.home.room.detail.timeline.item + +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.IdRes +import androidx.core.view.isVisible +import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.riotx.R +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.utils.DebouncedClickListener +import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController +import im.vector.riotx.features.reactions.widget.ReactionButton +import im.vector.riotx.features.ui.getMessageTextColor + +/** + * Base timeline item with reactions and read receipts. + * Manages associated click listeners and send status. + * Should not be used as this, use a subclass. + */ +abstract class AbsBaseMessageItem : BaseEventItem() { + + abstract val baseAttributes: Attributes + + private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener { + baseAttributes.readReceiptsCallback?.onReadReceiptsClicked(baseAttributes.informationData.readReceipts) + }) + + private var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener { + override fun onReacted(reactionButton: ReactionButton) { + baseAttributes.reactionPillCallback?.onClickOnReactionPill(baseAttributes.informationData, reactionButton.reactionString, true) + } + + override fun onUnReacted(reactionButton: ReactionButton) { + baseAttributes.reactionPillCallback?.onClickOnReactionPill(baseAttributes.informationData, reactionButton.reactionString, false) + } + + override fun onLongClick(reactionButton: ReactionButton) { + baseAttributes.reactionPillCallback?.onLongClickOnReactionPill(baseAttributes.informationData, reactionButton.reactionString) + } + } + + open fun shouldShowReactionAtBottom(): Boolean { + return true + } + + override fun getEventIds(): List { + return listOf(baseAttributes.informationData.eventId) + } + + override fun bind(holder: H) { + super.bind(holder) + holder.readReceiptsView.render( + baseAttributes.informationData.readReceipts, + baseAttributes.avatarRenderer, + _readReceiptsClickListener + ) + + val reactions = baseAttributes.informationData.orderedReactionList + if (!shouldShowReactionAtBottom() || reactions.isNullOrEmpty()) { + holder.reactionsContainer.isVisible = false + } else { + holder.reactionsContainer.isVisible = true + holder.reactionsContainer.removeAllViews() + reactions.take(8).forEach { reaction -> + val reactionButton = ReactionButton(holder.view.context) + reactionButton.reactedListener = reactionClickListener + reactionButton.setTag(R.id.reactionsContainer, reaction.key) + reactionButton.reactionString = reaction.key + reactionButton.reactionCount = reaction.count + reactionButton.setChecked(reaction.addedByMe) + reactionButton.isEnabled = reaction.synced + holder.reactionsContainer.addView(reactionButton) + } + holder.reactionsContainer.setOnLongClickListener(baseAttributes.itemLongClickListener) + } + + holder.view.setOnClickListener(baseAttributes.itemClickListener) + holder.view.setOnLongClickListener(baseAttributes.itemLongClickListener) + } + + override fun unbind(holder: H) { + holder.readReceiptsView.unbind() + super.unbind(holder) + } + + protected open fun renderSendState(root: View, textView: TextView?, failureIndicator: ImageView? = null) { + root.isClickable = baseAttributes.informationData.sendState.isSent() + val state = if (baseAttributes.informationData.hasPendingEdits) SendState.UNSENT else baseAttributes.informationData.sendState + textView?.setTextColor(baseAttributes.colorProvider.getMessageTextColor(state)) + failureIndicator?.isVisible = baseAttributes.informationData.sendState.hasFailed() + } + + abstract class Holder(@IdRes stubId: Int) : BaseEventItem.BaseHolder(stubId) { + val reactionsContainer by bind(R.id.reactionsContainer) + } + + /** + * This class holds all the common attributes for timeline items. + */ + interface Attributes { + // val avatarSize: Int, + val informationData: MessageInformationData + val avatarRenderer: AvatarRenderer + val colorProvider: ColorProvider + val itemLongClickListener: View.OnLongClickListener? + val itemClickListener: View.OnClickListener? + // val memberClickListener: View.OnClickListener? + val reactionPillCallback: TimelineEventController.ReactionPillCallback? + // val avatarCallback: TimelineEventController.AvatarCallback? + val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? +// val emojiTypeFace: Typeface? + } + +// data class AbsAttributes( +// override val informationData: MessageInformationData, +// override val avatarRenderer: AvatarRenderer, +// override val colorProvider: ColorProvider, +// override val itemLongClickListener: View.OnLongClickListener? = null, +// override val itemClickListener: View.OnClickListener? = null, +// override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null, +// override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null +// ) : Attributes +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt index 713b60d4d8..7d8fe3a10e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -18,22 +18,24 @@ package im.vector.riotx.features.home.room.detail.timeline.item import android.graphics.Typeface import android.view.View -import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.annotation.IdRes -import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute -import im.vector.matrix.android.api.session.room.send.SendState import im.vector.riotx.R import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController -import im.vector.riotx.features.reactions.widget.ReactionButton -import im.vector.riotx.features.ui.getMessageTextColor -abstract class AbsMessageItem : BaseEventItem() { +/** + * Base timeline item that adds an optional information bar with the sender avatar, name and time + * Adds associated click listeners (on avatar, displayname) + */ +abstract class AbsMessageItem : AbsBaseMessageItem() { + + override val baseAttributes: AbsBaseMessageItem.Attributes + get() = attributes @EpoxyAttribute lateinit var attributes: Attributes @@ -45,24 +47,6 @@ abstract class AbsMessageItem : BaseEventItem() { attributes.avatarCallback?.onMemberNameClicked(attributes.informationData) }) - private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener { - attributes.readReceiptsCallback?.onReadReceiptsClicked(attributes.informationData.readReceipts) - }) - - var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener { - override fun onReacted(reactionButton: ReactionButton) { - attributes.reactionPillCallback?.onClickOnReactionPill(attributes.informationData, reactionButton.reactionString, true) - } - - override fun onUnReacted(reactionButton: ReactionButton) { - attributes.reactionPillCallback?.onClickOnReactionPill(attributes.informationData, reactionButton.reactionString, false) - } - - override fun onLongClick(reactionButton: ReactionButton) { - attributes.reactionPillCallback?.onLongClickOnReactionPill(attributes.informationData, reactionButton.reactionString) - } - } - override fun bind(holder: H) { super.bind(holder) if (attributes.informationData.showInformation) { @@ -94,60 +78,12 @@ abstract class AbsMessageItem : BaseEventItem() { holder.avatarImageView.setOnLongClickListener(null) holder.memberNameView.setOnLongClickListener(null) } - holder.view.setOnClickListener(attributes.itemClickListener) - holder.view.setOnLongClickListener(attributes.itemLongClickListener) - - holder.readReceiptsView.render( - attributes.informationData.readReceipts, - attributes.avatarRenderer, - _readReceiptsClickListener - ) - - val reactions = attributes.informationData.orderedReactionList - if (!shouldShowReactionAtBottom() || reactions.isNullOrEmpty()) { - holder.reactionsContainer.isVisible = false - } else { - holder.reactionsContainer.isVisible = true - holder.reactionsContainer.removeAllViews() - reactions.take(8).forEach { reaction -> - val reactionButton = ReactionButton(holder.view.context) - reactionButton.reactedListener = reactionClickListener - reactionButton.setTag(R.id.reactionsContainer, reaction.key) - reactionButton.reactionString = reaction.key - reactionButton.reactionCount = reaction.count - reactionButton.setChecked(reaction.addedByMe) - reactionButton.isEnabled = reaction.synced - holder.reactionsContainer.addView(reactionButton) - } - holder.reactionsContainer.setOnLongClickListener(attributes.itemLongClickListener) - } } - override fun unbind(holder: H) { - holder.readReceiptsView.unbind() - super.unbind(holder) - } - - open fun shouldShowReactionAtBottom(): Boolean { - return true - } - - override fun getEventIds(): List { - return listOf(attributes.informationData.eventId) - } - - protected open fun renderSendState(root: View, textView: TextView?, failureIndicator: ImageView? = null) { - root.isClickable = attributes.informationData.sendState.isSent() - val state = if (attributes.informationData.hasPendingEdits) SendState.UNSENT else attributes.informationData.sendState - textView?.setTextColor(attributes.colorProvider.getMessageTextColor(state)) - failureIndicator?.isVisible = attributes.informationData.sendState.hasFailed() - } - - abstract class Holder(@IdRes stubId: Int) : BaseHolder(stubId) { + abstract class Holder(@IdRes stubId: Int) : AbsBaseMessageItem.Holder(stubId) { val avatarImageView by bind(R.id.messageAvatarImageView) val memberNameView by bind(R.id.messageMemberNameView) val timeView by bind(R.id.messageTimeView) - val reactionsContainer by bind(R.id.reactionsContainer) } /** @@ -155,15 +91,15 @@ abstract class AbsMessageItem : BaseEventItem() { */ data class Attributes( val avatarSize: Int, - val informationData: MessageInformationData, - val avatarRenderer: AvatarRenderer, - val colorProvider: ColorProvider, - val itemLongClickListener: View.OnLongClickListener? = null, - val itemClickListener: View.OnClickListener? = null, + override val informationData: MessageInformationData, + override val avatarRenderer: AvatarRenderer, + override val colorProvider: ColorProvider, + override val itemLongClickListener: View.OnLongClickListener? = null, + override val itemClickListener: View.OnClickListener? = null, val memberClickListener: View.OnClickListener? = null, - val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null, + override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null, val avatarCallback: TimelineEventController.AvatarCallback? = null, - val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, + override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, val emojiTypeFace: Typeface? = null - ) + ) : AbsBaseMessageItem.Attributes } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageInformationData.kt index 2dd581ce6f..5c0b521106 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail.timeline.item import android.os.Parcelable import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.internal.session.room.VerificationState import kotlinx.android.parcel.Parcelize @Parcelize @@ -33,7 +34,14 @@ data class MessageInformationData( val orderedReactionList: List? = null, val hasBeenEdited: Boolean = false, val hasPendingEdits: Boolean = false, - val readReceipts: List = emptyList() + val readReceipts: List = emptyList(), + val referencesInfoData: ReferencesInfoData? = null, + val sentByMe : Boolean +) : Parcelable + +@Parcelize +data class ReferencesInfoData( + val verificationStatus: VerificationState ) : Parcelable @Parcelize diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestConclusionItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestConclusionItem.kt new file mode 100644 index 0000000000..bddef2c130 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestConclusionItem.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.riotx.features.home.room.detail.timeline.item + +import android.annotation.SuppressLint +import android.graphics.Typeface +import android.view.View +import android.widget.RelativeLayout +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.view.updateLayoutParams +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController + +@EpoxyModelClass(layout = R.layout.item_timeline_event_base_state) +abstract class VerificationRequestConclusionItem : AbsBaseMessageItem() { + + override val baseAttributes: AbsBaseMessageItem.Attributes + get() = attributes + + @EpoxyAttribute + lateinit var attributes: Attributes + + override fun getViewType() = STUB_ID + + @SuppressLint("SetTextI18n") + override fun bind(holder: Holder) { + super.bind(holder) + holder.endGuideline.updateLayoutParams { + this.marginEnd = leftGuideline + } + holder.titleView.text = holder.view.context.getString(R.string.sas_verified) + holder.descriptionView.text = "${attributes.informationData.memberName} (${attributes.informationData.senderId})" + } + + class Holder : AbsBaseMessageItem.Holder(STUB_ID) { + val titleView by bind(R.id.itemVerificationDoneTitleTextView) + val descriptionView by bind(R.id.itemVerificationDoneDetailTextView) + val endGuideline by bind(R.id.messageEndGuideline) + } + + companion object { + private const val STUB_ID = R.id.messageVerificationDoneStub + } + + /** + * This class holds all the common attributes for timeline items. + */ + data class Attributes( + val toUserId: String, + val toUserName: String, + override val informationData: MessageInformationData, + override val avatarRenderer: AvatarRenderer, + override val colorProvider: ColorProvider, + override val itemLongClickListener: View.OnLongClickListener? = null, + override val itemClickListener: View.OnClickListener? = null, + override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null, + override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, + val emojiTypeFace: Typeface? = null + ) : AbsBaseMessageItem.Attributes +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt new file mode 100644 index 0000000000..1a0e89fc32 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/VerificationRequestItem.kt @@ -0,0 +1,177 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.riotx.features.home.room.detail.timeline.item + +import android.annotation.SuppressLint +import android.graphics.Typeface +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.matrix.android.internal.session.room.VerificationState +import im.vector.riotx.R +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.utils.DebouncedClickListener +import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.home.room.detail.RoomDetailAction +import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController + +@EpoxyModelClass(layout = R.layout.item_timeline_event_base_state) +abstract class VerificationRequestItem : AbsBaseMessageItem() { + + override val baseAttributes: AbsBaseMessageItem.Attributes + get() = attributes + + @EpoxyAttribute + lateinit var attributes: Attributes + + @EpoxyAttribute + var callback: TimelineEventController.Callback? = null + + override fun getViewType() = STUB_ID + + @SuppressLint("SetTextI18n") + override fun bind(holder: Holder) { + super.bind(holder) + + holder.endGuideline.updateLayoutParams { + this.marginEnd = leftGuideline + } + + holder.titleView.text = if (attributes.informationData.sentByMe) + holder.view.context.getString(R.string.verification_sent) +// + "\n ${attributes.informationData.referencesInfoData?.verificationStatus?.name +// ?: "??"}" + else + holder.view.context.getString(R.string.verification_request) +// + "\n ${attributes.informationData.referencesInfoData?.verificationStatus?.name +// ?: "??"}" + + holder.descriptionView.text = if (!attributes.informationData.sentByMe) + "${attributes.informationData.memberName} (${attributes.informationData.senderId})" + else + "${attributes.otherUserName} (${attributes.otherUserId})" + + when (attributes.informationData.referencesInfoData?.verificationStatus) { + VerificationState.REQUEST, + null -> { + holder.buttonBar.isVisible = !attributes.informationData.sentByMe + holder.statusTextView.text = null + holder.statusTextView.isVisible = false + } + VerificationState.CANCELED_BY_OTHER -> { + holder.buttonBar.isVisible = false + holder.statusTextView.text = holder.view.context.getString(R.string.verification_request_other_cancelled, attributes.informationData.memberName) + holder.statusTextView.isVisible = true + } + VerificationState.CANCELED_BY_ME -> { + holder.buttonBar.isVisible = false + holder.statusTextView.text = holder.view.context.getString(R.string.verification_request_you_cancelled) + holder.statusTextView.isVisible = true + } + VerificationState.WAITING -> { + holder.buttonBar.isVisible = false + holder.statusTextView.text = holder.view.context.getString(R.string.verification_request_waiting) + holder.statusTextView.isVisible = true + } + VerificationState.DONE -> { + holder.buttonBar.isVisible = false + holder.statusTextView.text = if (attributes.informationData.sentByMe) + holder.view.context.getString(R.string.verification_request_other_accepted, attributes.otherUserName) + else + holder.view.context.getString(R.string.verification_request_you_accepted) + holder.statusTextView.isVisible = true + } + else -> { + holder.buttonBar.isVisible = false + holder.statusTextView.text = null + holder.statusTextView.isVisible = false + } + } + + holder.callback = callback + holder.attributes = attributes + } + + override fun unbind(holder: Holder) { + super.unbind(holder) + holder.callback = null + holder.attributes = null + } + + class Holder : AbsBaseMessageItem.Holder(STUB_ID) { + + var callback: TimelineEventController.Callback? = null + var attributes: Attributes? = null + + private val _clickListener = DebouncedClickListener(View.OnClickListener { + val att = attributes ?: return@OnClickListener + if (it == acceptButton) { + callback?.onTimelineItemAction(RoomDetailAction.AcceptVerificationRequest( + att.referenceId, + att.otherUserId, + att.fromDevide)) + } else if (it == declineButton) { + callback?.onTimelineItemAction(RoomDetailAction.DeclineVerificationRequest(att.referenceId)) + } + }) + + val titleView by bind(R.id.itemVerificationTitleTextView) + val descriptionView by bind(R.id.itemVerificationDetailTextView) + val buttonBar by bind(R.id.itemVerificationButtonBar) + val statusTextView by bind(R.id.itemVerificationStatusText) + val endGuideline by bind(R.id.messageEndGuideline) + private val declineButton by bind