Handle non-normalized phone number responses.

fork-5.53.8
Greyson Parrelli 2021-10-27 11:09:17 -04:00
rodzic 2980e547cb
commit 48a81da883
8 zmienionych plików z 193 dodań i 47 usunięć

Wyświetl plik

@ -1,9 +1,12 @@
package org.thoughtcrime.securesms.registration
import org.whispersystems.signalservice.api.push.exceptions.ImpossiblePhoneNumberException
import org.whispersystems.signalservice.api.push.exceptions.LocalRateLimitException
import org.whispersystems.signalservice.api.push.exceptions.NonNormalizedPhoneNumberException
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
import org.whispersystems.signalservice.internal.push.RequestVerificationCodeResponse
import java.lang.IllegalStateException
/**
* Process responses from requesting an SMS or Phone code from the server.
@ -25,6 +28,32 @@ class RequestVerificationCodeResponseProcessor(response: ServiceResponse<Request
return error is LocalRateLimitException
}
fun isImpossibleNumber(): Boolean {
return error is ImpossiblePhoneNumberException
}
fun isNonNormalizedNumber(): Boolean {
return error is NonNormalizedPhoneNumberException
}
/** Should only be called if [isNonNormalizedNumber] */
fun getOriginalNumber(): String {
if (error !is NonNormalizedPhoneNumberException) {
throw IllegalStateException("This can only be called when isNonNormalizedNumber()")
}
return (error as NonNormalizedPhoneNumberException).originalNumber
}
/** Should only be called if [isNonNormalizedNumber] */
fun getNormalizedNumber(): String {
if (error !is NonNormalizedPhoneNumberException) {
throw IllegalStateException("This can only be called when isNonNormalizedNumber()")
}
return (error as NonNormalizedPhoneNumberException).normalizedNumber
}
companion object {
@JvmStatic
fun forLocalRateLimit(): RequestVerificationCodeResponseProcessor {

Wyświetl plik

@ -34,6 +34,9 @@ import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.tasks.Task;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
@ -45,9 +48,11 @@ import org.thoughtcrime.securesms.registration.VerifyAccountRepository.Mode;
import org.thoughtcrime.securesms.registration.util.RegistrationNumberInputController;
import org.thoughtcrime.securesms.registration.viewmodel.NumberViewState;
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.util.PlayServicesUtil;
import org.thoughtcrime.securesms.util.SupportEmailUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
@ -230,8 +235,15 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
} else if (processor.rateLimit()) {
Log.i(TAG, "Unable to request sms code due to rate limit");
Toast.makeText(register.getContext(), R.string.RegistrationActivity_rate_limited_to_service, Toast.LENGTH_LONG).show();
} else if (processor.isImpossibleNumber()) {
Log.w(TAG, "Impossible number", processor.getError());
Dialogs.showAlertDialog(requireContext(),
getString(R.string.RegistrationActivity_invalid_number),
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 {
Log.w(TAG, "Unable to request sms code", processor.getError());
Log.i(TAG, "Unknown error during verification code request", processor.getError());
Toast.makeText(register.getContext(), R.string.RegistrationActivity_unable_to_connect_to_service, Toast.LENGTH_LONG).show();
}
@ -273,6 +285,37 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
viewModel.onCountrySelected(null, countryCode);
}
private void handleNonNormalizedNumberError(@NonNull String originalNumber, @NonNull String normalizedNumber, @NonNull Mode mode) {
try {
Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(normalizedNumber, null);
new MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.RegistrationActivity_non_standard_number_format)
.setMessage(getString(R.string.RegistrationActivity_the_number_you_entered_appears_to_be_a_non_standard, originalNumber, normalizedNumber))
.setNegativeButton(android.R.string.no, (d, i) -> d.dismiss())
.setNeutralButton(R.string.RegistrationActivity_contact_signal_support, (d, i) -> {
String subject = getString(R.string.RegistrationActivity_signal_android_phone_number_format);
String body = SupportEmailUtil.generateSupportEmailBody(requireContext(), R.string.RegistrationActivity_signal_android_phone_number_format, null, null);
CommunicationActions.openEmail(requireContext(), SupportEmailUtil.getSupportEmailAddress(requireContext()), subject, body);
d.dismiss();
})
.setPositiveButton(R.string.yes, (d, i) -> {
countryCode.setText(String.valueOf(phoneNumber.getCountryCode()));
number.setText(String.valueOf(phoneNumber.getNationalNumber()));
requestVerificationCode(mode);
d.dismiss();
})
.show();
} catch (NumberParseException e) {
Log.w(TAG, "Failed to parse number!", e);
Dialogs.showAlertDialog(requireContext(),
getString(R.string.RegistrationActivity_invalid_number),
String.format(getString(R.string.RegistrationActivity_the_number_you_specified_s_is_invalid), viewModel.getNumber().getFullFormattedNumber()));
}
}
private void handlePromptForNoPlayServices(@NonNull Context context) {
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.RegistrationActivity_missing_google_play_services)

Wyświetl plik

@ -20,24 +20,25 @@ import android.content.Context;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.thoughtcrime.securesms.R;
public class Dialogs {
public static void showAlertDialog(Context context, String title, String message) {
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
dialog.setTitle(title);
dialog.setMessage(message);
dialog.setIcon(R.drawable.ic_warning);
dialog.setPositiveButton(android.R.string.ok, null);
dialog.show();
new MaterialAlertDialogBuilder(context)
.setTitle(title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.show();
}
public static void showInfoDialog(Context context, String title, String message) {
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
dialog.setTitle(title);
dialog.setMessage(message);
dialog.setIcon(R.drawable.ic_info_outline);
dialog.setPositiveButton(android.R.string.ok, null);
dialog.show();
new MaterialAlertDialogBuilder(context)
.setTitle(title)
.setMessage(message)
.setIcon(R.drawable.ic_info_outline)
.setPositiveButton(android.R.string.ok, null)
.show();
}
}

Wyświetl plik

@ -1560,6 +1560,9 @@
<string name="RegistrationActivity_signal_needs_access_to_your_contacts_in_order_to_connect_with_friends">Signal needs access to your contacts in order to connect with friends, exchange messages, and make secure calls</string>
<string name="RegistrationActivity_rate_limited_to_service">You\'ve made too many attempts to register this number. Please try again later.</string>
<string name="RegistrationActivity_unable_to_connect_to_service">Unable to connect to service. Please check network connection and try again.</string>
<string name="RegistrationActivity_non_standard_number_format">Non-standard number format</string>
<string name="RegistrationActivity_the_number_you_entered_appears_to_be_a_non_standard">The number you entered (%1$s) appears to be a non-standard format.\n\nDid you mean %2$s?</string>
<string name="RegistrationActivity_signal_android_phone_number_format">Signal Android - Phone Number Format</string>
<string name="RegistrationActivity_call_requested">Call requested</string>
<plurals name="RegistrationActivity_debug_log_hint">
<item quantity="one">You are now %d step away from submitting a debug log.</item>

Wyświetl plik

@ -0,0 +1,10 @@
package org.whispersystems.signalservice.api.push.exceptions;
/**
* An exception indicating that the server believes the number provided is 'impossible', meaning it fails the most basic libphonenumber checks.
*/
public class ImpossiblePhoneNumberException extends NonSuccessfulResponseCodeException {
public ImpossiblePhoneNumberException() {
super(400);
}
}

Wyświetl plik

@ -0,0 +1,46 @@
package org.whispersystems.signalservice.api.push.exceptions;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.whispersystems.signalservice.internal.util.JsonUtil;
import java.io.IOException;
import io.reactivex.rxjava3.annotations.NonNull;
/**
* Response indicating we gave the server a non-normalized phone number. The expected normalized version of the number is provided.
*/
public class NonNormalizedPhoneNumberException extends NonSuccessfulResponseCodeException {
private final String originalNumber;
private final String normalizedNumber;
public static NonNormalizedPhoneNumberException forResponse(@NonNull String responseBody) throws MalformedResponseException {
JsonResponse response = JsonUtil.fromJsonResponse(responseBody, JsonResponse.class);
return new NonNormalizedPhoneNumberException(response.originalNumber, response.normalizedNumber);
}
public NonNormalizedPhoneNumberException(String originalNumber, String normalizedNumber) {
super(400);
this.originalNumber = originalNumber;
this.normalizedNumber = normalizedNumber;
}
public String getOriginalNumber() {
return originalNumber;
}
public String getNormalizedNumber() {
return normalizedNumber;
}
private static class JsonResponse {
@JsonProperty
private String originalNumber;
@JsonProperty
private String normalizedNumber;
}
}

Wyświetl plik

@ -59,9 +59,11 @@ import org.whispersystems.signalservice.api.push.exceptions.ConflictException;
import org.whispersystems.signalservice.api.push.exceptions.ContactManifestMismatchException;
import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
import org.whispersystems.signalservice.api.push.exceptions.ExpectationFailedException;
import org.whispersystems.signalservice.api.push.exceptions.ImpossiblePhoneNumberException;
import org.whispersystems.signalservice.api.push.exceptions.MalformedResponseException;
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
import org.whispersystems.signalservice.api.push.exceptions.NoContentException;
import org.whispersystems.signalservice.api.push.exceptions.NonNormalizedPhoneNumberException;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResumableUploadResponseCodeException;
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
@ -141,7 +143,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Future;
@ -304,14 +305,7 @@ public class PushServiceSocket {
path += "&challenge=" + challenge.get();
}
makeServiceRequest(path, "GET", null, NO_HEADERS, new ResponseCodeHandler() {
@Override
public void handle(int responseCode) throws NonSuccessfulResponseCodeException {
if (responseCode == 402) {
throw new CaptchaRequiredException();
}
}
});
makeServiceRequest(path, "GET", null, NO_HEADERS, new VerificationCodeResponseHandler());
}
public void requestVoiceVerificationCode(Locale locale, Optional<String> captchaToken, Optional<String> challenge) throws IOException {
@ -324,14 +318,7 @@ public class PushServiceSocket {
path += "?challenge=" + challenge.get();
}
makeServiceRequest(path, "GET", null, headers, new ResponseCodeHandler() {
@Override
public void handle(int responseCode) throws NonSuccessfulResponseCodeException {
if (responseCode == 402) {
throw new CaptchaRequiredException();
}
}
});
makeServiceRequest(path, "GET", null, headers, new VerificationCodeResponseHandler());
}
public UUID getOwnUuid() throws IOException {
@ -848,13 +835,10 @@ public class PushServiceSocket {
}
public void setUsername(String username) throws IOException {
makeServiceRequest(String.format(SET_USERNAME_PATH, username), "PUT", "", NO_HEADERS, new ResponseCodeHandler() {
@Override
public void handle(int responseCode) throws NonSuccessfulResponseCodeException {
switch (responseCode) {
case 400: throw new UsernameMalformedException();
case 409: throw new UsernameTakenException();
}
makeServiceRequest(String.format(SET_USERNAME_PATH, username), "PUT", "", NO_HEADERS, (responseCode, body) -> {
switch (responseCode) {
case 400: throw new UsernameMalformedException();
case 409: throw new UsernameTakenException();
}
}, Optional.<UnidentifiedAccess>absent());
}
@ -932,7 +916,7 @@ public class PushServiceSocket {
String.format(SUBSCRIPTION_RECEIPT_CREDENTIALS, subscriptionId),
"POST",
payload,
code -> {
(code, body) -> {
if (code == 204) throw new NonSuccessfulResponseCodeException(204);
});
@ -1636,7 +1620,7 @@ public class PushServiceSocket {
{
Response response = getServiceConnection(urlFragment, method, body, headers, unidentifiedAccessKey, doNotAddAuthenticationOrUnidentifiedAccessKey);
responseCodeHandler.handle(response.code());
responseCodeHandler.handle(response.code(), response.body());
return validateServiceResponse(response);
}
@ -1916,7 +1900,7 @@ public class PushServiceSocket {
}
}
responseCodeHandler.handle(response.code());
responseCodeHandler.handle(response.code(), response.body());
switch (response.code()) {
case 204:
@ -2231,12 +2215,12 @@ public class PushServiceSocket {
}
private interface ResponseCodeHandler {
void handle(int responseCode) throws NonSuccessfulResponseCodeException, PushNetworkException;
void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException;
}
private static class EmptyResponseCodeHandler implements ResponseCodeHandler {
@Override
public void handle(int responseCode) { }
public void handle(int responseCode, ResponseBody body) { }
}
public enum ClientSet { ContactDiscovery, KeyBackup }
@ -2254,20 +2238,20 @@ public class PushServiceSocket {
return JsonUtil.fromJson(response, CredentialResponse.class);
}
private static final ResponseCodeHandler GROUPS_V2_PUT_RESPONSE_HANDLER = responseCode -> {
private static final ResponseCodeHandler GROUPS_V2_PUT_RESPONSE_HANDLER = (responseCode, body) -> {
if (responseCode == 409) throw new GroupExistsException();
};;
private static final ResponseCodeHandler GROUPS_V2_GET_LOGS_HANDLER = NO_HANDLER;
private static final ResponseCodeHandler GROUPS_V2_GET_CURRENT_HANDLER = responseCode -> {
private static final ResponseCodeHandler GROUPS_V2_GET_CURRENT_HANDLER = (responseCode, body) -> {
switch (responseCode) {
case 403: throw new NotInGroupException();
case 404: throw new GroupNotFoundException();
}
};
private static final ResponseCodeHandler GROUPS_V2_PATCH_RESPONSE_HANDLER = responseCode -> {
private static final ResponseCodeHandler GROUPS_V2_PATCH_RESPONSE_HANDLER = (responseCode, body) -> {
if (responseCode == 400) throw new GroupPatchNotAcceptedException();
};
private static final ResponseCodeHandler GROUPS_V2_GET_JOIN_INFO_HANDLER = responseCode -> {
private static final ResponseCodeHandler GROUPS_V2_GET_JOIN_INFO_HANDLER = (responseCode, body) -> {
if (responseCode == 403) throw new ForbiddenException();
};
@ -2404,6 +2388,34 @@ public class PushServiceSocket {
makeServiceRequest(String.format(REPORT_SPAM, e164, serverGuid), "POST", "");
}
private static class VerificationCodeResponseHandler implements ResponseCodeHandler {
@Override
public void handle(int responseCode, ResponseBody responseBody) throws NonSuccessfulResponseCodeException, PushNetworkException {
switch (responseCode) {
case 400:
String body;
try {
body = responseBody != null ? responseBody.string() : "";
} catch (IOException e) {
throw new PushNetworkException(e);
}
if (body.isEmpty()) {
throw new ImpossiblePhoneNumberException();
} else {
try {
throw NonNormalizedPhoneNumberException.forResponse(body);
} catch (MalformedResponseException e) {
Log.w(TAG, "Unable to parse 400 response! Assuming a generic 400.");
throw new ImpossiblePhoneNumberException();
}
}
case 402:
throw new CaptchaRequiredException();
}
}
}
public static final class GroupHistory {
private final GroupChanges groupChanges;
private final Optional<ContentRange> contentRange;

Wyświetl plik

@ -2,6 +2,8 @@ package org.whispersystems.signalservice.internal.push.exceptions;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import okhttp3.ResponseBody;
public final class PaymentsRegionException extends NonSuccessfulResponseCodeException {
public PaymentsRegionException(int code) {
super(code);
@ -10,7 +12,7 @@ public final class PaymentsRegionException extends NonSuccessfulResponseCodeExce
/**
* Promotes a 403 to this exception type.
*/
public static void responseCodeHandler(int responseCode) throws PaymentsRegionException {
public static void responseCodeHandler(int responseCode, ResponseBody body) throws PaymentsRegionException {
if (responseCode == 403) {
throw new PaymentsRegionException(responseCode);
}