Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NSE - Prevent playback on already read messages and enable on device … #6265

Merged
merged 2 commits into from
Jun 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 65 additions & 39 deletions RiotNSE/NotificationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class NotificationService: UNNotificationServiceExtension {
// preprocess the payload, will attempt to fetch room display name
self.preprocessPayload(forEventId: eventId, roomId: roomId)
// fetch the event first
self.fetchEvent(withEventId: eventId, roomId: roomId)
self.fetchAndProcessEvent(withEventId: eventId, roomId: roomId)
}
}

Expand Down Expand Up @@ -232,21 +232,43 @@ class NotificationService: UNNotificationServiceExtension {
// At this stage we don't know the message type, so leave the body as set in didReceive.
}

private func fetchEvent(withEventId eventId: String, roomId: String, allowSync: Bool = true) {
MXLog.debug("[NotificationService] fetchEvent")

NotificationService.backgroundSyncService.event(withEventId: eventId,
inRoom: roomId,
completion: { (response) in
switch response {
case .success(let event):
MXLog.debug("[NotificationService] fetchEvent: Event fetched successfully")
self.processEvent(event)
case .failure(let error):
MXLog.debug("[NotificationService] fetchEvent: error: \(error)")
self.fallbackToBestAttemptContent(forEventId: eventId)
}
})
private func fetchAndProcessEvent(withEventId eventId: String, roomId: String) {
MXLog.debug("[NotificationService] fetchAndProcessEvent")

NotificationService.backgroundSyncService.event(withEventId: eventId, inRoom: roomId) { [weak self] (response) in
switch response {
case .success(let event):
MXLog.debug("[NotificationService] fetchAndProcessEvent: Event fetched successfully")
self?.checkPlaybackAndContinueProcessing(event, roomId: roomId)
case .failure(let error):
MXLog.error("[NotificationService] fetchAndProcessEvent: Failed fetching notification event with error: \(error)")
self?.fallbackToBestAttemptContent(forEventId: eventId)
}
}
}

private func checkPlaybackAndContinueProcessing(_ notificationEvent: MXEvent, roomId: String) {
NotificationService.backgroundSyncService.readMarkerEvent(forRoomId: roomId) { [weak self] response in
switch response {
case .success(let readMarkerEvent):
MXLog.debug("[NotificationService] checkPlaybackAndContinueProcessing: Read marker event fetched successfully")

// As origin server timestamps are not always correct data in a federated environment, we add 10 minutes
// to the calculation to reduce the possibility that an event is marked as read which isn't.
let notificationTimestamp = notificationEvent.originServerTs + (10 * 60 * 1000)

if readMarkerEvent.originServerTs > notificationTimestamp {
MXLog.error("[NotificationService] checkPlaybackAndContinueProcessing: Event already read, discarding.")
self?.discardEvent(event: notificationEvent)
} else {
self?.processEvent(notificationEvent)
}

case .failure(let error):
MXLog.error("[NotificationService] checkPlaybackAndContinueProcessing: Failed fetching read marker event with error: \(error)")
self?.processEvent(notificationEvent)
}
}
}

private func processEvent(_ event: MXEvent) {
Expand All @@ -259,24 +281,24 @@ class NotificationService: UNNotificationServiceExtension {
return
}

self.notificationContent(forEvent: event, forAccount: userAccount) { (notificationContent, ignoreBadgeUpdate) in
var isUnwantedNotification = false
self.notificationContent(forEvent: event, forAccount: userAccount) { [weak self] (notificationContent, ignoreBadgeUpdate) in
guard let self = self else { return }

// Modify the notification content here...
if let newContent = notificationContent {
content.title = newContent.title
content.subtitle = newContent.subtitle
content.body = newContent.body
content.threadIdentifier = newContent.threadIdentifier
content.categoryIdentifier = newContent.categoryIdentifier
content.userInfo = newContent.userInfo
content.sound = newContent.sound
} else {
// this is an unwanted notification, mark as to be deleted when app is foregrounded again OR a new push came
guard let newContent = notificationContent else {
// We still want them removed if the NSE filtering entitlement is not available
ismailgulek marked this conversation as resolved.
Show resolved Hide resolved
content.categoryIdentifier = Constants.toBeRemovedNotificationCategoryIdentifier
isUnwantedNotification = true
self.discardEvent(event: event)
return
}

content.title = newContent.title
content.subtitle = newContent.subtitle
content.body = newContent.body
content.threadIdentifier = newContent.threadIdentifier
content.categoryIdentifier = newContent.categoryIdentifier
content.userInfo = newContent.userInfo
content.sound = newContent.sound

if ignoreBadgeUpdate {
content.badge = nil
}
Expand All @@ -289,18 +311,16 @@ class NotificationService: UNNotificationServiceExtension {
// When it completes, it'll continue with the bestAttemptContent.
return
} else {
MXLog.debug("[NotificationService] processEvent: Calling content handler for: \(String(describing: event.eventId)), isUnwanted: \(isUnwantedNotification)")
self.contentHandlers[event.eventId]?(content)
// clear maps
self.contentHandlers.removeValue(forKey: event.eventId)
self.bestAttemptContents.removeValue(forKey: event.eventId)

// We are done for this push
MXLog.debug("--------------------------------------------------------------------------------")
self.finishProcessing(forEventId: event.eventId, withContent: content)
}
}
}

private func discardEvent(event:MXEvent) {
MXLog.debug("[NotificationService] discardEvent: Discarding event: \(String(describing: event.eventId))")
finishProcessing(forEventId: event.eventId, withContent: UNNotificationContent())
}

private func fallbackToBestAttemptContent(forEventId eventId: String) {
MXLog.debug("[NotificationService] fallbackToBestAttemptContent: method called.")

Expand All @@ -309,8 +329,14 @@ class NotificationService: UNNotificationServiceExtension {
return
}

// call contentHandler
finishProcessing(forEventId: eventId, withContent: content)
}

private func finishProcessing(forEventId eventId: String, withContent content: UNNotificationContent) {
MXLog.debug("[NotificationService] finishProcessingEvent: Calling content handler for: \(String(describing: eventId))")

contentHandlers[eventId]?(content)

// clear maps
contentHandlers.removeValue(forKey: eventId)
bestAttemptContents.removeValue(forKey: eventId)
Expand Down
2 changes: 2 additions & 0 deletions RiotNSE/RiotNSE.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
<array>
<string>$(APPLICATION_GROUP_IDENTIFIER)</string>
</array>
<key>com.apple.developer.usernotifications.filtering</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(KEYCHAIN_ACCESS_GROUP)</string>
Expand Down
1 change: 1 addition & 0 deletions changelog.d/pr-6265.change
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Security fix: prevent playback on already read messages through push notifications, enable on device silencing.