Support accounts without pins in AEP restore flows.

This commit is contained in:
Cody Henthorne
2025-04-25 11:26:29 -04:00
parent b298cb6f89
commit 87cbe305f0
23 changed files with 65 additions and 85 deletions

View File

@@ -195,7 +195,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
private boolean userMustCreateSignalPin() {
return !SignalStore.registration().isRegistrationComplete() &&
!SignalStore.svr().hasOptedInWithAccess() &&
!SignalStore.svr().hasPin() &&
!SignalStore.svr().lastPinCreateFailed() &&
!SignalStore.svr().hasOptedOut();
}

View File

@@ -69,10 +69,10 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag
@Suppress("DEPRECATION")
clickPref(
title = DSLSettingsText.from(if (state.hasOptedInWithAccess) R.string.preferences_app_protection__change_your_pin else R.string.preferences_app_protection__create_a_pin),
isEnabled = state.isDeprecatedOrUnregistered(),
title = DSLSettingsText.from(if (state.hasPin || state.hasRestoredAep) R.string.preferences_app_protection__change_your_pin else R.string.preferences_app_protection__create_a_pin),
isEnabled = state.isNotDeprecatedOrUnregistered(),
onClick = {
if (state.hasOptedInWithAccess) {
if (state.hasPin) {
startActivityForResult(CreateSvrPinActivity.getIntentForPinChangeFromSettings(requireContext()), CreateSvrPinActivity.REQUEST_NEW_PIN)
} else {
startActivityForResult(CreateSvrPinActivity.getIntentForPinCreate(requireContext()), CreateSvrPinActivity.REQUEST_NEW_PIN)
@@ -84,7 +84,7 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag
title = DSLSettingsText.from(R.string.preferences_app_protection__pin_reminders),
summary = DSLSettingsText.from(R.string.AccountSettingsFragment__youll_be_asked_less_frequently),
isChecked = state.hasPin && state.pinRemindersEnabled,
isEnabled = state.hasPin && state.isDeprecatedOrUnregistered(),
isEnabled = state.hasPin && state.isNotDeprecatedOrUnregistered(),
onClick = {
setPinRemindersEnabled(!state.pinRemindersEnabled)
}
@@ -94,7 +94,7 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag
title = DSLSettingsText.from(R.string.preferences_app_protection__registration_lock),
summary = DSLSettingsText.from(R.string.AccountSettingsFragment__require_your_signal_pin),
isChecked = state.registrationLockEnabled,
isEnabled = (state.hasOptedInWithAccess) && state.isDeprecatedOrUnregistered(),
isEnabled = state.hasPin && state.isNotDeprecatedOrUnregistered(),
onClick = {
setRegistrationLockEnabled(!state.registrationLockEnabled)
}
@@ -102,7 +102,7 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag
clickPref(
title = DSLSettingsText.from(R.string.preferences__advanced_pin_settings),
isEnabled = state.isDeprecatedOrUnregistered(),
isEnabled = state.isNotDeprecatedOrUnregistered(),
onClick = {
Navigation.findNavController(requireView()).safeNavigate(R.id.action_accountSettingsFragment_to_advancedPinSettingsActivity)
}
@@ -115,7 +115,7 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag
if (SignalStore.account.isRegistered) {
clickPref(
title = DSLSettingsText.from(R.string.AccountSettingsFragment__change_phone_number),
isEnabled = state.isDeprecatedOrUnregistered(),
isEnabled = state.isNotDeprecatedOrUnregistered(),
onClick = {
Navigation.findNavController(requireView()).safeNavigate(R.id.action_accountSettingsFragment_to_changePhoneNumberFragment)
}
@@ -125,7 +125,7 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag
clickPref(
title = DSLSettingsText.from(R.string.preferences_chats__transfer_account),
summary = DSLSettingsText.from(R.string.preferences_chats__transfer_account_to_a_new_android_device),
isEnabled = state.isDeprecatedOrUnregistered(),
isEnabled = state.isNotDeprecatedOrUnregistered(),
onClick = {
Navigation.findNavController(requireView()).safeNavigate(R.id.action_accountSettingsFragment_to_oldDeviceTransferActivity)
}
@@ -133,13 +133,13 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag
clickPref(
title = DSLSettingsText.from(R.string.AccountSettingsFragment__request_account_data),
isEnabled = state.isDeprecatedOrUnregistered(),
isEnabled = state.isNotDeprecatedOrUnregistered(),
onClick = {
Navigation.findNavController(requireView()).safeNavigate(R.id.action_accountSettingsFragment_to_exportAccountFragment)
}
)
if (!state.isDeprecatedOrUnregistered()) {
if (!state.isNotDeprecatedOrUnregistered()) {
if (state.clientDeprecated) {
clickPref(
title = DSLSettingsText.from(R.string.preferences_account_update_signal),
@@ -174,8 +174,8 @@ class AccountSettingsFragment : DSLSettingsFragment(R.string.AccountSettingsFrag
}
clickPref(
title = DSLSettingsText.from(R.string.preferences__delete_account, ContextCompat.getColor(requireContext(), if (state.isDeprecatedOrUnregistered()) R.color.signal_alert_primary else R.color.signal_alert_primary_50)),
isEnabled = state.isDeprecatedOrUnregistered(),
title = DSLSettingsText.from(R.string.preferences__delete_account, ContextCompat.getColor(requireContext(), if (state.isNotDeprecatedOrUnregistered()) R.color.signal_alert_primary else R.color.signal_alert_primary_50)),
isEnabled = state.isNotDeprecatedOrUnregistered(),
onClick = {
Navigation.findNavController(requireView()).safeNavigate(R.id.action_accountSettingsFragment_to_deleteAccountFragment)
}

View File

@@ -2,13 +2,13 @@ package org.thoughtcrime.securesms.components.settings.app.account
data class AccountSettingsState(
val hasPin: Boolean,
val hasOptedInWithAccess: Boolean,
val hasRestoredAep: Boolean,
val pinRemindersEnabled: Boolean,
val registrationLockEnabled: Boolean,
val userUnregistered: Boolean,
val clientDeprecated: Boolean
) {
fun isDeprecatedOrUnregistered(): Boolean {
fun isNotDeprecatedOrUnregistered(): Boolean {
return !(userUnregistered || clientDeprecated)
}
}

View File

@@ -19,7 +19,7 @@ class AccountSettingsViewModel : ViewModel() {
private fun getCurrentState(): AccountSettingsState {
return AccountSettingsState(
hasPin = SignalStore.svr.hasPin() && !SignalStore.svr.hasOptedOut(),
hasOptedInWithAccess = SignalStore.svr.hasOptedInWithAccess(),
hasRestoredAep = SignalStore.account.restoredAccountEntropyPool,
pinRemindersEnabled = SignalStore.pin.arePinRemindersEnabled() && SignalStore.svr.hasPin(),
registrationLockEnabled = SignalStore.svr.isRegistrationLockEnabled,
userUnregistered = TextSecurePreferences.isUnauthorizedReceived(AppDependencies.application),

View File

@@ -409,16 +409,14 @@ class ChangeNumberViewModel : ViewModel() {
private suspend fun changeNumberWithRecoveryPassword(): Boolean {
Log.v(TAG, "changeNumberWithRecoveryPassword()")
SignalStore.svr.recoveryPassword?.let { recoveryPassword ->
if (SignalStore.svr.hasOptedInWithAccess()) {
val result = repository.changeNumberWithRecoveryPassword(recoveryPassword = recoveryPassword, newE164 = number.e164Number)
val result = repository.changeNumberWithRecoveryPassword(recoveryPassword = recoveryPassword, newE164 = number.e164Number)
if (result is ChangeNumberResult.Success) {
handleSuccessfulChangedRemoteNumber(e164 = result.numberChangeResult.number, pni = ServiceId.PNI.parseOrThrow(result.numberChangeResult.pni), changeNumberOutcome = ChangeNumberOutcome.RecoveryPasswordWorked)
return true
}
Log.d(TAG, "Encountered error while trying to change number with recovery password.", result.getCause())
if (result is ChangeNumberResult.Success) {
handleSuccessfulChangedRemoteNumber(e164 = result.numberChangeResult.number, pni = ServiceId.PNI.parseOrThrow(result.numberChangeResult.pni), changeNumberOutcome = ChangeNumberOutcome.RecoveryPasswordWorked)
return true
}
Log.d(TAG, "Encountered error while trying to change number with recovery password.", result.getCause())
}
return false
}

View File

@@ -93,7 +93,7 @@ public class RefreshAttributesJob extends BaseJob {
String registrationLockV2 = null;
SvrValues svrValues = SignalStore.svr();
int pniRegistrationId = RegistrationRepository.getPniRegistrationId();
String recoveryPassword = svrValues.hasPin() ? svrValues.getMasterKey().deriveRegistrationRecoveryPassword() : null;
String recoveryPassword = svrValues.getMasterKey().deriveRegistrationRecoveryPassword();
if (svrValues.isRegistrationLockEnabled()) {
registrationLockV2 = svrValues.getRegistrationLockToken();
@@ -104,8 +104,8 @@ public class RefreshAttributesJob extends BaseJob {
String deviceName = SignalStore.account().getDeviceName();
byte[] encryptedDeviceName = (deviceName == null) ? null : DeviceNameCipher.encryptDeviceName(deviceName.getBytes(StandardCharsets.UTF_8), SignalStore.account().getAciIdentityKey());
AccountAttributes.Capabilities capabilities = AppCapabilities.getCapabilities(svrValues.hasOptedInWithAccess() && !svrValues.hasOptedOut());
Log.i(TAG, "Calling setAccountAttributes() reglockV2? " + !TextUtils.isEmpty(registrationLockV2) + ", pin? " + svrValues.hasPin() + ", access? " + svrValues.hasOptedInWithAccess() +
AccountAttributes.Capabilities capabilities = AppCapabilities.getCapabilities(svrValues.hasPin() && !svrValues.hasOptedOut());
Log.i(TAG, "Calling setAccountAttributes() reglockV2? " + !TextUtils.isEmpty(registrationLockV2) + ", pin? " + svrValues.hasPin() + ", restoredAEP? " + SignalStore.account().restoredAccountEntropyPool() +
"\n Recovery password? " + !TextUtils.isEmpty(recoveryPassword) +
"\n Phone number discoverable : " + phoneNumberDiscoverable +
"\n Device Name : " + (encryptedDeviceName != null) +

View File

@@ -107,8 +107,8 @@ public class RefreshOwnProfileJob extends BaseJob {
return;
}
if (SignalStore.svr().hasOptedInWithAccess() && !SignalStore.svr().hasOptedOut() && SignalStore.storageService().getLastSyncTime() == 0) {
Log.i(TAG, "Registered with PIN but haven't completed storage sync yet.");
if ((SignalStore.svr().hasPin() || SignalStore.account().restoredAccountEntropyPool()) && !SignalStore.svr().hasOptedOut() && SignalStore.storageService().getLastSyncTime() == 0) {
Log.i(TAG, "Registered with PIN or AEP but haven't completed storage sync yet.");
return;
}

View File

@@ -24,7 +24,7 @@ class RefreshSvrCredentialsJob private constructor(parameters: Parameters) : Bas
@JvmStatic
fun enqueueIfNecessary() {
if (SignalStore.svr.hasOptedInWithAccess() && SignalStore.account.isRegistered) {
if (SignalStore.svr.hasPin() && SignalStore.account.isRegistered) {
val lastTimestamp = SignalStore.svr.lastRefreshAuthTimestamp
if (lastTimestamp + FREQUENCY.inWholeMilliseconds < System.currentTimeMillis() || lastTimestamp > System.currentTimeMillis()) {
AppDependencies.jobManager.add(RefreshSvrCredentialsJob())

View File

@@ -165,8 +165,8 @@ class StorageSyncJob private constructor(parameters: Parameters, private var loc
@Throws(IOException::class, RetryLaterException::class, UntrustedIdentityException::class)
override fun onRun() {
if (!SignalStore.svr.hasOptedInWithAccess() && !SignalStore.svr.hasOptedOut()) {
Log.i(TAG, "Doesn't have a PIN. Skipping.")
if (!(SignalStore.svr.hasPin() || SignalStore.account.restoredAccountEntropyPool) && !SignalStore.svr.hasOptedOut()) {
Log.i(TAG, "Doesn't have access to storage service. Skipping.")
return
}

View File

@@ -161,6 +161,7 @@ class AccountValues internal constructor(store: KeyValueStore, context: Context)
}
}
@get:JvmName("restoredAccountEntropyPool")
@get:Synchronized
val restoredAccountEntropyPool by booleanValue(KEY_RESTORED_ACCOUNT_ENTROPY_KEY, false)

View File

@@ -128,13 +128,7 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
@get:Synchronized
val recoveryPassword: String?
get() {
return if (hasOptedInWithAccess()) {
masterKeyForInitialDataRestore?.deriveRegistrationRecoveryPassword() ?: masterKey.deriveRegistrationRecoveryPassword()
} else {
null
}
}
get() = masterKeyForInitialDataRestore?.deriveRegistrationRecoveryPassword() ?: masterKey.deriveRegistrationRecoveryPassword()
@get:Synchronized
val pin: String? by stringValue(PIN, null)
@@ -142,11 +136,6 @@ class SvrValues internal constructor(store: KeyValueStore) : SignalStoreValues(s
@get:Synchronized
val localPinHash: String? by stringValue(LOCK_LOCAL_PIN_HASH, null)
@Synchronized
fun hasOptedInWithAccess(): Boolean {
return hasPin() || SignalStore.account.restoredAccountEntropyPool
}
@Synchronized
fun hasPin(): Boolean {
return localPinHash != null

View File

@@ -101,7 +101,7 @@ public abstract class BaseSvrPinFragment<ViewModel extends BaseSvrPinViewModel>
@Override
public void onPrepareOptionsMenu(@NonNull Menu menu) {
if (SignalStore.svr().isRegistrationLockEnabled() ||
SignalStore.svr().hasOptedInWithAccess() ||
SignalStore.svr().hasPin() ||
SignalStore.svr().hasOptedOut())
{
menu.clear();

View File

@@ -115,7 +115,7 @@ public final class SvrSplashFragment extends Fragment {
private void onCreatePin() {
SvrSplashFragmentDirections.ActionCreateKbsPin action = SvrSplashFragmentDirections.actionCreateKbsPin();
action.setIsPinChange(SignalStore.svr().hasOptedInWithAccess());
action.setIsPinChange(SignalStore.svr().hasPin());
SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), action);
}

View File

@@ -20,7 +20,7 @@ public class LogSectionPin implements LogSection {
.append("Next Reminder Interval: ").append(SignalStore.pin().getCurrentInterval()).append("\n")
.append("Reglock: ").append(SignalStore.svr().isRegistrationLockEnabled()).append("\n")
.append("Signal PIN: ").append(SignalStore.svr().hasPin()).append("\n")
.append("Restored via AEP: ").append(SignalStore.account().getRestoredAccountEntropyPool()).append("\n")
.append("Restored via AEP: ").append(SignalStore.account().restoredAccountEntropyPool()).append("\n")
.append("Opted Out: ").append(SignalStore.svr().hasOptedOut()).append("\n")
.append("Last Creation Failed: ").append(SignalStore.svr().lastPinCreateFailed()).append("\n")
.append("Needs Account Restore: ").append(SignalStore.storageService().getNeedsAccountRestore()).append("\n")

View File

@@ -45,7 +45,7 @@ class PinsForAllSchedule implements MegaphoneSchedule {
return false;
}
if (SignalStore.svr().hasOptedInWithAccess()) {
if (SignalStore.svr().hasPin()) {
return false;
}
@@ -62,6 +62,6 @@ class PinsForAllSchedule implements MegaphoneSchedule {
private static boolean pinCreationFailedDuringRegistration() {
return SignalStore.registration().pinWasRequiredAtRegistration() &&
!SignalStore.svr().hasOptedInWithAccess();
!SignalStore.svr().hasPin();
}
}

View File

@@ -37,7 +37,7 @@ public final class PinOptOutMigration extends MigrationJob {
@Override
void performMigration() {
if (SignalStore.svr().hasOptedOut() && SignalStore.svr().hasOptedInWithAccess()) {
if (SignalStore.svr().hasOptedOut() && SignalStore.svr().hasPin()) {
Log.w(TAG, "Discovered a legacy opt-out user! Resetting the state.");
SignalStore.svr().optOut();

View File

@@ -359,7 +359,7 @@ object SvrRepository {
@Throws(IOException::class)
fun enableRegistrationLockForUserWithPin() {
operationLock.withLock {
check(SignalStore.svr.hasOptedInWithAccess() && !SignalStore.svr.hasOptedOut()) { "Must have a PIN to set a registration lock!" }
check(SignalStore.svr.hasPin() && !SignalStore.svr.hasOptedOut()) { "Must have a PIN to set a registration lock!" }
Log.i(TAG, "[enableRegistrationLockForUserWithPin] Enabling registration lock.", true)
NetworkResultUtil.toBasicLegacy(SignalNetwork.account.enableRegistrationLock(SignalStore.svr.masterKey.deriveRegistrationLock()))
@@ -373,7 +373,7 @@ object SvrRepository {
@Throws(IOException::class)
fun disableRegistrationLockForUserWithPin() {
operationLock.withLock {
check(SignalStore.svr.hasOptedInWithAccess() && !SignalStore.svr.hasOptedOut()) { "Must have a PIN to disable registration lock!" }
check(SignalStore.svr.hasPin() && !SignalStore.svr.hasOptedOut()) { "Must have a PIN to disable registration lock!" }
Log.i(TAG, "[disableRegistrationLockForUserWithPin] Disabling registration lock.", true)
NetworkResultUtil.toBasicLegacy(SignalNetwork.account.disableRegistrationLock())
@@ -403,7 +403,7 @@ object SvrRepository {
false
}
if (newToken && SignalStore.svr.hasOptedInWithAccess()) {
if (newToken && SignalStore.svr.hasPin()) {
BackupManager(AppDependencies.application).dataChanged()
}
} catch (e: Throwable) {
@@ -464,7 +464,7 @@ object SvrRepository {
private val hasNoRegistrationLock: Boolean
get() {
return !SignalStore.svr.isRegistrationLockEnabled &&
!SignalStore.svr.hasOptedInWithAccess() &&
!SignalStore.svr.hasPin() &&
!SignalStore.svr.hasOptedOut()
}
}

View File

@@ -32,7 +32,7 @@ public final class RegistrationUtil {
if (!SignalStore.registration().isRegistrationComplete() &&
SignalStore.account().isRegistered() &&
!Recipient.self().getProfileName().isEmpty() &&
(SignalStore.svr().hasOptedInWithAccess() || SignalStore.svr().hasOptedOut()) &&
(SignalStore.svr().hasPin() || SignalStore.svr().hasOptedOut()) &&
(!RemoteConfig.restoreAfterRegistration() || RestoreDecisionStateUtil.isTerminal(SignalStore.registration().getRestoreDecisionState())))
{
Log.i(TAG, "Marking registration completed.", new Throwable());

View File

@@ -67,11 +67,6 @@ object QuickRegistrationRepository {
return TransferAccountResult.FAILED
}
val pin = SignalStore.svr.pin ?: run {
Log.w(TAG, "No pin")
return TransferAccountResult.FAILED
}
SignalNetwork
.provisioning
.sendReRegisterDeviceProvisioningMessage(
@@ -81,15 +76,15 @@ object QuickRegistrationRepository {
e164 = SignalStore.account.requireE164(),
aci = SignalStore.account.requireAci().toByteString(),
accountEntropyPool = SignalStore.account.accountEntropyPool.value,
pin = pin,
pin = SignalStore.svr.pin,
platform = RegistrationProvisionMessage.Platform.ANDROID,
backupTimestampMs = SignalStore.backup.lastBackupTime.coerceAtLeast(0L),
backupTimestampMs = SignalStore.backup.lastBackupTime.coerceAtLeast(0L).takeIf { it > 0 },
tier = when (SignalStore.backup.backupTier) {
MessageBackupTier.PAID -> RegistrationProvisionMessage.Tier.PAID
MessageBackupTier.FREE -> RegistrationProvisionMessage.Tier.FREE
null -> null
},
backupSizeBytes = SignalStore.backup.totalBackupSize,
backupSizeBytes = SignalStore.backup.totalBackupSize.takeIf { it > 0 },
restoreMethodToken = restoreMethodToken
)
)

View File

@@ -894,12 +894,12 @@ class RegistrationViewModel : ViewModel() {
Log.w(TAG, "Unable to start auth websocket", e)
}
if (!remoteResult.storageCapable && SignalStore.registration.restoreDecisionState.isDecisionPending) {
Log.v(TAG, "Not storage capable and still pending restore decision, likely an account with no data to restore, skipping post register restore")
if (!remoteResult.storageCapable && !SignalStore.account.restoredAccountEntropyPool && SignalStore.registration.restoreDecisionState.isDecisionPending) {
Log.v(TAG, "Not storage capable or restored with AEP, and still pending restore decision, likely an account with no data to restore, skipping post register restore")
SignalStore.registration.restoreDecisionState = RestoreDecisionState.NewAccount
}
if (reglockEnabled || SignalStore.svr.hasOptedInWithAccess()) {
if (reglockEnabled || SignalStore.account.restoredAccountEntropyPool) {
SignalStore.onboarding.clearAll()
if (SignalStore.registration.restoreDecisionState.isTerminal) {

View File

@@ -158,17 +158,15 @@ class RestoreViaQrViewModel : ViewModel() {
Log.i(TAG, "Saving restore method token: ***${result.message.restoreMethodToken.takeLast(4)}")
SignalStore.registration.restoreMethodToken = result.message.restoreMethodToken
SignalStore.registration.isOtherDeviceAndroid = result.message.platform == RegistrationProvisionMessage.Platform.ANDROID
if (result.message.backupTimestampMs > 0) {
SignalStore.backup.backupTier = result.message.tier.let {
when (it) {
RegistrationProvisionMessage.Tier.FREE -> MessageBackupTier.FREE
RegistrationProvisionMessage.Tier.PAID -> MessageBackupTier.PAID
null -> null
}
}
SignalStore.backup.lastBackupTime = result.message.backupTimestampMs
SignalStore.backup.usedBackupMediaSpace = result.message.backupSizeBytes
SignalStore.backup.lastBackupTime = result.message.backupTimestampMs ?: 0
SignalStore.backup.usedBackupMediaSpace = result.message.backupSizeBytes ?: 0
SignalStore.backup.backupTier = when (result.message.tier) {
RegistrationProvisionMessage.Tier.FREE -> MessageBackupTier.FREE
RegistrationProvisionMessage.Tier.PAID -> MessageBackupTier.PAID
null -> null
}
store.update { it.copy(isRegistering = true, provisioningMessage = result.message, qrState = QrState.Scanned) }
shutdown()
} else {