Skip to content

Commit b181d0d

Browse files
BIT-2438: Update push notification processing logic to be more lenient (#3393)
1 parent f5039d7 commit b181d0d

File tree

2 files changed

+114
-93
lines changed

2 files changed

+114
-93
lines changed

app/src/main/java/com/x8bit/bitwarden/data/platform/manager/PushManagerImpl.kt

Lines changed: 99 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ class PushManagerImpl @Inject constructor(
120120
onMessageReceived(notification)
121121
}
122122

123-
@Suppress("LongMethod", "CyclomaticComplexMethod", "ReturnCount")
123+
@Suppress("LongMethod", "CyclomaticComplexMethod")
124124
private fun onMessageReceived(notification: BitwardenNotification) {
125125
if (authDiskSource.uniqueAppId == notification.contextId) return
126126

@@ -130,62 +130,67 @@ class PushManagerImpl @Inject constructor(
130130
NotificationType.AUTH_REQUEST,
131131
NotificationType.AUTH_REQUEST_RESPONSE,
132132
-> {
133-
val payload: NotificationPayload.PasswordlessRequestNotification =
134-
json.decodeFromString(string = notification.payload)
135-
mutablePasswordlessRequestSharedFlow.tryEmit(
136-
PasswordlessRequestData(
137-
loginRequestId = payload.id,
138-
userId = payload.userId,
139-
),
140-
)
133+
json
134+
.decodeFromString<NotificationPayload.PasswordlessRequestNotification>(
135+
string = notification.payload,
136+
)
137+
.takeIf { it.loginRequestId != null && it.userId != null }
138+
?.let {
139+
mutablePasswordlessRequestSharedFlow.tryEmit(
140+
PasswordlessRequestData(
141+
loginRequestId = requireNotNull(it.loginRequestId),
142+
userId = requireNotNull(it.userId),
143+
),
144+
)
145+
}
141146
}
142147

143148
NotificationType.LOG_OUT -> {
144-
val payload: NotificationPayload.UserNotification =
145-
json.decodeFromString(notification.payload)
146-
mutableLogoutSharedFlow.tryEmit(
147-
NotificationLogoutData(payload.userId),
148-
)
149+
json
150+
.decodeFromString<NotificationPayload.UserNotification>(
151+
string = notification.payload,
152+
)
153+
.userId
154+
?.let { mutableLogoutSharedFlow.tryEmit(NotificationLogoutData(it)) }
149155
}
150156

151157
NotificationType.SYNC_CIPHER_CREATE,
152158
NotificationType.SYNC_CIPHER_UPDATE,
153159
-> {
154-
val payload: NotificationPayload.SyncCipherNotification =
155-
json.decodeFromString(notification.payload)
156-
@Suppress("ComplexCondition")
157-
if (payload.id == null ||
158-
payload.revisionDate == null ||
159-
!isLoggedIn(userId) ||
160-
!payload.userMatchesNotification(userId)
161-
) {
162-
return
163-
}
164-
mutableSyncCipherUpsertSharedFlow.tryEmit(
165-
SyncCipherUpsertData(
166-
cipherId = payload.id,
167-
revisionDate = payload.revisionDate,
168-
organizationId = payload.organizationId,
169-
collectionIds = payload.collectionIds,
170-
isUpdate = type == NotificationType.SYNC_CIPHER_UPDATE,
171-
),
172-
)
160+
json
161+
.decodeFromString<NotificationPayload.SyncCipherNotification>(
162+
string = notification.payload,
163+
)
164+
.takeIf { isLoggedIn(userId) && it.userMatchesNotification(userId) }
165+
?.takeIf {
166+
it.cipherId != null &&
167+
it.revisionDate != null &&
168+
it.organizationId != null &&
169+
it.collectionIds != null
170+
}
171+
?.let {
172+
mutableSyncCipherUpsertSharedFlow.tryEmit(
173+
SyncCipherUpsertData(
174+
cipherId = requireNotNull(it.cipherId),
175+
revisionDate = requireNotNull(it.revisionDate),
176+
organizationId = requireNotNull(it.organizationId),
177+
collectionIds = requireNotNull(it.collectionIds),
178+
isUpdate = type == NotificationType.SYNC_CIPHER_UPDATE,
179+
),
180+
)
181+
}
173182
}
174183

175184
NotificationType.SYNC_CIPHER_DELETE,
176185
NotificationType.SYNC_LOGIN_DELETE,
177186
-> {
178-
val payload: NotificationPayload.SyncCipherNotification =
179-
json.decodeFromString(notification.payload)
180-
if (payload.id == null ||
181-
!isLoggedIn(userId) ||
182-
!payload.userMatchesNotification(userId)
183-
) {
184-
return
185-
}
186-
mutableSyncCipherDeleteSharedFlow.tryEmit(
187-
SyncCipherDeleteData(payload.id),
188-
)
187+
json
188+
.decodeFromString<NotificationPayload.SyncCipherNotification>(
189+
string = notification.payload,
190+
)
191+
.takeIf { isLoggedIn(userId) && it.userMatchesNotification(userId) }
192+
?.cipherId
193+
?.let { mutableSyncCipherDeleteSharedFlow.tryEmit(SyncCipherDeleteData(it)) }
189194
}
190195

191196
NotificationType.SYNC_CIPHERS,
@@ -198,55 +203,67 @@ class PushManagerImpl @Inject constructor(
198203
NotificationType.SYNC_FOLDER_CREATE,
199204
NotificationType.SYNC_FOLDER_UPDATE,
200205
-> {
201-
val payload: NotificationPayload.SyncFolderNotification =
202-
json.decodeFromString(notification.payload)
203-
if (!isLoggedIn(userId) || !payload.userMatchesNotification(userId)) return
204-
mutableSyncFolderUpsertSharedFlow.tryEmit(
205-
SyncFolderUpsertData(
206-
folderId = payload.id,
207-
revisionDate = payload.revisionDate,
208-
isUpdate = type == NotificationType.SYNC_FOLDER_UPDATE,
209-
),
210-
)
206+
json
207+
.decodeFromString<NotificationPayload.SyncFolderNotification>(
208+
string = notification.payload,
209+
)
210+
.takeIf { isLoggedIn(userId) && it.userMatchesNotification(userId) }
211+
?.takeIf { it.folderId != null && it.revisionDate != null }
212+
?.let {
213+
mutableSyncFolderUpsertSharedFlow.tryEmit(
214+
SyncFolderUpsertData(
215+
folderId = requireNotNull(it.folderId),
216+
revisionDate = requireNotNull(it.revisionDate),
217+
isUpdate = type == NotificationType.SYNC_FOLDER_UPDATE,
218+
),
219+
)
220+
}
211221
}
212222

213223
NotificationType.SYNC_FOLDER_DELETE -> {
214-
val payload: NotificationPayload.SyncFolderNotification =
215-
json.decodeFromString(notification.payload)
216-
if (!isLoggedIn(userId) || !payload.userMatchesNotification(userId)) return
217-
218-
mutableSyncFolderDeleteSharedFlow.tryEmit(
219-
SyncFolderDeleteData(payload.id),
220-
)
224+
json
225+
.decodeFromString<NotificationPayload.SyncFolderNotification>(
226+
string = notification.payload,
227+
)
228+
.takeIf { isLoggedIn(userId) && it.userMatchesNotification(userId) }
229+
?.folderId
230+
?.let { mutableSyncFolderDeleteSharedFlow.tryEmit(SyncFolderDeleteData(it)) }
221231
}
222232

223233
NotificationType.SYNC_ORG_KEYS -> {
224-
if (!isLoggedIn(userId)) return
225-
mutableSyncOrgKeysSharedFlow.tryEmit(Unit)
234+
if (isLoggedIn(userId)) {
235+
mutableSyncOrgKeysSharedFlow.tryEmit(Unit)
236+
}
226237
}
227238

228239
NotificationType.SYNC_SEND_CREATE,
229240
NotificationType.SYNC_SEND_UPDATE,
230241
-> {
231-
val payload: NotificationPayload.SyncSendNotification =
232-
json.decodeFromString(notification.payload)
233-
if (!isLoggedIn(userId) || !payload.userMatchesNotification(userId)) return
234-
mutableSyncSendUpsertSharedFlow.tryEmit(
235-
SyncSendUpsertData(
236-
sendId = payload.id,
237-
revisionDate = payload.revisionDate,
238-
isUpdate = type == NotificationType.SYNC_SEND_UPDATE,
239-
),
240-
)
242+
json
243+
.decodeFromString<NotificationPayload.SyncSendNotification>(
244+
string = notification.payload,
245+
)
246+
.takeIf { isLoggedIn(userId) && it.userMatchesNotification(userId) }
247+
?.takeIf { it.sendId != null && it.revisionDate != null }
248+
?.let {
249+
mutableSyncSendUpsertSharedFlow.tryEmit(
250+
SyncSendUpsertData(
251+
sendId = requireNotNull(it.sendId),
252+
revisionDate = requireNotNull(it.revisionDate),
253+
isUpdate = type == NotificationType.SYNC_SEND_UPDATE,
254+
),
255+
)
256+
}
241257
}
242258

243259
NotificationType.SYNC_SEND_DELETE -> {
244-
val payload: NotificationPayload.SyncSendNotification =
245-
json.decodeFromString(notification.payload)
246-
if (!isLoggedIn(userId) || !payload.userMatchesNotification(userId)) return
247-
mutableSyncSendDeleteSharedFlow.tryEmit(
248-
SyncSendDeleteData(payload.id),
249-
)
260+
json
261+
.decodeFromString<NotificationPayload.SyncSendNotification>(
262+
string = notification.payload,
263+
)
264+
.takeIf { isLoggedIn(userId) && it.userMatchesNotification(userId) }
265+
?.sendId
266+
?.let { mutableSyncSendDeleteSharedFlow.tryEmit(SyncSendDeleteData(it)) }
250267
}
251268
}
252269
}
@@ -290,15 +307,15 @@ class PushManagerImpl @Inject constructor(
290307
if (token == currentToken) {
291308
// Our token is up-to-date, so just update the last registration date
292309
pushDiskSource.storeLastPushTokenRegistrationDate(
293-
userId,
294-
ZonedDateTime.ofInstant(clock.instant(), ZoneOffset.UTC),
310+
userId = userId,
311+
registrationDate = ZonedDateTime.ofInstant(clock.instant(), ZoneOffset.UTC),
295312
)
296313
return
297314
}
298315

299316
pushService
300317
.putDeviceToken(
301-
PushTokenRequest(token),
318+
body = PushTokenRequest(token),
302319
)
303320
.fold(
304321
onSuccess = {
Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package com.x8bit.bitwarden.data.platform.manager.model
22

3+
import com.x8bit.bitwarden.data.platform.manager.PushManager
34
import kotlinx.serialization.Contextual
45
import kotlinx.serialization.SerialName
56
import kotlinx.serialization.Serializable
67
import java.time.ZonedDateTime
78

89
/**
910
* The payload of a push notification.
11+
*
12+
* Note: The data we receive is not always reliable, so everything is nullable and we validate the
13+
* data in the [PushManager] as necessary.
1014
*/
1115
@Serializable
1216
sealed class NotificationPayload {
@@ -20,7 +24,7 @@ sealed class NotificationPayload {
2024
*/
2125
@Serializable
2226
data class SyncCipherNotification(
23-
@SerialName("Id") val id: String?,
27+
@SerialName("Id") val cipherId: String?,
2428
@SerialName("UserId") override val userId: String?,
2529
@SerialName("OrganizationId") val organizationId: String?,
2630
@SerialName("CollectionIds") val collectionIds: List<String>?,
@@ -33,39 +37,39 @@ sealed class NotificationPayload {
3337
*/
3438
@Serializable
3539
data class SyncFolderNotification(
36-
@SerialName("Id") val id: String,
37-
@SerialName("UserId") override val userId: String,
40+
@SerialName("Id") val folderId: String?,
41+
@SerialName("UserId") override val userId: String?,
3842
@Contextual
39-
@SerialName("RevisionDate") val revisionDate: ZonedDateTime,
43+
@SerialName("RevisionDate") val revisionDate: ZonedDateTime?,
4044
) : NotificationPayload()
4145

4246
/**
4347
* A notification payload for user-based operations.
4448
*/
4549
@Serializable
4650
data class UserNotification(
47-
@SerialName("UserId") override val userId: String,
51+
@SerialName("UserId") override val userId: String?,
4852
@Contextual
49-
@SerialName("Date") val date: ZonedDateTime,
53+
@SerialName("Date") val date: ZonedDateTime?,
5054
) : NotificationPayload()
5155

5256
/**
5357
* A notification payload for sync send operations.
5458
*/
5559
@Serializable
5660
data class SyncSendNotification(
57-
@SerialName("Id") val id: String,
58-
@SerialName("UserId") override val userId: String,
61+
@SerialName("Id") val sendId: String?,
62+
@SerialName("UserId") override val userId: String?,
5963
@Contextual
60-
@SerialName("RevisionDate") val revisionDate: ZonedDateTime,
64+
@SerialName("RevisionDate") val revisionDate: ZonedDateTime?,
6165
) : NotificationPayload()
6266

6367
/**
6468
* A notification payload for passwordless requests.
6569
*/
6670
@Serializable
6771
data class PasswordlessRequestNotification(
68-
@SerialName("UserId") override val userId: String,
69-
@SerialName("Id") val id: String,
72+
@SerialName("UserId") override val userId: String?,
73+
@SerialName("Id") val loginRequestId: String?,
7074
) : NotificationPayload()
7175
}

0 commit comments

Comments
 (0)