diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberConfirmFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberConfirmFragment.kt index 09576e8be0..5b3b6326ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberConfirmFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberConfirmFragment.kt @@ -45,7 +45,7 @@ class ChangeNumberConfirmFragment : LoggingFragment(R.layout.fragment_change_num task.addOnSuccessListener { Log.i(TAG, "Successfully registered SMS listener.") - navigateToVerify() + navigateToVerify(smsListenerEnabled = true) } task.addOnFailureListener { e -> @@ -57,8 +57,8 @@ class ChangeNumberConfirmFragment : LoggingFragment(R.layout.fragment_change_num } } - private fun navigateToVerify() { - findNavController().safeNavigate(R.id.action_changePhoneNumberConfirmFragment_to_changePhoneNumberVerifyFragment) + private fun navigateToVerify(smsListenerEnabled: Boolean = false) { + findNavController().safeNavigate(R.id.action_changePhoneNumberConfirmFragment_to_changePhoneNumberVerifyFragment, ChangeNumberVerifyFragmentArgs.Builder().setSmsListenerEnabled(smsListenerEnabled).build().toBundle()) } companion object { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt index 746fc7cd08..233b381228 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt @@ -37,6 +37,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.push.SignedPreKeyEntity import org.whispersystems.signalservice.internal.ServiceResponse import org.whispersystems.signalservice.internal.push.OutgoingPushMessage +import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage import org.whispersystems.signalservice.internal.push.VerifyAccountResponse import org.whispersystems.signalservice.internal.push.WhoAmIResponse @@ -94,7 +95,7 @@ class ChangeNumberRepository( .timeout(15, TimeUnit.SECONDS) } - fun changeNumber(code: String, newE164: String, pniUpdateMode: Boolean = false): Single> { + fun changeNumber(sessionId: String, newE164: String, pniUpdateMode: Boolean = false): Single> { return Single.fromCallable { var completed = false var attempts = 0 @@ -102,7 +103,7 @@ class ChangeNumberRepository( while (!completed && attempts < 5) { val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest( - code = code, + sessionId = sessionId, newE164 = newE164, registrationLock = null, pniUpdateMode = pniUpdateMode @@ -127,7 +128,7 @@ class ChangeNumberRepository( } fun changeNumber( - code: String, + sessionId: String, newE164: String, pin: String, tokenData: TokenData @@ -153,7 +154,7 @@ class ChangeNumberRepository( while (!completed && attempts < 5) { val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest( - code = code, + sessionId = sessionId, newE164 = newE164, registrationLock = registrationLock, pniUpdateMode = false @@ -280,7 +281,7 @@ class ChangeNumberRepository( @Suppress("UsePropertyAccessSyntax") @WorkerThread private fun createChangeNumberRequest( - code: String, + sessionId: String, newE164: String, registrationLock: String?, pniUpdateMode: Boolean @@ -336,8 +337,9 @@ class ChangeNumberRepository( } val request = ChangePhoneNumberRequest( + sessionId, + null, newE164, - code, registrationLock, pniIdentity.publicKey, deviceMessages, @@ -355,5 +357,11 @@ class ChangeNumberRepository( return ChangeNumberRequestData(request, metadata) } + fun verifyAccount(sessionId: String, code: String): Single> { + return Single.fromCallable { + accountManager.verifyAccount(code, sessionId) + }.subscribeOn(Schedulers.io()) + } + data class ChangeNumberRequestData(val changeNumberRequest: ChangePhoneNumberRequest, val pendingChangeNumberMetadata: PendingChangeNumberMetadata) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberVerifyFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberVerifyFragment.kt index 699815857f..740458788f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberVerifyFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberVerifyFragment.kt @@ -49,11 +49,12 @@ class ChangeNumberVerifyFragment : LoggingFragment(R.layout.fragment_change_phon } private fun requestCode() { + val mode = if (ChangeNumberVerifyFragmentArgs.fromBundle(requireArguments()).smsListenerEnabled) VerifyAccountRepository.Mode.SMS_WITH_LISTENER else VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER val mccMncProducer = MccMncProducer(requireContext()) lifecycleDisposable += viewModel .ensureDecryptionsDrained() .onErrorComplete() - .andThen(viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, mccMncProducer.mcc, mccMncProducer.mnc)) + .andThen(viewModel.requestVerificationCode(mode, mccMncProducer.mcc, mccMncProducer.mnc)) .observeOn(AndroidSchedulers.mainThread()) .subscribe { processor -> if (processor.hasResult()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt index 931e7dc06f..8413bcfec7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt @@ -9,13 +9,16 @@ import androidx.lifecycle.ViewModel import androidx.savedstate.SavedStateRegistryOwner import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.PhoneNumberUtil +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.schedulers.Schedulers import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.pin.KbsRepository import org.thoughtcrime.securesms.pin.TokenData +import org.thoughtcrime.securesms.registration.RegistrationSessionProcessor import org.thoughtcrime.securesms.registration.SmsRetrieverReceiver import org.thoughtcrime.securesms.registration.VerifyAccountRepository import org.thoughtcrime.securesms.registration.VerifyResponse @@ -26,6 +29,7 @@ import org.thoughtcrime.securesms.registration.viewmodel.BaseRegistrationViewMod import org.thoughtcrime.securesms.registration.viewmodel.NumberViewState import org.thoughtcrime.securesms.util.DefaultValueLiveData import org.whispersystems.signalservice.api.push.PNI +import org.whispersystems.signalservice.api.push.exceptions.IncorrectCodeException import org.whispersystems.signalservice.internal.ServiceResponse import java.util.Objects @@ -152,11 +156,32 @@ class ChangeNumberViewModel( } override fun verifyAccountWithoutRegistrationLock(): Single> { - return changeNumberRepository.changeNumber(textCodeEntered, number.e164Number) + val sessionId = sessionId ?: throw IllegalStateException("No valid registration session") + + return changeNumberRepository.verifyAccount(sessionId, textCodeEntered) + .map { RegistrationSessionProcessor.RegistrationSessionProcessorForVerification(it) } + .observeOn(AndroidSchedulers.mainThread()) + .doOnSuccess { + if (it.hasResult()) { + setCanSmsAtTime(it.getNextCodeViaSmsAttempt()) + setCanCallAtTime(it.getNextCodeViaCallAttempt()) + } + } + .observeOn(Schedulers.io()) + .flatMap { processor -> + if (processor.isAlreadyVerified() || processor.hasResult() && processor.isVerified()) { + changeNumberRepository.changeNumber(sessionId, number.e164Number) + } else if (processor.error == null) { + Single.just>(ServiceResponse.forApplicationError(IncorrectCodeException(), 403, null)) + } else { + Single.just>(ServiceResponse.coerceError(processor.response)) + } + } } override fun verifyAccountWithRegistrationLock(pin: String, kbsTokenData: TokenData): Single> { - return changeNumberRepository.changeNumber(textCodeEntered, number.e164Number, pin, kbsTokenData) + val sessionId = sessionId ?: throw IllegalStateException("No valid registration session") + return changeNumberRepository.changeNumber(sessionId, number.e164Number, pin, kbsTokenData) } @WorkerThread diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt index bdfae33392..b986343117 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PnpInitializeDevicesJob.kt @@ -24,7 +24,7 @@ class PnpInitializeDevicesJob private constructor(parameters: Parameters) : Base companion object { const val KEY = "PnpInitializeDevicesJob" private val TAG = Log.tag(PnpInitializeDevicesJob::class.java) - private const val PLACEHOLDER_CODE = "123456" + private const val PLACEHOLDER_SESSION_ID = "123456789" @JvmStatic fun enqueueIfNecessary() { @@ -88,7 +88,7 @@ class PnpInitializeDevicesJob private constructor(parameters: Parameters) : Base try { Log.i(TAG, "Calling change number with our current number to distribute PNI messages") changeNumberRepository - .changeNumber(code = PLACEHOLDER_CODE, newE164 = e164, pniUpdateMode = true) + .changeNumber(sessionId = PLACEHOLDER_SESSION_ID, newE164 = e164, pniUpdateMode = true) .map(::VerifyResponseWithoutKbs) .safeBlockingGet() .resultOrThrow diff --git a/app/src/main/res/navigation/app_settings_change_number.xml b/app/src/main/res/navigation/app_settings_change_number.xml index 8b71578221..cf70228145 100644 --- a/app/src/main/res/navigation/app_settings_change_number.xml +++ b/app/src/main/res/navigation/app_settings_change_number.xml @@ -73,6 +73,10 @@ android:name="org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberVerifyFragment" tools:layout="@layout/fragment_change_phone_number_verify"> + + verifyAccount(String verificationCode, - String sessionId) - { + public ServiceResponse verifyAccount(@Nonnull String verificationCode, @Nonnull String sessionId) { try { RegistrationSessionMetadataResponse response = pushServiceSocket.submitVerificationCode(sessionId, verificationCode); return ServiceResponse.forResult(response, 200, null); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/ChangePhoneNumberRequest.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/ChangePhoneNumberRequest.java index c6d6c72489..8910e177e2 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/ChangePhoneNumberRequest.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/account/ChangePhoneNumberRequest.java @@ -14,10 +14,13 @@ import java.util.Map; public final class ChangePhoneNumberRequest { @JsonProperty - private String number; + private String sessionId; @JsonProperty - private String code; + private String recoveryPassword; + + @JsonProperty + private String number; @JsonProperty("reglock") private String registrationLock; @@ -36,18 +39,21 @@ public final class ChangePhoneNumberRequest { @JsonProperty private Map pniRegistrationIds; + @SuppressWarnings("unused") public ChangePhoneNumberRequest() {} - public ChangePhoneNumberRequest(String number, - String code, + public ChangePhoneNumberRequest(String sessionId, + String recoveryPassword, + String number, String registrationLock, IdentityKey pniIdentityKey, List deviceMessages, Map devicePniSignedPrekeys, Map pniRegistrationIds) { + this.sessionId = sessionId; + this.recoveryPassword = recoveryPassword; this.number = number; - this.code = code; this.registrationLock = registrationLock; this.pniIdentityKey = pniIdentityKey; this.deviceMessages = deviceMessages; @@ -59,10 +65,6 @@ public final class ChangePhoneNumberRequest { return number; } - public String getCode() { - return code; - } - public String getRegistrationLock() { return registrationLock; } diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java index 00f60733d5..5ac79f4cfd 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java @@ -210,7 +210,7 @@ public class PushServiceSocket { private static final String RESERVE_USERNAME_PATH = "/v1/accounts/username_hash/reserve"; private static final String CONFIRM_USERNAME_PATH = "/v1/accounts/username_hash/confirm"; private static final String DELETE_ACCOUNT_PATH = "/v1/accounts/me"; - private static final String CHANGE_NUMBER_PATH = "/v1/accounts/number"; + private static final String CHANGE_NUMBER_PATH = "/v2/accounts/number"; private static final String IDENTIFIER_REGISTERED_PATH = "/v1/accounts/account/%s"; private static final String PREKEY_METADATA_PATH = "/v2/keys?identity=%s";