Clear auth credentials post restore and when a user disables backups.

This commit is contained in:
Alex Hart
2025-08-01 13:07:54 -03:00
committed by Cody Henthorne
parent 9fd9760264
commit 962375e422
9 changed files with 57 additions and 37 deletions

View File

@@ -229,7 +229,7 @@ object BackupRepository {
BackupMessagesJob.enqueue() BackupMessagesJob.enqueue()
} }
private fun resetInitializedStateAndAuthCredentials() { fun resetInitializedStateAndAuthCredentials() {
SignalStore.backup.backupsInitialized = false SignalStore.backup.backupsInitialized = false
SignalStore.backup.messageCredentials.clearAll() SignalStore.backup.messageCredentials.clearAll()
SignalStore.backup.mediaCredentials.clearAll() SignalStore.backup.mediaCredentials.clearAll()

View File

@@ -56,6 +56,7 @@ class BackupDeleteJob private constructor(
if (result.isFailure) { if (result.isFailure) {
clearLocalBackupStateOnFailure() clearLocalBackupStateOnFailure()
BackupRepository.resetInitializedStateAndAuthCredentials()
} }
return result return result
@@ -110,6 +111,7 @@ class BackupDeleteJob private constructor(
val result = checkResults(results) val result = checkResults(results)
if (result.isSuccess) { if (result.isSuccess) {
Log.i(TAG, "Backup deletion was successful.") Log.i(TAG, "Backup deletion was successful.")
BackupRepository.resetInitializedStateAndAuthCredentials()
SignalStore.backup.deletionState = DeletionState.COMPLETE SignalStore.backup.deletionState = DeletionState.COMPLETE
} }

View File

@@ -237,7 +237,8 @@ class InAppPaymentRecurringContextJob private constructor(
} }
if (tier != MessageBackupTier.PAID) { 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 return false
} }

View File

@@ -28,6 +28,7 @@ import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration
import java.math.BigDecimal import java.math.BigDecimal
import java.util.Currency import java.util.Currency
import java.util.Locale 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. * 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.") warning("Could not resolve price, using empty price.")
} }
info("Creating a pending payment...") InAppPaymentSubscriberRecord.Type.BACKUP.lock.withLock {
val id = SignalDatabase.inAppPayments.insert( if (SignalDatabase.inAppPayments.hasPendingBackupRedemption()) {
type = InAppPaymentType.RECURRING_BACKUP, warning("Backup is already pending redemption. Exiting.")
state = InAppPaymentTable.State.PENDING, return Result.success()
subscriberId = InAppPaymentsRepository.requireSubscriber(InAppPaymentSubscriberRecord.Type.BACKUP).subscriberId, }
endOfPeriod = null,
inAppPaymentData = InAppPaymentData( info("Creating a pending payment...")
badge = null, val id = SignalDatabase.inAppPayments.insert(
amount = price.toFiatValue(), type = InAppPaymentType.RECURRING_BACKUP,
level = SubscriptionsConfiguration.BACKUPS_LEVEL.toLong(), state = InAppPaymentTable.State.PENDING,
recipientId = Recipient.self().id.serialize(), subscriberId = InAppPaymentsRepository.requireSubscriber(InAppPaymentSubscriberRecord.Type.BACKUP).subscriberId,
paymentMethodType = InAppPaymentData.PaymentMethodType.GOOGLE_PLAY_BILLING, endOfPeriod = null,
redemption = InAppPaymentData.RedemptionState( inAppPaymentData = InAppPaymentData(
stage = InAppPaymentData.RedemptionState.Stage.INIT 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.") info("Submitting job chain.")
InAppPaymentPurchaseTokenJob.createJobChain( InAppPaymentPurchaseTokenJob.createJobChain(
inAppPayment = SignalDatabase.inAppPayments.getById(id)!! inAppPayment = SignalDatabase.inAppPayments.getById(id)!!
).enqueue() ).enqueue()
}
return Result.success() return Result.success()
} }

View File

@@ -6,6 +6,7 @@
package org.thoughtcrime.securesms.registration.util; package org.thoughtcrime.securesms.registration.util;
import org.signal.core.util.logging.Log; 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.backup.v2.MessageBackupTier;
import org.thoughtcrime.securesms.dependencies.AppDependencies; import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobs.ArchiveBackupIdReservationJob; import org.thoughtcrime.securesms.jobs.ArchiveBackupIdReservationJob;
@@ -53,14 +54,13 @@ public final class RegistrationUtil {
.then(new DirectoryRefreshJob(false)) .then(new DirectoryRefreshJob(false))
.enqueue(); .enqueue();
if (SignalStore.backup().getBackupTier() == MessageBackupTier.PAID) {
AppDependencies.getJobManager().add(new PostRegistrationBackupRedemptionJob());
}
SignalStore.emoji().clearSearchIndexMetadata(); SignalStore.emoji().clearSearchIndexMetadata();
EmojiSearchIndexDownloadJob.scheduleImmediately(); EmojiSearchIndexDownloadJob.scheduleImmediately();
BackupRepository.INSTANCE.resetInitializedStateAndAuthCredentials();
AppDependencies.getJobManager().add(new ArchiveBackupIdReservationJob()); AppDependencies.getJobManager().add(new ArchiveBackupIdReservationJob());
AppDependencies.getJobManager().add(new PostRegistrationBackupRedemptionJob());
} else if (!SignalStore.registration().isRegistrationComplete()) { } else if (!SignalStore.registration().isRegistrationComplete()) {
Log.i(TAG, "Registration is not yet complete.", new Throwable()); Log.i(TAG, "Registration is not yet complete.", new Throwable());

View File

@@ -13,6 +13,7 @@ import assertk.assertions.hasSize
import assertk.assertions.isEmpty import assertk.assertions.isEmpty
import assertk.assertions.isEqualTo import assertk.assertions.isEqualTo
import io.mockk.every import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject import io.mockk.mockkObject
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.unmockkAll import io.mockk.unmockkAll
@@ -57,6 +58,14 @@ class RegistrationUtilTest {
initialize(logRecorder) initialize(logRecorder)
every { SignalStore.backup.backupTier } returns null 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 @After

View File

@@ -16,16 +16,16 @@ import android.widget.Button
import org.signal.debuglogsviewer.app.R import org.signal.debuglogsviewer.app.R
fun setupWebView( fun setupWebView(
context: Context, context: Context,
webview: WebView, webview: WebView,
findButton: Button, findButton: Button,
filterLevelButton: Button, filterLevelButton: Button,
editButton: Button, editButton: Button,
cancelEditButton: Button, cancelEditButton: Button,
copyButton: Button copyButton: Button
) { ) {
val originalContent = org.json.JSONObject.quote(getLogText(context)) val originalContent = org.json.JSONObject.quote(getLogText(context))
var readOnly = true var readOnly = true
webview.settings.apply { webview.settings.apply {
javaScriptEnabled = true javaScriptEnabled = true
@@ -73,7 +73,7 @@ fun setupWebView(
} }
copyButton.setOnClickListener { // In Signal app, use Util.writeTextToClipboard(context, value) instead 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 clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(context.getString(R.string.app_name), value) val clip = ClipData.newPlainText(context.getString(R.string.app_name), value)
clipboard.setPrimaryClip(clip) clipboard.setPrimaryClip(clip)

View File

@@ -83,7 +83,7 @@ object DebugLogsViewer {
@JvmStatic @JvmStatic
fun onCopy(webview: WebView, context: Context, appName: String) { 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 clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(appName, value) val clip = ClipData.newPlainText(appName, value)
clipboard.setPrimaryClip(clip) clipboard.setPrimaryClip(clip)