Skip to content

Commit

Permalink
Support multiple sequential captcha challenges.
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholas-signal committed Mar 3, 2023
1 parent 89ad213 commit d1d73fe
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 2 deletions.
Expand Up @@ -8,6 +8,7 @@ import org.whispersystems.signalservice.api.push.exceptions.MustRequestNewCodeEx
import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException
import org.whispersystems.signalservice.api.push.exceptions.NonNormalizedPhoneNumberException
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException
import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException
import org.whispersystems.signalservice.api.util.Preconditions
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
Expand Down Expand Up @@ -41,6 +42,10 @@ sealed class RegistrationSessionProcessor(response: ServiceResponse<Registration
return PUSH_CHALLENGE_KEY == getChallenge()
}

fun isTokenRejected(): Boolean {
return error is TokenNotAcceptedException
}

fun isImpossibleNumber(): Boolean {
return error is ImpossiblePhoneNumberException
}
Expand Down
Expand Up @@ -299,6 +299,9 @@ private void requestVerificationCode(@NonNull Mode mode) {
String.format(getString(R.string.RegistrationActivity_the_number_you_specified_s_is_invalid), viewModel.getNumber().getFullFormattedNumber()));
} else if (processor.isNonNormalizedNumber()) {
handleNonNormalizedNumberError(processor.getOriginalNumber(), processor.getNormalizedNumber(), mode);
} else if (processor.isTokenRejected()) {
Log.i(TAG, "The server did not accept the information.", processor.getError());
showErrorDialog(register.getContext(), getString(R.string.RegistrationActivity_we_need_to_verify_that_youre_human));
} else {
Log.i(TAG, "Unknown error during verification code request", processor.getError());
showErrorDialog(register.getContext(), getString(R.string.RegistrationActivity_unable_to_connect_to_service));
Expand Down
Expand Up @@ -10,6 +10,7 @@
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;

import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.pin.KbsRepository;
import org.thoughtcrime.securesms.pin.TokenData;
Expand All @@ -36,6 +37,8 @@
*/
public abstract class BaseRegistrationViewModel extends ViewModel {

private static final String TAG = Log.tag(BaseRegistrationViewModel.class);

private static final String STATE_NUMBER = "NUMBER";
private static final String STATE_REGISTRATION_SECRET = "REGISTRATION_SECRET";
private static final String STATE_VERIFICATION_CODE = "TEXT_CODE_ENTERED";
Expand Down Expand Up @@ -280,10 +283,14 @@ public Single<RegistrationSessionProcessor> handleRequiredChallenges(Registratio
}

if (hasCaptchaToken() && processor.captchaRequired()) {
return verifyAccountRepository.verifyCaptcha(sessionId, Objects.requireNonNull(getCaptchaToken()), e164, getRegistrationSecret())
Log.d(TAG, "Submitting completed captcha challenge");
final String captcha = Objects.requireNonNull(getCaptchaToken());
clearCaptchaResponse();
return verifyAccountRepository.verifyCaptcha(sessionId, captcha, e164, getRegistrationSecret())
.map(RegistrationSessionProcessor.RegistrationSessionProcessorForSession::new);
} else {
String challenge = processor.getChallenge();
Log.d(TAG, "Handling challenge of type " + challenge);
if (challenge != null) {
switch (challenge) {
case RegistrationSessionProcessor.PUSH_CHALLENGE_KEY:
Expand Down
@@ -0,0 +1,6 @@
package org.whispersystems.signalservice.api.push.exceptions

/**
* Exception representing that the submitted information was not accepted (e.g. the push challenge token or captcha did not match)
*/
class TokenNotAcceptedException : NonSuccessfulResponseCodeException(403)
Expand Up @@ -86,6 +86,7 @@
import org.whispersystems.signalservice.api.push.exceptions.RemoteAttestationResponseExpiredException;
import org.whispersystems.signalservice.api.push.exceptions.ResumeLocationInvalidException;
import org.whispersystems.signalservice.api.push.exceptions.ServerRejectedException;
import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.api.push.exceptions.UsernameIsNotAssociatedWithAnAccountException;
import org.whispersystems.signalservice.api.push.exceptions.UsernameIsNotReservedException;
Expand Down Expand Up @@ -348,7 +349,7 @@ public RegistrationSessionMetadataResponse patchVerificationSession(String sessi
String path = VERIFICATION_SESSION_PATH + "/" + sessionId;

final UpdateVerificationSessionRequestBody requestBody = new UpdateVerificationSessionRequestBody(captchaToken, pushToken, pushChallengeToken, mcc, mnc);
try (Response response = makeServiceRequest(path, "PATCH", jsonRequestBody(JsonUtil.toJson(requestBody)), NO_HEADERS, new RegistrationSessionResponseHandler(), Optional.empty(), false)) {
try (Response response = makeServiceRequest(path, "PATCH", jsonRequestBody(JsonUtil.toJson(requestBody)), NO_HEADERS, new PatchRegistrationSessionResponseHandler(), Optional.empty(), false)) {
return parseSessionMetadataResponse(response);
}
}
Expand Down Expand Up @@ -2543,6 +2544,35 @@ public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResp
}
}


private static class PatchRegistrationSessionResponseHandler implements ResponseCodeHandler {

@Override
public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException {
switch (responseCode) {
case 403:
throw new TokenNotAcceptedException();
case 404:
throw new NoSuchSessionException();
case 409:
RegistrationSessionMetadataJson response;
try {
response = JsonUtil.fromJson(body.string(), RegistrationSessionMetadataJson.class);
} catch (IOException e) {
Log.e(TAG, "Unable to read response body.", e);
throw new NonSuccessfulResponseCodeException(409);
}
if (response.pushChallengedRequired()) {
throw new PushChallengeRequiredException();
} else if (response.captchaRequired()) {
throw new CaptchaRequiredException();
} else {
throw new HttpConflictException();
}
}
}
}

private static class RegistrationCodeRequestResponseHandler implements ResponseCodeHandler {
@Override public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException {
switch (responseCode) {
Expand Down

0 comments on commit d1d73fe

Please sign in to comment.