Fix incorrect registration challenge handling when requesting verification codes.

This commit is contained in:
Cody Henthorne
2025-01-14 15:41:37 -05:00
parent cc3c75c870
commit 039bebb30c
6 changed files with 25 additions and 47 deletions

View File

@@ -5,19 +5,17 @@
package org.thoughtcrime.securesms.registration.data.network package org.thoughtcrime.securesms.registration.data.network
import okio.IOException
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.registration.data.RegistrationRepository import org.thoughtcrime.securesms.registration.data.RegistrationRepository
import org.whispersystems.signalservice.api.NetworkResult import org.whispersystems.signalservice.api.NetworkResult
import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException
import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException import org.whispersystems.signalservice.api.push.exceptions.ChallengeRequiredException
import org.whispersystems.signalservice.api.push.exceptions.ExternalServiceFailureException import org.whispersystems.signalservice.api.push.exceptions.ExternalServiceFailureException
import org.whispersystems.signalservice.api.push.exceptions.ImpossiblePhoneNumberException import org.whispersystems.signalservice.api.push.exceptions.ImpossiblePhoneNumberException
import org.whispersystems.signalservice.api.push.exceptions.InvalidTransportModeException import org.whispersystems.signalservice.api.push.exceptions.InvalidTransportModeException
import org.whispersystems.signalservice.api.push.exceptions.MalformedRequestException import org.whispersystems.signalservice.api.push.exceptions.MalformedRequestException
import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException
import org.whispersystems.signalservice.api.push.exceptions.NonNormalizedPhoneNumberException import org.whispersystems.signalservice.api.push.exceptions.NonNormalizedPhoneNumberException
import org.whispersystems.signalservice.api.push.exceptions.PushChallengeRequiredException
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException import org.whispersystems.signalservice.api.push.exceptions.RateLimitException
import org.whispersystems.signalservice.api.push.exceptions.RegistrationRetryException import org.whispersystems.signalservice.api.push.exceptions.RegistrationRetryException
import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException
@@ -26,7 +24,6 @@ import org.whispersystems.signalservice.internal.push.AuthCredentials
import org.whispersystems.signalservice.internal.push.LockedException import org.whispersystems.signalservice.internal.push.LockedException
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataJson import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataJson
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse
import org.whispersystems.signalservice.internal.util.JsonUtil
/** /**
* This is a processor to map a [RegistrationSessionMetadataResponse] to all the known outcomes. * This is a processor to map a [RegistrationSessionMetadataResponse] to all the known outcomes.
@@ -61,8 +58,7 @@ sealed class VerificationCodeRequestResult(cause: Throwable?) : RegistrationResu
is NetworkResult.NetworkError -> UnknownError(networkResult.exception) is NetworkResult.NetworkError -> UnknownError(networkResult.exception)
is NetworkResult.StatusCodeError -> { is NetworkResult.StatusCodeError -> {
when (val cause = networkResult.exception) { when (val cause = networkResult.exception) {
is PushChallengeRequiredException -> createChallengeRequiredProcessor(networkResult) is ChallengeRequiredException -> createChallengeRequiredProcessor(cause.response)
is CaptchaRequiredException -> createChallengeRequiredProcessor(networkResult)
is RateLimitException -> createRateLimitProcessor(cause) is RateLimitException -> createRateLimitProcessor(cause)
is ImpossiblePhoneNumberException -> ImpossibleNumber(cause) is ImpossiblePhoneNumberException -> ImpossibleNumber(cause)
is NonNormalizedPhoneNumberException -> NonNormalizedNumber(cause = cause, originalNumber = cause.originalNumber, normalizedNumber = cause.normalizedNumber) is NonNormalizedPhoneNumberException -> NonNormalizedNumber(cause = cause, originalNumber = cause.originalNumber, normalizedNumber = cause.normalizedNumber)
@@ -80,19 +76,8 @@ sealed class VerificationCodeRequestResult(cause: Throwable?) : RegistrationResu
} }
} }
private fun createChallengeRequiredProcessor(errorResult: NetworkResult.StatusCodeError<RegistrationSessionMetadataResponse>): VerificationCodeRequestResult { private fun createChallengeRequiredProcessor(response: RegistrationSessionMetadataJson): VerificationCodeRequestResult {
if (errorResult.stringBody == null) {
Log.w(TAG, "Attempted to parse error body with response code ${errorResult.code} for list of requested information, but body was null.")
return UnknownError(errorResult.exception)
}
try {
val response = JsonUtil.fromJson(errorResult.stringBody, RegistrationSessionMetadataJson::class.java)
return ChallengeRequired(Challenge.parse(response.requestedInformation)) return ChallengeRequired(Challenge.parse(response.requestedInformation))
} catch (parseException: IOException) {
Log.w(TAG, "Attempted to parse error body for list of requested information, but encountered exception.", parseException)
return UnknownError(parseException)
}
} }
private fun createRateLimitProcessor(exception: RateLimitException): VerificationCodeRequestResult { private fun createRateLimitProcessor(exception: RateLimitException): VerificationCodeRequestResult {

View File

@@ -1,7 +0,0 @@
package org.whispersystems.signalservice.api.push.exceptions;
public class CaptchaRequiredException extends NonSuccessfulResponseCodeException {
public CaptchaRequiredException() {
super(402);
}
}

View File

@@ -0,0 +1,14 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.push.exceptions
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataJson
/**
* We tried to do something on registration endpoints that didn't go well, so now we have to do a challenge. And not a
* fun one involving ice buckets.
*/
class ChallengeRequiredException(val response: RegistrationSessionMetadataJson) : NonSuccessfulResponseCodeException(409)

