mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-27 04:04:43 +01:00
Add skip SMS flow.
This commit is contained in:
committed by
Greyson Parrelli
parent
a47e3900c1
commit
4f458a022f
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.registration;
|
||||
|
||||
import android.app.Application;
|
||||
import android.app.backup.BackupManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -41,6 +42,7 @@ import org.whispersystems.signalservice.api.push.PNI;
|
||||
import org.whispersystems.signalservice.api.push.ServiceIdType;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
import org.whispersystems.signalservice.internal.push.BackupAuthCheckProcessor;
|
||||
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -95,12 +97,13 @@ public final class RegistrationRepository {
|
||||
}
|
||||
|
||||
public Single<ServiceResponse<VerifyResponse>> registerAccount(@NonNull RegistrationData registrationData,
|
||||
@NonNull VerifyResponse response)
|
||||
@NonNull VerifyResponse response,
|
||||
boolean setRegistrationLockEnabled)
|
||||
{
|
||||
return Single.<ServiceResponse<VerifyResponse>>fromCallable(() -> {
|
||||
try {
|
||||
String pin = response.getPin();
|
||||
registerAccountInternal(registrationData, response.getVerifyAccountResponse(), pin, response.getKbsData());
|
||||
registerAccountInternal(registrationData, response.getVerifyAccountResponse(), pin, response.getKbsData(), setRegistrationLockEnabled);
|
||||
|
||||
if (pin != null && !pin.isEmpty()) {
|
||||
PinState.onPinChangedOrCreated(context, pin, SignalStore.pinValues().getKeyboardType());
|
||||
@@ -124,7 +127,8 @@ public final class RegistrationRepository {
|
||||
private void registerAccountInternal(@NonNull RegistrationData registrationData,
|
||||
@NonNull VerifyAccountResponse response,
|
||||
@Nullable String pin,
|
||||
@Nullable KbsPinData kbsData)
|
||||
@Nullable KbsPinData kbsData,
|
||||
boolean setRegistrationLockEnabled)
|
||||
throws IOException
|
||||
{
|
||||
ACI aci = ACI.parseOrThrow(response.getUuid());
|
||||
@@ -172,7 +176,10 @@ public final class RegistrationRepository {
|
||||
TextSecurePreferences.setPromptedPushRegistration(context, true);
|
||||
NotificationManagerCompat.from(context).cancel(NotificationIds.UNREGISTERED_NOTIFICATION_ID);
|
||||
|
||||
PinState.onRegistration(context, kbsData, pin, hasPin);
|
||||
PinState.onRegistration(context, kbsData, pin, hasPin, setRegistrationLockEnabled);
|
||||
|
||||
ApplicationDependencies.closeConnections();
|
||||
ApplicationDependencies.getIncomingMessageObserver();
|
||||
}
|
||||
|
||||
private void generateAndRegisterPreKeys(@NonNull ServiceIdType serviceIdType,
|
||||
@@ -210,8 +217,15 @@ public final class RegistrationRepository {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getRecoveryPassword() {
|
||||
return SignalStore.kbsValues().getRegistrationRecoveryPassword();
|
||||
public Single<BackupAuthCheckProcessor> getKbsAuthCredential(@NonNull RegistrationData registrationData) {
|
||||
SignalServiceAccountManager accountManager = AccountManagerFactory.createUnauthenticated(context, registrationData.getE164(), SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.getPassword());
|
||||
|
||||
return accountManager.checkBackupAuthCredentials(registrationData.getE164(), SignalStore.kbsValues().getKbsAuthTokenList())
|
||||
.map(BackupAuthCheckProcessor::new)
|
||||
.doOnSuccess(processor -> {
|
||||
if (SignalStore.kbsValues().removeAuthTokens(processor.getInvalid())) {
|
||||
new BackupManager(context).dataChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ class VerifyAccountRepository(private val context: Application) {
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun registerAccount(sessionId: String, registrationData: RegistrationData, pin: String? = null, kbsPinDataProducer: KbsPinDataProducer? = null): Single<ServiceResponse<VerifyResponse>> {
|
||||
fun registerAccount(sessionId: String?, registrationData: RegistrationData, pin: String? = null, kbsPinDataProducer: KbsPinDataProducer? = null): Single<ServiceResponse<VerifyResponse>> {
|
||||
val universalUnidentifiedAccess: Boolean = TextSecurePreferences.isUniversalUnidentifiedAccess(context)
|
||||
val unidentifiedAccessKey: ByteArray = UnidentifiedAccess.deriveAccessKeyFrom(registrationData.profileKey)
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public abstract class BaseRegistrationLockFragment extends LoggingFragment {
|
||||
/**
|
||||
* Applies to both V1 and V2 pins, because some V2 pins may have been migrated from V1.
|
||||
*/
|
||||
private static final int MINIMUM_PIN_LENGTH = 4;
|
||||
public static final int MINIMUM_PIN_LENGTH = 4;
|
||||
|
||||
private EditText pinEntry;
|
||||
private View forgotPin;
|
||||
|
||||
@@ -180,7 +180,7 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
|
||||
PlayServicesUtil.PlayServicesStatus fcmStatus = PlayServicesUtil.getPlayServicesStatus(context);
|
||||
|
||||
if (fcmStatus == PlayServicesUtil.PlayServicesStatus.SUCCESS) {
|
||||
confirmNumberPrompt(context, e164number, () -> handleRequestVerification(context, true));
|
||||
confirmNumberPrompt(context, e164number, () -> onE164EnteredSuccessfully(context, true));
|
||||
} else if (fcmStatus == PlayServicesUtil.PlayServicesStatus.MISSING) {
|
||||
confirmNumberPrompt(context, e164number, () -> handlePromptForNoPlayServices(context));
|
||||
} else if (fcmStatus == PlayServicesUtil.PlayServicesStatus.NEEDS_UPDATE) {
|
||||
@@ -192,10 +192,26 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRequestVerification(@NonNull Context context, boolean fcmSupported) {
|
||||
private void onE164EnteredSuccessfully(@NonNull Context context, boolean fcmSupported) {
|
||||
register.setSpinning();
|
||||
disableAllEntries();
|
||||
|
||||
Disposable disposable = viewModel.canEnterSkipSmsFlow()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.onErrorReturnItem(false)
|
||||
.subscribe(canEnter -> {
|
||||
if (canEnter) {
|
||||
Log.i(TAG, "Enter skip flow");
|
||||
SafeNavigation.safeNavigate(NavHostFragment.findNavController(this), EnterPhoneNumberFragmentDirections.actionReRegisterWithPinFragment());
|
||||
} else {
|
||||
Log.i(TAG, "Unable to collect necessary data to enter skip flow, returning to normal");
|
||||
handleRequestVerification(context, fcmSupported);
|
||||
}
|
||||
});
|
||||
disposables.add(disposable);
|
||||
}
|
||||
|
||||
private void handleRequestVerification(@NonNull Context context, boolean fcmSupported) {
|
||||
if (fcmSupported) {
|
||||
SmsRetrieverClient client = SmsRetriever.getClient(context);
|
||||
Task<Void> task = client.startSmsRetriever();
|
||||
@@ -378,7 +394,7 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.RegistrationActivity_missing_google_play_services)
|
||||
.setMessage(R.string.RegistrationActivity_this_device_is_missing_google_play_services)
|
||||
.setPositiveButton(R.string.RegistrationActivity_i_understand, (dialog1, which) -> handleRequestVerification(context, false))
|
||||
.setPositiveButton(R.string.RegistrationActivity_i_understand, (dialog1, which) -> onE164EnteredSuccessfully(context, false))
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
package org.thoughtcrime.securesms.registration.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.LoggingFragment
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.databinding.FragmentRegistrationLockBinding
|
||||
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
/**
|
||||
* Using a recovery password or restored KBS token attempt to register in the skip flow.
|
||||
*/
|
||||
class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration_lock) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ReRegisterWithPinFragment::class.java)
|
||||
}
|
||||
|
||||
private var _binding: FragmentRegistrationLockBinding? = null
|
||||
private val binding: FragmentRegistrationLockBinding
|
||||
get() = _binding!!
|
||||
|
||||
private val viewModel: RegistrationViewModel by activityViewModels()
|
||||
private val disposables = LifecycleDisposable()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
_binding = FragmentRegistrationLockBinding.bind(view)
|
||||
|
||||
disposables.bindTo(viewLifecycleOwner.lifecycle)
|
||||
|
||||
RegistrationViewDelegate.setDebugLogSubmitMultiTapView(binding.kbsLockPinTitle)
|
||||
|
||||
binding.kbsLockForgotPin.visibility = View.GONE
|
||||
|
||||
binding.kbsLockPinInput.imeOptions = EditorInfo.IME_ACTION_DONE
|
||||
binding.kbsLockPinInput.setOnEditorActionListener { v, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
ViewUtil.hideKeyboard(requireContext(), v!!)
|
||||
handlePinEntry()
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
enableAndFocusPinEntry()
|
||||
|
||||
binding.kbsLockPinConfirm.setOnClickListener {
|
||||
ViewUtil.hideKeyboard(requireContext(), binding.kbsLockPinInput)
|
||||
handlePinEntry()
|
||||
}
|
||||
|
||||
binding.kbsLockKeyboardToggle.setOnClickListener { v: View? ->
|
||||
val keyboardType: PinKeyboardType = getPinEntryKeyboardType()
|
||||
updateKeyboard(keyboardType.other)
|
||||
binding.kbsLockKeyboardToggle.setText(resolveKeyboardToggleText(keyboardType))
|
||||
}
|
||||
|
||||
val keyboardType: PinKeyboardType = getPinEntryKeyboardType().other
|
||||
binding.kbsLockKeyboardToggle.setText(resolveKeyboardToggleText(keyboardType))
|
||||
}
|
||||
|
||||
private fun handlePinEntry() {
|
||||
binding.kbsLockPinInput.isEnabled = false
|
||||
|
||||
val pin: String? = binding.kbsLockPinInput.text?.toString()
|
||||
|
||||
val trimmedLength = pin?.replace(" ", "")?.length ?: 0
|
||||
if (trimmedLength == 0) {
|
||||
Toast.makeText(requireContext(), R.string.RegistrationActivity_you_must_enter_your_registration_lock_PIN, Toast.LENGTH_LONG).show()
|
||||
enableAndFocusPinEntry()
|
||||
return
|
||||
}
|
||||
|
||||
if (trimmedLength < BaseRegistrationLockFragment.MINIMUM_PIN_LENGTH) {
|
||||
Toast.makeText(requireContext(), getString(R.string.RegistrationActivity_your_pin_has_at_least_d_digits_or_characters, BaseRegistrationLockFragment.MINIMUM_PIN_LENGTH), Toast.LENGTH_LONG).show()
|
||||
enableAndFocusPinEntry()
|
||||
return
|
||||
}
|
||||
|
||||
binding.kbsLockPinConfirm.setSpinning()
|
||||
|
||||
disposables += viewModel.verifyReRegisterWithPin(pin!!)
|
||||
.subscribe { p ->
|
||||
if (p.hasResult()) {
|
||||
Log.i(TAG, "Successfully re-registered via skip flow")
|
||||
findNavController().safeNavigate(R.id.action_reRegisterWithPinFragment_to_registrationCompletePlaceHolderFragment)
|
||||
} else {
|
||||
Log.w(TAG, "Unable to continue skip flow, resuming normal flow", p.error)
|
||||
// todo handle the various error conditions
|
||||
Toast.makeText(requireContext(), "retry or nav TODO ERROR See log", Toast.LENGTH_SHORT).show()
|
||||
binding.kbsLockPinInput.isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun enableAndFocusPinEntry() {
|
||||
binding.kbsLockPinInput.isEnabled = true
|
||||
binding.kbsLockPinInput.isFocusable = true
|
||||
if (binding.kbsLockPinInput.requestFocus()) {
|
||||
ServiceUtil.getInputMethodManager(binding.kbsLockPinInput.context).showSoftInput(binding.kbsLockPinInput, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPinEntryKeyboardType(): PinKeyboardType {
|
||||
val isNumeric = binding.kbsLockPinInput.inputType and InputType.TYPE_MASK_CLASS == InputType.TYPE_CLASS_NUMBER
|
||||
return if (isNumeric) PinKeyboardType.NUMERIC else PinKeyboardType.ALPHA_NUMERIC
|
||||
}
|
||||
|
||||
private fun updateKeyboard(keyboard: PinKeyboardType) {
|
||||
val isAlphaNumeric = keyboard == PinKeyboardType.ALPHA_NUMERIC
|
||||
binding.kbsLockPinInput.inputType = if (isAlphaNumeric) InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD else InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
|
||||
binding.kbsLockPinInput.text.clear()
|
||||
}
|
||||
|
||||
@StringRes
|
||||
private fun resolveKeyboardToggleText(keyboard: PinKeyboardType): Int {
|
||||
return if (keyboard == PinKeyboardType.ALPHA_NUMERIC) {
|
||||
R.string.RegistrationLockFragment__enter_alphanumeric_pin
|
||||
} else {
|
||||
R.string.RegistrationLockFragment__enter_numeric_pin
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ public abstract class BaseRegistrationViewModel extends ViewModel {
|
||||
private static final String STATE_TIME_REMAINING = "TIME_REMAINING";
|
||||
private static final String STATE_CAN_CALL_AT_TIME = "CAN_CALL_AT_TIME";
|
||||
private static final String STATE_CAN_SMS_AT_TIME = "CAN_SMS_AT_TIME";
|
||||
private static final String STATE_RECOVERY_PASSWORD = "RECOVERY_PASSWORD";
|
||||
|
||||
protected final SavedStateHandle savedState;
|
||||
protected final VerifyAccountRepository verifyAccountRepository;
|
||||
@@ -62,9 +63,10 @@ public abstract class BaseRegistrationViewModel extends ViewModel {
|
||||
setInitialDefaultValue(STATE_VERIFICATION_CODE, "");
|
||||
setInitialDefaultValue(STATE_SUCCESSFUL_CODE_REQUEST_ATTEMPTS, 0);
|
||||
setInitialDefaultValue(STATE_REQUEST_RATE_LIMITER, new LocalCodeRequestRateLimiter(60_000));
|
||||
setInitialDefaultValue(STATE_RECOVERY_PASSWORD, SignalStore.kbsValues().getRecoveryPassword());
|
||||
}
|
||||
|
||||
protected <T> void setInitialDefaultValue(@NonNull String key, @NonNull T initialValue) {
|
||||
protected <T> void setInitialDefaultValue(@NonNull String key, @Nullable T initialValue) {
|
||||
if (!savedState.contains(key) || savedState.get(key) == null) {
|
||||
savedState.set(key, initialValue);
|
||||
}
|
||||
@@ -164,6 +166,14 @@ public abstract class BaseRegistrationViewModel extends ViewModel {
|
||||
savedState.set(STATE_KBS_TOKEN, tokenData);
|
||||
}
|
||||
|
||||
public void setRecoveryPassword(@Nullable String recoveryPassword) {
|
||||
savedState.set(STATE_RECOVERY_PASSWORD, recoveryPassword);
|
||||
}
|
||||
|
||||
public @Nullable String getRecoveryPassword() {
|
||||
return savedState.get(STATE_RECOVERY_PASSWORD);
|
||||
}
|
||||
|
||||
public LiveData<Long> getLockedTimeRemaining() {
|
||||
return savedState.getLiveData(STATE_TIME_REMAINING, 0L);
|
||||
}
|
||||
|
||||
@@ -3,14 +3,21 @@ package org.thoughtcrime.securesms.registration.viewmodel;
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.lifecycle.AbstractSavedStateViewModelFactory;
|
||||
import androidx.lifecycle.SavedStateHandle;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.savedstate.SavedStateRegistryOwner;
|
||||
|
||||
import org.signal.core.util.Stopwatch;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob;
|
||||
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.PinHashing;
|
||||
import org.thoughtcrime.securesms.pin.KbsRepository;
|
||||
import org.thoughtcrime.securesms.pin.KeyBackupSystemWrongPinException;
|
||||
import org.thoughtcrime.securesms.pin.TokenData;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationData;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationRepository;
|
||||
@@ -19,13 +26,18 @@ import org.thoughtcrime.securesms.registration.VerifyAccountRepository;
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponse;
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponseProcessor;
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponseWithRegistrationLockProcessor;
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponseWithSuccessfulKbs;
|
||||
import org.thoughtcrime.securesms.registration.VerifyResponseWithoutKbs;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.signalservice.api.KbsPinData;
|
||||
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
@@ -33,6 +45,8 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
|
||||
private static final String TAG = Log.tag(RegistrationViewModel.class);
|
||||
|
||||
private static final String STATE_FCM_TOKEN = "FCM_TOKEN";
|
||||
private static final String STATE_RESTORE_FLOW_SHOWN = "RESTORE_FLOW_SHOWN";
|
||||
private static final String STATE_IS_REREGISTER = "IS_REREGISTER";
|
||||
@@ -113,7 +127,7 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
setCanCallAtTime(processor.getNextCodeViaCallAttempt());
|
||||
})
|
||||
.observeOn(Schedulers.io())
|
||||
.flatMap( processor -> {
|
||||
.flatMap(processor -> {
|
||||
if (processor.isAlreadyVerified() || (processor.hasResult() && processor.isVerified())) {
|
||||
return verifyAccountRepository.registerAccount(sessionId, getRegistrationData(), null, null);
|
||||
} else {
|
||||
@@ -155,7 +169,7 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
setCanCallAtTime(processor.getNextCodeViaCallAttempt());
|
||||
}
|
||||
})
|
||||
.flatMap( processor -> {
|
||||
.flatMap(processor -> {
|
||||
if (processor.isAlreadyVerified() || (processor.hasResult() && processor.isVerified())) {
|
||||
return verifyAccountRepository.registerAccount(sessionId, getRegistrationData(), pin, () -> Objects.requireNonNull(KbsRepository.restoreMasterKey(pin, kbsTokenData.getEnclave(), kbsTokenData.getBasicAuth(), kbsTokenData.getTokenResponse())));
|
||||
} else {
|
||||
@@ -166,13 +180,13 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
|
||||
@Override
|
||||
protected Single<VerifyResponseProcessor> onVerifySuccess(@NonNull VerifyResponseProcessor processor) {
|
||||
return registrationRepository.registerAccount(getRegistrationData(), processor.getResult())
|
||||
return registrationRepository.registerAccount(getRegistrationData(), processor.getResult(), false)
|
||||
.map(VerifyResponseWithoutKbs::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Single<VerifyResponseWithRegistrationLockProcessor> onVerifySuccessWithRegistrationLock(@NonNull VerifyResponseWithRegistrationLockProcessor processor, String pin) {
|
||||
return registrationRepository.registerAccount(getRegistrationData(), processor.getResult())
|
||||
return registrationRepository.registerAccount(getRegistrationData(), processor.getResult(), true)
|
||||
.map(processor::updatedIfRegistrationFailed);
|
||||
}
|
||||
|
||||
@@ -184,7 +198,169 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
|
||||
registrationRepository.getProfileKey(getNumber().getE164Number()),
|
||||
getFcmToken(),
|
||||
registrationRepository.getPniRegistrationId(),
|
||||
registrationRepository.getRecoveryPassword());
|
||||
getRecoveryPassword());
|
||||
}
|
||||
|
||||
public @NonNull Single<VerifyResponseProcessor> verifyReRegisterWithPin(@NonNull String pin) {
|
||||
return Single.fromCallable(() -> verifyReRegisterWithPinInternal(pin))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io())
|
||||
.flatMap(data -> {
|
||||
if (data.canProceed) {
|
||||
return verifyReRegisterWithRecoveryPassword(pin, data.pinData);
|
||||
} else {
|
||||
throw new IllegalStateException("Unable to get token or master key");
|
||||
}
|
||||
})
|
||||
.onErrorReturn(t -> new VerifyResponseWithoutKbs(ServiceResponse.forUnknownError(t)))
|
||||
.doOnSuccess(p -> {
|
||||
if (p.hasResult()) {
|
||||
restoreFromStorageService();
|
||||
}
|
||||
})
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private @NonNull ReRegistrationData verifyReRegisterWithPinInternal(@NonNull String pin)
|
||||
throws KeyBackupSystemWrongPinException, IOException, KeyBackupSystemNoDataException
|
||||
{
|
||||
String localPinHash = SignalStore.kbsValues().getLocalPinHash();
|
||||
|
||||
if (hasRecoveryPassword() && localPinHash != null && PinHashing.verifyLocalPinHash(localPinHash, pin)) {
|
||||
Log.i(TAG, "Local pin matches input, attempting registration");
|
||||
return ReRegistrationData.canProceed(new KbsPinData(SignalStore.kbsValues().getOrCreateMasterKey(), SignalStore.kbsValues().getRegistrationLockTokenResponse()));
|
||||
} else {
|
||||
TokenData data = getKeyBackupCurrentToken();
|
||||
if (data == null) {
|
||||
Log.w(TAG, "No token data, abort skip flow");
|
||||
return ReRegistrationData.cannotProceed();
|
||||
}
|
||||
|
||||
KbsPinData kbsPinData = KbsRepository.restoreMasterKey(pin, data.getEnclave(), data.getBasicAuth(), data.getTokenResponse());
|
||||
if (kbsPinData == null || kbsPinData.getMasterKey() == null) {
|
||||
Log.w(TAG, "No kbs data, abort skip flow");
|
||||
return ReRegistrationData.cannotProceed();
|
||||
}
|
||||
|
||||
setRecoveryPassword(kbsPinData.getMasterKey().deriveRegistrationRecoveryPassword());
|
||||
return ReRegistrationData.canProceed(kbsPinData);
|
||||
}
|
||||
}
|
||||
|
||||
private Single<VerifyResponseProcessor> verifyReRegisterWithRecoveryPassword(@NonNull String pin, @NonNull KbsPinData pinData) {
|
||||
RegistrationData registrationData = getRegistrationData();
|
||||
if (registrationData.getRecoveryPassword() == null) {
|
||||
throw new IllegalStateException("No valid recovery password");
|
||||
}
|
||||
|
||||
return verifyAccountRepository.registerAccount(null, registrationData, null, null)
|
||||
.onErrorReturn(ServiceResponse::forUnknownError)
|
||||
.map(VerifyResponseWithoutKbs::new)
|
||||
.flatMap(processor -> {
|
||||
if (processor.registrationLock()) {
|
||||
return verifyAccountRepository.registerAccount(null, registrationData, pin, () -> pinData)
|
||||
.onErrorReturn(ServiceResponse::forUnknownError)
|
||||
.map(r -> new VerifyResponseWithRegistrationLockProcessor(r, getKeyBackupCurrentToken()));
|
||||
} else {
|
||||
return Single.just(processor);
|
||||
}
|
||||
})
|
||||
.<VerifyResponseProcessor>flatMap(processor -> {
|
||||
if (processor.hasResult()) {
|
||||
VerifyResponse verifyResponse = processor.getResult();
|
||||
boolean setRegistrationLockEnabled = verifyResponse.getKbsData() != null;
|
||||
|
||||
if (!setRegistrationLockEnabled) {
|
||||
verifyResponse = new VerifyResponse(processor.getResult().getVerifyAccountResponse(), pinData, pin);
|
||||
}
|
||||
|
||||
return registrationRepository.registerAccount(registrationData, verifyResponse, setRegistrationLockEnabled)
|
||||
.map(r -> {
|
||||
return setRegistrationLockEnabled ? new VerifyResponseWithRegistrationLockProcessor(r, getKeyBackupCurrentToken())
|
||||
: new VerifyResponseWithoutKbs(r);
|
||||
});
|
||||
} else {
|
||||
return Single.just(processor);
|
||||
}
|
||||
})
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
public @NonNull Single<Boolean> canEnterSkipSmsFlow() {
|
||||
return Single.just(hasRecoveryPassword())
|
||||
.flatMap(hasRecoveryPassword -> {
|
||||
if (hasRecoveryPassword) {
|
||||
Log.d(TAG, "Have valid recovery password but still checking kbs credentials as a backup");
|
||||
return checkForValidKbsAuthCredentials().map(unused -> true);
|
||||
} else {
|
||||
return checkForValidKbsAuthCredentials();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Single<Boolean> checkForValidKbsAuthCredentials() {
|
||||
return registrationRepository.getKbsAuthCredential(getRegistrationData())
|
||||
.flatMap(p -> {
|
||||
if (p.getValid() != null) {
|
||||
return kbsRepository.getToken(p.getValid())
|
||||
.flatMap(r -> {
|
||||
if (r.getResult().isPresent()) {
|
||||
setKeyBackupTokenData(r.getResult().get());
|
||||
return Single.just(true);
|
||||
} else {
|
||||
return Single.just(false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return Single.just(false);
|
||||
}
|
||||
})
|
||||
.onErrorReturnItem(false)
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
private void restoreFromStorageService() {
|
||||
SignalStore.onboarding().clearAll();
|
||||
|
||||
Stopwatch stopwatch = new Stopwatch("ReRegisterRestore");
|
||||
|
||||
ApplicationDependencies.getJobManager().runSynchronously(new StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN);
|
||||
stopwatch.split("AccountRestore");
|
||||
|
||||
ApplicationDependencies.getJobManager().runSynchronously(new StorageSyncJob(), TimeUnit.SECONDS.toMillis(10));
|
||||
stopwatch.split("ContactRestore");
|
||||
|
||||
try {
|
||||
FeatureFlags.refreshSync();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to refresh flags.", e);
|
||||
}
|
||||
stopwatch.split("FeatureFlags");
|
||||
|
||||
stopwatch.stop(TAG);
|
||||
}
|
||||
|
||||
private boolean hasRecoveryPassword() {
|
||||
return getRecoveryPassword() != null && Objects.equals(getRegistrationData().getE164(), SignalStore.account().getE164());
|
||||
}
|
||||
|
||||
private static class ReRegistrationData {
|
||||
public boolean canProceed;
|
||||
public KbsPinData pinData;
|
||||
|
||||
private ReRegistrationData(boolean canProceed, @Nullable KbsPinData pinData) {
|
||||
this.canProceed = canProceed;
|
||||
this.pinData = pinData;
|
||||
}
|
||||
|
||||
public static ReRegistrationData cannotProceed() {
|
||||
return new ReRegistrationData(false, null);
|
||||
}
|
||||
|
||||
public static ReRegistrationData canProceed(@NonNull KbsPinData pinData) {
|
||||
return new ReRegistrationData(true, pinData);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Factory extends AbstractSavedStateViewModelFactory {
|
||||
|
||||
Reference in New Issue
Block a user