diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java index de7897136..087a2b594 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java @@ -74,6 +74,7 @@ import org.whispersystems.textsecuregcm.registration.MessageTransport; import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient; import org.whispersystems.textsecuregcm.registration.RegistrationServiceException; import org.whispersystems.textsecuregcm.registration.RegistrationServiceSenderException; +import org.whispersystems.textsecuregcm.registration.TransportNotAllowedException; import org.whispersystems.textsecuregcm.registration.VerificationSession; import org.whispersystems.textsecuregcm.spam.Extract; import org.whispersystems.textsecuregcm.spam.FilterSpam; @@ -488,8 +489,13 @@ public class VerificationController { throw registrationServiceException.getRegistrationSession() .map(s -> buildResponse(s, verificationSession)) - .map(verificationSessionResponse -> new ClientErrorException( - Response.status(Response.Status.CONFLICT).entity(verificationSessionResponse).build())) + .map(verificationSessionResponse -> { + final Response response = registrationServiceException instanceof TransportNotAllowedException + ? Response.status(418).entity(verificationSessionResponse).build() + : Response.status(Response.Status.CONFLICT).entity(verificationSessionResponse).build(); + + return new ClientErrorException(response); + }) .orElseGet(NotFoundException::new); } else if (unwrappedException instanceof RegistrationServiceSenderException) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/registration/RegistrationServiceClient.java b/service/src/main/java/org/whispersystems/textsecuregcm/registration/RegistrationServiceClient.java index d3fcfb330..5e83c7caf 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/registration/RegistrationServiceClient.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/registration/RegistrationServiceClient.java @@ -157,6 +157,8 @@ public class RegistrationServiceClient implements Managed { RegistrationServiceSenderException.illegalArgument(response.getError().getMayRetry())); case SEND_VERIFICATION_CODE_ERROR_TYPE_UNSPECIFIED -> throw new CompletionException( RegistrationServiceSenderException.unknown(response.getError().getMayRetry())); + case SEND_VERIFICATION_CODE_ERROR_TYPE_TRANSPORT_NOT_ALLOWED -> throw new CompletionException( + new TransportNotAllowedException(buildSessionResponseFromMetadata(response.getSessionMetadata()))); default -> throw new CompletionException( new RuntimeException("Failed to send verification code: " + response.getError().getErrorType())); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/registration/TransportNotAllowedException.java b/service/src/main/java/org/whispersystems/textsecuregcm/registration/TransportNotAllowedException.java new file mode 100644 index 000000000..095858ac6 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/registration/TransportNotAllowedException.java @@ -0,0 +1,14 @@ +package org.whispersystems.textsecuregcm.registration; + +import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession; + +/** + * Indicates that a request to send a verification code failed because the destination number does not support the + * requested transport (e.g. the caller asked to send an SMS to a landline number). + */ +public class TransportNotAllowedException extends RegistrationServiceException { + + public TransportNotAllowedException(RegistrationServiceSession registrationServiceSession) { + super(registrationServiceSession); + } +} diff --git a/service/src/main/proto/RegistrationService.proto b/service/src/main/proto/RegistrationService.proto index ea3f33ca2..75c6ac3ad 100644 --- a/service/src/main/proto/RegistrationService.proto +++ b/service/src/main/proto/RegistrationService.proto @@ -318,6 +318,13 @@ enum SendVerificationCodeErrorType { * been verified. */ SEND_VERIFICATION_CODE_ERROR_TYPE_SESSION_ALREADY_VERIFIED = 5; + + /** + * A verification code could not be sent via the requested transport because + * the destination phone number (or the sender) does not support the requested + * transport. + */ + SEND_VERIFICATION_CODE_ERROR_TYPE_TRANSPORT_NOT_ALLOWED = 6; } message CheckVerificationCodeRequest { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java index 1b0db7350..ad8fe0838 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java @@ -68,6 +68,7 @@ import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient; import org.whispersystems.textsecuregcm.registration.RegistrationServiceException; import org.whispersystems.textsecuregcm.registration.RegistrationServiceSenderException; +import org.whispersystems.textsecuregcm.registration.TransportNotAllowedException; import org.whispersystems.textsecuregcm.registration.VerificationSession; import org.whispersystems.textsecuregcm.spam.ScoreThresholdProvider; import org.whispersystems.textsecuregcm.storage.Account; @@ -983,6 +984,38 @@ class VerificationControllerTest { } } + @Test + void requestVerificationCodeTransportNotAllowed() { + final String encodedSessionId = encodeSessionId(SESSION_ID); + final RegistrationServiceSession registrationServiceSession = new RegistrationServiceSession(SESSION_ID, NUMBER, + false, null, null, + null, SESSION_EXPIRATION_SECONDS); + when(registrationServiceClient.getSession(any(), any())) + .thenReturn(CompletableFuture.completedFuture(Optional.of(registrationServiceSession))); + when(verificationSessionManager.findForId(any())) + .thenReturn(CompletableFuture.completedFuture( + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), true, + clock.millis(), clock.millis(), registrationServiceSession.expiration())))); + when(registrationServiceClient.sendVerificationCode(any(), any(), any(), any(), any())) + .thenReturn(CompletableFuture.failedFuture( + new CompletionException(new TransportNotAllowedException(registrationServiceSession)))); + + final Invocation.Builder request = resources.getJerseyTest() + .target("/v1/verification/session/" + encodedSessionId + "/code") + .request() + .header(HttpHeaders.X_FORWARDED_FOR, "127.0.0.1"); + + try (final Response response = request.post(Entity.json(requestVerificationCodeJson("sms", "android")))) { + assertEquals(418, response.getStatus()); + + final VerificationSessionResponse verificationSessionResponse = + response.readEntity(VerificationSessionResponse.class); + + assertTrue(verificationSessionResponse.allowedToRequestCode()); + assertTrue(verificationSessionResponse.requestedInformation().isEmpty()); + } + } + @Test void requestVerificationCodeSuccess() { final String encodedSessionId = encodeSessionId(SESSION_ID);