Merge pull request #6328 from vector-im/feature/bca/verif_resist_no_age

Feature/bca/verif resist no age
This commit is contained in:
Valere 2022-06-28 14:13:37 +02:00 committed by GitHub
commit a2aa0471c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 152 additions and 18 deletions

1
changelog.d/6328.bugfix Normal file
View file

@ -0,0 +1 @@
Fix | Some user verification requests couldn't be accepted/declined

View file

@ -62,7 +62,7 @@ internal class VerificationMessageProcessor @Inject constructor(
// If the request is in the future by more than 5 minutes or more than 10 minutes in the past, // 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. // the message should be ignored by the receiver.
if (event.ageLocalTs != null && !VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also { if (!VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also {
Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated age:$event.ageLocalTs ms") Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated age:$event.ageLocalTs ms")
} }

View file

@ -271,7 +271,7 @@ private fun HashMap<String, RoomMemberContent?>.addSenderState(realm: Realm, roo
* Create an EventEntity for the root thread event or get an existing one. * Create an EventEntity for the root thread event or get an existing one.
*/ */
private fun createEventEntity(realm: Realm, roomId: String, event: Event, currentTimeMillis: Long): EventEntity { private fun createEventEntity(realm: Realm, roomId: String, event: Event, currentTimeMillis: Long): EventEntity {
val ageLocalTs = event.unsignedData?.age?.let { currentTimeMillis - it } val ageLocalTs = currentTimeMillis - (event.unsignedData?.age ?: 0)
return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
} }

View file

@ -130,7 +130,7 @@ internal fun EventEntity.asDomain(castJsonNumbers: Boolean = false): Event {
internal fun Event.toEntity( internal fun Event.toEntity(
roomId: String, roomId: String,
sendState: SendState, sendState: SendState,
ageLocalTs: Long?, ageLocalTs: Long,
contentToInject: String? = null contentToInject: String? = null
): EventEntity { ): EventEntity {
return EventMapper.map(this, roomId).apply { return EventMapper.map(this, roomId).apply {

View file

@ -114,7 +114,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
if (roomMemberEvent.eventId == null || roomMemberEvent.stateKey == null || roomMemberEvent.type == null) { if (roomMemberEvent.eventId == null || roomMemberEvent.stateKey == null || roomMemberEvent.type == null) {
continue continue
} }
val ageLocalTs = roomMemberEvent.unsignedData?.age?.let { now - it } val ageLocalTs = now - (roomMemberEvent.unsignedData?.age ?: 0)
val eventEntity = roomMemberEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) val eventEntity = roomMemberEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
CurrentStateEventEntity.getOrCreate( CurrentStateEventEntity.getOrCreate(
realm, realm,

View file

@ -209,7 +209,8 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
* Create an EventEntity to be added in the TimelineEventEntity. * Create an EventEntity to be added in the TimelineEventEntity.
*/ */
private fun createEventEntity(roomId: String, event: Event, realm: Realm): EventEntity { private fun createEventEntity(roomId: String, event: Event, realm: Realm): EventEntity {
val ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it } val now = clock.epochMillis()
val ageLocalTs = now - (event.unsignedData?.age ?: 0)
return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
} }

View file

@ -61,7 +61,7 @@ internal class DefaultGetEventTask @Inject constructor(
} }
} }
event.ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it } event.ageLocalTs = clock.epochMillis() - (event.unsignedData?.age ?: 0)
return event return event
} }

View file

@ -142,7 +142,7 @@ internal class TokenChunkEventPersistor @Inject constructor(
val now = clock.epochMillis() val now = clock.epochMillis()
stateEvents?.forEach { stateEvent -> stateEvents?.forEach { stateEvent ->
val ageLocalTs = stateEvent.unsignedData?.age?.let { now - it } val ageLocalTs = now - (stateEvent.unsignedData?.age ?: 0)
val stateEventEntity = stateEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) val stateEventEntity = stateEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
currentChunk.addStateEvent(roomId, stateEventEntity, direction) currentChunk.addStateEvent(roomId, stateEventEntity, direction)
if (stateEvent.type == EventType.STATE_ROOM_MEMBER && stateEvent.stateKey != null) { if (stateEvent.type == EventType.STATE_ROOM_MEMBER && stateEvent.stateKey != null) {
@ -155,7 +155,7 @@ internal class TokenChunkEventPersistor @Inject constructor(
if (event.eventId == null || event.senderId == null) { if (event.eventId == null || event.senderId == null) {
return@forEach return@forEach
} }
val ageLocalTs = event.unsignedData?.age?.let { now - it } val ageLocalTs = now - (event.unsignedData?.age ?: 0)
val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
if (event.type == EventType.STATE_ROOM_MEMBER && event.stateKey != null) { if (event.type == EventType.STATE_ROOM_MEMBER && event.stateKey != null) {
val contentToUse = if (direction == PaginationDirection.BACKWARDS) { val contentToUse = if (direction == PaginationDirection.BACKWARDS) {

View file

@ -244,7 +244,7 @@ internal class RoomSyncHandler @Inject constructor(
if (event.eventId == null || event.stateKey == null || event.type == null) { if (event.eventId == null || event.stateKey == null || event.type == null) {
continue continue
} }
val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0)
val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType)
Timber.v("## received state event ${event.type} and key ${event.stateKey}") Timber.v("## received state event ${event.type} and key ${event.stateKey}")
CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
@ -306,7 +306,7 @@ internal class RoomSyncHandler @Inject constructor(
if (event.stateKey == null || event.type == null) { if (event.stateKey == null || event.type == null) {
return@forEach return@forEach
} }
val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0)
val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType)
CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
eventId = eventEntity.eventId eventId = eventEntity.eventId
@ -336,7 +336,7 @@ internal class RoomSyncHandler @Inject constructor(
if (event.eventId == null || event.stateKey == null || event.type == null) { if (event.eventId == null || event.stateKey == null || event.type == null) {
continue continue
} }
val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0)
val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType)
CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
eventId = event.eventId eventId = event.eventId
@ -348,7 +348,7 @@ internal class RoomSyncHandler @Inject constructor(
if (event.eventId == null || event.senderId == null || event.type == null) { if (event.eventId == null || event.senderId == null || event.type == null) {
continue continue
} }
val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0)
val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType)
if (event.stateKey != null) { if (event.stateKey != null) {
CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
@ -401,7 +401,10 @@ internal class RoomSyncHandler @Inject constructor(
for (rawEvent in eventList) { for (rawEvent in eventList) {
// It's annoying roomId is not there, but lot of code rely on it. // It's annoying roomId is not there, but lot of code rely on it.
// And had to do it now as copy would delete all decryption results.. // And had to do it now as copy would delete all decryption results..
val event = rawEvent.copy(roomId = roomId) val ageLocalTs = syncLocalTimestampMillis - (rawEvent.unsignedData?.age ?: 0)
val event = rawEvent.copy(roomId = roomId).also {
it.ageLocalTs = ageLocalTs
}
if (event.eventId == null || event.senderId == null || event.type == null) { if (event.eventId == null || event.senderId == null || event.type == null) {
continue continue
} }
@ -423,7 +426,6 @@ internal class RoomSyncHandler @Inject constructor(
contentToInject = threadsAwarenessHandler.makeEventThreadAware(realm, roomId, event) contentToInject = threadsAwarenessHandler.makeEventThreadAware(realm, roomId, event)
} }
val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it }
val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs, contentToInject).copyToRealmOrIgnore(realm, insertType) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs, contentToInject).copyToRealmOrIgnore(realm, insertType)
if (event.stateKey != null) { if (event.stateKey != null) {
CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {

View file

@ -53,6 +53,7 @@ import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask
import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.util.awaitTransaction
import org.matrix.android.sdk.internal.util.time.Clock
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -64,7 +65,8 @@ internal class ThreadsAwarenessHandler @Inject constructor(
private val permalinkFactory: PermalinkFactory, private val permalinkFactory: PermalinkFactory,
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
private val lightweightSettingsStorage: LightweightSettingsStorage, private val lightweightSettingsStorage: LightweightSettingsStorage,
private val getEventTask: GetEventTask private val getEventTask: GetEventTask,
private val clock: Clock,
) { ) {
// This caching is responsible to improve the performance when we receive a root event // This caching is responsible to improve the performance when we receive a root event
@ -120,7 +122,7 @@ internal class ThreadsAwarenessHandler @Inject constructor(
private suspend fun fetchThreadsEvents(threadsToFetch: Map<String, String>) { private suspend fun fetchThreadsEvents(threadsToFetch: Map<String, String>) {
val eventEntityList = threadsToFetch.mapNotNull { (eventId, roomId) -> val eventEntityList = threadsToFetch.mapNotNull { (eventId, roomId) ->
fetchEvent(eventId, roomId)?.let { fetchEvent(eventId, roomId)?.let {
it.toEntity(roomId, SendState.SYNCED, it.ageLocalTs) it.toEntity(roomId, SendState.SYNCED, it.ageLocalTs ?: clock.epochMillis())
} }
} }

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.ui.list
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.google.android.material.button.MaterialButton
import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.onClick
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
/**
* A generic button list item.
*/
@EpoxyModelClass(layout = R.layout.item_positive_destrutive_buttons)
abstract class ButtonPositiveDestructiveButtonBarItem : VectorEpoxyModel<ButtonPositiveDestructiveButtonBarItem.Holder>() {
@EpoxyAttribute
var positiveText: EpoxyCharSequence? = null
@EpoxyAttribute
var destructiveText: EpoxyCharSequence? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var positiveButtonClickAction: ClickListener? = null
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var destructiveButtonClickAction: ClickListener? = null
override fun bind(holder: Holder) {
super.bind(holder)
positiveText?.charSequence?.let { holder.positiveButton.text = it }
destructiveText?.charSequence?.let { holder.destructiveButton.text = it }
holder.positiveButton.onClick(positiveButtonClickAction)
holder.destructiveButton.onClick(destructiveButtonClickAction)
}
class Holder : VectorEpoxyHolder() {
val destructiveButton by bind<MaterialButton>(R.id.destructive_button)
val positiveButton by bind<MaterialButton>(R.id.positive_button)
}
}

View file

@ -30,6 +30,8 @@ sealed class VerificationAction : VectorViewModelAction {
data class GotItConclusion(val verified: Boolean) : VerificationAction() data class GotItConclusion(val verified: Boolean) : VerificationAction()
object SkipVerification : VerificationAction() object SkipVerification : VerificationAction()
object VerifyFromPassphrase : VerificationAction() object VerifyFromPassphrase : VerificationAction()
object ReadyPendingVerification : VerificationAction()
object CancelPendingVerification : VerificationAction()
data class GotResultFromSsss(val cypherData: String, val alias: String) : VerificationAction() data class GotResultFromSsss(val cypherData: String, val alias: String) : VerificationAction()
object CancelledFromSsss : VerificationAction() object CancelledFromSsss : VerificationAction()
object SecuredStorageHasBeenReset : VerificationAction() object SecuredStorageHasBeenReset : VerificationAction()

View file

@ -360,6 +360,27 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
as? SasVerificationTransaction) as? SasVerificationTransaction)
?.shortCodeDoesNotMatch() ?.shortCodeDoesNotMatch()
} }
is VerificationAction.ReadyPendingVerification -> {
state.pendingRequest.invoke()?.let { request ->
// will only be there for dm verif
if (state.roomId != null) {
session.cryptoService().verificationService()
.readyPendingVerificationInDMs(
supportedVerificationMethodsProvider.provide(),
state.otherUserId,
state.roomId,
request.transactionId ?: ""
)
}
}
}
is VerificationAction.CancelPendingVerification -> {
state.pendingRequest.invoke()?.let {
session.cryptoService().verificationService()
.cancelVerificationRequest(it)
}
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
}
is VerificationAction.GotItConclusion -> { is VerificationAction.GotItConclusion -> {
if (state.isVerificationRequired && !action.verified) { if (state.isVerificationRequired && !action.verified) {
// we should go back to first screen // we should go back to first screen

View file

@ -21,6 +21,7 @@ import im.vector.app.R
import im.vector.app.core.epoxy.bottomSheetDividerItem import im.vector.app.core.epoxy.bottomSheetDividerItem
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.buttonPositiveDestructiveButtonBarItem
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationQrCodeItem import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationQrCodeItem
@ -108,6 +109,15 @@ class VerificationChooseMethodController @Inject constructor(
iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary)) iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
listener { host.listener?.doVerifyBySas() } listener { host.listener?.doVerifyBySas() }
} }
} else if (!state.isReadySent) {
// a bit of a special case, if you tapped on the timeline cell but not on a button
buttonPositiveDestructiveButtonBarItem {
id("accept_decline")
positiveText(host.stringProvider.getString(R.string.action_accept).toEpoxyCharSequence())
destructiveText(host.stringProvider.getString(R.string.action_decline).toEpoxyCharSequence())
positiveButtonClickAction { host.listener?.acceptRequest() }
destructiveButtonClickAction { host.listener?.declineRequest() }
}
} }
if (state.isMe && state.canCrossSign) { if (state.isMe && state.canCrossSign) {
@ -131,5 +141,7 @@ class VerificationChooseMethodController @Inject constructor(
fun openCamera() fun openCamera()
fun doVerifyBySas() fun doVerifyBySas()
fun onClickOnWasNotMe() fun onClickOnWasNotMe()
fun acceptRequest()
fun declineRequest()
} }
} }

View file

@ -100,6 +100,14 @@ class VerificationChooseMethodFragment @Inject constructor(
sharedViewModel.itWasNotMe() sharedViewModel.itWasNotMe()
} }
override fun acceptRequest() {
sharedViewModel.handle(VerificationAction.ReadyPendingVerification)
}
override fun declineRequest() {
sharedViewModel.handle(VerificationAction.CancelPendingVerification)
}
private fun doOpenQRCodeScanner() { private fun doOpenQRCodeScanner() {
QrCodeScannerActivity.startForResult(requireActivity(), scanActivityResultLauncher) QrCodeScannerActivity.startForResult(requireActivity(), scanActivityResultLauncher)
} }

View file

@ -44,7 +44,8 @@ data class VerificationChooseMethodViewState(
val qrCodeText: String? = null, val qrCodeText: String? = null,
val sasModeAvailable: Boolean = false, val sasModeAvailable: Boolean = false,
val isMe: Boolean = false, val isMe: Boolean = false,
val canCrossSign: Boolean = false val canCrossSign: Boolean = false,
val isReadySent: Boolean = false
) : MavericksState ) : MavericksState
class VerificationChooseMethodViewModel @AssistedInject constructor( class VerificationChooseMethodViewModel @AssistedInject constructor(
@ -81,7 +82,8 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
copy( copy(
otherCanShowQrCode = pvr?.otherCanShowQrCode().orFalse(), otherCanShowQrCode = pvr?.otherCanShowQrCode().orFalse(),
otherCanScanQrCode = pvr?.otherCanScanQrCode().orFalse(), otherCanScanQrCode = pvr?.otherCanScanQrCode().orFalse(),
sasModeAvailable = pvr?.isSasSupported().orFalse() sasModeAvailable = pvr?.isSasSupported().orFalse(),
isReadySent = pvr?.isReady.orFalse(),
) )
} }
} }

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/destructive_button"
style="@style/Widget.Vector.Button.Destructive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
tools:text="@string/action_decline" />
<Button
android:id="@+id/positive_button"
style="@style/Widget.Vector.Button.Positive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/action_accept" />
</LinearLayout>