diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java index bb71a9a6fc..0641458004 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java @@ -31,7 +31,6 @@ import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest; import org.thoughtcrime.securesms.registration.service.RegistrationCodeRequest; import org.thoughtcrime.securesms.registration.service.RegistrationService; import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel; -import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener; import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; @@ -122,30 +121,38 @@ public final class EnterCodeFragment extends BaseRegistrationFragment { } @Override - public void onIncorrectRegistrationLockPin(long timeRemaining, String storageCredentials) { - model.setStorageCredentials(storageCredentials); + public void onV1RegistrationLockPinRequiredOrIncorrect(long timeRemaining) { + model.setTimeRemaining(timeRemaining); keyboard.displayLocked().addListener(new AssertedSuccessListener() { @Override public void onSuccess(Boolean r) { - if (FeatureFlags.pinsForAll()) { - Navigation.findNavController(requireView()) - .navigate(EnterCodeFragmentDirections.actionRequireKbsLockPin(timeRemaining)); - } else { - Navigation.findNavController(requireView()) - .navigate(EnterCodeFragmentDirections.actionRequireRegistrationLockPin(timeRemaining)); - } + Navigation.findNavController(requireView()) + .navigate(EnterCodeFragmentDirections.actionRequireKbsLockPin(timeRemaining)); } }); } @Override - public void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse triesRemaining) { - // Unexpected, because at this point, no pin has been provided by the user. - throw new AssertionError(); + public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse tokenResponse, @NonNull String kbsStorageCredentials) { + model.setTimeRemaining(timeRemaining); + model.setStorageCredentials(kbsStorageCredentials); + model.setKeyBackupCurrentToken(tokenResponse); + keyboard.displayLocked().addListener(new AssertedSuccessListener() { + @Override + public void onSuccess(Boolean r) { + Navigation.findNavController(requireView()) + .navigate(EnterCodeFragmentDirections.actionRequireKbsLockPin(timeRemaining)); + } + }); } @Override - public void onTooManyAttempts() { + public void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse tokenResponse) { + throw new AssertionError("Unexpected, user has made no pin guesses"); + } + + @Override + public void onRateLimited() { keyboard.displayFailure().addListener(new AssertedSuccessListener() { @Override public void onSuccess(Boolean r) { @@ -163,6 +170,14 @@ public final class EnterCodeFragment extends BaseRegistrationFragment { }); } + @Override + public void onKbsAccountLocked(long timeRemaining) { + model.setTimeRemaining(timeRemaining); + RegistrationLockFragmentDirections.ActionAccountLocked action = RegistrationLockFragmentDirections.actionAccountLocked(timeRemaining); + + Navigation.findNavController(requireView()).navigate(action); + } + @Override public void onError() { Toast.makeText(requireContext(), R.string.RegistrationActivity_error_connecting_to_service, Toast.LENGTH_LONG).show(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/KbsLockFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/KbsLockFragment.java deleted file mode 100644 index 34465c03fe..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/KbsLockFragment.java +++ /dev/null @@ -1,221 +0,0 @@ -package org.thoughtcrime.securesms.registration.fragments; - -import android.os.Bundle; -import android.text.InputType; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.inputmethod.EditorInfo; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import androidx.appcompat.app.AlertDialog; -import androidx.navigation.Navigation; - -import com.dd.CircularProgressButton; - -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.lock.v2.KbsKeyboardType; -import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest; -import org.thoughtcrime.securesms.registration.service.RegistrationService; -import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel; -import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; - -import java.util.concurrent.TimeUnit; - -public final class KbsLockFragment extends BaseRegistrationFragment { - - private EditText pinEntry; - private CircularProgressButton pinButton; - private TextView errorLabel; - private TextView keyboardToggle; - private long timeRemaining; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.kbs_lock_fragment, container, false); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - setDebugLogSubmitMultiTapView(view.findViewById(R.id.kbs_lock_pin_title)); - - pinEntry = view.findViewById(R.id.kbs_lock_pin_input); - pinButton = view.findViewById(R.id.kbs_lock_pin_confirm); - errorLabel = view.findViewById(R.id.kbs_lock_pin_input_label); - keyboardToggle = view.findViewById(R.id.kbs_lock_keyboard_toggle); - - View pinForgotButton = view.findViewById(R.id.kbs_lock_forgot_pin); - - timeRemaining = KbsLockFragmentArgs.fromBundle(requireArguments()).getTimeRemaining(); - - pinForgotButton.setOnClickListener(v -> handleForgottenPin(timeRemaining)); - - pinEntry.setImeOptions(EditorInfo.IME_ACTION_DONE); - pinEntry.setOnEditorActionListener((v, actionId, event) -> { - if (actionId == EditorInfo.IME_ACTION_DONE) { - hideKeyboard(requireContext(), v); - handlePinEntry(); - return true; - } - return false; - }); - - pinButton.setOnClickListener((v) -> { - hideKeyboard(requireContext(), pinEntry); - handlePinEntry(); - }); - - keyboardToggle.setOnClickListener((v) -> { - KbsKeyboardType keyboardType = getPinEntryKeyboardType(); - - updateKeyboard(keyboardType); - keyboardToggle.setText(resolveKeyboardToggleText(keyboardType)); - }); - - RegistrationViewModel model = getModel(); - model.getTokenResponseCredentialsPair().observe(getViewLifecycleOwner(), pair -> { - if (pair.first().getTries() == 0) { - lockAccount(); - } - }); - - model.onRegistrationLockFragmentCreate(); - } - - private KbsKeyboardType getPinEntryKeyboardType() { - boolean isNumeric = (pinEntry.getImeOptions() & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_NUMBER; - - return isNumeric ? KbsKeyboardType.NUMERIC : KbsKeyboardType.ALPHA_NUMERIC; - } - - private void handlePinEntry() { - final String pin = pinEntry.getText().toString(); - - if (TextUtils.isEmpty(pin) || TextUtils.isEmpty(pin.replace(" ", ""))) { - Toast.makeText(requireContext(), R.string.RegistrationActivity_you_must_enter_your_registration_lock_PIN, Toast.LENGTH_LONG).show(); - return; - } - - RegistrationViewModel model = getModel(); - RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret()); - String storageCredentials = model.getBasicStorageCredentials(); - TokenResponse tokenResponse = model.getKeyBackupCurrentToken(); - - setSpinning(pinButton); - - registrationService.verifyAccount(requireActivity(), - model.getFcmToken(), - model.getTextCodeEntered(), - pin, storageCredentials, tokenResponse, - - new CodeVerificationRequest.VerifyCallback() { - - @Override - public void onSuccessfulRegistration() { - cancelSpinning(pinButton); - SignalStore.kbsValues().setKeyboardType(getPinEntryKeyboardType()); - - Navigation.findNavController(requireView()).navigate(KbsLockFragmentDirections.actionSuccessfulRegistration()); - } - - @Override - public void onIncorrectRegistrationLockPin(long timeRemaining, String storageCredentials) { - model.setStorageCredentials(storageCredentials); - cancelSpinning(pinButton); - - pinEntry.setText(""); - errorLabel.setText(R.string.KbsLockFragment__incorrect_pin); - } - - @Override - public void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse tokenResponse) { - cancelSpinning(pinButton); - - model.setKeyBackupCurrentToken(tokenResponse); - - int triesRemaining = tokenResponse.getTries(); - - if (triesRemaining == 0) { - lockAccount(); - return; - } - - if (triesRemaining == 3) { - long daysRemaining = getLockoutDays(timeRemaining); - - new AlertDialog.Builder(requireContext()) - .setTitle(R.string.KbsLockFragment__incorrect_pin) - .setMessage(getString(R.string.KbsLockFragment__you_have_d_attempts_remaining, triesRemaining, daysRemaining, daysRemaining)) - .setPositiveButton(android.R.string.ok, null) - .show(); - } - - if (triesRemaining > 5) { - errorLabel.setText(R.string.KbsLockFragment__incorrect_pin_try_again); - } else { - errorLabel.setText(getString(R.string.KbsLockFragment__incorrect_pin_d_attempts_remaining, triesRemaining)); - } - } - - @Override - public void onTooManyAttempts() { - cancelSpinning(pinButton); - - new AlertDialog.Builder(requireContext()) - .setTitle(R.string.RegistrationActivity_too_many_attempts) - .setMessage(R.string.RegistrationActivity_you_have_made_too_many_incorrect_registration_lock_pin_attempts_please_try_again_in_a_day) - .setPositiveButton(android.R.string.ok, null) - .show(); - } - - @Override - public void onError() { - cancelSpinning(pinButton); - - Toast.makeText(requireContext(), R.string.RegistrationActivity_error_connecting_to_service, Toast.LENGTH_LONG).show(); - } - }); - } - - private void handleForgottenPin(long timeRemainingMs) { - new AlertDialog.Builder(requireContext()) - .setTitle(R.string.KbsLockFragment__forgot_your_pin) - .setMessage(getString(R.string.KbsLockFragment__for_your_privacy_and_security_there_is_no_way_to_recover, getLockoutDays(timeRemainingMs))) - .setPositiveButton(android.R.string.ok, null) - .show(); - } - - private long getLockoutDays(long timeRemainingMs) { - return TimeUnit.MILLISECONDS.toDays(timeRemainingMs) + 1; - } - - private void lockAccount() { - KbsLockFragmentDirections.ActionAccountLocked action = KbsLockFragmentDirections.actionAccountLocked(timeRemaining); - - Navigation.findNavController(requireView()).navigate(action); - } - - private void updateKeyboard(@NonNull KbsKeyboardType keyboard) { - boolean isAlphaNumeric = keyboard == KbsKeyboardType.ALPHA_NUMERIC; - - pinEntry.setInputType(isAlphaNumeric ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD - : InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD); - } - - private @StringRes int resolveKeyboardToggleText(@NonNull KbsKeyboardType keyboard) { - if (keyboard == KbsKeyboardType.ALPHA_NUMERIC) { - return R.string.KbsLockFragment__enter_alphanumeric_pin; - } else { - return R.string.KbsLockFragment__enter_numeric_pin; - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationCompleteFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationCompleteFragment.java index b6c866b27e..841cfac4bb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationCompleteFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationCompleteFragment.java @@ -14,7 +14,6 @@ import androidx.navigation.ActivityNavigator; import org.thoughtcrime.securesms.MainActivity; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity; import org.thoughtcrime.securesms.lock.v2.PinUtil; import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity; diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java index fffeb5428e..1a951f2976 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java @@ -1,32 +1,33 @@ package org.thoughtcrime.securesms.registration.fragments; import android.os.Bundle; -import android.text.Editable; +import android.text.InputType; import android.text.TextUtils; -import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.widget.EditText; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; import androidx.navigation.Navigation; import com.dd.CircularProgressButton; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.lock.v2.KbsKeyboardType; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest; import org.thoughtcrime.securesms.registration.service.RegistrationService; import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel; -import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; -import java.io.IOException; import java.util.concurrent.TimeUnit; public final class RegistrationLockFragment extends BaseRegistrationFragment { @@ -35,11 +36,12 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment { private EditText pinEntry; private CircularProgressButton pinButton; + private TextView errorLabel; + private TextView keyboardToggle; private long timeRemaining; @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_registration_lock, container, false); } @@ -47,39 +49,19 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - setDebugLogSubmitMultiTapView(view.findViewById(R.id.verify_header)); + setDebugLogSubmitMultiTapView(view.findViewById(R.id.kbs_lock_pin_title)); - pinEntry = view.findViewById(R.id.pin); - pinButton = view.findViewById(R.id.pinButton); + pinEntry = view.findViewById(R.id.kbs_lock_pin_input); + pinButton = view.findViewById(R.id.kbs_lock_pin_confirm); + errorLabel = view.findViewById(R.id.kbs_lock_pin_input_label); + keyboardToggle = view.findViewById(R.id.kbs_lock_keyboard_toggle); - View clarificationLabel = view.findViewById(R.id.clarification_label); - View subHeader = view.findViewById(R.id.verify_subheader); - View pinForgotButton = view.findViewById(R.id.forgot_button); - - String code = getModel().getTextCodeEntered(); + View pinForgotButton = view.findViewById(R.id.kbs_lock_forgot_pin); timeRemaining = RegistrationLockFragmentArgs.fromBundle(requireArguments()).getTimeRemaining(); pinForgotButton.setOnClickListener(v -> handleForgottenPin(timeRemaining)); - pinEntry.addTextChangedListener(new TextWatcher() { - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - boolean matchesTextCode = s != null && s.toString().equals(code); - clarificationLabel.setVisibility(matchesTextCode ? View.VISIBLE : View.INVISIBLE); - subHeader.setVisibility(matchesTextCode ? View.INVISIBLE : View.VISIBLE); - } - }); - pinEntry.setImeOptions(EditorInfo.IME_ACTION_DONE); pinEntry.setOnEditorActionListener((v, actionId, event) -> { if (actionId == EditorInfo.IME_ACTION_DONE) { @@ -95,35 +77,21 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment { handlePinEntry(); }); - RegistrationViewModel model = getModel(); - model.getTokenResponseCredentialsPair() - .observe(this, pair -> { - TokenResponse token = pair.first(); - String credentials = pair.second(); - updateContinueText(token, credentials); - }); + keyboardToggle.setOnClickListener((v) -> { + KbsKeyboardType keyboardType = getPinEntryKeyboardType(); - model.onRegistrationLockFragmentCreate(); + updateKeyboard(keyboardType); + keyboardToggle.setText(resolveKeyboardToggleText(keyboardType)); + }); + + getModel().getTimeRemaining() + .observe(getViewLifecycleOwner(), t -> timeRemaining = t); } - private void updateContinueText(@Nullable TokenResponse tokenResponse, @Nullable String storageCredentials) { - if (tokenResponse == null) { - if (storageCredentials == null) { - pinButton.setIdleText(getString(R.string.RegistrationActivity_continue)); - } else { - // TODO: This is the case where we can determine they are locked out - // no token, but do have storage credentials. Might want to change text. - pinButton.setIdleText(getString(R.string.RegistrationActivity_continue)); - } - } else { - int triesRemaining = tokenResponse.getTries(); - if (triesRemaining == 1) { - pinButton.setIdleText(getString(R.string.RegistrationActivity_continue_last_attempt)); - } else { - pinButton.setIdleText(getString(R.string.RegistrationActivity_continue_d_attempts_left, triesRemaining)); - } - } - pinButton.setText(pinButton.getIdleText()); + private KbsKeyboardType getPinEntryKeyboardType() { + boolean isNumeric = (pinEntry.getImeOptions() & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_NUMBER; + + return isNumeric ? KbsKeyboardType.NUMERIC : KbsKeyboardType.ALPHA_NUMERIC; } private void handlePinEntry() { @@ -134,58 +102,79 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment { return; } - RegistrationViewModel model = getModel(); - RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret()); - String storageCredentials = model.getBasicStorageCredentials(); - TokenResponse tokenResponse = model.getKeyBackupCurrentToken(); + RegistrationViewModel model = getModel(); + RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret()); + TokenResponse tokenResponse = model.getKeyBackupCurrentToken(); + String basicStorageCredentials = model.getBasicStorageCredentials(); setSpinning(pinButton); registrationService.verifyAccount(requireActivity(), model.getFcmToken(), model.getTextCodeEntered(), - pin, storageCredentials, tokenResponse, + pin, + basicStorageCredentials, + tokenResponse, new CodeVerificationRequest.VerifyCallback() { @Override public void onSuccessfulRegistration() { cancelSpinning(pinButton); + SignalStore.kbsValues().setKeyboardType(getPinEntryKeyboardType()); Navigation.findNavController(requireView()).navigate(RegistrationLockFragmentDirections.actionSuccessfulRegistration()); } @Override - public void onIncorrectRegistrationLockPin(long timeRemaining, String storageCredentials) { - model.setStorageCredentials(storageCredentials); - cancelSpinning(pinButton); + public void onV1RegistrationLockPinRequiredOrIncorrect(long timeRemaining) { + getModel().setTimeRemaining(timeRemaining); - pinEntry.setText(""); - Toast.makeText(requireContext(), R.string.RegistrationActivity_incorrect_registration_lock_pin, Toast.LENGTH_LONG).show(); + cancelSpinning(pinButton); + pinEntry.getText().clear(); + + errorLabel.setText(R.string.KbsLockFragment__incorrect_pin); + } + + @Override + public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse kbsTokenResponse, @NonNull String kbsStorageCredentials) { + throw new AssertionError("Not expected after a pin guess"); } @Override public void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse tokenResponse) { cancelSpinning(pinButton); + pinEntry.getText().clear(); model.setKeyBackupCurrentToken(tokenResponse); int triesRemaining = tokenResponse.getTries(); if (triesRemaining == 0) { - handleForgottenPin(timeRemaining); + Log.w(TAG, "Account locked. User out of attempts on KBS."); + lockAccount(timeRemaining); return; } - new AlertDialog.Builder(requireContext()) - .setTitle(R.string.RegistrationActivity_pin_incorrect) - .setMessage(getString(R.string.RegistrationActivity_you_have_d_tries_remaining, triesRemaining)) - .setPositiveButton(android.R.string.ok, null) - .show(); + if (triesRemaining == 3) { + long daysRemaining = getLockoutDays(timeRemaining); + + new AlertDialog.Builder(requireContext()) + .setTitle(R.string.KbsLockFragment__incorrect_pin) + .setMessage(getString(R.string.KbsLockFragment__you_have_d_attempts_remaining, triesRemaining, daysRemaining, daysRemaining)) + .setPositiveButton(android.R.string.ok, null) + .show(); + } + + if (triesRemaining > 5) { + errorLabel.setText(R.string.KbsLockFragment__incorrect_pin_try_again); + } else { + errorLabel.setText(getString(R.string.KbsLockFragment__incorrect_pin_d_attempts_remaining, triesRemaining)); + } } @Override - public void onTooManyAttempts() { + public void onRateLimited() { cancelSpinning(pinButton); new AlertDialog.Builder(requireContext()) @@ -195,6 +184,13 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment { .show(); } + @Override + public void onKbsAccountLocked(long timeRemaining) { + getModel().setTimeRemaining(timeRemaining); + + lockAccount(timeRemaining); + } + @Override public void onError() { cancelSpinning(pinButton); @@ -206,9 +202,34 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment { private void handleForgottenPin(long timeRemainingMs) { new AlertDialog.Builder(requireContext()) - .setTitle(R.string.RegistrationActivity_oh_no) - .setMessage(getString(R.string.RegistrationActivity_registration_of_this_phone_number_will_be_possible_without_your_registration_lock_pin_after_seven_days_have_passed, (TimeUnit.MILLISECONDS.toDays(timeRemainingMs) + 1))) + .setTitle(R.string.KbsLockFragment__forgot_your_pin) + .setMessage(getString(R.string.KbsLockFragment__for_your_privacy_and_security_there_is_no_way_to_recover, getLockoutDays(timeRemainingMs))) .setPositiveButton(android.R.string.ok, null) .show(); } + + private static long getLockoutDays(long timeRemainingMs) { + return TimeUnit.MILLISECONDS.toDays(timeRemainingMs) + 1; + } + + private void lockAccount(long timeRemaining) { + RegistrationLockFragmentDirections.ActionAccountLocked action = RegistrationLockFragmentDirections.actionAccountLocked(timeRemaining); + + Navigation.findNavController(requireView()).navigate(action); + } + + private void updateKeyboard(@NonNull KbsKeyboardType keyboard) { + boolean isAlphaNumeric = keyboard == KbsKeyboardType.ALPHA_NUMERIC; + + pinEntry.setInputType(isAlphaNumeric ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD + : InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD); + } + + private @StringRes static int resolveKeyboardToggleText(@NonNull KbsKeyboardType keyboard) { + if (keyboard == KbsKeyboardType.ALPHA_NUMERIC) { + return R.string.KbsLockFragment__enter_alphanumeric_pin; + } else { + return R.string.KbsLockFragment__enter_numeric_pin; + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java b/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java index 2dbdc72172..e266c51a3a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java @@ -53,16 +53,12 @@ public final class CodeVerificationRequest { private static final String TAG = Log.tag(CodeVerificationRequest.class); - static TokenResponse getToken(@Nullable String basicStorageCredentials) throws IOException { - if (basicStorageCredentials == null) return null; - return ApplicationDependencies.getKeyBackupService().getToken(basicStorageCredentials); - } - private enum Result { SUCCESS, PIN_LOCKED, KBS_WRONG_PIN, RATE_LIMITED, + KBS_ACCOUNT_LOCKED, ERROR } @@ -87,17 +83,39 @@ public final class CodeVerificationRequest { { new AsyncTask() { - private volatile LockedException lockedException; - private volatile KeyBackupSystemWrongPinException keyBackupSystemWrongPinException; + private volatile LockedException lockedException; + private volatile TokenResponse kbsToken; @Override protected Result doInBackground(Void... voids) { + final boolean pinSupplied = pin != null; + final boolean tryKbs = kbsTokenResponse != null; + try { - verifyAccount(context, credentials, code, pin, basicStorageCredentials, kbsTokenResponse, fcmToken); + kbsToken = kbsTokenResponse; + verifyAccount(context, credentials, code, pin, kbsTokenResponse, basicStorageCredentials, fcmToken); return Result.SUCCESS; + } catch (KeyBackupSystemWrongPinException e) { + kbsToken = e.getTokenResponse(); + return Result.KBS_WRONG_PIN; } catch (LockedException e) { + if (pinSupplied && tryKbs) { + throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!"); + } + Log.w(TAG, e); lockedException = e; + if (e.getBasicStorageCredentials() != null) { + try { + kbsToken = getToken(e.getBasicStorageCredentials()); + if (kbsToken == null || kbsToken.getTries() == 0) { + return Result.KBS_ACCOUNT_LOCKED; + } + } catch (IOException ex) { + Log.w(TAG, e); + return Result.ERROR; + } + } return Result.PIN_LOCKED; } catch (RateLimitException e) { Log.w(TAG, e); @@ -105,9 +123,6 @@ public final class CodeVerificationRequest { } catch (IOException e) { Log.w(TAG, e); return Result.ERROR; - } catch (KeyBackupSystemWrongPinException e) { - keyBackupSystemWrongPinException = e; - return Result.KBS_WRONG_PIN; } } @@ -119,22 +134,41 @@ public final class CodeVerificationRequest { callback.onSuccessfulRegistration(); break; case PIN_LOCKED: - callback.onIncorrectRegistrationLockPin(lockedException.getTimeRemaining(), lockedException.getBasicStorageCredentials()); + if (kbsToken != null) { + if (lockedException.getBasicStorageCredentials() == null) { + throw new AssertionError("KBS Token set, but no storage credentials supplied."); + } + Log.w(TAG, "Reg Locked: V2 pin needed for registration"); + callback.onKbsRegistrationLockPinRequired(lockedException.getTimeRemaining(), kbsToken, lockedException.getBasicStorageCredentials()); + } else { + Log.w(TAG, "Reg Locked: V1 pin needed for registration"); + callback.onV1RegistrationLockPinRequiredOrIncorrect(lockedException.getTimeRemaining()); + } break; case RATE_LIMITED: - callback.onTooManyAttempts(); + callback.onRateLimited(); break; case ERROR: callback.onError(); break; case KBS_WRONG_PIN: - callback.onIncorrectKbsRegistrationLockPin(keyBackupSystemWrongPinException.getTokenResponse()); + Log.w(TAG, "KBS Pin was wrong"); + callback.onIncorrectKbsRegistrationLockPin(kbsToken); + break; + case KBS_ACCOUNT_LOCKED: + Log.w(TAG, "KBS Account is locked"); + callback.onKbsAccountLocked(lockedException.getTimeRemaining()); break; } } }.execute(); } + private static TokenResponse getToken(@Nullable String basicStorageCredentials) throws IOException { + if (basicStorageCredentials == null) return null; + return ApplicationDependencies.getKeyBackupService().getToken(basicStorageCredentials); + } + private static void handleSuccessfulRegistration(@NonNull Context context) { JobManager jobManager = ApplicationDependencies.getJobManager(); jobManager.add(new DirectoryRefreshJob(false)); @@ -148,11 +182,12 @@ public final class CodeVerificationRequest { @NonNull Credentials credentials, @NonNull String code, @Nullable String pin, - @Nullable String basicStorageCredentials, @Nullable TokenResponse kbsTokenResponse, + @Nullable String kbsStorageCredentials, @Nullable String fcmToken) throws IOException, KeyBackupSystemWrongPinException { + boolean isV2KbsPin = kbsTokenResponse != null; int registrationId = KeyHelper.generateRegistrationId(false); byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(context); boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context); @@ -161,10 +196,10 @@ public final class CodeVerificationRequest { SessionUtil.archiveAllSessions(context); SignalServiceAccountManager accountManager = AccountManagerFactory.createUnauthenticated(context, credentials.getE164number(), credentials.getPassword()); - RegistrationLockData kbsData = restoreMasterKey(pin, basicStorageCredentials, kbsTokenResponse); + RegistrationLockData kbsData = isV2KbsPin ? restoreMasterKey(pin, kbsStorageCredentials, kbsTokenResponse) : null; String registrationLock = kbsData != null ? kbsData.getMasterKey().deriveRegistrationLock() : null; boolean present = fcmToken != null; - String pinForServer = basicStorageCredentials == null ? pin : null; + String pinForServer = isV2KbsPin ? null : pin; UUID uuid = accountManager.verifyAccountWithCode(code, null, registrationId, !present, pinForServer, registrationLock, @@ -251,14 +286,13 @@ public final class CodeVerificationRequest { private static @Nullable RegistrationLockData restoreMasterKey(@Nullable String pin, @Nullable String basicStorageCredentials, - @Nullable TokenResponse tokenResponse) + @NonNull TokenResponse tokenResponse) throws IOException, KeyBackupSystemWrongPinException { if (pin == null) return null; if (basicStorageCredentials == null) { - Log.i(TAG, "No storage credentials supplied, pin is not on KBS"); - return null; + throw new AssertionError("Cannot restore KBS key, no storage credentials supplied"); } KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService(); @@ -290,13 +324,32 @@ public final class CodeVerificationRequest { void onSuccessfulRegistration(); /** + * The account is locked with a V1 (non-KBS) pin. + * * @param timeRemaining Time until pin expires and number can be reused. */ - void onIncorrectRegistrationLockPin(long timeRemaining, String storageCredentials); + void onV1RegistrationLockPinRequiredOrIncorrect(long timeRemaining); + /** + * The account is locked with a V2 (KBS) pin. Called before any user pin guesses. + */ + void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse kbsTokenResponse, @NonNull String kbsStorageCredentials); + + /** + * The account is locked with a V2 (KBS) pin. Called after a user pin guess. + *

+ * i.e. an attempt has likely been used. + */ void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse kbsTokenResponse); - void onTooManyAttempts(); + /** + * V2 (KBS) pin is set, but there is no data on KBS + * + * @param timeRemaining Time until pin expires and number can be reused. + */ + void onKbsAccountLocked(long timeRemaining); + + void onRateLimited(); void onError(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/service/RegistrationService.java b/app/src/main/java/org/thoughtcrime/securesms/registration/service/RegistrationService.java index 37a0737361..29ce4052b4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/service/RegistrationService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/service/RegistrationService.java @@ -45,8 +45,4 @@ public final class RegistrationService { { CodeVerificationRequest.verifyAccount(activity, credentials, fcmToken, code, pin, basicStorageCredentials, tokenResponse, callback); } - - public @Nullable TokenResponse getToken(@Nullable String basicStorageCredentials) throws IOException { - return CodeVerificationRequest.getToken(basicStorageCredentials); - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java index cd31a5ff35..95557e82db 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java @@ -6,15 +6,10 @@ import androidx.annotation.Nullable; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.SavedStateHandle; -import androidx.lifecycle.Transformations; import androidx.lifecycle.ViewModel; import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.registration.service.RegistrationService; import org.thoughtcrime.securesms.util.Util; -import org.thoughtcrime.securesms.util.concurrent.SimpleTask; -import org.thoughtcrime.securesms.util.livedata.LiveDataPair; -import org.whispersystems.libsignal.util.Pair; import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; import org.whispersystems.signalservice.internal.util.JsonUtil; @@ -34,8 +29,7 @@ public final class RegistrationViewModel extends ViewModel { private final MutableLiveData successfulCodeRequestAttempts; private final MutableLiveData requestLimiter; private final MutableLiveData keyBackupcurrentTokenJson; - private final LiveData keyBackupcurrentToken; - private final LiveData> tokenResponseCredentialsPair; + private final MutableLiveData timeRemaining; public RegistrationViewModel(@NonNull SavedStateHandle savedStateHandle) { secret = loadValue(savedStateHandle, "REGISTRATION_SECRET", Util.getSecret(18)); @@ -49,19 +43,7 @@ public final class RegistrationViewModel extends ViewModel { successfulCodeRequestAttempts = savedStateHandle.getLiveData("SUCCESSFUL_CODE_REQUEST_ATTEMPTS", 0); requestLimiter = savedStateHandle.getLiveData("REQUEST_RATE_LIMITER", new LocalCodeRequestRateLimiter(60_000)); keyBackupcurrentTokenJson = savedStateHandle.getLiveData("KBS_TOKEN"); - - keyBackupcurrentToken = Transformations.map(keyBackupcurrentTokenJson, json -> - { - if (json == null) return null; - try { - return JsonUtil.fromJson(json, TokenResponse.class); - } catch (IOException e) { - Log.w(TAG, e); - return null; - } - }); - - tokenResponseCredentialsPair = new LiveDataPair<>(keyBackupcurrentToken, basicStorageCredentials); + timeRemaining = savedStateHandle.getLiveData("TIME_REMAINING", 0L); } private static T loadValue(@NonNull SavedStateHandle savedStateHandle, @NonNull String key, @NonNull T initialValue) { @@ -179,26 +161,26 @@ public final class RegistrationViewModel extends ViewModel { } public @Nullable TokenResponse getKeyBackupCurrentToken() { - return keyBackupcurrentToken.getValue(); + String json = keyBackupcurrentTokenJson.getValue(); + if (json == null) return null; + try { + return JsonUtil.fromJson(json, TokenResponse.class); + } catch (IOException e) { + Log.w(TAG, e); + return null; + } } public void setKeyBackupCurrentToken(TokenResponse tokenResponse) { - keyBackupcurrentTokenJson.setValue(tokenResponse == null ? null : JsonUtil.toJson(tokenResponse)); + String json = tokenResponse == null ? null : JsonUtil.toJson(tokenResponse); + keyBackupcurrentTokenJson.setValue(json); } - public LiveData> getTokenResponseCredentialsPair() { - return tokenResponseCredentialsPair; + public LiveData getTimeRemaining() { + return timeRemaining; } - public void onRegistrationLockFragmentCreate() { - SimpleTask.run(() -> { - RegistrationService registrationService = RegistrationService.getInstance(getNumber().getE164Number(), getRegistrationSecret()); - try { - return registrationService.getToken(getBasicStorageCredentials()); - } catch (IOException e) { - Log.w(TAG, e); - return null; - } - }, this::setKeyBackupCurrentToken); + public void setTimeRemaining(long timeRemaining) { + this.timeRemaining.setValue(timeRemaining); } } diff --git a/app/src/main/res/layout/fragment_registration_lock.xml b/app/src/main/res/layout/fragment_registration_lock.xml index af2f1befa3..bce57fd272 100644 --- a/app/src/main/res/layout/fragment_registration_lock.xml +++ b/app/src/main/res/layout/fragment_registration_lock.xml @@ -1,113 +1,95 @@ - + android:layout_height="match_parent"> - + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:text="@string/KbsLockFragment__enter_your_pin" + android:textAppearance="@style/TextAppearance.Signal.Title1" + app:layout_constraintBottom_toTopOf="@id/kbs_lock_keyboard_toggle" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.20" /> - + - + - + - +