From 4d8383390821815cf8e97e38bdffcd942b4b36aa Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Tue, 25 Aug 2026 17:51:30 +0200 Subject: [PATCH 1/2] fix(notifications): abort NotificationWorker when user init fails initDecryptedData() could silently leave `user` uninitialized (invalid signature, or an exception swallowed by the catch block), after which doWork() unconditionally called initNcApiAndCredentials(), crashing with UninitializedPropertyAccessException and dropping the notification. Now initDecryptedData() reports success/failure and doWork() bails out early with a log when it fails. Assisted-by: Claude Code:claude-sonnet-5 Signed-off-by: Marcel Hibbe --- .../nextcloud/talk/jobs/NotificationWorker.kt | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt index 6ea912878c..db8207aa45 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt @@ -158,7 +158,10 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor logger.d(TAG, "NotificationWorker::doWork") - initDecryptedData(inputData) + if (!initDecryptedData(inputData)) { + logger.e(TAG, "Aborting NotificationWorker::doWork because user/pushMessage could not be initialized") + return Result.failure() + } initNcApiAndCredentials() notificationManager = NotificationManagerCompat.from(context!!) @@ -423,14 +426,14 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor } @Suppress("TooGenericExceptionCaught", "NestedBlockDepth", "ComplexMethod", "LongMethod") - private fun initDecryptedData(inputData: Data) { + private fun initDecryptedData(inputData: Data): Boolean { try { if (inputData.hasKeyWithValueOfType(BundleKeys.KEY_NOTIFICATION_CLEARTEXT_SUBJECT, String::class.java)) { val subject = inputData.getString(BundleKeys.KEY_NOTIFICATION_CLEARTEXT_SUBJECT) val id = inputData.getLong(BundleKeys.KEY_NOTIFICATION_USER_ID, -1) user = userManager.getUserWithId(id).blockingGet() pushMessage = LoganSquare.parse(subject, DecryptedPushMessage::class.java) - return + return true } val subject = inputData.getString(BundleKeys.KEY_NOTIFICATION_SUBJECT) @@ -453,17 +456,21 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor DecryptedPushMessage::class.java ) user = signatureVerification.user!! + return true + } else { + logger.e(TAG, "Signature verification failed, discarding push message") } } catch (e: NoSuchAlgorithmException) { - Log.e(TAG, "No proper algorithm to decrypt the message ", e) + logger.e(TAG, "No proper algorithm to decrypt the message ", e) } catch (e: NoSuchPaddingException) { - Log.e(TAG, "No proper padding to decrypt the message ", e) + logger.e(TAG, "No proper padding to decrypt the message ", e) } catch (e: InvalidKeyException) { - Log.e(TAG, "Invalid private key ", e) + logger.e(TAG, "Invalid private key ", e) } } catch (e: Exception) { - Log.e(TAG, "Error occurred while initializing decoded data ", e) + logger.e(TAG, "Error occurred while initializing decoded data ", e) } + return false } private fun decryptSubject(privateKey: PrivateKey, base64DecodedSubject: ByteArray): ByteArray = From d23f9d70512553bcdc4628a15ca0d7288fca7cf0 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Wed, 26 Aug 2026 08:53:21 +0200 Subject: [PATCH 2/2] fix(detekt): Split initDecryptedData to keep the detekt score at 110 Returning Boolean from initDecryptedData() gave it a third return statement, which detekt flags as ReturnCount. Split the cleartext and encrypted subject paths into their own helpers so each has a single exit. The complexity the early bail-out adds to doWork() is left as is, since master carries that same finding. Once the import removal from #6584 is in, the score lands at exactly 110. Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Andy Scherzinger --- .../nextcloud/talk/jobs/NotificationWorker.kt | 89 +++++++++++-------- 1 file changed, 50 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt index db8207aa45..0816301256 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt @@ -425,52 +425,63 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor ) } - @Suppress("TooGenericExceptionCaught", "NestedBlockDepth", "ComplexMethod", "LongMethod") - private fun initDecryptedData(inputData: Data): Boolean { + @Suppress("TooGenericExceptionCaught") + private fun initDecryptedData(inputData: Data): Boolean = try { if (inputData.hasKeyWithValueOfType(BundleKeys.KEY_NOTIFICATION_CLEARTEXT_SUBJECT, String::class.java)) { - val subject = inputData.getString(BundleKeys.KEY_NOTIFICATION_CLEARTEXT_SUBJECT) - val id = inputData.getLong(BundleKeys.KEY_NOTIFICATION_USER_ID, -1) - user = userManager.getUserWithId(id).blockingGet() - pushMessage = LoganSquare.parse(subject, DecryptedPushMessage::class.java) - return true + initFromCleartextSubject(inputData) + } else { + initFromEncryptedSubject(inputData) } + } catch (e: Exception) { + logger.e(TAG, "Error occurred while initializing decoded data ", e) + false + } - val subject = inputData.getString(BundleKeys.KEY_NOTIFICATION_SUBJECT) - val signature = inputData.getString(BundleKeys.KEY_NOTIFICATION_SIGNATURE) - - val base64DecodedSubject = Base64.decode(subject, Base64.DEFAULT) - val base64DecodedSignature = Base64.decode(signature, Base64.DEFAULT) - val pushUtils = PushUtils() - val privateKey = pushUtils.readKeyFromFile(false) as PrivateKey - try { - val signatureVerification = pushUtils.verifySignature( - base64DecodedSignature, - base64DecodedSubject - ) - if (signatureVerification.signatureValid) { - val decryptedSubject = decryptSubject(privateKey, base64DecodedSubject) + private fun initFromCleartextSubject(inputData: Data): Boolean { + val subject = inputData.getString(BundleKeys.KEY_NOTIFICATION_CLEARTEXT_SUBJECT) + val id = inputData.getLong(BundleKeys.KEY_NOTIFICATION_USER_ID, -1) + user = userManager.getUserWithId(id).blockingGet() + pushMessage = LoganSquare.parse(subject, DecryptedPushMessage::class.java) + return true + } - pushMessage = LoganSquare.parse( - String(decryptedSubject), - DecryptedPushMessage::class.java - ) - user = signatureVerification.user!! - return true - } else { - logger.e(TAG, "Signature verification failed, discarding push message") - } - } catch (e: NoSuchAlgorithmException) { - logger.e(TAG, "No proper algorithm to decrypt the message ", e) - } catch (e: NoSuchPaddingException) { - logger.e(TAG, "No proper padding to decrypt the message ", e) - } catch (e: InvalidKeyException) { - logger.e(TAG, "Invalid private key ", e) + private fun initFromEncryptedSubject(inputData: Data): Boolean { + val subject = inputData.getString(BundleKeys.KEY_NOTIFICATION_SUBJECT) + val signature = inputData.getString(BundleKeys.KEY_NOTIFICATION_SIGNATURE) + + val base64DecodedSubject = Base64.decode(subject, Base64.DEFAULT) + val base64DecodedSignature = Base64.decode(signature, Base64.DEFAULT) + val pushUtils = PushUtils() + val privateKey = pushUtils.readKeyFromFile(false) as PrivateKey + return try { + val signatureVerification = pushUtils.verifySignature( + base64DecodedSignature, + base64DecodedSubject + ) + if (signatureVerification.signatureValid) { + val decryptedSubject = decryptSubject(privateKey, base64DecodedSubject) + + pushMessage = LoganSquare.parse( + String(decryptedSubject), + DecryptedPushMessage::class.java + ) + user = signatureVerification.user!! + true + } else { + logger.e(TAG, "Signature verification failed, discarding push message") + false } - } catch (e: Exception) { - logger.e(TAG, "Error occurred while initializing decoded data ", e) + } catch (e: NoSuchAlgorithmException) { + logger.e(TAG, "No proper algorithm to decrypt the message ", e) + false + } catch (e: NoSuchPaddingException) { + logger.e(TAG, "No proper padding to decrypt the message ", e) + false + } catch (e: InvalidKeyException) { + logger.e(TAG, "Invalid private key ", e) + false } - return false } private fun decryptSubject(privateKey: PrivateKey, base64DecodedSubject: ByteArray): ByteArray =