mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 00:59:49 +01:00
Require key rotation to disable pins.
This commit is contained in:
committed by
Michelle Tang
parent
4cce6d3c86
commit
e6f11c7443
@@ -20,6 +20,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import org.signal.core.ui.compose.Dialogs
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsKeyRecordMode
|
||||
@@ -42,6 +43,7 @@ class BackupKeyDisplayFragment : ComposeFragment() {
|
||||
}
|
||||
|
||||
private val viewModel: BackupKeyDisplayViewModel by viewModel { BackupKeyDisplayViewModel() }
|
||||
private val args: BackupKeyDisplayFragmentArgs by navArgs()
|
||||
|
||||
@Composable
|
||||
override fun FragmentContent() {
|
||||
@@ -54,6 +56,12 @@ class BackupKeyDisplayFragment : ComposeFragment() {
|
||||
navController.enableOnBackPressed(true)
|
||||
}
|
||||
|
||||
LaunchedEffect(args.startWithKeyRotation, state.rotationState) {
|
||||
if (args.startWithKeyRotation && state.rotationState == BackupKeyRotationState.NOT_STARTED) {
|
||||
viewModel.rotateBackupKey()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(state.rotationState) {
|
||||
if (state.rotationState == BackupKeyRotationState.FINISHED) {
|
||||
setFragmentResult(AEP_ROTATION_KEY, bundleOf(AEP_ROTATION_KEY to true))
|
||||
|
||||
@@ -200,7 +200,7 @@ public abstract class BaseSvrPinFragment<ViewModel extends BaseSvrPinViewModel>
|
||||
}
|
||||
|
||||
private void onPinSkipped() {
|
||||
PinOptOutDialog.show(requireContext(), () -> {
|
||||
PinOptOutDialog.show(requireContext(), false, () -> {
|
||||
RegistrationUtil.maybeMarkRegistrationComplete();
|
||||
closeNavGraphBranch();
|
||||
});
|
||||
|
||||
@@ -125,6 +125,6 @@ public final class SvrSplashFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void onPinSkipped() {
|
||||
PinOptOutDialog.show(requireContext(), () -> requireActivity().finish());
|
||||
PinOptOutDialog.show(requireContext(), false, () -> requireActivity().finish());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,11 @@ public final class PinOptOutDialog {
|
||||
|
||||
private static final String TAG = Log.tag(PinOptOutDialog.class);
|
||||
|
||||
public static void show(@NonNull Context context, @NonNull Runnable onSuccess) {
|
||||
/**
|
||||
* @param rotateAep If true, this will rotate the AEP as part of the process of opting out. Only do this if the user has not enabled backups! If the user
|
||||
* has backups enabled, you should guide them through rotating the AEP first, and then call this with [rotateAep] = false.
|
||||
*/
|
||||
public static void show(@NonNull Context context, boolean rotateAep, @NonNull Runnable onSuccess) {
|
||||
Log.i(TAG, "show()");
|
||||
AlertDialog dialog = new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.PinOptOutDialog_warning)
|
||||
@@ -29,7 +33,7 @@ public final class PinOptOutDialog {
|
||||
AlertDialog progress = SimpleProgressDialog.show(context);
|
||||
|
||||
SimpleTask.run(() -> {
|
||||
SvrRepository.optOutOfPin();
|
||||
SvrRepository.optOutOfPin(rotateAep);
|
||||
return null;
|
||||
}, success -> {
|
||||
Log.i(TAG, "Disable operation finished.");
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.JobTracker
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceKeysUpdateJob
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
|
||||
import org.thoughtcrime.securesms.jobs.ResetSvrGuessCountJob
|
||||
import org.thoughtcrime.securesms.jobs.StorageForcePushJob
|
||||
@@ -26,6 +27,7 @@ import org.thoughtcrime.securesms.megaphone.Megaphones
|
||||
import org.thoughtcrime.securesms.net.SignalNetwork
|
||||
import org.thoughtcrime.securesms.registration.ui.restore.StorageServiceRestore
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.SvrAuthCredentialSet
|
||||
import org.whispersystems.signalservice.api.AccountEntropyPool
|
||||
import org.whispersystems.signalservice.api.NetworkResultUtil
|
||||
import org.whispersystems.signalservice.api.SvrNoDataException
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey
|
||||
@@ -357,12 +359,21 @@ object SvrRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rotateAep If true, this will rotate the AEP as part of the process of opting out. Only do this if the user has not enabled backups! If the user
|
||||
* has backups enabled, you should guide them through rotating the AEP first, and then call this with [rotateAep] = false.
|
||||
*/
|
||||
@JvmStatic
|
||||
@WorkerThread
|
||||
fun optOutOfPin() {
|
||||
fun optOutOfPin(rotateAep: Boolean) {
|
||||
operationLock.withLock {
|
||||
SignalStore.svr.optOut()
|
||||
|
||||
if (rotateAep) {
|
||||
SignalStore.account.rotateAccountEntropyPool(AccountEntropyPool.generate())
|
||||
AppDependencies.jobManager.add(MultiDeviceKeysUpdateJob())
|
||||
}
|
||||
|
||||
AppDependencies.megaphoneRepository.markFinished(Megaphones.Event.PINS_FOR_ALL)
|
||||
|
||||
bestEffortRefreshAttributes()
|
||||
|
||||
@@ -18,11 +18,13 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.fragment.app.setFragmentResultListener
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -33,11 +35,13 @@ import org.signal.core.ui.compose.Rows
|
||||
import org.signal.core.ui.compose.Scaffolds
|
||||
import org.signal.core.ui.compose.Snackbars
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.app.backups.remote.BackupKeyDisplayFragment
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity
|
||||
import org.thoughtcrime.securesms.payments.backup.PaymentsRecoveryStartFragmentArgs.Builder
|
||||
import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity
|
||||
import org.thoughtcrime.securesms.pin.PinOptOutDialog
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
/**
|
||||
* Fragment which allows user to enable or disable their PIN
|
||||
@@ -56,7 +60,7 @@ class AdvancedPinSettingsFragment : ComposeFragment() {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
viewModel.event.collectLatest {
|
||||
when (it) {
|
||||
AdvancedPinSettingsViewModel.Event.SHOW_OPT_OUT_DIALOG -> PinOptOutDialog.show(requireContext()) {
|
||||
AdvancedPinSettingsViewModel.Event.SHOW_BACKUPS_DISABLED_OPT_OUT_DIALOG -> PinOptOutDialog.show(requireContext(), true) {
|
||||
viewModel.onPinOptOutSuccess()
|
||||
displayOptOutSnackbar()
|
||||
}
|
||||
@@ -70,10 +74,20 @@ class AdvancedPinSettingsFragment : ComposeFragment() {
|
||||
|
||||
startActivity(intent)
|
||||
}
|
||||
AdvancedPinSettingsViewModel.Event.SHOW_PIN_DISABLED_SNACKBAR -> {
|
||||
displayOptOutSnackbar()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setFragmentResultListener(BackupKeyDisplayFragment.AEP_ROTATION_KEY) { key, bundle ->
|
||||
val didRotate = bundle.getBoolean(BackupKeyDisplayFragment.AEP_ROTATION_KEY, false)
|
||||
if (didRotate) {
|
||||
viewModel.onAepRotatedForPinDisable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
@@ -109,7 +123,22 @@ class AdvancedPinSettingsFragment : ComposeFragment() {
|
||||
viewModel.dismissDialog()
|
||||
}
|
||||
)
|
||||
else -> Unit
|
||||
AdvancedPinSettingsViewModel.Dialog.ROTATE_AEP -> RotateAepDialog(
|
||||
onConfirm = {
|
||||
viewModel.dismissDialog()
|
||||
val bundle = Bundle()
|
||||
bundle.putBoolean("start_with_key_rotation", true)
|
||||
findNavController().safeNavigate(
|
||||
AdvancedPinSettingsFragmentDirections
|
||||
.actionAdvancedPinSettingsFragmentToBackupKeyDisplayFragment()
|
||||
.setStartWithKeyRotation(true)
|
||||
)
|
||||
},
|
||||
onDismiss = {
|
||||
viewModel.dismissDialog()
|
||||
}
|
||||
)
|
||||
AdvancedPinSettingsViewModel.Dialog.NONE -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +220,21 @@ private fun RecordPaymentsRecoveryPhraseDialog(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RotateAepDialog(
|
||||
onConfirm: () -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
Dialogs.SimpleAlertDialog(
|
||||
title = stringResource(R.string.AdvancedPinSettingsFragment_rotate_aep_dialog_title),
|
||||
body = stringResource(R.string.AdvancedPinSettingsFragment_rotate_aep_dialog_body),
|
||||
confirm = stringResource(R.string.AdvancedPinSettingsFragment_rotate_aep_dialog_positive_button),
|
||||
onConfirm = onConfirm,
|
||||
dismiss = stringResource(android.R.string.cancel),
|
||||
onDismiss = onDismiss
|
||||
)
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun AdvancedPinSettingsFragmentContentEnabledPreview() {
|
||||
@@ -226,3 +270,11 @@ private fun RecordPaymentsRecoveryPhraseDialogPreview() {
|
||||
RecordPaymentsRecoveryPhraseDialog({}, {})
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
private fun RotateAepDialogPreview() {
|
||||
Previews.Preview {
|
||||
RotateAepDialog({}, {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,19 +14,22 @@ import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.pin.SvrRepository
|
||||
|
||||
class AdvancedPinSettingsViewModel : ViewModel() {
|
||||
|
||||
enum class Dialog {
|
||||
NONE,
|
||||
REGISTRATION_LOCK,
|
||||
RECORD_PAYMENTS_RECOVERY_PHRASE
|
||||
RECORD_PAYMENTS_RECOVERY_PHRASE,
|
||||
ROTATE_AEP
|
||||
}
|
||||
|
||||
enum class Event {
|
||||
SHOW_OPT_OUT_DIALOG,
|
||||
SHOW_BACKUPS_DISABLED_OPT_OUT_DIALOG,
|
||||
LAUNCH_PIN_CREATION_FLOW,
|
||||
LAUNCH_RECOVERY_PHRASE_HANDLING
|
||||
LAUNCH_RECOVERY_PHRASE_HANDLING,
|
||||
SHOW_PIN_DISABLED_SNACKBAR
|
||||
}
|
||||
|
||||
private val internalDialog = MutableStateFlow(Dialog.NONE)
|
||||
@@ -52,9 +55,12 @@ class AdvancedPinSettingsViewModel : ViewModel() {
|
||||
!enabled && SignalStore.payments.mobileCoinPaymentsEnabled() && !SignalStore.payments.userConfirmedMnemonic -> {
|
||||
internalDialog.value = Dialog.RECORD_PAYMENTS_RECOVERY_PHRASE
|
||||
}
|
||||
!enabled -> {
|
||||
!enabled && SignalStore.backup.areBackupsEnabled -> {
|
||||
internalDialog.value = Dialog.ROTATE_AEP
|
||||
}
|
||||
!enabled && !SignalStore.backup.areBackupsEnabled -> {
|
||||
dismissDialog()
|
||||
emitEvent(Event.SHOW_OPT_OUT_DIALOG)
|
||||
emitEvent(Event.SHOW_BACKUPS_DISABLED_OPT_OUT_DIALOG)
|
||||
}
|
||||
else -> {
|
||||
dismissDialog()
|
||||
@@ -75,6 +81,14 @@ class AdvancedPinSettingsViewModel : ViewModel() {
|
||||
internalDialog.value = Dialog.NONE
|
||||
}
|
||||
|
||||
fun onAepRotatedForPinDisable() {
|
||||
internalDialog.value = Dialog.NONE
|
||||
viewModelScope.launch {
|
||||
SvrRepository.optOutOfPin(rotateAep = false)
|
||||
emitEvent(Event.SHOW_PIN_DISABLED_SNACKBAR)
|
||||
}
|
||||
}
|
||||
|
||||
private fun emitEvent(event: Event) {
|
||||
viewModelScope.launch {
|
||||
internalEvent.emit(event)
|
||||
|
||||
Reference in New Issue
Block a user