From 962375e422a78a4925d96b8c957f31dd58fe6e12 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Fri, 1 Aug 2025 13:07:54 -0300 Subject: [PATCH] Clear auth credentials post restore and when a user disables backups. --- .../securesms/backup/v2/BackupRepository.kt | 2 +- .../securesms/jobs/BackupDeleteJob.kt | 2 + .../jobs/InAppPaymentRecurringContextJob.kt | 3 +- .../PostRegistrationBackupRedemptionJob.kt | 46 +++++++++++-------- .../registration/util/RegistrationUtil.java | 8 ++-- .../registration/util/RegistrationUtilTest.kt | 9 ++++ .../debuglogsviewer/app/MainActivity.kt | 2 +- .../debuglogsviewer/app/webview/WebView.kt | 18 ++++---- .../signal/debuglogsviewer/DebugLogsViewer.kt | 4 +- 9 files changed, 57 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt index b274a1d8bc..88e267c779 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt @@ -229,7 +229,7 @@ object BackupRepository { BackupMessagesJob.enqueue() } - private fun resetInitializedStateAndAuthCredentials() { + fun resetInitializedStateAndAuthCredentials() { SignalStore.backup.backupsInitialized = false SignalStore.backup.messageCredentials.clearAll() SignalStore.backup.mediaCredentials.clearAll() diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupDeleteJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupDeleteJob.kt index f291bc01e4..fddf169381 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupDeleteJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/BackupDeleteJob.kt @@ -56,6 +56,7 @@ class BackupDeleteJob private constructor( if (result.isFailure) { clearLocalBackupStateOnFailure() + BackupRepository.resetInitializedStateAndAuthCredentials() } return result @@ -110,6 +111,7 @@ class BackupDeleteJob private constructor( val result = checkResults(results) if (result.isSuccess) { Log.i(TAG, "Backup deletion was successful.") + BackupRepository.resetInitializedStateAndAuthCredentials() SignalStore.backup.deletionState = DeletionState.COMPLETE } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/InAppPaymentRecurringContextJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/InAppPaymentRecurringContextJob.kt index 53a19e86ef..9476769174 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/InAppPaymentRecurringContextJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/InAppPaymentRecurringContextJob.kt @@ -237,7 +237,8 @@ class InAppPaymentRecurringContextJob private constructor( } if (tier != MessageBackupTier.PAID) { - warning("ZK credential does not align with entitlement. Forcing a redemption.") + warning("ZK credential does not align with entitlement. Clearing backup credentials and forcing a redemption.") + BackupRepository.resetInitializedStateAndAuthCredentials() return false } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PostRegistrationBackupRedemptionJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/PostRegistrationBackupRedemptionJob.kt index d9b9347a48..b228a4518e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PostRegistrationBackupRedemptionJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PostRegistrationBackupRedemptionJob.kt @@ -28,6 +28,7 @@ import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration import java.math.BigDecimal import java.util.Currency import java.util.Locale +import kotlin.concurrent.withLock /** * Runs after registration to make sure we are on the backup level we expect on this device. @@ -107,28 +108,35 @@ class PostRegistrationBackupRedemptionJob : CoroutineJob { warning("Could not resolve price, using empty price.") } - info("Creating a pending payment...") - val id = SignalDatabase.inAppPayments.insert( - type = InAppPaymentType.RECURRING_BACKUP, - state = InAppPaymentTable.State.PENDING, - subscriberId = InAppPaymentsRepository.requireSubscriber(InAppPaymentSubscriberRecord.Type.BACKUP).subscriberId, - endOfPeriod = null, - inAppPaymentData = InAppPaymentData( - badge = null, - amount = price.toFiatValue(), - level = SubscriptionsConfiguration.BACKUPS_LEVEL.toLong(), - recipientId = Recipient.self().id.serialize(), - paymentMethodType = InAppPaymentData.PaymentMethodType.GOOGLE_PLAY_BILLING, - redemption = InAppPaymentData.RedemptionState( - stage = InAppPaymentData.RedemptionState.Stage.INIT + InAppPaymentSubscriberRecord.Type.BACKUP.lock.withLock { + if (SignalDatabase.inAppPayments.hasPendingBackupRedemption()) { + warning("Backup is already pending redemption. Exiting.") + return Result.success() + } + + info("Creating a pending payment...") + val id = SignalDatabase.inAppPayments.insert( + type = InAppPaymentType.RECURRING_BACKUP, + state = InAppPaymentTable.State.PENDING, + subscriberId = InAppPaymentsRepository.requireSubscriber(InAppPaymentSubscriberRecord.Type.BACKUP).subscriberId, + endOfPeriod = null, + inAppPaymentData = InAppPaymentData( + badge = null, + amount = price.toFiatValue(), + level = SubscriptionsConfiguration.BACKUPS_LEVEL.toLong(), + recipientId = Recipient.self().id.serialize(), + paymentMethodType = InAppPaymentData.PaymentMethodType.GOOGLE_PLAY_BILLING, + redemption = InAppPaymentData.RedemptionState( + stage = InAppPaymentData.RedemptionState.Stage.INIT + ) ) ) - ) - info("Submitting job chain.") - InAppPaymentPurchaseTokenJob.createJobChain( - inAppPayment = SignalDatabase.inAppPayments.getById(id)!! - ).enqueue() + info("Submitting job chain.") + InAppPaymentPurchaseTokenJob.createJobChain( + inAppPayment = SignalDatabase.inAppPayments.getById(id)!! + ).enqueue() + } return Result.success() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationUtil.java b/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationUtil.java index 13af8a016e..dc69f189ef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/util/RegistrationUtil.java @@ -6,6 +6,7 @@ package org.thoughtcrime.securesms.registration.util; import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.backup.v2.BackupRepository; import org.thoughtcrime.securesms.backup.v2.MessageBackupTier; import org.thoughtcrime.securesms.dependencies.AppDependencies; import org.thoughtcrime.securesms.jobs.ArchiveBackupIdReservationJob; @@ -53,14 +54,13 @@ public final class RegistrationUtil { .then(new DirectoryRefreshJob(false)) .enqueue(); - if (SignalStore.backup().getBackupTier() == MessageBackupTier.PAID) { - AppDependencies.getJobManager().add(new PostRegistrationBackupRedemptionJob()); - } - SignalStore.emoji().clearSearchIndexMetadata(); EmojiSearchIndexDownloadJob.scheduleImmediately(); + + BackupRepository.INSTANCE.resetInitializedStateAndAuthCredentials(); AppDependencies.getJobManager().add(new ArchiveBackupIdReservationJob()); + AppDependencies.getJobManager().add(new PostRegistrationBackupRedemptionJob()); } else if (!SignalStore.registration().isRegistrationComplete()) { Log.i(TAG, "Registration is not yet complete.", new Throwable()); diff --git a/app/src/test/java/org/thoughtcrime/securesms/registration/util/RegistrationUtilTest.kt b/app/src/test/java/org/thoughtcrime/securesms/registration/util/RegistrationUtilTest.kt index ffe8c0848e..80fce4dcb2 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/registration/util/RegistrationUtilTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/registration/util/RegistrationUtilTest.kt @@ -13,6 +13,7 @@ import assertk.assertions.hasSize import assertk.assertions.isEmpty import assertk.assertions.isEqualTo import io.mockk.every +import io.mockk.mockk import io.mockk.mockkObject import io.mockk.mockkStatic import io.mockk.unmockkAll @@ -57,6 +58,14 @@ class RegistrationUtilTest { initialize(logRecorder) every { SignalStore.backup.backupTier } returns null + every { SignalStore.backup.backupsInitialized = any() } answers { } + every { SignalStore.backup.cachedMediaCdnPath = any() } answers { } + every { SignalStore.backup.mediaCredentials } returns mockk { + every { clearAll() } answers {} + } + every { SignalStore.backup.messageCredentials } returns mockk { + every { clearAll() } answers {} + } } @After diff --git a/debuglogs-viewer/app/src/main/java/org/signal/debuglogsviewer/app/MainActivity.kt b/debuglogs-viewer/app/src/main/java/org/signal/debuglogsviewer/app/MainActivity.kt index 8cf83050b5..7dcba860c5 100644 --- a/debuglogs-viewer/app/src/main/java/org/signal/debuglogsviewer/app/MainActivity.kt +++ b/debuglogs-viewer/app/src/main/java/org/signal/debuglogsviewer/app/MainActivity.kt @@ -28,4 +28,4 @@ class MainActivity : ComponentActivity() { setupWebView(this, webview, findButton, filterLevelButton, editButton, cancelEditButton, copyButton) } -} \ No newline at end of file +} diff --git a/debuglogs-viewer/app/src/main/java/org/signal/debuglogsviewer/app/webview/WebView.kt b/debuglogs-viewer/app/src/main/java/org/signal/debuglogsviewer/app/webview/WebView.kt index 5fddb55b46..d0f1dcafa5 100644 --- a/debuglogs-viewer/app/src/main/java/org/signal/debuglogsviewer/app/webview/WebView.kt +++ b/debuglogs-viewer/app/src/main/java/org/signal/debuglogsviewer/app/webview/WebView.kt @@ -16,16 +16,16 @@ import android.widget.Button import org.signal.debuglogsviewer.app.R fun setupWebView( - context: Context, - webview: WebView, - findButton: Button, + context: Context, + webview: WebView, + findButton: Button, filterLevelButton: Button, - editButton: Button, - cancelEditButton: Button, - copyButton: Button + editButton: Button, + cancelEditButton: Button, + copyButton: Button ) { val originalContent = org.json.JSONObject.quote(getLogText(context)) - var readOnly = true + var readOnly = true webview.settings.apply { javaScriptEnabled = true @@ -73,7 +73,7 @@ fun setupWebView( } copyButton.setOnClickListener { // In Signal app, use Util.writeTextToClipboard(context, value) instead - webview.evaluateJavascript ("editor.getValue();") { value -> + webview.evaluateJavascript("editor.getValue();") { value -> val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText(context.getString(R.string.app_name), value) clipboard.setPrimaryClip(clip) @@ -87,4 +87,4 @@ fun getLogText(context: Context): String { } catch (e: Exception) { "Error loading file: ${e.message}" } -} \ No newline at end of file +} diff --git a/debuglogs-viewer/lib/src/main/java/org/signal/debuglogsviewer/DebugLogsViewer.kt b/debuglogs-viewer/lib/src/main/java/org/signal/debuglogsviewer/DebugLogsViewer.kt index 0b65b5d9bf..55dcdffe5e 100644 --- a/debuglogs-viewer/lib/src/main/java/org/signal/debuglogsviewer/DebugLogsViewer.kt +++ b/debuglogs-viewer/lib/src/main/java/org/signal/debuglogsviewer/DebugLogsViewer.kt @@ -83,10 +83,10 @@ object DebugLogsViewer { @JvmStatic fun onCopy(webview: WebView, context: Context, appName: String) { - webview.evaluateJavascript ("editor.getValue();") { value -> + webview.evaluateJavascript("editor.getValue();") { value -> val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText(appName, value) clipboard.setPrimaryClip(clip) } } -} \ No newline at end of file +}