BIT-2438: Update push notification processing logic to be more lenient (#3393)

This commit is contained in:
David Perez 2024-07-02 14:53:29 -05:00 committed by GitHub
parent f5039d72b9
commit b181d0d026
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 114 additions and 93 deletions

View file

@ -120,7 +120,7 @@ class PushManagerImpl @Inject constructor(
onMessageReceived(notification) onMessageReceived(notification)
} }
@Suppress("LongMethod", "CyclomaticComplexMethod", "ReturnCount") @Suppress("LongMethod", "CyclomaticComplexMethod")
private fun onMessageReceived(notification: BitwardenNotification) { private fun onMessageReceived(notification: BitwardenNotification) {
if (authDiskSource.uniqueAppId == notification.contextId) return if (authDiskSource.uniqueAppId == notification.contextId) return
@ -130,62 +130,67 @@ class PushManagerImpl @Inject constructor(
NotificationType.AUTH_REQUEST, NotificationType.AUTH_REQUEST,
NotificationType.AUTH_REQUEST_RESPONSE, NotificationType.AUTH_REQUEST_RESPONSE,
-> { -> {
val payload: NotificationPayload.PasswordlessRequestNotification = json
json.decodeFromString(string = notification.payload) .decodeFromString<NotificationPayload.PasswordlessRequestNotification>(
string = notification.payload,
)
.takeIf { it.loginRequestId != null && it.userId != null }
?.let {
mutablePasswordlessRequestSharedFlow.tryEmit( mutablePasswordlessRequestSharedFlow.tryEmit(
PasswordlessRequestData( PasswordlessRequestData(
loginRequestId = payload.id, loginRequestId = requireNotNull(it.loginRequestId),
userId = payload.userId, userId = requireNotNull(it.userId),
), ),
) )
} }
}
NotificationType.LOG_OUT -> { NotificationType.LOG_OUT -> {
val payload: NotificationPayload.UserNotification = json
json.decodeFromString(notification.payload) .decodeFromString<NotificationPayload.UserNotification>(
mutableLogoutSharedFlow.tryEmit( string = notification.payload,
NotificationLogoutData(payload.userId),
) )
.userId
?.let { mutableLogoutSharedFlow.tryEmit(NotificationLogoutData(it)) }
} }
NotificationType.SYNC_CIPHER_CREATE, NotificationType.SYNC_CIPHER_CREATE,
NotificationType.SYNC_CIPHER_UPDATE, NotificationType.SYNC_CIPHER_UPDATE,
-> { -> {
val payload: NotificationPayload.SyncCipherNotification = json
json.decodeFromString(notification.payload) .decodeFromString<NotificationPayload.SyncCipherNotification>(
@Suppress("ComplexCondition") string = notification.payload,
if (payload.id == null || )
payload.revisionDate == null || .takeIf { isLoggedIn(userId) && it.userMatchesNotification(userId) }
!isLoggedIn(userId) || ?.takeIf {
!payload.userMatchesNotification(userId) it.cipherId != null &&
) { it.revisionDate != null &&
return it.organizationId != null &&
it.collectionIds != null
} }
?.let {
mutableSyncCipherUpsertSharedFlow.tryEmit( mutableSyncCipherUpsertSharedFlow.tryEmit(
SyncCipherUpsertData( SyncCipherUpsertData(
cipherId = payload.id, cipherId = requireNotNull(it.cipherId),
revisionDate = payload.revisionDate, revisionDate = requireNotNull(it.revisionDate),
organizationId = payload.organizationId, organizationId = requireNotNull(it.organizationId),
collectionIds = payload.collectionIds, collectionIds = requireNotNull(it.collectionIds),
isUpdate = type == NotificationType.SYNC_CIPHER_UPDATE, isUpdate = type == NotificationType.SYNC_CIPHER_UPDATE,
), ),
) )
} }
}
NotificationType.SYNC_CIPHER_DELETE, NotificationType.SYNC_CIPHER_DELETE,
NotificationType.SYNC_LOGIN_DELETE, NotificationType.SYNC_LOGIN_DELETE,
-> { -> {
val payload: NotificationPayload.SyncCipherNotification = json
json.decodeFromString(notification.payload) .decodeFromString<NotificationPayload.SyncCipherNotification>(
if (payload.id == null || string = notification.payload,
!isLoggedIn(userId) ||
!payload.userMatchesNotification(userId)
) {
return
}
mutableSyncCipherDeleteSharedFlow.tryEmit(
SyncCipherDeleteData(payload.id),
) )
.takeIf { isLoggedIn(userId) && it.userMatchesNotification(userId) }
?.cipherId
?.let { mutableSyncCipherDeleteSharedFlow.tryEmit(SyncCipherDeleteData(it)) }
} }
NotificationType.SYNC_CIPHERS, NotificationType.SYNC_CIPHERS,
@ -198,55 +203,67 @@ class PushManagerImpl @Inject constructor(
NotificationType.SYNC_FOLDER_CREATE, NotificationType.SYNC_FOLDER_CREATE,
NotificationType.SYNC_FOLDER_UPDATE, NotificationType.SYNC_FOLDER_UPDATE,
-> { -> {
val payload: NotificationPayload.SyncFolderNotification = json
json.decodeFromString(notification.payload) .decodeFromString<NotificationPayload.SyncFolderNotification>(
if (!isLoggedIn(userId) || !payload.userMatchesNotification(userId)) return string = notification.payload,
)
.takeIf { isLoggedIn(userId) && it.userMatchesNotification(userId) }
?.takeIf { it.folderId != null && it.revisionDate != null }
?.let {
mutableSyncFolderUpsertSharedFlow.tryEmit( mutableSyncFolderUpsertSharedFlow.tryEmit(
SyncFolderUpsertData( SyncFolderUpsertData(
folderId = payload.id, folderId = requireNotNull(it.folderId),
revisionDate = payload.revisionDate, revisionDate = requireNotNull(it.revisionDate),
isUpdate = type == NotificationType.SYNC_FOLDER_UPDATE, isUpdate = type == NotificationType.SYNC_FOLDER_UPDATE,
), ),
) )
} }
}
NotificationType.SYNC_FOLDER_DELETE -> { NotificationType.SYNC_FOLDER_DELETE -> {
val payload: NotificationPayload.SyncFolderNotification = json
json.decodeFromString(notification.payload) .decodeFromString<NotificationPayload.SyncFolderNotification>(
if (!isLoggedIn(userId) || !payload.userMatchesNotification(userId)) return string = notification.payload,
mutableSyncFolderDeleteSharedFlow.tryEmit(
SyncFolderDeleteData(payload.id),
) )
.takeIf { isLoggedIn(userId) && it.userMatchesNotification(userId) }
?.folderId
?.let { mutableSyncFolderDeleteSharedFlow.tryEmit(SyncFolderDeleteData(it)) }
} }
NotificationType.SYNC_ORG_KEYS -> { NotificationType.SYNC_ORG_KEYS -> {
if (!isLoggedIn(userId)) return if (isLoggedIn(userId)) {
mutableSyncOrgKeysSharedFlow.tryEmit(Unit) mutableSyncOrgKeysSharedFlow.tryEmit(Unit)
} }
}
NotificationType.SYNC_SEND_CREATE, NotificationType.SYNC_SEND_CREATE,
NotificationType.SYNC_SEND_UPDATE, NotificationType.SYNC_SEND_UPDATE,
-> { -> {
val payload: NotificationPayload.SyncSendNotification = json
json.decodeFromString(notification.payload) .decodeFromString<NotificationPayload.SyncSendNotification>(
if (!isLoggedIn(userId) || !payload.userMatchesNotification(userId)) return string = notification.payload,
)
.takeIf { isLoggedIn(userId) && it.userMatchesNotification(userId) }
?.takeIf { it.sendId != null && it.revisionDate != null }
?.let {
mutableSyncSendUpsertSharedFlow.tryEmit( mutableSyncSendUpsertSharedFlow.tryEmit(
SyncSendUpsertData( SyncSendUpsertData(
sendId = payload.id, sendId = requireNotNull(it.sendId),
revisionDate = payload.revisionDate, revisionDate = requireNotNull(it.revisionDate),
isUpdate = type == NotificationType.SYNC_SEND_UPDATE, isUpdate = type == NotificationType.SYNC_SEND_UPDATE,
), ),
) )
} }
}
NotificationType.SYNC_SEND_DELETE -> { NotificationType.SYNC_SEND_DELETE -> {
val payload: NotificationPayload.SyncSendNotification = json
json.decodeFromString(notification.payload) .decodeFromString<NotificationPayload.SyncSendNotification>(
if (!isLoggedIn(userId) || !payload.userMatchesNotification(userId)) return string = notification.payload,
mutableSyncSendDeleteSharedFlow.tryEmit(
SyncSendDeleteData(payload.id),
) )
.takeIf { isLoggedIn(userId) && it.userMatchesNotification(userId) }
?.sendId
?.let { mutableSyncSendDeleteSharedFlow.tryEmit(SyncSendDeleteData(it)) }
} }
} }
} }
@ -290,15 +307,15 @@ class PushManagerImpl @Inject constructor(
if (token == currentToken) { if (token == currentToken) {
// Our token is up-to-date, so just update the last registration date // Our token is up-to-date, so just update the last registration date
pushDiskSource.storeLastPushTokenRegistrationDate( pushDiskSource.storeLastPushTokenRegistrationDate(
userId, userId = userId,
ZonedDateTime.ofInstant(clock.instant(), ZoneOffset.UTC), registrationDate = ZonedDateTime.ofInstant(clock.instant(), ZoneOffset.UTC),
) )
return return
} }
pushService pushService
.putDeviceToken( .putDeviceToken(
PushTokenRequest(token), body = PushTokenRequest(token),
) )
.fold( .fold(
onSuccess = { onSuccess = {

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.platform.manager.model package com.x8bit.bitwarden.data.platform.manager.model
import com.x8bit.bitwarden.data.platform.manager.PushManager
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -7,6 +8,9 @@ import java.time.ZonedDateTime
/** /**
* The payload of a push notification. * The payload of a push notification.
*
* Note: The data we receive is not always reliable, so everything is nullable and we validate the
* data in the [PushManager] as necessary.
*/ */
@Serializable @Serializable
sealed class NotificationPayload { sealed class NotificationPayload {
@ -20,7 +24,7 @@ sealed class NotificationPayload {
*/ */
@Serializable @Serializable
data class SyncCipherNotification( data class SyncCipherNotification(
@SerialName("Id") val id: String?, @SerialName("Id") val cipherId: String?,
@SerialName("UserId") override val userId: String?, @SerialName("UserId") override val userId: String?,
@SerialName("OrganizationId") val organizationId: String?, @SerialName("OrganizationId") val organizationId: String?,
@SerialName("CollectionIds") val collectionIds: List<String>?, @SerialName("CollectionIds") val collectionIds: List<String>?,
@ -33,10 +37,10 @@ sealed class NotificationPayload {
*/ */
@Serializable @Serializable
data class SyncFolderNotification( data class SyncFolderNotification(
@SerialName("Id") val id: String, @SerialName("Id") val folderId: String?,
@SerialName("UserId") override val userId: String, @SerialName("UserId") override val userId: String?,
@Contextual @Contextual
@SerialName("RevisionDate") val revisionDate: ZonedDateTime, @SerialName("RevisionDate") val revisionDate: ZonedDateTime?,
) : NotificationPayload() ) : NotificationPayload()
/** /**
@ -44,9 +48,9 @@ sealed class NotificationPayload {
*/ */
@Serializable @Serializable
data class UserNotification( data class UserNotification(
@SerialName("UserId") override val userId: String, @SerialName("UserId") override val userId: String?,
@Contextual @Contextual
@SerialName("Date") val date: ZonedDateTime, @SerialName("Date") val date: ZonedDateTime?,
) : NotificationPayload() ) : NotificationPayload()
/** /**
@ -54,10 +58,10 @@ sealed class NotificationPayload {
*/ */
@Serializable @Serializable
data class SyncSendNotification( data class SyncSendNotification(
@SerialName("Id") val id: String, @SerialName("Id") val sendId: String?,
@SerialName("UserId") override val userId: String, @SerialName("UserId") override val userId: String?,
@Contextual @Contextual
@SerialName("RevisionDate") val revisionDate: ZonedDateTime, @SerialName("RevisionDate") val revisionDate: ZonedDateTime?,
) : NotificationPayload() ) : NotificationPayload()
/** /**
@ -65,7 +69,7 @@ sealed class NotificationPayload {
*/ */
@Serializable @Serializable
data class PasswordlessRequestNotification( data class PasswordlessRequestNotification(
@SerialName("UserId") override val userId: String, @SerialName("UserId") override val userId: String?,
@SerialName("Id") val id: String, @SerialName("Id") val loginRequestId: String?,
) : NotificationPayload() ) : NotificationPayload()
} }