handle new RegistrationService proto error
This commit is contained in:
parent
9d3d4a3698
commit
ca47a7b663
|
@ -844,7 +844,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||||
config.getCdnConfiguration().bucket()),
|
config.getCdnConfiguration().bucket()),
|
||||||
new VerificationController(registrationServiceClient, new VerificationSessionManager(verificationSessions),
|
new VerificationController(registrationServiceClient, new VerificationSessionManager(verificationSessions),
|
||||||
pushNotificationManager, registrationCaptchaManager, registrationRecoveryPasswordsManager, rateLimiters,
|
pushNotificationManager, registrationCaptchaManager, registrationRecoveryPasswordsManager, rateLimiters,
|
||||||
accountsManager, useRemoteAddress, clock)
|
accountsManager, useRemoteAddress, dynamicConfigurationManager, clock)
|
||||||
);
|
);
|
||||||
if (config.getSubscription() != null && config.getOneTimeDonations() != null) {
|
if (config.getSubscription() != null && config.getOneTimeDonations() != null) {
|
||||||
commonControllers.add(new SubscriptionController(clock, config.getSubscription(), config.getOneTimeDonations(),
|
commonControllers.add(new SubscriptionController(clock, config.getSubscription(), config.getOneTimeDonations(),
|
||||||
|
|
|
@ -59,6 +59,11 @@ public class DynamicConfiguration {
|
||||||
@Valid
|
@Valid
|
||||||
DynamicInboundMessageByteLimitConfiguration inboundMessageByteLimit = new DynamicInboundMessageByteLimitConfiguration(true);
|
DynamicInboundMessageByteLimitConfiguration inboundMessageByteLimit = new DynamicInboundMessageByteLimitConfiguration(true);
|
||||||
|
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@Valid
|
||||||
|
DynamicRegistrationConfiguration registrationConfiguration = new DynamicRegistrationConfiguration(false);
|
||||||
|
|
||||||
public Optional<DynamicExperimentEnrollmentConfiguration> getExperimentEnrollmentConfiguration(
|
public Optional<DynamicExperimentEnrollmentConfiguration> getExperimentEnrollmentConfiguration(
|
||||||
final String experimentName) {
|
final String experimentName) {
|
||||||
return Optional.ofNullable(experiments.get(experimentName));
|
return Optional.ofNullable(experiments.get(experimentName));
|
||||||
|
@ -104,4 +109,8 @@ public class DynamicConfiguration {
|
||||||
public DynamicInboundMessageByteLimitConfiguration getInboundMessageByteLimitConfiguration() {
|
public DynamicInboundMessageByteLimitConfiguration getInboundMessageByteLimitConfiguration() {
|
||||||
return inboundMessageByteLimit;
|
return inboundMessageByteLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DynamicRegistrationConfiguration getRegistrationConfiguration() {
|
||||||
|
return registrationConfiguration;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
package org.whispersystems.textsecuregcm.configuration.dynamic;
|
||||||
|
|
||||||
|
public record DynamicRegistrationConfiguration(boolean squashDeclinedAttemptErrors) {}
|
|
@ -59,6 +59,7 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.textsecuregcm.captcha.AssessmentResult;
|
import org.whispersystems.textsecuregcm.captcha.AssessmentResult;
|
||||||
import org.whispersystems.textsecuregcm.captcha.RegistrationCaptchaManager;
|
import org.whispersystems.textsecuregcm.captcha.RegistrationCaptchaManager;
|
||||||
|
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.entities.CreateVerificationSessionRequest;
|
import org.whispersystems.textsecuregcm.entities.CreateVerificationSessionRequest;
|
||||||
import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession;
|
import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession;
|
||||||
import org.whispersystems.textsecuregcm.entities.SubmitVerificationCodeRequest;
|
import org.whispersystems.textsecuregcm.entities.SubmitVerificationCodeRequest;
|
||||||
|
@ -72,6 +73,7 @@ import org.whispersystems.textsecuregcm.push.PushNotification;
|
||||||
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
|
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
|
||||||
import org.whispersystems.textsecuregcm.registration.ClientType;
|
import org.whispersystems.textsecuregcm.registration.ClientType;
|
||||||
import org.whispersystems.textsecuregcm.registration.MessageTransport;
|
import org.whispersystems.textsecuregcm.registration.MessageTransport;
|
||||||
|
import org.whispersystems.textsecuregcm.registration.RegistrationFraudException;
|
||||||
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
|
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
|
||||||
import org.whispersystems.textsecuregcm.registration.RegistrationServiceException;
|
import org.whispersystems.textsecuregcm.registration.RegistrationServiceException;
|
||||||
import org.whispersystems.textsecuregcm.registration.RegistrationServiceSenderException;
|
import org.whispersystems.textsecuregcm.registration.RegistrationServiceSenderException;
|
||||||
|
@ -81,6 +83,7 @@ import org.whispersystems.textsecuregcm.spam.Extract;
|
||||||
import org.whispersystems.textsecuregcm.spam.FilterSpam;
|
import org.whispersystems.textsecuregcm.spam.FilterSpam;
|
||||||
import org.whispersystems.textsecuregcm.spam.ScoreThreshold;
|
import org.whispersystems.textsecuregcm.spam.ScoreThreshold;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
|
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
|
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
|
||||||
import org.whispersystems.textsecuregcm.util.ExceptionUtils;
|
import org.whispersystems.textsecuregcm.util.ExceptionUtils;
|
||||||
|
@ -119,6 +122,7 @@ public class VerificationController {
|
||||||
private final AccountsManager accountsManager;
|
private final AccountsManager accountsManager;
|
||||||
|
|
||||||
private final boolean useRemoteAddress;
|
private final boolean useRemoteAddress;
|
||||||
|
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
|
||||||
public VerificationController(final RegistrationServiceClient registrationServiceClient,
|
public VerificationController(final RegistrationServiceClient registrationServiceClient,
|
||||||
|
@ -129,6 +133,7 @@ public class VerificationController {
|
||||||
final RateLimiters rateLimiters,
|
final RateLimiters rateLimiters,
|
||||||
final AccountsManager accountsManager,
|
final AccountsManager accountsManager,
|
||||||
final boolean useRemoteAddress,
|
final boolean useRemoteAddress,
|
||||||
|
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
|
||||||
final Clock clock) {
|
final Clock clock) {
|
||||||
this.registrationServiceClient = registrationServiceClient;
|
this.registrationServiceClient = registrationServiceClient;
|
||||||
this.verificationSessionManager = verificationSessionManager;
|
this.verificationSessionManager = verificationSessionManager;
|
||||||
|
@ -138,6 +143,7 @@ public class VerificationController {
|
||||||
this.rateLimiters = rateLimiters;
|
this.rateLimiters = rateLimiters;
|
||||||
this.accountsManager = accountsManager;
|
this.accountsManager = accountsManager;
|
||||||
this.useRemoteAddress = useRemoteAddress;
|
this.useRemoteAddress = useRemoteAddress;
|
||||||
|
this.dynamicConfigurationManager = dynamicConfigurationManager;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -501,10 +507,14 @@ public class VerificationController {
|
||||||
})
|
})
|
||||||
.orElseGet(NotFoundException::new);
|
.orElseGet(NotFoundException::new);
|
||||||
|
|
||||||
|
} else if (unwrappedException instanceof RegistrationFraudException) {
|
||||||
|
if (dynamicConfigurationManager.getConfiguration().getRegistrationConfiguration().squashDeclinedAttemptErrors()) {
|
||||||
|
return buildResponse(registrationServiceSession, verificationSession);
|
||||||
|
} else {
|
||||||
|
throw unwrappedException.getCause();
|
||||||
|
}
|
||||||
} else if (unwrappedException instanceof RegistrationServiceSenderException) {
|
} else if (unwrappedException instanceof RegistrationServiceSenderException) {
|
||||||
|
|
||||||
throw unwrappedException;
|
throw unwrappedException;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
logger.error("Registration service failure", unwrappedException);
|
logger.error("Registration service failure", unwrappedException);
|
||||||
throw new ServerErrorException(Response.Status.INTERNAL_SERVER_ERROR);
|
throw new ServerErrorException(Response.Status.INTERNAL_SERVER_ERROR);
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.whispersystems.textsecuregcm.registration;
|
||||||
|
|
||||||
|
public class RegistrationFraudException extends Exception {
|
||||||
|
public RegistrationFraudException(final RegistrationServiceSenderException cause) {
|
||||||
|
super(null, cause, true, false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -146,6 +146,9 @@ public class RegistrationServiceClient implements Managed {
|
||||||
|
|
||||||
case SEND_VERIFICATION_CODE_ERROR_TYPE_SENDER_REJECTED -> throw new CompletionException(
|
case SEND_VERIFICATION_CODE_ERROR_TYPE_SENDER_REJECTED -> throw new CompletionException(
|
||||||
RegistrationServiceSenderException.rejected(response.getError().getMayRetry()));
|
RegistrationServiceSenderException.rejected(response.getError().getMayRetry()));
|
||||||
|
case SEND_VERIFICATION_CODE_ERROR_TYPE_SUSPECTED_FRAUD ->
|
||||||
|
throw new CompletionException(new RegistrationFraudException(
|
||||||
|
RegistrationServiceSenderException.rejected(response.getError().getMayRetry())));
|
||||||
case SEND_VERIFICATION_CODE_ERROR_TYPE_SENDER_ILLEGAL_ARGUMENT -> throw new CompletionException(
|
case SEND_VERIFICATION_CODE_ERROR_TYPE_SENDER_ILLEGAL_ARGUMENT -> throw new CompletionException(
|
||||||
RegistrationServiceSenderException.illegalArgument(response.getError().getMayRetry()));
|
RegistrationServiceSenderException.illegalArgument(response.getError().getMayRetry()));
|
||||||
case SEND_VERIFICATION_CODE_ERROR_TYPE_UNSPECIFIED -> throw new CompletionException(
|
case SEND_VERIFICATION_CODE_ERROR_TYPE_UNSPECIFIED -> throw new CompletionException(
|
||||||
|
|
|
@ -323,6 +323,12 @@ enum SendVerificationCodeErrorType {
|
||||||
* transport.
|
* transport.
|
||||||
*/
|
*/
|
||||||
SEND_VERIFICATION_CODE_ERROR_TYPE_TRANSPORT_NOT_ALLOWED = 6;
|
SEND_VERIFICATION_CODE_ERROR_TYPE_TRANSPORT_NOT_ALLOWED = 6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sender declined to send the verification code due to suspected fraud
|
||||||
|
*/
|
||||||
|
SEND_VERIFICATION_CODE_ERROR_TYPE_SUSPECTED_FRAUD = 7;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message CheckVerificationCodeRequest {
|
message CheckVerificationCodeRequest {
|
||||||
|
|
|
@ -56,6 +56,8 @@ import org.junit.jupiter.params.provider.ValueSource;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.whispersystems.textsecuregcm.captcha.AssessmentResult;
|
import org.whispersystems.textsecuregcm.captcha.AssessmentResult;
|
||||||
import org.whispersystems.textsecuregcm.captcha.RegistrationCaptchaManager;
|
import org.whispersystems.textsecuregcm.captcha.RegistrationCaptchaManager;
|
||||||
|
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||||
|
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicRegistrationConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession;
|
import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession;
|
||||||
import org.whispersystems.textsecuregcm.entities.VerificationSessionResponse;
|
import org.whispersystems.textsecuregcm.entities.VerificationSessionResponse;
|
||||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||||
|
@ -65,6 +67,7 @@ import org.whispersystems.textsecuregcm.mappers.NonNormalizedPhoneNumberExceptio
|
||||||
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
|
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
|
||||||
import org.whispersystems.textsecuregcm.mappers.RegistrationServiceSenderExceptionMapper;
|
import org.whispersystems.textsecuregcm.mappers.RegistrationServiceSenderExceptionMapper;
|
||||||
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
|
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
|
||||||
|
import org.whispersystems.textsecuregcm.registration.RegistrationFraudException;
|
||||||
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
|
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
|
||||||
import org.whispersystems.textsecuregcm.registration.RegistrationServiceException;
|
import org.whispersystems.textsecuregcm.registration.RegistrationServiceException;
|
||||||
import org.whispersystems.textsecuregcm.registration.RegistrationServiceSenderException;
|
import org.whispersystems.textsecuregcm.registration.RegistrationServiceSenderException;
|
||||||
|
@ -73,6 +76,7 @@ import org.whispersystems.textsecuregcm.registration.VerificationSession;
|
||||||
import org.whispersystems.textsecuregcm.spam.ScoreThresholdProvider;
|
import org.whispersystems.textsecuregcm.spam.ScoreThresholdProvider;
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
|
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
|
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
|
||||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||||
|
@ -97,6 +101,9 @@ class VerificationControllerTest {
|
||||||
|
|
||||||
private final RateLimiter captchaLimiter = mock(RateLimiter.class);
|
private final RateLimiter captchaLimiter = mock(RateLimiter.class);
|
||||||
private final RateLimiter pushChallengeLimiter = mock(RateLimiter.class);
|
private final RateLimiter pushChallengeLimiter = mock(RateLimiter.class);
|
||||||
|
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager = mock(
|
||||||
|
DynamicConfigurationManager.class);
|
||||||
|
private final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
|
||||||
|
|
||||||
private final ResourceExtension resources = ResourceExtension.builder()
|
private final ResourceExtension resources = ResourceExtension.builder()
|
||||||
.addProperty(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE)
|
.addProperty(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE)
|
||||||
|
@ -110,7 +117,7 @@ class VerificationControllerTest {
|
||||||
.addResource(
|
.addResource(
|
||||||
new VerificationController(registrationServiceClient, verificationSessionManager, pushNotificationManager,
|
new VerificationController(registrationServiceClient, verificationSessionManager, pushNotificationManager,
|
||||||
registrationCaptchaManager, registrationRecoveryPasswordsManager, rateLimiters, accountsManager, true,
|
registrationCaptchaManager, registrationRecoveryPasswordsManager, rateLimiters, accountsManager, true,
|
||||||
clock))
|
dynamicConfigurationManager, clock))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
@ -119,8 +126,12 @@ class VerificationControllerTest {
|
||||||
.thenReturn(captchaLimiter);
|
.thenReturn(captchaLimiter);
|
||||||
when(rateLimiters.getVerificationPushChallengeLimiter())
|
when(rateLimiters.getVerificationPushChallengeLimiter())
|
||||||
.thenReturn(pushChallengeLimiter);
|
.thenReturn(pushChallengeLimiter);
|
||||||
|
when(accountsManager.getByE164(any()))
|
||||||
when(accountsManager.getByE164(any())).thenReturn(Optional.empty());
|
.thenReturn(Optional.empty());
|
||||||
|
when(dynamicConfiguration.getRegistrationConfiguration())
|
||||||
|
.thenReturn(new DynamicRegistrationConfiguration(false));
|
||||||
|
when(dynamicConfigurationManager.getConfiguration())
|
||||||
|
.thenReturn(dynamicConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
|
@ -1089,6 +1100,44 @@ class VerificationControllerTest {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(booleans = {false, true})
|
||||||
|
void fraudError(boolean shadowFailure) {
|
||||||
|
if (shadowFailure) {
|
||||||
|
when(this.dynamicConfiguration.getRegistrationConfiguration())
|
||||||
|
.thenReturn(new DynamicRegistrationConfiguration(true));
|
||||||
|
}
|
||||||
|
final String encodedSessionId = encodeSessionId(SESSION_ID);
|
||||||
|
final RegistrationServiceSession registrationServiceSession = new RegistrationServiceSession(SESSION_ID, NUMBER,
|
||||||
|
false, null, null, 0L,
|
||||||
|
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 RegistrationFraudException(RegistrationServiceSenderException.rejected(true)))));
|
||||||
|
|
||||||
|
final Invocation.Builder request = resources.getJerseyTest()
|
||||||
|
.target("/v1/verification/session/" + encodedSessionId + "/code")
|
||||||
|
.request()
|
||||||
|
.header(HttpHeaders.X_FORWARDED_FOR, "127.0.0.1");
|
||||||
|
try (Response response = request.post(Entity.json(requestVerificationCodeJson("voice", "ios")))) {
|
||||||
|
if (shadowFailure) {
|
||||||
|
assertEquals(200, response.getStatus());
|
||||||
|
} else {
|
||||||
|
assertEquals(RegistrationServiceSenderExceptionMapper.REMOTE_SERVICE_REJECTED_REQUEST_STATUS, response.getStatus());
|
||||||
|
final Map<String, Object> responseMap = response.readEntity(Map.class);
|
||||||
|
assertEquals("providerRejected", responseMap.get("reason"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void verifyCodeServerError() {
|
void verifyCodeServerError() {
|
||||||
final String encodedSessionId = encodeSessionId(SESSION_ID);
|
final String encodedSessionId = encodeSessionId(SESSION_ID);
|
||||||
|
|
Loading…
Reference in New Issue