Use recovery flow for change number when possible.

main
Cody Henthorne 2023-02-24 16:22:43 -05:00 zatwierdzone przez GitHub
rodzic ff76c4cdef
commit 06bec76371
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
8 zmienionych plików z 114 dodań i 42 usunięć

Wyświetl plik

@ -89,8 +89,9 @@ abstract class DSLSettingsFragment(
}
override fun onDestroyView() {
super.onDestroyView()
recyclerView = null
toolbar = null
super.onDestroyView()
}
fun setTitle(@StringRes resId: Int) {

Wyświetl plik

@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.LabeledEditText
import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberUtil.getViewModel
import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberViewModel.ContinueStatus
import org.thoughtcrime.securesms.databinding.FragmentChangeNumberEnterPhoneNumberBinding
import org.thoughtcrime.securesms.registration.fragments.CountryPickerFragment
import org.thoughtcrime.securesms.registration.fragments.CountryPickerFragmentArgs
import org.thoughtcrime.securesms.registration.util.ChangeNumberInputController
@ -25,19 +26,30 @@ private const val NEW_NUMBER_COUNTRY_SELECT = "new_number_country"
class ChangeNumberEnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_change_number_enter_phone_number) {
private lateinit var scrollView: ScrollView
private var binding: FragmentChangeNumberEnterPhoneNumberBinding? = null
private lateinit var oldNumberCountrySpinner: Spinner
private lateinit var oldNumberCountryCode: LabeledEditText
private lateinit var oldNumber: LabeledEditText
private val scrollView: ScrollView
get() = binding!!.changeNumberEnterPhoneNumberScroll
private lateinit var newNumberCountrySpinner: Spinner
private lateinit var newNumberCountryCode: LabeledEditText
private lateinit var newNumber: LabeledEditText
private val oldNumberCountrySpinner: Spinner
get() = binding!!.changeNumberEnterPhoneNumberOldNumberSpinner
private val oldNumberCountryCode: LabeledEditText
get() = binding!!.changeNumberEnterPhoneNumberOldNumberCountryCode
private val oldNumber: LabeledEditText
get() = binding!!.changeNumberEnterPhoneNumberOldNumberNumber
private val newNumberCountrySpinner: Spinner
get() = binding!!.changeNumberEnterPhoneNumberNewNumberSpinner
private val newNumberCountryCode: LabeledEditText
get() = binding!!.changeNumberEnterPhoneNumberNewNumberCountryCode
private val newNumber: LabeledEditText
get() = binding!!.changeNumberEnterPhoneNumberNewNumberNumber
private lateinit var viewModel: ChangeNumberViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding = FragmentChangeNumberEnterPhoneNumberBinding.bind(view)
viewModel = getViewModel(this)
val toolbar: Toolbar = view.findViewById(R.id.toolbar)
@ -48,12 +60,6 @@ class ChangeNumberEnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_c
onContinue()
}
scrollView = view.findViewById(R.id.change_number_enter_phone_number_scroll)
oldNumberCountrySpinner = view.findViewById(R.id.change_number_enter_phone_number_old_number_spinner)
oldNumberCountryCode = view.findViewById(R.id.change_number_enter_phone_number_old_number_country_code)
oldNumber = view.findViewById(R.id.change_number_enter_phone_number_old_number_number)
val oldController = ChangeNumberInputController(
requireContext(),
oldNumberCountryCode,
@ -87,10 +93,6 @@ class ChangeNumberEnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_c
}
)
newNumberCountrySpinner = view.findViewById(R.id.change_number_enter_phone_number_new_number_spinner)
newNumberCountryCode = view.findViewById(R.id.change_number_enter_phone_number_new_number_country_code)
newNumber = view.findViewById(R.id.change_number_enter_phone_number_new_number_number)
val newController = ChangeNumberInputController(
requireContext(),
newNumberCountryCode,
@ -136,6 +138,11 @@ class ChangeNumberEnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_c
viewModel.getLiveNewNumber().observe(viewLifecycleOwner, newController::updateNumber)
}
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
private fun onContinue() {
if (TextUtils.isEmpty(oldNumberCountryCode.text)) {
Toast.makeText(context, getString(R.string.ChangeNumberEnterPhoneNumberFragment__you_must_specify_your_old_number_country_code), Toast.LENGTH_LONG).show()

Wyświetl plik

@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingChangeNumberMetadata
import org.thoughtcrime.securesms.database.model.toProtoByteString
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
import org.thoughtcrime.securesms.keyvalue.CertificateType
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.pin.KbsRepository
@ -86,16 +87,31 @@ class ChangeNumberRepository(
fun ensureDecryptionsDrained(): Completable {
return Completable.create { emitter ->
val drainedListener = object : Runnable {
override fun run() {
emitter.onComplete()
ApplicationDependencies
.getIncomingMessageObserver()
.removeDecryptionDrainedListener(this)
}
}
emitter.setCancellable {
ApplicationDependencies
.getIncomingMessageObserver()
.removeDecryptionDrainedListener(drainedListener)
}
ApplicationDependencies
.getIncomingMessageObserver()
.addDecryptionDrainedListener {
emitter.onComplete()
}
.addDecryptionDrainedListener(drainedListener)
}.subscribeOn(Schedulers.single())
.timeout(15, TimeUnit.SECONDS)
}
fun changeNumber(sessionId: String, newE164: String, pniUpdateMode: Boolean = false): Single<ServiceResponse<VerifyResponse>> {
fun changeNumber(sessionId: String? = null, recoveryPassword: String? = null, newE164: String, pniUpdateMode: Boolean = false): Single<ServiceResponse<VerifyResponse>> {
check((sessionId != null && recoveryPassword == null) || (sessionId == null && recoveryPassword != null))
return Single.fromCallable {
var completed = false
var attempts = 0
@ -104,8 +120,8 @@ class ChangeNumberRepository(
while (!completed && attempts < 5) {
val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest(
sessionId = sessionId,
recoveryPassword = recoveryPassword,
newE164 = newE164,
registrationLock = null,
pniUpdateMode = pniUpdateMode
)
@ -156,8 +172,7 @@ class ChangeNumberRepository(
val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest(
sessionId = sessionId,
newE164 = newE164,
registrationLock = registrationLock,
pniUpdateMode = false
registrationLock = registrationLock
)
SignalStore.misc().setPendingChangeNumberMetadata(metadata)
@ -254,6 +269,8 @@ class ChangeNumberRepository(
ApplicationDependencies.closeConnections()
ApplicationDependencies.getIncomingMessageObserver()
ApplicationDependencies.getJobManager().add(RefreshAttributesJob())
return rotateCertificates()
}
@ -281,10 +298,11 @@ class ChangeNumberRepository(
@Suppress("UsePropertyAccessSyntax")
@WorkerThread
private fun createChangeNumberRequest(
sessionId: String,
sessionId: String? = null,
recoveryPassword: String? = null,
newE164: String,
registrationLock: String?,
pniUpdateMode: Boolean
registrationLock: String? = null,
pniUpdateMode: Boolean = false
): ChangeNumberRequestData {
val selfIdentifier: String = SignalStore.account().requireAci().toString()
val aciProtocolStore: SignalProtocolStore = ApplicationDependencies.getProtocolStore().aci()
@ -338,7 +356,7 @@ class ChangeNumberRepository(
val request = ChangePhoneNumberRequest(
sessionId,
null,
recoveryPassword,
newE164,
registrationLock,
pniIdentity.publicKey,

Wyświetl plik

@ -7,11 +7,14 @@ import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import androidx.navigation.fragment.findNavController
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Single
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.LoggingFragment
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberUtil.changeNumberSuccess
import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberUtil.getCaptchaArguments
import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberUtil.getViewModel
import org.thoughtcrime.securesms.registration.RegistrationSessionProcessor
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
import org.thoughtcrime.securesms.util.LifecycleDisposable
import org.thoughtcrime.securesms.util.dualsim.MccMncProducer
@ -54,9 +57,24 @@ class ChangeNumberVerifyFragment : LoggingFragment(R.layout.fragment_change_phon
lifecycleDisposable += viewModel
.ensureDecryptionsDrained()
.onErrorComplete()
.andThen(viewModel.requestVerificationCode(mode, mccMncProducer.mcc, mccMncProducer.mnc))
.andThen(viewModel.changeNumberWithRecoveryPassword())
.flatMap { changed ->
if (changed) {
Single.just(RequestCodeResult.RecoveryPasswordWorked)
} else {
viewModel.requestVerificationCode(mode, mccMncProducer.mcc, mccMncProducer.mnc)
.map { p -> RequestCodeResult.RequestedVerificationCode(p) }
}
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe { processor ->
.subscribe { result ->
if (result is RequestCodeResult.RecoveryPasswordWorked) {
changeNumberSuccess()
return@subscribe
}
val processor = (result as RequestCodeResult.RequestedVerificationCode).processor
if (processor.hasResult()) {
findNavController().safeNavigate(R.id.action_changePhoneNumberVerifyFragment_to_changeNumberEnterCodeFragment)
} else if (processor.captchaRequired()) {
@ -74,4 +92,9 @@ class ChangeNumberVerifyFragment : LoggingFragment(R.layout.fragment_change_phon
}
}
}
private sealed interface RequestCodeResult {
object RecoveryPasswordWorked : RequestCodeResult
class RequestedVerificationCode(val processor: RegistrationSessionProcessor) : RequestCodeResult
}
}

Wyświetl plik

@ -170,7 +170,7 @@ class ChangeNumberViewModel(
.observeOn(Schedulers.io())
.flatMap { processor ->
if (processor.isAlreadyVerified() || processor.hasResult() && processor.isVerified()) {
changeNumberRepository.changeNumber(sessionId, number.e164Number)
changeNumberRepository.changeNumber(sessionId = sessionId, newE164 = number.e164Number)
} else if (processor.error == null) {
Single.just<ServiceResponse<VerifyResponse>>(ServiceResponse.forApplicationError(IncorrectCodeException(), 403, null))
} else {
@ -203,6 +203,24 @@ class ChangeNumberViewModel(
}
}
fun changeNumberWithRecoveryPassword(): Single<Boolean> {
val recoveryPassword = SignalStore.kbsValues().recoveryPassword
return if (SignalStore.kbsValues().hasPin() && recoveryPassword != null) {
changeNumberRepository.changeNumber(recoveryPassword = recoveryPassword, newE164 = number.e164Number)
.map { r -> VerifyResponseWithoutKbs(r) }
.flatMap { p ->
if (p.hasResult()) {
onVerifySuccess(p).map { true }
} else {
Single.just(false)
}
}
} else {
Single.just(false)
}
}
class Factory(owner: SavedStateRegistryOwner) : AbstractSavedStateViewModelFactory(owner, null) {
override fun <T : ViewModel> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {

Wyświetl plik

@ -131,6 +131,10 @@ public class IncomingMessageObserver {
}
}
public synchronized void removeDecryptionDrainedListener(@NonNull Runnable listener) {
decryptionDrainedListeners.remove(listener);
}
public boolean isDecryptionDrained() {
return decryptionDrained;
}

Wyświetl plik

@ -16,20 +16,22 @@ import androidx.core.content.ContextCompat;
import org.thoughtcrime.securesms.R;
import java.lang.ref.WeakReference;
public class LongClickMovementMethod extends LinkMovementMethod {
@SuppressLint("StaticFieldLeak")
private static LongClickMovementMethod sInstance;
private final GestureDetector gestureDetector;
private View widget;
private LongClickCopySpan currentSpan;
private final GestureDetector gestureDetector;
private WeakReference<View> widget;
private LongClickCopySpan currentSpan;
private LongClickMovementMethod(final Context context) {
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public void onLongPress(MotionEvent e) {
if (currentSpan != null && widget != null) {
currentSpan.onLongClick(widget);
if (currentSpan != null && widget != null && widget.get() != null) {
currentSpan.onLongClick(widget.get());
widget = null;
currentSpan = null;
}
@ -37,8 +39,8 @@ public class LongClickMovementMethod extends LinkMovementMethod {
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (currentSpan != null && widget != null) {
currentSpan.onClick(widget);
if (currentSpan != null && widget != null && widget.get() != null) {
currentSpan.onClick(widget.get());
widget = null;
currentSpan = null;
}
@ -80,7 +82,7 @@ public class LongClickMovementMethod extends LinkMovementMethod {
}
this.currentSpan = aSingleSpan;
this.widget = widget;
this.widget = new WeakReference<>(widget);
return gestureDetector.onTouchEvent(event);
} else if (action == MotionEvent.ACTION_UP && Selection.getSelectionEnd(buffer) > 0){
Selection.setSelection(buffer, 0);

Wyświetl plik

@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:viewBindingIgnore="true"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">