Enforce phone number normalization when creating accounts or changing numbers

This commit is contained in:
Jon Chambers 2021-10-20 16:13:39 -04:00 committed by Jon Chambers
parent 7762afc497
commit 534c577f59
10 changed files with 235 additions and 91 deletions

View File

@ -52,6 +52,7 @@ import java.util.concurrent.TimeUnit;
import javax.servlet.DispatcherType; import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration; import javax.servlet.FilterRegistration;
import javax.servlet.ServletRegistration; import javax.servlet.ServletRegistration;
import javax.ws.rs.ext.ExceptionMapper;
import org.eclipse.jetty.servlets.CrossOriginFilter; import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.glassfish.jersey.server.ServerProperties; import org.glassfish.jersey.server.ServerProperties;
import org.jdbi.v3.core.Jdbi; import org.jdbi.v3.core.Jdbi;
@ -111,7 +112,9 @@ import org.whispersystems.textsecuregcm.limits.UnsealedSenderRateLimiter;
import org.whispersystems.textsecuregcm.liquibase.NameableMigrationsBundle; import org.whispersystems.textsecuregcm.liquibase.NameableMigrationsBundle;
import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper; import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.IOExceptionMapper; import org.whispersystems.textsecuregcm.mappers.IOExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.ImpossiblePhoneNumberExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.InvalidWebsocketAddressExceptionMapper; import org.whispersystems.textsecuregcm.mappers.InvalidWebsocketAddressExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.NonNormalizedPhoneNumberExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.RateLimitChallengeExceptionMapper; import org.whispersystems.textsecuregcm.mappers.RateLimitChallengeExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper; import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.RetryLaterExceptionMapper; import org.whispersystems.textsecuregcm.mappers.RetryLaterExceptionMapper;
@ -708,29 +711,22 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
private void registerExceptionMappers(Environment environment, private void registerExceptionMappers(Environment environment,
WebSocketEnvironment<AuthenticatedAccount> webSocketEnvironment, WebSocketEnvironment<AuthenticatedAccount> webSocketEnvironment,
WebSocketEnvironment<AuthenticatedAccount> provisioningEnvironment) { WebSocketEnvironment<AuthenticatedAccount> provisioningEnvironment) {
environment.jersey().register(new LoggingUnhandledExceptionMapper());
environment.jersey().register(new IOExceptionMapper());
environment.jersey().register(new RateLimitExceededExceptionMapper());
environment.jersey().register(new InvalidWebsocketAddressExceptionMapper());
environment.jersey().register(new DeviceLimitExceededExceptionMapper());
environment.jersey().register(new RetryLaterExceptionMapper());
environment.jersey().register(new ServerRejectedExceptionMapper());
webSocketEnvironment.jersey().register(new LoggingUnhandledExceptionMapper()); List.of(
webSocketEnvironment.jersey().register(new IOExceptionMapper()); new LoggingUnhandledExceptionMapper(),
webSocketEnvironment.jersey().register(new RateLimitExceededExceptionMapper()); new IOExceptionMapper(),
webSocketEnvironment.jersey().register(new InvalidWebsocketAddressExceptionMapper()); new RateLimitExceededExceptionMapper(),
webSocketEnvironment.jersey().register(new DeviceLimitExceededExceptionMapper()); new InvalidWebsocketAddressExceptionMapper(),
webSocketEnvironment.jersey().register(new RetryLaterExceptionMapper()); new DeviceLimitExceededExceptionMapper(),
webSocketEnvironment.jersey().register(new ServerRejectedExceptionMapper()); new RetryLaterExceptionMapper(),
new ServerRejectedExceptionMapper(),
provisioningEnvironment.jersey().register(new LoggingUnhandledExceptionMapper()); new ImpossiblePhoneNumberExceptionMapper(),
provisioningEnvironment.jersey().register(new IOExceptionMapper()); new NonNormalizedPhoneNumberExceptionMapper()
provisioningEnvironment.jersey().register(new RateLimitExceededExceptionMapper()); ).forEach(exceptionMapper -> {
provisioningEnvironment.jersey().register(new InvalidWebsocketAddressExceptionMapper()); environment.jersey().register(exceptionMapper);
provisioningEnvironment.jersey().register(new DeviceLimitExceededExceptionMapper()); webSocketEnvironment.jersey().register(exceptionMapper);
provisioningEnvironment.jersey().register(new RetryLaterExceptionMapper()); provisioningEnvironment.jersey().register(exceptionMapper);
provisioningEnvironment.jersey().register(new ServerRejectedExceptionMapper()); });
} }
private void registerCorsFilter(Environment environment) { private void registerCorsFilter(Environment environment) {

View File

@ -80,6 +80,8 @@ import org.whispersystems.textsecuregcm.storage.UsernamesManager;
import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.Constants;
import org.whispersystems.textsecuregcm.util.ForwardedIpUtil; import org.whispersystems.textsecuregcm.util.ForwardedIpUtil;
import org.whispersystems.textsecuregcm.util.Hex; import org.whispersystems.textsecuregcm.util.Hex;
import org.whispersystems.textsecuregcm.util.ImpossiblePhoneNumberException;
import org.whispersystems.textsecuregcm.util.NonNormalizedPhoneNumberException;
import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.util.Util;
import org.whispersystems.textsecuregcm.util.VerificationCode; import org.whispersystems.textsecuregcm.util.VerificationCode;
@ -160,18 +162,18 @@ public class AccountController {
@Timed @Timed
@GET @GET
@Path("/{type}/preauth/{token}/{number}") @Path("/{type}/preauth/{token}/{number}")
@Produces(MediaType.APPLICATION_JSON)
public Response getPreAuth(@PathParam("type") String pushType, public Response getPreAuth(@PathParam("type") String pushType,
@PathParam("token") String pushToken, @PathParam("token") String pushToken,
@PathParam("number") String number, @PathParam("number") String number,
@QueryParam("voip") Optional<Boolean> useVoip) @QueryParam("voip") Optional<Boolean> useVoip)
{ throws ImpossiblePhoneNumberException, NonNormalizedPhoneNumberException {
if (!"apn".equals(pushType) && !"fcm".equals(pushType)) { if (!"apn".equals(pushType) && !"fcm".equals(pushType)) {
return Response.status(400).build(); return Response.status(400).build();
} }
if (!Util.isValidNumber(number)) { Util.requireNormalizedNumber(number);
return Response.status(400).build();
}
String pushChallenge = generatePushChallenge(); String pushChallenge = generatePushChallenge();
StoredVerificationCode storedVerificationCode = new StoredVerificationCode(null, StoredVerificationCode storedVerificationCode = new StoredVerificationCode(null,
@ -195,6 +197,7 @@ public class AccountController {
@Timed @Timed
@GET @GET
@Path("/{transport}/code/{number}") @Path("/{transport}/code/{number}")
@Produces(MediaType.APPLICATION_JSON)
public Response createAccount(@PathParam("transport") String transport, public Response createAccount(@PathParam("transport") String transport,
@PathParam("number") String number, @PathParam("number") String number,
@HeaderParam("X-Forwarded-For") String forwardedFor, @HeaderParam("X-Forwarded-For") String forwardedFor,
@ -203,12 +206,9 @@ public class AccountController {
@QueryParam("client") Optional<String> client, @QueryParam("client") Optional<String> client,
@QueryParam("captcha") Optional<String> captcha, @QueryParam("captcha") Optional<String> captcha,
@QueryParam("challenge") Optional<String> pushChallenge) @QueryParam("challenge") Optional<String> pushChallenge)
throws RateLimitExceededException, RetryLaterException throws RateLimitExceededException, RetryLaterException, ImpossiblePhoneNumberException, NonNormalizedPhoneNumberException {
{
if (!Util.isValidNumber(number)) { Util.requireNormalizedNumber(number);
logger.info("Invalid number: " + number);
throw new WebApplicationException(Response.status(400).build());
}
String sourceHost = ForwardedIpUtil.getMostRecentProxy(forwardedFor).orElseThrow(); String sourceHost = ForwardedIpUtil.getMostRecentProxy(forwardedFor).orElseThrow();
@ -340,6 +340,9 @@ public class AccountController {
rateLimiters.getVerifyLimiter().validate(number); rateLimiters.getVerifyLimiter().validate(number);
// Note that successful verification depends on being able to find a stored verification code for the given number.
// We check that numbers are normalized before we store verification codes, and so don't need to re-assert
// normalization here.
Optional<StoredVerificationCode> storedVerificationCode = pendingAccounts.getCodeForNumber(number); Optional<StoredVerificationCode> storedVerificationCode = pendingAccounts.getCodeForNumber(number);
if (storedVerificationCode.isEmpty() || !storedVerificationCode.get().isValid(verificationCode)) { if (storedVerificationCode.isEmpty() || !storedVerificationCode.get().isValid(verificationCode)) {
@ -385,7 +388,7 @@ public class AccountController {
@Path("/number") @Path("/number")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public void changeNumber(@Auth final AuthenticatedAccount authenticatedAccount, @Valid final ChangePhoneNumberRequest request) public void changeNumber(@Auth final AuthenticatedAccount authenticatedAccount, @Valid final ChangePhoneNumberRequest request)
throws RateLimitExceededException, InterruptedException { throws RateLimitExceededException, InterruptedException, ImpossiblePhoneNumberException, NonNormalizedPhoneNumberException {
if (request.getNumber().equals(authenticatedAccount.getAccount().getNumber())) { if (request.getNumber().equals(authenticatedAccount.getAccount().getNumber())) {
// This may be a request that got repeated due to poor network conditions or other client error; take no action, // This may be a request that got repeated due to poor network conditions or other client error; take no action,
@ -393,6 +396,8 @@ public class AccountController {
return; return;
} }
Util.requireNormalizedNumber(request.getNumber());
rateLimiters.getVerifyLimiter().validate(request.getNumber()); rateLimiters.getVerifyLimiter().validate(request.getNumber());
final Optional<StoredVerificationCode> storedVerificationCode = final Optional<StoredVerificationCode> storedVerificationCode =

View File

@ -0,0 +1,18 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.mappers;
import org.whispersystems.textsecuregcm.util.ImpossiblePhoneNumberException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
public class ImpossiblePhoneNumberExceptionMapper implements ExceptionMapper<ImpossiblePhoneNumberException> {
@Override
public Response toResponse(final ImpossiblePhoneNumberException exception) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.mappers;
import org.whispersystems.textsecuregcm.util.NonNormalizedPhoneNumberException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
public class NonNormalizedPhoneNumberExceptionMapper implements ExceptionMapper<NonNormalizedPhoneNumberException> {
@Override
public Response toResponse(final NonNormalizedPhoneNumberException exception) {
return Response.status(Status.BAD_REQUEST)
.entity(new NonNormalizedPhoneNumberResponse(exception.getOriginalNumber(), exception.getNormalizedNumber()))
.build();
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.mappers;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class NonNormalizedPhoneNumberResponse {
private final String originalNumber;
private final String normalizedNumber;
@JsonCreator
NonNormalizedPhoneNumberResponse(@JsonProperty("originalNumber") final String originalNumber,
@JsonProperty("normalizedNumber") final String normalizedNumber) {
this.originalNumber = originalNumber;
this.normalizedNumber = normalizedNumber;
}
public String getOriginalNumber() {
return originalNumber;
}
public String getNormalizedNumber() {
return normalizedNumber;
}
}

View File

@ -5,13 +5,13 @@
package org.whispersystems.textsecuregcm.util; package org.whispersystems.textsecuregcm.util;
public class ImpossibleNumberException extends Exception { public class ImpossiblePhoneNumberException extends Exception {
public ImpossibleNumberException() { public ImpossiblePhoneNumberException() {
super(); super();
} }
public ImpossibleNumberException(final Throwable cause) { public ImpossiblePhoneNumberException(final Throwable cause) {
super(cause); super(cause);
} }
} }

View File

@ -5,12 +5,12 @@
package org.whispersystems.textsecuregcm.util; package org.whispersystems.textsecuregcm.util;
public class NonNormalizedNumberException extends Exception { public class NonNormalizedPhoneNumberException extends Exception {
private final String originalNumber; private final String originalNumber;
private final String normalizedNumber; private final String normalizedNumber;
public NonNormalizedNumberException(final String originalNumber, final String normalizedNumber) { public NonNormalizedPhoneNumberException(final String originalNumber, final String normalizedNumber) {
this.originalNumber = originalNumber; this.originalNumber = originalNumber;
this.normalizedNumber = normalizedNumber; this.normalizedNumber = normalizedNumber;
} }

View File

@ -45,21 +45,17 @@ public class Util {
} }
} }
public static boolean isValidNumber(String number) {
return number.matches("^\\+[0-9]+") && PHONE_NUMBER_UTIL.isPossibleNumber(number, null);
}
/** /**
* Checks that the given number is a valid, E164-normalized phone number. * Checks that the given number is a valid, E164-normalized phone number.
* *
* @param number the number to check * @param number the number to check
* *
* @throws ImpossibleNumberException if the given number is not a valid phone number at all * @throws ImpossiblePhoneNumberException if the given number is not a valid phone number at all
* @throws NonNormalizedNumberException if the given number is a valid phone number, but isn't E164-normalized * @throws NonNormalizedPhoneNumberException if the given number is a valid phone number, but isn't E164-normalized
*/ */
public static void requireNormalizedNumber(final String number) throws ImpossibleNumberException, NonNormalizedNumberException { public static void requireNormalizedNumber(final String number) throws ImpossiblePhoneNumberException, NonNormalizedPhoneNumberException {
if (!PHONE_NUMBER_UTIL.isPossibleNumber(number, null)) { if (!PHONE_NUMBER_UTIL.isPossibleNumber(number, null)) {
throw new ImpossibleNumberException(); throw new ImpossiblePhoneNumberException();
} }
try { try {
@ -67,10 +63,10 @@ public class Util {
final String normalizedNumber = PHONE_NUMBER_UTIL.format(phoneNumber, PhoneNumberFormat.E164); final String normalizedNumber = PHONE_NUMBER_UTIL.format(phoneNumber, PhoneNumberFormat.E164);
if (!number.equals(normalizedNumber)) { if (!number.equals(normalizedNumber)) {
throw new NonNormalizedNumberException(number, normalizedNumber); throw new NonNormalizedPhoneNumberException(number, normalizedNumber);
} }
} catch (final NumberParseException e) { } catch (final NumberParseException e) {
throw new ImpossibleNumberException(e); throw new ImpossiblePhoneNumberException(e);
} }
} }

View File

@ -72,6 +72,9 @@ import org.whispersystems.textsecuregcm.entities.RegistrationLock;
import org.whispersystems.textsecuregcm.entities.RegistrationLockFailure; import org.whispersystems.textsecuregcm.entities.RegistrationLockFailure;
import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.mappers.ImpossiblePhoneNumberExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.NonNormalizedPhoneNumberExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.NonNormalizedPhoneNumberResponse;
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper; import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.push.APNSender; import org.whispersystems.textsecuregcm.push.APNSender;
import org.whispersystems.textsecuregcm.push.ApnMessage; import org.whispersystems.textsecuregcm.push.ApnMessage;
@ -154,6 +157,8 @@ class AccountControllerTest {
ImmutableSet.of(AuthenticatedAccount.class, ImmutableSet.of(AuthenticatedAccount.class,
DisabledPermittedAuthenticatedAccount.class))) DisabledPermittedAuthenticatedAccount.class)))
.addProvider(new RateLimitExceededExceptionMapper()) .addProvider(new RateLimitExceededExceptionMapper())
.addProvider(new ImpossiblePhoneNumberExceptionMapper())
.addProvider(new NonNormalizedPhoneNumberExceptionMapper())
.setMapper(SystemMapper.getMapper()) .setMapper(SystemMapper.getMapper())
.setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new AccountController(pendingAccountsManager, .addResource(new AccountController(pendingAccountsManager,
@ -395,6 +400,39 @@ class AccountControllerTest {
verifyNoMoreInteractions(gcmSender); verifyNoMoreInteractions(gcmSender);
} }
@Test
void testGetPreauthImpossibleNumber() {
final Response response = resources.getJerseyTest()
.target("/v1/accounts/fcm/preauth/mytoken/BogusNumber")
.request()
.get();
assertThat(response.getStatus()).isEqualTo(400);
assertThat(response.readEntity(String.class)).isBlank();
verifyNoMoreInteractions(gcmSender);
verifyNoMoreInteractions(apnSender);
}
@Test
void testGetPreauthNonNormalized() {
final String number = "+4407700900111";
final Response response = resources.getJerseyTest()
.target("/v1/accounts/fcm/preauth/mytoken/" + number)
.request()
.get();
assertThat(response.getStatus()).isEqualTo(400);
final NonNormalizedPhoneNumberResponse responseEntity = response.readEntity(NonNormalizedPhoneNumberResponse.class);
assertThat(responseEntity.getOriginalNumber()).isEqualTo(number);
assertThat(responseEntity.getNormalizedNumber()).isEqualTo("+447700900111");
verifyNoMoreInteractions(gcmSender);
verifyNoMoreInteractions(apnSender);
}
@ParameterizedTest @ParameterizedTest
@ValueSource(booleans = {false, true}) @ValueSource(booleans = {false, true})
void testSendCode(final boolean enrolledInVerifyExperiment) throws Exception { void testSendCode(final boolean enrolledInVerifyExperiment) throws Exception {
@ -434,6 +472,43 @@ class AccountControllerTest {
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(NICE_HOST)); verify(abusiveHostRules).getAbusiveHostRulesFor(eq(NICE_HOST));
} }
@Test
void testSendCodeImpossibleNumber() {
final Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", "Definitely not a real number"))
.queryParam("challenge", "1234-push")
.request()
.header("X-Forwarded-For", NICE_HOST)
.get();
assertThat(response.getStatus()).isEqualTo(400);
assertThat(response.readEntity(String.class)).isBlank();
verify(smsSender, never()).deliverSmsVerification(any(), any(), any());
}
@Test
void testSendCodeNonNormalized() {
final String number = "+4407700900111";
final Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", number))
.queryParam("challenge", "1234-push")
.request()
.header("X-Forwarded-For", NICE_HOST)
.get();
assertThat(response.getStatus()).isEqualTo(400);
final NonNormalizedPhoneNumberResponse responseEntity = response.readEntity(NonNormalizedPhoneNumberResponse.class);
assertThat(responseEntity.getOriginalNumber()).isEqualTo(number);
assertThat(responseEntity.getNormalizedNumber()).isEqualTo("+447700900111");
verify(smsSender, never()).deliverSmsVerification(any(), any(), any());
}
@ParameterizedTest @ParameterizedTest
@ValueSource(booleans = {false, true}) @ValueSource(booleans = {false, true})
public void testSendCodeVoiceNoLocale(final boolean enrolledInVerifyExperiment) throws Exception { public void testSendCodeVoiceNoLocale(final boolean enrolledInVerifyExperiment) throws Exception {
@ -1151,6 +1226,46 @@ class AccountControllerTest {
verify(accountsManager).changeNumber(AuthHelper.VALID_ACCOUNT, number); verify(accountsManager).changeNumber(AuthHelper.VALID_ACCOUNT, number);
} }
@Test
void testChangePhoneNumberImpossibleNumber() throws InterruptedException {
final String number = "This is not a real phone number";
final String code = "987654";
final Response response =
resources.getJerseyTest()
.target("/v1/accounts/number")
.request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.put(Entity.entity(new ChangePhoneNumberRequest(number, code, null),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(400);
assertThat(response.readEntity(String.class)).isBlank();
verify(accountsManager, never()).changeNumber(any(), any());
}
@Test
void testChangePhoneNumberNonNormalized() throws InterruptedException {
final String number = "+4407700900111";
final String code = "987654";
final Response response =
resources.getJerseyTest()
.target("/v1/accounts/number")
.request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.put(Entity.entity(new ChangePhoneNumberRequest(number, code, null),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(400);
final NonNormalizedPhoneNumberResponse responseEntity = response.readEntity(NonNormalizedPhoneNumberResponse.class);
assertThat(responseEntity.getOriginalNumber()).isEqualTo(number);
assertThat(responseEntity.getNormalizedNumber()).isEqualTo("+447700900111");
verify(accountsManager, never()).changeNumber(any(), any());
}
@Test @Test
void testChangePhoneNumberSameNumber() throws InterruptedException { void testChangePhoneNumberSameNumber() throws InterruptedException {
final Response response = final Response response =

View File

@ -10,8 +10,8 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import org.whispersystems.textsecuregcm.util.ImpossibleNumberException; import org.whispersystems.textsecuregcm.util.ImpossiblePhoneNumberException;
import org.whispersystems.textsecuregcm.util.NonNormalizedNumberException; import org.whispersystems.textsecuregcm.util.NonNormalizedPhoneNumberException;
import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.util.Util;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -22,44 +22,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
class ValidNumberTest { class ValidNumberTest {
@ParameterizedTest
@MethodSource
void isValid(final String number, final boolean expectValid) {
assertEquals(expectValid, Util.isValidNumber(number));
}
private static Stream<Arguments> isValid() {
return Stream.of(
// Valid numbers
Arguments.of("+14151231234", true),
Arguments.of("+71234567890", true),
Arguments.of("+447535742222", true),
Arguments.of("+4915174108888", true),
// Invalid e164s
Arguments.of("+141512312341", false),
Arguments.of("+712345678901", false),
Arguments.of("+4475357422221", false),
Arguments.of("+491517410888811111", false),
// Non-e164s
Arguments.of("+1 415 123 1234", false),
Arguments.of("+1 (415) 123-1234", false),
Arguments.of("+1 415)123-1234", false),
Arguments.of("71234567890", false),
Arguments.of("001447535742222", false),
Arguments.of(" +14151231234", false),
Arguments.of("+1415123123a", false),
// Short region
Arguments.of("+298123456", true),
Arguments.of("+299123456", true),
Arguments.of("+376123456", true),
Arguments.of("+68512345", true),
Arguments.of("+689123456", true)
);
}
@ParameterizedTest @ParameterizedTest
@ValueSource(strings = { @ValueSource(strings = {
"+447700900111", "+447700900111",
@ -78,7 +40,7 @@ class ValidNumberTest {
@Test @Test
void requireNormalizedNumberNull() { void requireNormalizedNumberNull() {
assertThrows(ImpossibleNumberException.class, () -> Util.requireNormalizedNumber(null)); assertThrows(ImpossiblePhoneNumberException.class, () -> Util.requireNormalizedNumber(null));
} }
@ParameterizedTest @ParameterizedTest
@ -93,7 +55,7 @@ class ValidNumberTest {
"+1415123123a" "+1415123123a"
}) })
void requireNormalizedNumberImpossibleNumber(final String number) { void requireNormalizedNumberImpossibleNumber(final String number) {
assertThrows(ImpossibleNumberException.class, () -> Util.requireNormalizedNumber(number)); assertThrows(ImpossiblePhoneNumberException.class, () -> Util.requireNormalizedNumber(number));
} }
@ParameterizedTest @ParameterizedTest
@ -104,6 +66,6 @@ class ValidNumberTest {
"+1 415)123-1234", "+1 415)123-1234",
" +14151231234"}) " +14151231234"})
void requireNormalizedNumberNonNormalized(final String number) { void requireNormalizedNumberNonNormalized(final String number) {
assertThrows(NonNormalizedNumberException.class, () -> Util.requireNormalizedNumber(number)); assertThrows(NonNormalizedPhoneNumberException.class, () -> Util.requireNormalizedNumber(number));
} }
} }