Fixes for restoring a backup after completing registration.
This commit is contained in:
parent
57adab858c
commit
6424c6bc99
12 changed files with 180 additions and 21 deletions
|
@ -187,7 +187,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||
}
|
||||
|
||||
private boolean userCanTransferOrRestore() {
|
||||
return !SignalStore.registration().isRegistrationComplete() && RemoteConfig.restoreAfterRegistration() && !SignalStore.registration().hasSkippedTransferOrRestore();
|
||||
return !SignalStore.registration().isRegistrationComplete() && RemoteConfig.restoreAfterRegistration() && !SignalStore.registration().hasSkippedTransferOrRestore() && !SignalStore.registration().hasCompletedRestore();
|
||||
}
|
||||
|
||||
private boolean userMustCreateSignalPin() {
|
||||
|
@ -241,6 +241,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||
|
||||
private Intent getTransferOrRestoreIntent() {
|
||||
Intent intent = RestoreActivity.getIntentForTransferOrRestore(this);
|
||||
intent.putExtra(NEXT_INTENT_EXTRA, MainActivity.clearTop(this));
|
||||
return getRoutedIntent(intent, getIntent());
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.greenrobot.eventbus.ThreadMode;
|
|||
import org.signal.devicetransfer.DeviceToDeviceTransferService;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.devicetransfer.DeviceTransferFragment;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.util.navigation.SafeNavigation;
|
||||
|
||||
/**
|
||||
|
@ -61,6 +62,7 @@ public final class NewDeviceTransferFragment extends DeviceTransferFragment {
|
|||
case SUCCESS:
|
||||
transferFinished = true;
|
||||
DeviceToDeviceTransferService.stop(requireContext());
|
||||
SignalStore.registration().markRestoreCompleted();
|
||||
navigateToTransferComplete();
|
||||
break;
|
||||
case FAILURE_VERSION_DOWNGRADE:
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.keyvalue
|
||||
|
||||
import org.signal.core.util.ByteSerializer
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.LocalRegistrationMetadata
|
||||
|
||||
/**
|
||||
* Serialize [LocalRegistrationMetadata]
|
||||
*/
|
||||
object LocalRegistrationMetadataSerializer : ByteSerializer<LocalRegistrationMetadata> {
|
||||
override fun serialize(data: LocalRegistrationMetadata): ByteArray = data.encode()
|
||||
override fun deserialize(data: ByteArray): LocalRegistrationMetadata = LocalRegistrationMetadata.ADAPTER.decode(data)
|
||||
}
|
|
@ -4,6 +4,8 @@ import androidx.annotation.CheckResult;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.LocalRegistrationMetadata;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -15,6 +17,8 @@ public final class RegistrationValues extends SignalStoreValues {
|
|||
private static final String SESSION_E164 = "registration.session_e164";
|
||||
private static final String SESSION_ID = "registration.session_id";
|
||||
private static final String SKIPPED_TRANSFER_OR_RESTORE = "registration.has_skipped_transfer_or_restore";
|
||||
private static final String LOCAL_REGISTRATION_DATA = "registration.local_registration_data";
|
||||
private static final String RESTORE_COMPLETED = "registration.backup_restore_completed";
|
||||
|
||||
RegistrationValues(@NonNull KeyValueStore store) {
|
||||
super(store);
|
||||
|
@ -54,6 +58,20 @@ public final class RegistrationValues extends SignalStoreValues {
|
|||
return getStore().getBoolean(REGISTRATION_COMPLETE, true);
|
||||
}
|
||||
|
||||
|
||||
public void setLocalRegistrationMetadata(LocalRegistrationMetadata data) {
|
||||
putObject(LOCAL_REGISTRATION_DATA, data, LocalRegistrationMetadataSerializer.INSTANCE);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LocalRegistrationMetadata getLocalRegistrationMetadata() {
|
||||
return getObject(LOCAL_REGISTRATION_DATA, null, LocalRegistrationMetadataSerializer.INSTANCE);
|
||||
}
|
||||
|
||||
public void clearLocalRegistrationMetadata() {
|
||||
remove(LOCAL_REGISTRATION_DATA);
|
||||
}
|
||||
|
||||
public boolean hasUploadedProfile() {
|
||||
return getBoolean(HAS_UPLOADED_PROFILE, true);
|
||||
}
|
||||
|
@ -95,4 +113,12 @@ public final class RegistrationValues extends SignalStoreValues {
|
|||
public String getSessionE164() {
|
||||
return getString(SESSION_E164, null);
|
||||
}
|
||||
|
||||
public boolean hasCompletedRestore() {
|
||||
return getBoolean(RESTORE_COMPLETED, false);
|
||||
}
|
||||
|
||||
public void markRestoreCompleted() {
|
||||
putBoolean(RESTORE_COMPLETED, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ public final class RegistrationUtil {
|
|||
{
|
||||
Log.i(TAG, "Marking registration completed.", new Throwable());
|
||||
SignalStore.registration().setRegistrationComplete();
|
||||
SignalStore.registration().clearLocalRegistrationMetadata();
|
||||
|
||||
if (SignalStore.phoneNumberPrivacy().getPhoneNumberDiscoverabilityMode() == PhoneNumberDiscoverabilityMode.UNDECIDED) {
|
||||
Log.w(TAG, "Phone number discoverability mode is still UNDECIDED. Setting to DISCOVERABLE.");
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.registration.data
|
||||
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.libsignal.protocol.IdentityKey
|
||||
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
|
||||
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.LocalRegistrationMetadata
|
||||
import org.thoughtcrime.securesms.registration.RegistrationData
|
||||
import org.whispersystems.signalservice.api.account.PreKeyCollection
|
||||
|
||||
/**
|
||||
* Takes the two sources of registration data ([RegistrationData], [RegistrationRepository.AccountRegistrationResult])
|
||||
* and combines them into a proto-backed class [LocalRegistrationMetadata] so they can be serialized & stored.
|
||||
*/
|
||||
object LocalRegistrationMetadataUtil {
|
||||
fun createLocalRegistrationMetadata(registrationData: RegistrationData, remoteResult: RegistrationRepository.AccountRegistrationResult, reglockEnabled: Boolean): LocalRegistrationMetadata {
|
||||
return LocalRegistrationMetadata.Builder().apply {
|
||||
aciIdentityKey = remoteResult.aciPreKeyCollection.identityKey.serialize().toByteString()
|
||||
aciSignedPreKey = remoteResult.aciPreKeyCollection.signedPreKey.serialize().toByteString()
|
||||
aciLastRestoreKyberPreKey = remoteResult.aciPreKeyCollection.signedPreKey.serialize().toByteString()
|
||||
pniIdentityKey = remoteResult.pniPreKeyCollection.identityKey.serialize().toByteString()
|
||||
pniSignedPreKey = remoteResult.pniPreKeyCollection.signedPreKey.serialize().toByteString()
|
||||
pniLastRestoreKyberPreKey = remoteResult.pniPreKeyCollection.signedPreKey.serialize().toByteString()
|
||||
aci = remoteResult.uuid
|
||||
pni = remoteResult.pni
|
||||
hasPin = remoteResult.storageCapable
|
||||
remoteResult.pin?.let {
|
||||
pin = it
|
||||
}
|
||||
remoteResult.masterKey?.serialize()?.toByteString()?.let {
|
||||
masterKey = it
|
||||
}
|
||||
e164 = registrationData.e164
|
||||
fcmEnabled = registrationData.isFcm
|
||||
profileKey = registrationData.profileKey.serialize().toByteString()
|
||||
servicePassword = registrationData.password
|
||||
this.reglockEnabled = reglockEnabled
|
||||
}.build()
|
||||
}
|
||||
|
||||
fun LocalRegistrationMetadata.getAciPreKeyCollection(): PreKeyCollection {
|
||||
return PreKeyCollection(
|
||||
IdentityKey(aciIdentityKey.toByteArray()),
|
||||
SignedPreKeyRecord(aciSignedPreKey.toByteArray()),
|
||||
KyberPreKeyRecord(aciLastRestoreKyberPreKey.toByteArray())
|
||||
)
|
||||
}
|
||||
|
||||
fun LocalRegistrationMetadata.getPniPreKeyCollection(): PreKeyCollection {
|
||||
return PreKeyCollection(
|
||||
IdentityKey(pniIdentityKey.toByteArray()),
|
||||
SignedPreKeyRecord(pniSignedPreKey.toByteArray()),
|
||||
KyberPreKeyRecord(pniLastRestoreKyberPreKey.toByteArray())
|
||||
)
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore
|
|||
import org.thoughtcrime.securesms.crypto.storage.SignalServiceAccountDataStoreImpl
|
||||
import org.thoughtcrime.securesms.database.IdentityTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.LocalRegistrationMetadata
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.gcm.FcmUtil
|
||||
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob
|
||||
|
@ -45,6 +46,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
|||
import org.thoughtcrime.securesms.registration.PushChallengeRequest
|
||||
import org.thoughtcrime.securesms.registration.RegistrationData
|
||||
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
|
||||
import org.thoughtcrime.securesms.registration.data.LocalRegistrationMetadataUtil.getAciPreKeyCollection
|
||||
import org.thoughtcrime.securesms.registration.data.LocalRegistrationMetadataUtil.getPniPreKeyCollection
|
||||
import org.thoughtcrime.securesms.registration.data.network.BackupAuthCheckResult
|
||||
import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult
|
||||
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionCheckResult
|
||||
|
@ -159,14 +162,14 @@ object RegistrationRepository {
|
|||
* Takes a server response from a successful registration and persists the relevant data.
|
||||
*/
|
||||
@JvmStatic
|
||||
suspend fun registerAccountLocally(context: Context, registrationData: RegistrationData, response: AccountRegistrationResult, reglockEnabled: Boolean) =
|
||||
suspend fun registerAccountLocally(context: Context, data: LocalRegistrationMetadata) =
|
||||
withContext(Dispatchers.IO) {
|
||||
Log.v(TAG, "registerAccountLocally()")
|
||||
val aciPreKeyCollection: PreKeyCollection = response.aciPreKeyCollection
|
||||
val pniPreKeyCollection: PreKeyCollection = response.pniPreKeyCollection
|
||||
val aci: ACI = ACI.parseOrThrow(response.uuid)
|
||||
val pni: PNI = PNI.parseOrThrow(response.pni)
|
||||
val hasPin: Boolean = response.storageCapable
|
||||
val aciPreKeyCollection = data.getAciPreKeyCollection()
|
||||
val pniPreKeyCollection = data.getPniPreKeyCollection()
|
||||
val aci: ACI = ACI.parseOrThrow(data.aci)
|
||||
val pni: PNI = PNI.parseOrThrow(data.pni)
|
||||
val hasPin: Boolean = data.hasPin
|
||||
|
||||
SignalStore.account.setAci(aci)
|
||||
SignalStore.account.setPni(pni)
|
||||
|
@ -187,30 +190,31 @@ object RegistrationRepository {
|
|||
storeSignedAndLastResortPreKeys(pniProtocolStore, pniMetadataStore, pniPreKeyCollection)
|
||||
|
||||
val recipientTable = SignalDatabase.recipients
|
||||
val selfId = Recipient.trustedPush(aci, pni, registrationData.e164).id
|
||||
val selfId = Recipient.trustedPush(aci, pni, data.e164).id
|
||||
|
||||
recipientTable.setProfileSharing(selfId, true)
|
||||
recipientTable.markRegisteredOrThrow(selfId, aci)
|
||||
recipientTable.linkIdsForSelf(aci, pni, registrationData.e164)
|
||||
recipientTable.setProfileKey(selfId, registrationData.profileKey)
|
||||
recipientTable.linkIdsForSelf(aci, pni, data.e164)
|
||||
recipientTable.setProfileKey(selfId, ProfileKey(data.profileKey.toByteArray()))
|
||||
|
||||
AppDependencies.recipientCache.clearSelf()
|
||||
|
||||
SignalStore.account.setE164(registrationData.e164)
|
||||
SignalStore.account.fcmToken = registrationData.fcmToken
|
||||
SignalStore.account.fcmEnabled = registrationData.isFcm
|
||||
SignalStore.account.setE164(data.e164)
|
||||
SignalStore.account.fcmToken = data.fcmToken
|
||||
SignalStore.account.fcmEnabled = data.fcmEnabled
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
saveOwnIdentityKey(selfId, aci, aciProtocolStore, now)
|
||||
saveOwnIdentityKey(selfId, pni, pniProtocolStore, now)
|
||||
|
||||
SignalStore.account.setServicePassword(registrationData.password)
|
||||
SignalStore.account.setServicePassword(data.servicePassword)
|
||||
SignalStore.account.setRegistered(true)
|
||||
TextSecurePreferences.setPromptedPushRegistration(context, true)
|
||||
TextSecurePreferences.setUnauthorizedReceived(context, false)
|
||||
NotificationManagerCompat.from(context).cancel(NotificationIds.UNREGISTERED_NOTIFICATION_ID)
|
||||
|
||||
SvrRepository.onRegistrationComplete(response.masterKey, response.pin, hasPin, reglockEnabled)
|
||||
val masterKey = if (data.masterKey != null) MasterKey(data.masterKey.toByteArray()) else null
|
||||
SvrRepository.onRegistrationComplete(masterKey, data.pin, hasPin, data.reglockEnabled)
|
||||
|
||||
AppDependencies.resetNetwork()
|
||||
AppDependencies.incomingMessageObserver
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.pin.SvrRepository
|
|||
import org.thoughtcrime.securesms.pin.SvrWrongPinException
|
||||
import org.thoughtcrime.securesms.registration.RegistrationData
|
||||
import org.thoughtcrime.securesms.registration.RegistrationUtil
|
||||
import org.thoughtcrime.securesms.registration.data.LocalRegistrationMetadataUtil
|
||||
import org.thoughtcrime.securesms.registration.data.RegistrationRepository
|
||||
import org.thoughtcrime.securesms.registration.data.network.BackupAuthCheckResult
|
||||
import org.thoughtcrime.securesms.registration.data.network.Challenge
|
||||
|
@ -815,7 +816,11 @@ class RegistrationViewModel : ViewModel() {
|
|||
|
||||
private suspend fun onSuccessfulRegistration(context: Context, registrationData: RegistrationData, remoteResult: RegistrationRepository.AccountRegistrationResult, reglockEnabled: Boolean) {
|
||||
Log.v(TAG, "onSuccessfulRegistration()")
|
||||
RegistrationRepository.registerAccountLocally(context, registrationData, remoteResult, reglockEnabled)
|
||||
val metadata = LocalRegistrationMetadataUtil.createLocalRegistrationMetadata(registrationData, remoteResult, reglockEnabled)
|
||||
if (RemoteConfig.restoreAfterRegistration) {
|
||||
SignalStore.registration.localRegistrationMetadata = metadata
|
||||
}
|
||||
RegistrationRepository.registerAccountLocally(context, metadata)
|
||||
|
||||
if (reglockEnabled) {
|
||||
SignalStore.onboarding.clearAll()
|
||||
|
|
|
@ -59,6 +59,7 @@ import org.thoughtcrime.securesms.backup.v2.ui.subscription.RemoteRestoreViewMod
|
|||
import org.thoughtcrime.securesms.conversation.v2.registerForLifecycle
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobs.ProfileUploadJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper
|
||||
import org.thoughtcrime.securesms.profiles.edit.CreateProfileActivity
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
@ -101,6 +102,7 @@ class RemoteRestoreActivity : BaseActivity() {
|
|||
)
|
||||
if (state.importState == RemoteRestoreViewModel.ImportState.RESTORED) {
|
||||
SideEffect {
|
||||
SignalStore.registration.markRestoreCompleted()
|
||||
RegistrationUtil.maybeMarkRegistrationComplete()
|
||||
AppDependencies.jobManager.add(ProfileUploadJob())
|
||||
startActivity(MainActivity.clearTop(this))
|
||||
|
|
|
@ -99,6 +99,7 @@ class RestoreLocalBackupFragment : LoggingFragment(R.layout.fragment_restore_loc
|
|||
onBackupCompletedSuccessfully()
|
||||
} else {
|
||||
handleBackupImportError(importResult)
|
||||
restoreLocalBackupViewModel.backupImportErrorShown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,9 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.launch
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.backup.BackupEvent
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.registration.RegistrationUtil
|
||||
import org.thoughtcrime.securesms.registration.data.RegistrationRepository
|
||||
import org.thoughtcrime.securesms.restore.RestoreRepository
|
||||
|
||||
/**
|
||||
|
@ -82,13 +85,23 @@ class RestoreLocalBackupViewModel(fileBackupUri: Uri) : ViewModel() {
|
|||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
val importResult = RestoreRepository.restoreBackupAsynchronously(context, backupFileUri, backupPassphrase)
|
||||
val importResult: RestoreRepository.BackupImportResult = RestoreRepository.restoreBackupAsynchronously(context, backupFileUri, backupPassphrase)
|
||||
|
||||
if (importResult == RestoreRepository.BackupImportResult.SUCCESS) {
|
||||
SignalStore.registration.localRegistrationMetadata?.let {
|
||||
RegistrationRepository.registerAccountLocally(context, it)
|
||||
SignalStore.registration.clearLocalRegistrationMetadata()
|
||||
RegistrationUtil.maybeMarkRegistrationComplete()
|
||||
}
|
||||
|
||||
SignalStore.registration.markRestoreCompleted()
|
||||
}
|
||||
|
||||
store.update {
|
||||
it.copy(
|
||||
backupImportResult = if (importResult == RestoreRepository.BackupImportResult.SUCCESS) null else importResult,
|
||||
restoreInProgress = false,
|
||||
backupRestoreComplete = true,
|
||||
backupRestoreComplete = importResult == RestoreRepository.BackupImportResult.SUCCESS,
|
||||
backupEstimatedTotalCount = -1L,
|
||||
backupProgressCount = -1L,
|
||||
backupVerifyingInProgress = false
|
||||
|
@ -102,9 +115,7 @@ class RestoreLocalBackupViewModel(fileBackupUri: Uri) : ViewModel() {
|
|||
it.copy(
|
||||
backupProgressCount = event.count,
|
||||
backupEstimatedTotalCount = event.estimatedTotalCount,
|
||||
backupVerifyingInProgress = event.type == BackupEvent.Type.PROGRESS_VERIFYING,
|
||||
backupRestoreComplete = event.type == BackupEvent.Type.FINISHED,
|
||||
restoreInProgress = event.type != BackupEvent.Type.FINISHED
|
||||
backupVerifyingInProgress = event.type == BackupEvent.Type.PROGRESS_VERIFYING
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +124,14 @@ class RestoreLocalBackupViewModel(fileBackupUri: Uri) : ViewModel() {
|
|||
store.update { it.copy(backupFileStateError = null) }
|
||||
}
|
||||
|
||||
fun backupImportErrorShown() {
|
||||
store.update {
|
||||
it.copy(
|
||||
backupImportResult = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(RestoreLocalBackupViewModel::class.java)
|
||||
}
|
||||
|
|
|
@ -514,3 +514,23 @@ message PaymentTombstone {
|
|||
CryptoValue amount = 2;
|
||||
CryptoValue fee = 3;
|
||||
}
|
||||
|
||||
message LocalRegistrationMetadata {
|
||||
bytes aciIdentityKey = 1;
|
||||
bytes aciSignedPreKey = 2;
|
||||
bytes aciLastRestoreKyberPreKey = 3;
|
||||
bytes pniIdentityKey = 4;
|
||||
bytes pniSignedPreKey = 5;
|
||||
bytes pniLastRestoreKyberPreKey = 6;
|
||||
string aci = 7;
|
||||
string pni = 8;
|
||||
bool hasPin = 9;
|
||||
optional string pin = 10;
|
||||
optional bytes masterKey = 11;
|
||||
string e164 = 12;
|
||||
bool fcmEnabled = 13;
|
||||
string fcmToken = 14;
|
||||
bytes profileKey = 15;
|
||||
string servicePassword = 16;
|
||||
bool reglockEnabled = 17;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue