diff --git a/changelog.d/8170.bugfix b/changelog.d/8170.bugfix new file mode 100644 index 0000000000..62ded3b2ca --- /dev/null +++ b/changelog.d/8170.bugfix @@ -0,0 +1 @@ +Reapply local push rules after event decryption diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt index 15d5cd3153..c3ddbf14b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt index 03672ae81c..ac9c61a32a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt @@ -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 -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt index 3dfac694ed..d000d709a9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt @@ -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 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() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 5e4886ce1e..f37e384b51 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -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") } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt index 3ddf940241..50d084755b 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt @@ -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 * ========================================================================================== */ diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index 14a36d9922..e1026bbf04 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt @@ -66,9 +66,6 @@ class NotifiableEventResolver @Inject constructor( private val buildMeta: BuildMeta, ) { - private val nonEncryptedNotifiableEventTypes: List = - 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