Merge pull request #8170 from vector-im/feature/fre/apply_push_rules_after_decryption

Reapply push rules on the decrypted event source (PSG-1146)
This commit is contained in:
Florian Renaud 2023-03-07 10:39:48 +01:00 committed by GitHub
commit 39c702f41b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 122 additions and 16 deletions

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

@ -0,0 +1 @@
Reapply local push rules after event decryption

View file

@ -39,8 +39,17 @@ class EventMatchCondition(
override fun technicalDescription() = "'$key' matches '$pattern'"
fun isSatisfied(event: Event): Boolean {
// TODO encrypted events?
val rawJson = MoshiProvider.providesMoshi().adapter(Event::class.java).toJsonValue(event) as? Map<*, *>
val rawJson: Map<*, *> = (MoshiProvider.providesMoshi().adapter(Event::class.java).toJsonValue(event) as? Map<*, *>)
?.let { rawJson ->
val decryptedRawJson = event.mxDecryptionResult?.payload
if (decryptedRawJson != null) {
rawJson
.toMutableMap()
.apply { putAll(decryptedRawJson) }
} else {
rawJson
}
}
?: return false
val value = extractField(rawJson, key) ?: return false

View file

@ -94,7 +94,10 @@ internal class EventDecryptor @Inject constructor(
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
*/
suspend fun decryptEventAndSaveResult(event: Event, timeline: String) {
tryOrNull(message = "Unable to decrypt the event") {
// event is not encrypted or already decrypted
if (event.getClearType() != EventType.ENCRYPTED) return
tryOrNull(message = "decryptEventAndSaveResult | Unable to decrypt the event") {
decryptEvent(event, timeline)
}
?.let { result ->

View file

@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.isInvitation
import org.matrix.android.sdk.api.session.pushrules.PushEvents
import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse
import org.matrix.android.sdk.internal.crypto.EventDecryptor
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.task.Task
import timber.log.Timber
@ -36,21 +37,22 @@ internal interface ProcessEventForPushTask : Task<ProcessEventForPushTask.Params
internal class DefaultProcessEventForPushTask @Inject constructor(
private val defaultPushRuleService: DefaultPushRuleService,
private val pushRuleFinder: PushRuleFinder,
@UserId private val userId: String
@UserId private val userId: String,
private val eventDecryptor: EventDecryptor,
) : ProcessEventForPushTask {
override suspend fun execute(params: ProcessEventForPushTask.Params) {
val newJoinEvents = params.syncResponse.join
.mapNotNull { (key, value) ->
value.timeline?.events?.mapNotNull {
it.takeIf { !it.isInvitation() }?.copy(roomId = key)
it.takeIf { !it.isInvitation() }?.copyAll(roomId = key)
}
}
.flatten()
val inviteEvents = params.syncResponse.invite
.mapNotNull { (key, value) ->
value.inviteState?.events?.map { it.copy(roomId = key) }
value.inviteState?.events?.map { it.copyAll(roomId = key) }
}
.flatten()

View file

@ -437,6 +437,10 @@ internal class RoomSyncHandler @Inject constructor(
if (event.isEncrypted() && !isInitialSync) {
try {
decryptIfNeeded(event, roomId)
// share the decryption result with the rawEvent because the decryption is done on a copy containing the roomId, see previous comment
rawEvent.mxDecryptionResult = event.mxDecryptionResult
rawEvent.mCryptoError = event.mCryptoError
rawEvent.mCryptoErrorReason = event.mCryptoErrorReason
} catch (e: InterruptedException) {
Timber.i("Decryption got interrupted")
}

View file

@ -23,7 +23,11 @@ import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.matrix.android.sdk.MatrixTest
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.members.MembershipService
@ -38,15 +42,40 @@ class PushRulesConditionTest : MatrixTest {
* Test EventMatchCondition
* ========================================================================================== */
private fun createFakeEncryptedEvent() = Event(
type = EventType.ENCRYPTED,
eventId = "mx0",
roomId = "!fakeRoom",
content = EncryptedEventContent(
algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
ciphertext = "AwgBEpACQEKOkd4Gp0+gSXG4M+btcrnPgsF23xs/lUmS2I4YjmqF...",
sessionId = "TO2G4u2HlnhtbIJk",
senderKey = "5e3EIqg3JfooZnLQ2qHIcBarbassQ4qXblai0",
deviceId = "FAKEE"
).toContent()
)
private fun createSimpleTextEvent(text: String): Event {
return Event(
type = "m.room.message",
type = EventType.MESSAGE,
eventId = "mx0",
content = MessageTextContent("m.text", text).toContent(),
originServerTs = 0
originServerTs = 0,
)
}
private fun createSimpleTextEventEncrypted(text: String): Event {
return createFakeEncryptedEvent().apply {
mxDecryptionResult = OlmDecryptionResult(
payload = mapOf(
"type" to EventType.MESSAGE,
"content" to MessageTextContent("m.text", text).toContent(),
),
senderKey = "the_real_sender_key",
)
}
}
@Test
fun test_eventmatch_type_condition() {
val condition = EventMatchCondition("type", "m.room.message")
@ -70,6 +99,26 @@ class PushRulesConditionTest : MatrixTest {
assertFalse(condition.isSatisfied(simpleRoomMemberEvent))
}
@Test
fun test_decrypted_eventmatch_type_condition() {
val condition = EventMatchCondition("type", "m.room.message")
val simpleDecryptedTextEvent = createSimpleTextEventEncrypted("Yo wtf?")
val encryptedDummyEvent = createFakeEncryptedEvent().apply {
mxDecryptionResult = OlmDecryptionResult(
payload = mapOf(
"type" to EventType.DUMMY,
)
)
}
val encryptedEvent = createFakeEncryptedEvent()
assert(condition.isSatisfied(simpleDecryptedTextEvent))
assertFalse(condition.isSatisfied(encryptedDummyEvent))
assertFalse(condition.isSatisfied(encryptedEvent))
}
@Test
fun test_eventmatch_path_condition() {
val condition = EventMatchCondition("content.msgtype", "m.text")
@ -125,6 +174,22 @@ class PushRulesConditionTest : MatrixTest {
assert(condition.isSatisfied(createSimpleTextEvent("BEN")))
}
@Test
fun test_encrypted_eventmatch_words_only_condition() {
val condition = EventMatchCondition("content.body", "ben")
assertFalse(condition.isSatisfied(createSimpleTextEventEncrypted("benoit")))
assertFalse(condition.isSatisfied(createSimpleTextEventEncrypted("Hello benoit")))
assertFalse(condition.isSatisfied(createSimpleTextEventEncrypted("superben")))
assert(condition.isSatisfied(createSimpleTextEventEncrypted("ben")))
assert(condition.isSatisfied(createSimpleTextEventEncrypted("hello ben")))
assert(condition.isSatisfied(createSimpleTextEventEncrypted("ben is there")))
assert(condition.isSatisfied(createSimpleTextEventEncrypted("hello ben!")))
assert(condition.isSatisfied(createSimpleTextEventEncrypted("hello Ben!")))
assert(condition.isSatisfied(createSimpleTextEventEncrypted("BEN")))
}
@Test
fun test_eventmatch_at_room_condition() {
val condition = EventMatchCondition("content.body", "@room")
@ -140,6 +205,21 @@ class PushRulesConditionTest : MatrixTest {
assert(condition.isSatisfied(createSimpleTextEvent("Don't ping @room!")))
}
@Test
fun test_encrypted_eventmatch_at_room_condition() {
val condition = EventMatchCondition("content.body", "@room")
assertFalse(condition.isSatisfied(createSimpleTextEventEncrypted("@roomba")))
assertFalse(condition.isSatisfied(createSimpleTextEventEncrypted("room benoit")))
assertFalse(condition.isSatisfied(createSimpleTextEventEncrypted("abc@roomba")))
assert(condition.isSatisfied(createSimpleTextEventEncrypted("@room")))
assert(condition.isSatisfied(createSimpleTextEventEncrypted("@room, ben")))
assert(condition.isSatisfied(createSimpleTextEventEncrypted("@ROOM")))
assert(condition.isSatisfied(createSimpleTextEventEncrypted("Use:@room")))
assert(condition.isSatisfied(createSimpleTextEventEncrypted("Don't ping @room!")))
}
@Test
fun test_notice_condition() {
val conditionEqual = EventMatchCondition("content.msgtype", "m.notice")
@ -155,6 +235,17 @@ class PushRulesConditionTest : MatrixTest {
}
}
@Test
fun test_eventmatch_encrypted_type_condition() {
val condition = EventMatchCondition("type", "m.room.encrypted")
val simpleDecryptedTextEvent = createSimpleTextEventEncrypted("Yo wtf?")
val encryptedEvent = createFakeEncryptedEvent()
assertFalse(condition.isSatisfied(simpleDecryptedTextEvent))
assert(condition.isSatisfied(encryptedEvent))
}
/* ==========================================================================================
* Test RoomMemberCountCondition
* ========================================================================================== */

View file

@ -66,9 +66,6 @@ class NotifiableEventResolver @Inject constructor(
private val buildMeta: BuildMeta,
) {
private val nonEncryptedNotifiableEventTypes: List<String> =
listOf(EventType.MESSAGE) + EventType.POLL_START.values + EventType.POLL_END.values + EventType.STATE_ROOM_BEACON_INFO.values
suspend fun resolveEvent(event: Event, session: Session, isNoisy: Boolean): NotifiableEvent? {
val roomID = event.roomId ?: return null
val eventId = event.eventId ?: return null
@ -76,9 +73,8 @@ class NotifiableEventResolver @Inject constructor(
return resolveStateRoomEvent(event, session, canBeReplaced = false, isNoisy = isNoisy)
}
val timelineEvent = session.getRoom(roomID)?.getTimelineEvent(eventId) ?: return null
return when (event.getClearType()) {
in nonEncryptedNotifiableEventTypes,
EventType.ENCRYPTED -> {
return when {
event.supportsNotification() || event.type == EventType.ENCRYPTED -> {
resolveMessageEvent(timelineEvent, session, canBeReplaced = false, isNoisy = isNoisy)
}
else -> {
@ -163,8 +159,8 @@ class NotifiableEventResolver @Inject constructor(
} else {
event.attemptToDecryptIfNeeded(session)
// only convert encrypted messages to NotifiableMessageEvents
when (event.root.getClearType()) {
in nonEncryptedNotifiableEventTypes -> {
when {
event.root.supportsNotification() -> {
val body = displayableEventFormatter.format(event, isDm = room.roomSummary()?.isDirect.orFalse(), appendAuthor = false).toString()
val roomName = room.roomSummary()?.displayName ?: ""
val senderDisplayName = event.senderInfo.disambiguatedDisplayName