View File

@@ -1,3 +0,0 @@
package org.whispersystems.signalservice.api.push.exceptions
class PushChallengeRequiredException : NonSuccessfulResponseCodeException(409)

View File

@@ -84,7 +84,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity; import org.whispersystems.signalservice.api.push.SignedPreKeyEntity;
import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException; import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException;
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException; import org.whispersystems.signalservice.api.push.exceptions.ChallengeRequiredException;
import org.whispersystems.signalservice.api.push.exceptions.ConflictException; import org.whispersystems.signalservice.api.push.exceptions.ConflictException;
import org.whispersystems.signalservice.api.push.exceptions.ContactManifestMismatchException; import org.whispersystems.signalservice.api.push.exceptions.ContactManifestMismatchException;
import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException; import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
@@ -103,7 +103,6 @@ import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulRespons
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResumableUploadResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResumableUploadResponseCodeException;
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException; import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException; import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException;
import org.whispersystems.signalservice.api.push.exceptions.PushChallengeRequiredException;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import org.whispersystems.signalservice.api.push.exceptions.RangeException; import org.whispersystems.signalservice.api.push.exceptions.RangeException;
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException; import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
@@ -3155,10 +3154,8 @@ public class PushServiceSocket {
} }
if (response.getVerified()) { if (response.getVerified()) {
throw new AlreadyVerifiedException(); throw new AlreadyVerifiedException();
} else if (response.pushChallengedRequired()) { } else if (response.pushChallengedRequired() || response.captchaRequired()) {
throw new PushChallengeRequiredException(); throw new ChallengeRequiredException(response);
} else if (response.captchaRequired()) {
throw new CaptchaRequiredException();
} else { } else {
Log.i(TAG, "Received 409 in reg session handler that is not verified, with required information: " + String.join(", ", response.getRequestedInformation())); Log.i(TAG, "Received 409 in reg session handler that is not verified, with required information: " + String.join(", ", response.getRequestedInformation()));
throw new HttpConflictException(); throw new HttpConflictException();
@@ -3198,10 +3195,8 @@ public class PushServiceSocket {
} }
if (response.getVerified()) { if (response.getVerified()) {
throw new AlreadyVerifiedException(); throw new AlreadyVerifiedException();
} else if (response.pushChallengedRequired()) { } else if (response.pushChallengedRequired() || response.captchaRequired()) {
throw new PushChallengeRequiredException(); throw new ChallengeRequiredException(response);
} else if (response.captchaRequired()) {
throw new CaptchaRequiredException();
} else { } else {
Log.i(TAG, "Received 409 in for reg code request that is not verified, with required information: " + String.join(", ", response.getRequestedInformation())); Log.i(TAG, "Received 409 in for reg code request that is not verified, with required information: " + String.join(", ", response.getRequestedInformation()));
throw new HttpConflictException(); throw new HttpConflictException();
@@ -3243,10 +3238,8 @@ public class PushServiceSocket {
} }
if (response.getVerified()) { if (response.getVerified()) {
throw new AlreadyVerifiedException(); throw new AlreadyVerifiedException();
} else if (response.pushChallengedRequired()) { } else if (response.pushChallengedRequired() || response.captchaRequired()) {
throw new PushChallengeRequiredException(); throw new ChallengeRequiredException(response);
} else if (response.captchaRequired()) {
throw new CaptchaRequiredException();
} else { } else {
Log.i(TAG, "Received 409 for patching reg session that is not verified, with required information: " + String.join(", ", response.getRequestedInformation())); Log.i(TAG, "Received 409 for patching reg session that is not verified, with required information: " + String.join(", ", response.getRequestedInformation()));
throw new HttpConflictException(); throw new HttpConflictException();

View File

@@ -3,7 +3,6 @@ package org.whispersystems.signalservice.internal.websocket;
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException;
import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException; import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
import org.whispersystems.signalservice.api.push.exceptions.ExpectationFailedException; import org.whispersystems.signalservice.api.push.exceptions.ExpectationFailedException;
import org.whispersystems.signalservice.api.push.exceptions.MalformedResponseException; import org.whispersystems.signalservice.api.push.exceptions.MalformedResponseException;
@@ -12,7 +11,6 @@ import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException; import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException;
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException; import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException; import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException;
import org.whispersystems.signalservice.internal.push.AuthCredentials;
import org.whispersystems.signalservice.internal.push.DeviceLimit; import org.whispersystems.signalservice.internal.push.DeviceLimit;
import org.whispersystems.signalservice.internal.push.DeviceLimitExceededException; import org.whispersystems.signalservice.internal.push.DeviceLimitExceededException;
import org.whispersystems.signalservice.internal.push.LockedException; import org.whispersystems.signalservice.internal.push.LockedException;
@@ -81,8 +79,6 @@ public final class DefaultErrorMapper implements ErrorMapper {
case 401: case 401:
case 403: case 403:
return new AuthorizationFailedException(status, "Authorization failed!"); return new AuthorizationFailedException(status, "Authorization failed!");
case 402:
return new CaptchaRequiredException();
case 404: case 404:
return new NotFoundException("Not found"); return new NotFoundException("Not found");
case 409: case 409: