Skip to content

Commit 6b98b30

Browse files
authored
Merge pull request #7851 from vector-im/feature/mna/poll-message-decryption-error
[Poll] Warning message on decryption failure of some events (PSG-1025)
2 parents 31e599f + 60e838a commit 6b98b30

File tree

28 files changed

+742
-71
lines changed

28 files changed

+742
-71
lines changed

changelog.d/7824.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[Poll] Warning message on decryption failure of some events

library/ui-strings/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3196,6 +3196,7 @@
31963196
<string name="closed_poll_option_title">Closed poll</string>
31973197
<string name="closed_poll_option_description">Results are only revealed when you end the poll</string>
31983198
<string name="ended_poll_indicator">Ended the poll.</string>
3199+
<string name="unable_to_decrypt_some_events_in_poll">Due to decryption errors, some votes may not be counted</string>
31993200
<string name="room_polls_active">Active polls</string>
32003201
<string name="room_polls_active_no_item">There are no active polls in this room</string>
32013202
<string name="room_polls_ended">Past polls</string>

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollResponseAggregatedSummary.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,7 @@ data class PollResponseAggregatedSummary(
2323
val nbOptions: Int = 0,
2424
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
2525
val sourceEvents: List<String>,
26-
val localEchos: List<String>
26+
val localEchos: List<String>,
27+
// list of related event ids which are encrypted due to decryption failure
28+
val encryptedRelatedEventIds: List<String>,
2729
)

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ internal class EventDecryptor @Inject constructor(
5959
private val sendToDeviceTask: SendToDeviceTask,
6060
private val deviceListManager: DeviceListManager,
6161
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
62-
private val cryptoStore: IMXCryptoStore
62+
private val cryptoStore: IMXCryptoStore,
6363
) {
6464

6565
/**

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,16 @@
1717
package org.matrix.android.sdk.internal.database
1818

1919
import com.zhuinden.monarchy.Monarchy
20+
import io.realm.Realm
2021
import io.realm.RealmConfiguration
2122
import io.realm.RealmResults
2223
import kotlinx.coroutines.launch
2324
import kotlinx.coroutines.sync.Mutex
2425
import kotlinx.coroutines.sync.withLock
26+
import org.matrix.android.sdk.api.session.events.model.Event
27+
import org.matrix.android.sdk.api.session.events.model.EventType
28+
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
29+
import org.matrix.android.sdk.api.session.events.model.toModel
2530
import org.matrix.android.sdk.internal.database.mapper.asDomain
2631
import org.matrix.android.sdk.internal.database.model.EventEntity
2732
import org.matrix.android.sdk.internal.database.model.EventInsertEntity
@@ -34,7 +39,7 @@ import javax.inject.Inject
3439

3540
internal class EventInsertLiveObserver @Inject constructor(
3641
@SessionDatabase realmConfiguration: RealmConfiguration,
37-
private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>
42+
private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>,
3843
) :
3944
RealmLiveEntityObserver<EventInsertEntity>(realmConfiguration) {
4045

@@ -50,48 +55,90 @@ internal class EventInsertLiveObserver @Inject constructor(
5055
if (!results.isLoaded || results.isEmpty()) {
5156
return@withLock
5257
}
53-
val idsToDeleteAfterProcess = ArrayList<String>()
54-
val filteredEvents = ArrayList<EventInsertEntity>(results.size)
58+
val eventsToProcess = ArrayList<EventInsertEntity>(results.size)
59+
val eventsToIgnore = ArrayList<EventInsertEntity>(results.size)
60+
5561
Timber.v("EventInsertEntity updated with ${results.size} results in db")
5662
results.forEach {
63+
// don't use copy from realm over there
64+
val copiedEvent = EventInsertEntity(
65+
eventId = it.eventId,
66+
eventType = it.eventType
67+
).apply {
68+
insertType = it.insertType
69+
}
70+
5771
if (shouldProcess(it)) {
58-
// don't use copy from realm over there
59-
val copiedEvent = EventInsertEntity(
60-
eventId = it.eventId,
61-
eventType = it.eventType
62-
).apply {
63-
insertType = it.insertType
64-
}
65-
filteredEvents.add(copiedEvent)
72+
eventsToProcess.add(copiedEvent)
73+
} else {
74+
eventsToIgnore.add(copiedEvent)
6675
}
67-
idsToDeleteAfterProcess.add(it.eventId)
6876
}
77+
6978
awaitTransaction(realmConfiguration) { realm ->
70-
Timber.v("##Transaction: There are ${filteredEvents.size} events to process ")
71-
filteredEvents.forEach { eventInsert ->
79+
Timber.v("##Transaction: There are ${eventsToProcess.size} events to process")
80+
81+
val idsToDeleteAfterProcess = ArrayList<String>()
82+
val idsOfEncryptedEvents = ArrayList<String>()
83+
val getAndTriageEvent: (EventInsertEntity) -> Event? = { eventInsert ->
7284
val eventId = eventInsert.eventId
73-
val event = EventEntity.where(realm, eventId).findFirst()
74-
if (event == null) {
75-
Timber.v("Event $eventId not found")
76-
return@forEach
85+
val event = getEvent(realm, eventId)
86+
if (event?.getClearType() == EventType.ENCRYPTED) {
87+
idsOfEncryptedEvents.add(eventId)
88+
} else {
89+
idsToDeleteAfterProcess.add(eventId)
7790
}
78-
val domainEvent = event.asDomain()
79-
processors.filter {
80-
it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
81-
}.forEach {
82-
it.process(realm, domainEvent)
91+
event
92+
}
93+
94+
eventsToProcess.forEach { eventInsert ->
95+
val eventId = eventInsert.eventId
96+
val event = getAndTriageEvent(eventInsert)
97+
98+
if (event != null && canProcessEvent(event)) {
99+
processors.filter {
100+
it.shouldProcess(eventId, event.getClearType(), eventInsert.insertType)
101+
}.forEach {
102+
it.process(realm, event)
103+
}
104+
} else {
105+
Timber.v("Cannot process event with id $eventId")
106+
return@forEach
83107
}
84108
}
109+
110+
eventsToIgnore.forEach { getAndTriageEvent(it) }
111+
85112
realm.where(EventInsertEntity::class.java)
86113
.`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray())
87114
.findAll()
88115
.deleteAllFromRealm()
116+
117+
// make the encrypted events not processable: they will be processed again after decryption
118+
realm.where(EventInsertEntity::class.java)
119+
.`in`(EventInsertEntityFields.EVENT_ID, idsOfEncryptedEvents.toTypedArray())
120+
.findAll()
121+
.forEach { it.canBeProcessed = false }
89122
}
90123
processors.forEach { it.onPostProcess() }
91124
}
92125
}
93126
}
94127

128+
private fun getEvent(realm: Realm, eventId: String): Event? {
129+
val event = EventEntity.where(realm, eventId).findFirst()
130+
if (event == null) {
131+
Timber.v("Event $eventId not found")
132+
}
133+
return event?.asDomain()
134+
}
135+
136+
private fun canProcessEvent(event: Event): Boolean {
137+
// event should be either not encrypted or if encrypted it should contain relatesTo content
138+
return event.getClearType() != EventType.ENCRYPTED ||
139+
event.content.toModel<EncryptedEventContent>()?.relatesTo != null
140+
}
141+
95142
private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {
96143
return processors.any {
97144
it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType, eventInsertEntity.insertType)

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo044
6464
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045
6565
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046
6666
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047
67+
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048
6768
import org.matrix.android.sdk.internal.util.Normalizer
6869
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
6970
import javax.inject.Inject
@@ -72,7 +73,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
7273
private val normalizer: Normalizer
7374
) : MatrixRealmMigration(
7475
dbName = "Session",
75-
schemaVersion = 47L,
76+
schemaVersion = 48L,
7677
) {
7778
/**
7879
* Forces all RealmSessionStoreMigration instances to be equal.
@@ -129,5 +130,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
129130
if (oldVersion < 45) MigrateSessionTo045(realm).perform()
130131
if (oldVersion < 46) MigrateSessionTo046(realm).perform()
131132
if (oldVersion < 47) MigrateSessionTo047(realm).perform()
133+
if (oldVersion < 48) MigrateSessionTo048(realm).perform()
132134
}
133135
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PollResponseAggregatedSummaryEntityMapper.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ internal object PollResponseAggregatedSummaryEntityMapper {
3030
closedTime = entity.closedTime,
3131
localEchos = entity.sourceLocalEchoEvents.toList(),
3232
sourceEvents = entity.sourceEvents.toList(),
33-
nbOptions = entity.nbOptions
33+
nbOptions = entity.nbOptions,
34+
encryptedRelatedEventIds = entity.encryptedRelatedEventIds.toList(),
3435
)
3536
}
3637

@@ -40,7 +41,8 @@ internal object PollResponseAggregatedSummaryEntityMapper {
4041
nbOptions = model.nbOptions,
4142
closedTime = model.closedTime,
4243
sourceEvents = RealmList<String>().apply { addAll(model.sourceEvents) },
43-
sourceLocalEchoEvents = RealmList<String>().apply { addAll(model.localEchos) }
44+
sourceLocalEchoEvents = RealmList<String>().apply { addAll(model.localEchos) },
45+
encryptedRelatedEventIds = RealmList<String>().apply { addAll(model.encryptedRelatedEventIds) },
4446
)
4547
}
4648
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.matrix.android.sdk.internal.database.migration
18+
19+
import io.realm.DynamicRealm
20+
import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntityFields
21+
import org.matrix.android.sdk.internal.util.database.RealmMigrator
22+
23+
/**
24+
* Adding a new field in poll summary to keep track of non decrypted related events.
25+
*/
26+
internal class MigrateSessionTo048(realm: DynamicRealm) : RealmMigrator(realm, 48) {
27+
28+
override fun doMigrate(realm: DynamicRealm) {
29+
realm.schema.get("PollResponseAggregatedSummaryEntity")
30+
?.addRealmListField(PollResponseAggregatedSummaryEntityFields.ENCRYPTED_RELATED_EVENT_IDS.`$`, String::class.java)
31+
}
32+
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ internal open class EventInsertEntity(
2727
var eventType: String = "",
2828
/**
2929
* This flag will be used to filter EventInsertEntity in EventInsertLiveObserver.
30-
* Currently it's set to false when the event content is encrypted.
30+
* Currently it's set to false after an event with encrypted content has been processed.
3131
*/
3232
var canBeProcessed: Boolean = true
3333
) : RealmObject() {

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ internal open class PollResponseAggregatedSummaryEntity(
3333

3434
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
3535
var sourceEvents: RealmList<String> = RealmList(),
36-
var sourceLocalEchoEvents: RealmList<String> = RealmList()
36+
var sourceLocalEchoEvents: RealmList<String> = RealmList(),
37+
// list of related event ids which are encrypted due to decryption failure
38+
var encryptedRelatedEventIds: RealmList<String> = RealmList(),
3739
) : RealmObject() {
3840

3941
companion object

0 commit comments

Comments
 (0)