From f5080f9bd62072fa9df47020a7ce9ae1db41c0e0 Mon Sep 17 00:00:00 2001 From: Ravi Khadiwala Date: Wed, 17 Jan 2024 11:01:25 -0600 Subject: [PATCH] Support configurable verification code sender overrides --- .../textsecuregcm/WhisperServerService.java | 6 +- .../controllers/VerificationController.java | 54 +++++++++++++-- .../RegistrationServiceClient.java | 5 ++ .../registration/VerificationSession.java | 20 ++++-- .../textsecuregcm/spam/ScoreThreshold.java | 5 +- .../spam/ScoreThresholdProvider.java | 9 ++- .../textsecuregcm/spam/SenderOverride.java | 61 +++++++++++++++++ .../spam/SenderOverrideProvider.java | 60 +++++++++++++++++ .../controllers/AccountControllerTest.java | 2 + .../controllers/ChallengeControllerTest.java | 2 + .../VerificationControllerTest.java | 67 ++++++++++--------- .../storage/VerificationSessionsTest.java | 6 +- 12 files changed, 244 insertions(+), 53 deletions(-) create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/spam/SenderOverride.java create mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/spam/SenderOverrideProvider.java diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 0a2fbd23c..48f9fda46 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -172,6 +172,7 @@ import org.whispersystems.textsecuregcm.spam.PushChallengeConfigProvider; import org.whispersystems.textsecuregcm.spam.RateLimitChallengeListener; import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider; import org.whispersystems.textsecuregcm.spam.ScoreThresholdProvider; +import org.whispersystems.textsecuregcm.spam.SenderOverrideProvider; import org.whispersystems.textsecuregcm.spam.SpamFilter; import org.whispersystems.textsecuregcm.storage.AccountLockManager; import org.whispersystems.textsecuregcm.storage.Accounts; @@ -886,8 +887,9 @@ public class WhisperServerService extends Application webSocketEnvironment, WebSocketEnvironment provisioningEnvironment) { List.of( - ScoreThresholdProvider.ScoreThresholdFeature.class, - PushChallengeConfigProvider.PushChallengeConfigFeature.class) + ScoreThresholdProvider.ScoreThresholdFeature.class, + SenderOverrideProvider.SenderOverrideFeature.class, + PushChallengeConfigProvider.PushChallengeConfigFeature.class) .forEach(feature -> { environment.jersey().register(feature); webSocketEnvironment.jersey().register(feature); 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 2e41c175b..cdbba86f9 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java @@ -82,6 +82,7 @@ import org.whispersystems.textsecuregcm.registration.VerificationSession; import org.whispersystems.textsecuregcm.spam.Extract; import org.whispersystems.textsecuregcm.spam.FilterSpam; import org.whispersystems.textsecuregcm.spam.ScoreThreshold; +import org.whispersystems.textsecuregcm.spam.SenderOverride; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager; @@ -184,7 +185,7 @@ public class VerificationController { } VerificationSession verificationSession = new VerificationSession(null, new ArrayList<>(), - Collections.emptyList(), false, + Collections.emptyList(), null, null, false, clock.millis(), clock.millis(), registrationServiceSession.expiration()); verificationSession = handlePushToken(pushTokenAndType, verificationSession); @@ -207,7 +208,8 @@ public class VerificationController { @HeaderParam(HttpHeaders.USER_AGENT) final String userAgent, @Context HttpServletRequest request, @NotNull @Valid final UpdateVerificationSessionRequest updateVerificationSessionRequest, - @NotNull @Extract final ScoreThreshold captchaScoreThreshold) { + @NotNull @Extract final ScoreThreshold scoreThreshold, + @NotNull @Extract final SenderOverride senderOverride) { final String sourceHost = useRemoteAddress ? request.getRemoteAddr() @@ -221,13 +223,16 @@ public class VerificationController { try { // these handle* methods ordered from least likely to fail to most, so take care when considering a change + + verificationSession = handleSenderOverrides(verificationSession, senderOverride); + verificationSession = handlePushToken(pushTokenAndType, verificationSession); verificationSession = handlePushChallenge(updateVerificationSessionRequest, registrationServiceSession, verificationSession); verificationSession = handleCaptcha(sourceHost, updateVerificationSessionRequest, registrationServiceSession, - verificationSession, userAgent, captchaScoreThreshold.getScoreThreshold()); + verificationSession, userAgent, scoreThreshold.getScoreThreshold()); } catch (final RateLimitExceededException e) { final Response response = buildResponseForRateLimitExceeded(verificationSession, registrationServiceSession, @@ -280,7 +285,8 @@ public class VerificationController { requestedInformation.addAll(verificationSession.requestedInformation()); verificationSession = new VerificationSession(generatePushChallenge(), requestedInformation, - verificationSession.submittedInformation(), verificationSession.allowedToRequestCode(), + verificationSession.submittedInformation(), verificationSession.smsSenderOverride(), + verificationSession.voiceSenderOverride(), verificationSession.allowedToRequestCode(), verificationSession.createdTimestamp(), clock.millis(), verificationSession.remoteExpirationSeconds() ); } @@ -348,7 +354,8 @@ public class VerificationController { && requestedInformation.isEmpty(); verificationSession = new VerificationSession(verificationSession.pushChallenge(), requestedInformation, - submittedInformation, allowedToRequestCode, verificationSession.createdTimestamp(), clock.millis(), + submittedInformation, verificationSession.smsSenderOverride(), verificationSession.voiceSenderOverride(), + allowedToRequestCode, verificationSession.createdTimestamp(), clock.millis(), verificationSession.remoteExpirationSeconds()); } else if (pushChallengePresent) { @@ -413,7 +420,8 @@ public class VerificationController { && requestedInformation.isEmpty(); verificationSession = new VerificationSession(verificationSession.pushChallenge(), requestedInformation, - submittedInformation, allowedToRequestCode, verificationSession.createdTimestamp(), clock.millis(), + submittedInformation, verificationSession.smsSenderOverride(), verificationSession.voiceSenderOverride(), + allowedToRequestCode, verificationSession.createdTimestamp(), clock.millis(), verificationSession.remoteExpirationSeconds()); } else { throw new ForbiddenException(); @@ -422,6 +430,31 @@ public class VerificationController { return verificationSession; } + /** + * Update the verification session with explicit sender overrides if present. When the session is used to send + * verification codes, these overrides will be used. + * + * @param verificationSession the session to update + * @param senderOverride configured sender overrides + * @return An updated {@link VerificationSession} + */ + private VerificationSession handleSenderOverrides( + VerificationSession verificationSession, + SenderOverride senderOverride) { + return new VerificationSession( + verificationSession.pushChallenge(), + verificationSession.requestedInformation(), + verificationSession.submittedInformation(), + Optional.ofNullable(verificationSession.smsSenderOverride()) + .or(senderOverride::getSmsSenderOverride).orElse(null), + Optional.ofNullable(verificationSession.voiceSenderOverride()) + .or(senderOverride::getVoiceSenderOverride) + .orElse(null), verificationSession.allowedToRequestCode(), + verificationSession.createdTimestamp(), + clock.millis(), + verificationSession.remoteExpirationSeconds()); + } + @GET @Path("/session/{sessionId}") @Produces(MediaType.APPLICATION_JSON) @@ -476,12 +509,19 @@ public class VerificationController { } }; + final String senderOverride = switch (messageTransport) { + case SMS -> verificationSession.smsSenderOverride(); + case VOICE -> verificationSession.voiceSenderOverride(); + }; + final RegistrationServiceSession resultSession; try { resultSession = registrationServiceClient.sendVerificationCode(registrationServiceSession.id(), messageTransport, clientType, - acceptLanguage.orElse(null), REGISTRATION_RPC_TIMEOUT).join(); + acceptLanguage.orElse(null), + senderOverride, + REGISTRATION_RPC_TIMEOUT).join(); } catch (final CancellationException e) { throw new ServerErrorException("registration service unavailable", Response.Status.SERVICE_UNAVAILABLE); } catch (final CompletionException e) { 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 e9333b85b..ed8eecfb5 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/registration/RegistrationServiceClient.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/registration/RegistrationServiceClient.java @@ -114,6 +114,7 @@ public class RegistrationServiceClient implements Managed { final MessageTransport messageTransport, final ClientType clientType, @Nullable final String acceptLanguage, + @Nullable final String senderOverride, final Duration timeout) { final SendVerificationCodeRequest.Builder requestBuilder = SendVerificationCodeRequest.newBuilder() @@ -125,6 +126,10 @@ public class RegistrationServiceClient implements Managed { requestBuilder.setAcceptLanguage(acceptLanguage); } + if (StringUtils.isNotBlank(senderOverride)) { + requestBuilder.setSenderName(senderOverride); + } + return toCompletableFuture(stub.withDeadline(toDeadline(timeout)) .sendVerificationCode(requestBuilder.build())) .thenApply(response -> { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/registration/VerificationSession.java b/service/src/main/java/org/whispersystems/textsecuregcm/registration/VerificationSession.java index d9f7c7810..b1514583e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/registration/VerificationSession.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/registration/VerificationSession.java @@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.registration; import com.fasterxml.jackson.annotation.JsonProperty; import java.time.Instant; +import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; import org.whispersystems.textsecuregcm.storage.SerializedExpireableJsonDynamoStore; @@ -20,6 +21,10 @@ import org.whispersystems.textsecuregcm.storage.SerializedExpireableJsonDynamoSt * @param pushChallenge the value of a push challenge sent to a client, after it submitted a push token * @param requestedInformation information requested that a client send to the server * @param submittedInformation information that a client has submitted and that the server has verified + * @param smsSenderOverride if present, indicates a sender override argument that should be forwarded to the + * Registration Service when requesting a code + * @param voiceSenderOverride if present, indicates a sender override argument that should be forwarded to the + * Registration Service when requesting a code * @param allowedToRequestCode whether the client is allowed to request a code. This request will be forwarded to * Registration Service * @param createdTimestamp when this session was created @@ -29,11 +34,16 @@ import org.whispersystems.textsecuregcm.storage.SerializedExpireableJsonDynamoSt * @see org.whispersystems.textsecuregcm.entities.RegistrationServiceSession * @see org.whispersystems.textsecuregcm.entities.VerificationSessionResponse */ -public record VerificationSession(@Nullable String pushChallenge, - List requestedInformation, List submittedInformation, - boolean allowedToRequestCode, long createdTimestamp, long updatedTimestamp, - long remoteExpirationSeconds) implements - SerializedExpireableJsonDynamoStore.Expireable { +public record VerificationSession( + @Nullable String pushChallenge, + List requestedInformation, + List submittedInformation, + @Nullable String smsSenderOverride, + @Nullable String voiceSenderOverride, + boolean allowedToRequestCode, + long createdTimestamp, + long updatedTimestamp, + long remoteExpirationSeconds) implements SerializedExpireableJsonDynamoStore.Expireable { @Override public long getExpirationEpochSeconds() { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/spam/ScoreThreshold.java b/service/src/main/java/org/whispersystems/textsecuregcm/spam/ScoreThreshold.java index e2d463435..d4b79c333 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/spam/ScoreThreshold.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/spam/ScoreThreshold.java @@ -12,10 +12,11 @@ import java.util.Optional; /** * A ScoreThreshold may be provided by an upstream request filter. If request contains a property for - * SCORE_THRESHOLD_PROPERTY_NAME it can be forwarded to a downstream filter to indicate it can use - * a more or less strict score threshold when evaluating whether a request should be allowed to continue. + * {@link #PROPERTY_NAME} it can be forwarded to a downstream filter to indicate it can use a more or less strict + * score threshold when evaluating whether a request should be allowed to continue. */ public class ScoreThreshold { + private static final Logger logger = LoggerFactory.getLogger(ScoreThreshold.class); public static final String PROPERTY_NAME = "scoreThreshold"; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/spam/ScoreThresholdProvider.java b/service/src/main/java/org/whispersystems/textsecuregcm/spam/ScoreThresholdProvider.java index 844294fa7..69b525472 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/spam/ScoreThresholdProvider.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/spam/ScoreThresholdProvider.java @@ -1,3 +1,7 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ package org.whispersystems.textsecuregcm.spam; import java.util.function.Function; @@ -11,11 +15,11 @@ import org.glassfish.jersey.server.spi.internal.ValueParamProvider; /** * Parses a {@link ScoreThreshold} out of a {@link ContainerRequest} to provide to jersey resources. - * + *

* A request filter may enrich a ContainerRequest with a scoreThreshold by providing a float property with the name * {@link ScoreThreshold#PROPERTY_NAME}. This indicates the desired scoreThreshold to use when evaluating whether a * request should proceed. - * + *

* A resource can consume a ScoreThreshold with by annotating a ScoreThreshold parameter with {@link Extract} */ public class ScoreThresholdProvider implements ValueParamProvider { @@ -24,6 +28,7 @@ public class ScoreThresholdProvider implements ValueParamProvider { * Configures the ScoreThresholdProvider */ public static class ScoreThresholdFeature implements Feature { + @Override public boolean configure(FeatureContext context) { context.register(new AbstractBinder() { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/spam/SenderOverride.java b/service/src/main/java/org/whispersystems/textsecuregcm/spam/SenderOverride.java new file mode 100644 index 000000000..0cfa77122 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/spam/SenderOverride.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.spam; + +import org.glassfish.jersey.server.ContainerRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.Optional; + +/** + * A SenderOverride may be provided by an upstream request filter. If request contains a property for + * {@link #SMS_SENDER_OVERRIDE_PROPERTY_NAME} or {@link #VOICE_SENDER_OVERRIDE_PROPERTY_NAME} it can be + * forwarded to a downstream filter to indicate a specific sender should be used when sending verification codes. + */ +public class SenderOverride { + + private static final Logger logger = LoggerFactory.getLogger(SenderOverride.class); + public static final String SMS_SENDER_OVERRIDE_PROPERTY_NAME = "smsSenderOverride"; + public static final String VOICE_SENDER_OVERRIDE_PROPERTY_NAME = "voiceSenderOverride"; + + /** + * The name of the sender to use to deliver a verification code via SMS + */ + private final Optional smsSenderOverride; + + /** + * The name of the sender to use to deliver a verification code via voice + */ + private final Optional voiceSenderOverride; + + public SenderOverride(final ContainerRequest containerRequest) { + this.smsSenderOverride = parse(String.class, SMS_SENDER_OVERRIDE_PROPERTY_NAME, containerRequest); + this.voiceSenderOverride = parse(String.class, VOICE_SENDER_OVERRIDE_PROPERTY_NAME, containerRequest); + } + + private static Optional parse(Class type, final String propertyName, + final ContainerRequest containerRequest) { + return Optional + .ofNullable(containerRequest.getProperty(propertyName)) + .flatMap(obj -> { + if (type.isInstance(obj)) { + return Optional.of(type.cast(obj)); + } + logger.warn("invalid format for filter provided property {}: {}", propertyName, obj); + return Optional.empty(); + }); + } + + + public Optional getSmsSenderOverride() { + return smsSenderOverride; + } + + public Optional getVoiceSenderOverride() { + return voiceSenderOverride; + } + +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/spam/SenderOverrideProvider.java b/service/src/main/java/org/whispersystems/textsecuregcm/spam/SenderOverrideProvider.java new file mode 100644 index 000000000..801cb4ec9 --- /dev/null +++ b/service/src/main/java/org/whispersystems/textsecuregcm/spam/SenderOverrideProvider.java @@ -0,0 +1,60 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.spam; + +import java.util.function.Function; +import javax.inject.Singleton; +import javax.ws.rs.core.Feature; +import javax.ws.rs.core.FeatureContext; +import org.glassfish.jersey.internal.inject.AbstractBinder; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.model.Parameter; +import org.glassfish.jersey.server.spi.internal.ValueParamProvider; + +/** + * Parses a {@link SenderOverride} out of a {@link ContainerRequest} to provide to jersey resources. + *

+ * A request filter may enrich a ContainerRequest with senderOverrides by providing a string property names defined in + * {@link SenderOverride}. This indicates the desired senderOverride to use when sending verification codes. + *

+ * A resource can consume a SenderOverride with by annotating a SenderOverride parameter with {@link Extract} + */ +public class SenderOverrideProvider implements ValueParamProvider { + + /** + * Configures the SenderOverrideProvider + */ + public static class SenderOverrideFeature implements Feature { + + @Override + public boolean configure(FeatureContext context) { + context.register(new AbstractBinder() { + @Override + protected void configure() { + bind(SenderOverrideProvider.class) + .to(ValueParamProvider.class) + .in(Singleton.class); + } + }); + return true; + } + } + + @Override + public Function getValueProvider(final Parameter parameter) { + if (parameter.getRawType().equals(SenderOverride.class) + && parameter.isAnnotationPresent(Extract.class)) { + return SenderOverride::new; + } + return null; + + } + + @Override + public PriorityType getPriority() { + return Priority.HIGH; + } +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java index c85f65ef5..0ef36844b 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/AccountControllerTest.java @@ -79,6 +79,7 @@ import org.whispersystems.textsecuregcm.mappers.JsonMappingExceptionMapper; import org.whispersystems.textsecuregcm.mappers.NonNormalizedPhoneNumberExceptionMapper; import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper; import org.whispersystems.textsecuregcm.spam.ScoreThresholdProvider; +import org.whispersystems.textsecuregcm.spam.SenderOverrideProvider; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager; @@ -149,6 +150,7 @@ class AccountControllerTest { .addProvider(new NonNormalizedPhoneNumberExceptionMapper()) .addProvider(new RateLimitByIpFilter(rateLimiters, true)) .addProvider(ScoreThresholdProvider.ScoreThresholdFeature.class) + .addProvider(SenderOverrideProvider.SenderOverrideFeature.class) .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource(new AccountController( diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ChallengeControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ChallengeControllerTest.java index 9288cfc89..24493f20a 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ChallengeControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/ChallengeControllerTest.java @@ -43,6 +43,8 @@ import org.whispersystems.textsecuregcm.push.NotPushRegisteredException; import org.whispersystems.textsecuregcm.spam.PushChallengeConfigProvider; import org.whispersystems.textsecuregcm.spam.ScoreThreshold; import org.whispersystems.textsecuregcm.spam.ScoreThresholdProvider; +import org.whispersystems.textsecuregcm.spam.SenderOverride; +import org.whispersystems.textsecuregcm.spam.SenderOverrideProvider; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.util.SystemMapper; 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 6f6c46051..7d3f03cb4 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/VerificationControllerTest.java @@ -74,6 +74,7 @@ import org.whispersystems.textsecuregcm.registration.RegistrationServiceSenderEx import org.whispersystems.textsecuregcm.registration.TransportNotAllowedException; import org.whispersystems.textsecuregcm.registration.VerificationSession; import org.whispersystems.textsecuregcm.spam.ScoreThresholdProvider; +import org.whispersystems.textsecuregcm.spam.SenderOverrideProvider; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; @@ -112,6 +113,7 @@ class VerificationControllerTest { .addProvider(new NonNormalizedPhoneNumberExceptionMapper()) .addProvider(new RegistrationServiceSenderExceptionMapper()) .addProvider(ScoreThresholdProvider.ScoreThresholdFeature.class) + .addProvider(SenderOverrideProvider.SenderOverrideFeature.class) .setMapper(SystemMapper.jsonMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .addResource( @@ -313,7 +315,7 @@ class VerificationControllerTest { .thenReturn(CompletableFuture.completedFuture( Optional.of( new VerificationSession(null, List.of(VerificationSession.Information.CAPTCHA), Collections.emptyList(), - false, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); + null, null, false, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); when(verificationSessionManager.update(any(), any())) .thenReturn(CompletableFuture.completedFuture(null)); @@ -356,7 +358,7 @@ class VerificationControllerTest { registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), false, + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), null, null, false, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); when(verificationSessionManager.update(any(), any())) @@ -392,7 +394,7 @@ class VerificationControllerTest { registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), false, + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), null, null, false, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); when(verificationSessionManager.update(any(), any())) @@ -429,7 +431,7 @@ class VerificationControllerTest { when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( Optional.of(new VerificationSession("challenge", List.of(VerificationSession.Information.PUSH_CHALLENGE), - Collections.emptyList(), false, clock.millis(), clock.millis(), + Collections.emptyList(), null, null, false, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); when(verificationSessionManager.update(any(), any())) .thenReturn(CompletableFuture.completedFuture(null)); @@ -463,7 +465,7 @@ class VerificationControllerTest { when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( Optional.of(new VerificationSession(null, List.of(VerificationSession.Information.CAPTCHA), - Collections.emptyList(), false, clock.millis(), clock.millis(), + Collections.emptyList(), null, null, false, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); when(registrationCaptchaManager.assessCaptcha(any(), any())) @@ -511,7 +513,8 @@ class VerificationControllerTest { .thenReturn(CompletableFuture.completedFuture( Optional.of(new VerificationSession("challenge", List.of(VerificationSession.Information.CAPTCHA), - List.of(VerificationSession.Information.PUSH_CHALLENGE), false, + List.of(VerificationSession.Information.PUSH_CHALLENGE), + null, null, false, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); when(verificationSessionManager.update(any(), any())) .thenReturn(CompletableFuture.completedFuture(null)); @@ -553,8 +556,8 @@ class VerificationControllerTest { Optional.of(registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession("challenge", List.of(), List.of(), true, clock.millis(), clock.millis(), - registrationServiceSession.expiration())))); + Optional.of(new VerificationSession("challenge", List.of(), List.of(), null, null, true, + clock.millis(), clock.millis(), registrationServiceSession.expiration())))); when(verificationSessionManager.update(any(), any())) .thenReturn(CompletableFuture.completedFuture(null)); @@ -591,7 +594,7 @@ class VerificationControllerTest { .thenReturn(CompletableFuture.completedFuture( Optional.of(new VerificationSession("challenge", List.of(VerificationSession.Information.PUSH_CHALLENGE, VerificationSession.Information.CAPTCHA), - Collections.emptyList(), false, clock.millis(), clock.millis(), + Collections.emptyList(), null, null, false, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); when(verificationSessionManager.update(any(), any())) .thenReturn(CompletableFuture.completedFuture(null)); @@ -634,7 +637,7 @@ class VerificationControllerTest { when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( Optional.of(new VerificationSession(null, List.of(VerificationSession.Information.CAPTCHA), - Collections.emptyList(), false, clock.millis(), clock.millis(), + Collections.emptyList(), null, null, false, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); when(registrationCaptchaManager.assessCaptcha(any(), any())) @@ -682,7 +685,7 @@ class VerificationControllerTest { .thenReturn(CompletableFuture.completedFuture( Optional.of(new VerificationSession("challenge", List.of(VerificationSession.Information.CAPTCHA, VerificationSession.Information.CAPTCHA), - Collections.emptyList(), false, clock.millis(), clock.millis(), + Collections.emptyList(), null, null, false, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); when(registrationCaptchaManager.assessCaptcha(any(), any())) @@ -729,7 +732,7 @@ class VerificationControllerTest { .thenReturn(CompletableFuture.completedFuture( Optional.of(new VerificationSession(null, List.of(VerificationSession.Information.CAPTCHA), - Collections.emptyList(), false, clock.millis(), clock.millis(), + Collections.emptyList(), null, null, false, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); when(registrationCaptchaManager.assessCaptcha(any(), any())) @@ -884,9 +887,9 @@ class VerificationControllerTest { .thenReturn(CompletableFuture.completedFuture(Optional.of(registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), true, + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), null, null, true, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); - when(registrationServiceClient.sendVerificationCode(any(), any(), any(), any(), any())) + when(registrationServiceClient.sendVerificationCode(any(), any(), any(), any(), any(), any())) .thenReturn(CompletableFuture.completedFuture(registrationServiceSession)); final Invocation.Builder request = resources.getJerseyTest() @@ -915,7 +918,7 @@ class VerificationControllerTest { registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture(Optional.of(new VerificationSession(null, List.of( - VerificationSession.Information.CAPTCHA), Collections.emptyList(), false, clock.millis(), clock.millis(), + VerificationSession.Information.CAPTCHA), Collections.emptyList(), null, null, false, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); final Invocation.Builder request = resources.getJerseyTest() @@ -946,7 +949,7 @@ class VerificationControllerTest { registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), false, + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), null, null, false, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); final Invocation.Builder request = resources.getJerseyTest() @@ -974,9 +977,9 @@ class VerificationControllerTest { .thenReturn(CompletableFuture.completedFuture(Optional.of(registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), true, + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), null, null, true, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); - when(registrationServiceClient.sendVerificationCode(any(), any(), any(), any(), any())) + when(registrationServiceClient.sendVerificationCode(any(), any(), any(), any(), any(), any())) .thenReturn(CompletableFuture.failedFuture( new CompletionException(new VerificationSessionRateLimitExceededException(registrationServiceSession, Duration.ofMinutes(1), true)))); @@ -1006,9 +1009,9 @@ class VerificationControllerTest { .thenReturn(CompletableFuture.completedFuture(Optional.of(registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), true, + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), null, null, true, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); - when(registrationServiceClient.sendVerificationCode(any(), any(), any(), any(), any())) + when(registrationServiceClient.sendVerificationCode(any(), any(), any(), any(), any(), any())) .thenReturn(CompletableFuture.failedFuture( new CompletionException(new TransportNotAllowedException(registrationServiceSession)))); @@ -1038,9 +1041,9 @@ class VerificationControllerTest { .thenReturn(CompletableFuture.completedFuture(Optional.of(registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), true, + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), null, null, true, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); - when(registrationServiceClient.sendVerificationCode(any(), any(), any(), any(), any())) + when(registrationServiceClient.sendVerificationCode(any(), any(), any(), any(), any(), any())) .thenReturn(CompletableFuture.completedFuture(registrationServiceSession)); final Invocation.Builder request = resources.getJerseyTest() @@ -1071,10 +1074,10 @@ class VerificationControllerTest { Optional.of(registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), true, + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), null, null, true, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); - when(registrationServiceClient.sendVerificationCode(any(), any(), any(), any(), any())) + when(registrationServiceClient.sendVerificationCode(any(), any(), any(), any(), any(), any())) .thenReturn( CompletableFuture.failedFuture(new CompletionException(exception))); @@ -1115,10 +1118,10 @@ class VerificationControllerTest { .thenReturn(CompletableFuture.completedFuture(Optional.of(registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), true, + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), null, null, true, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); - when(registrationServiceClient.sendVerificationCode(any(), any(), any(), any(), any())) + when(registrationServiceClient.sendVerificationCode(any(), any(), any(), any(), any(), any())) .thenReturn(CompletableFuture.failedFuture(new CompletionException( new RegistrationFraudException(RegistrationServiceSenderException.rejected(true))))); @@ -1149,7 +1152,7 @@ class VerificationControllerTest { Optional.of(registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), true, + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), null, null, true, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); when(registrationServiceClient.checkVerificationCode(any(), any(), any())) @@ -1176,7 +1179,7 @@ class VerificationControllerTest { Optional.of(registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), true, + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), null, null, true, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); final Invocation.Builder request = resources.getJerseyTest() @@ -1211,7 +1214,7 @@ class VerificationControllerTest { Optional.of(registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), true, + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), null, null, true, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); // There is no explicit indication in the exception that no code has been sent, but we treat all RegistrationServiceExceptions @@ -1248,7 +1251,7 @@ class VerificationControllerTest { Optional.of(registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), true, + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), null, null, true, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); when(registrationServiceClient.checkVerificationCode(any(), any(), any())) @@ -1274,7 +1277,7 @@ class VerificationControllerTest { Optional.of(registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), true, + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), null, null, true, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); when(registrationServiceClient.checkVerificationCode(any(), any(), any())) .thenReturn(CompletableFuture.failedFuture( @@ -1306,7 +1309,7 @@ class VerificationControllerTest { Optional.of(registrationServiceSession))); when(verificationSessionManager.findForId(any())) .thenReturn(CompletableFuture.completedFuture( - Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), true, + Optional.of(new VerificationSession(null, Collections.emptyList(), Collections.emptyList(), null, null, true, clock.millis(), clock.millis(), registrationServiceSession.expiration())))); final RegistrationServiceSession verifiedSession = new RegistrationServiceSession(SESSION_ID, NUMBER, true, null, diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationSessionsTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationSessionsTest.java index 4d6042b85..0c02a29d5 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationSessionsTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationSessionsTest.java @@ -47,7 +47,7 @@ class VerificationSessionsTest { final Duration remoteExpiration = Duration.ofMinutes(2); final VerificationSession verificationSession = new VerificationSession(null, - List.of(VerificationSession.Information.PUSH_CHALLENGE), Collections.emptyList(), true, + List.of(VerificationSession.Information.PUSH_CHALLENGE), Collections.emptyList(), null, null, true, created.toEpochMilli(), updates.toEpochMilli(), remoteExpiration.toSeconds()); assertEquals(updates.plus(remoteExpiration).getEpochSecond(), verificationSession.getExpirationEpochSeconds()); @@ -64,7 +64,7 @@ class VerificationSessionsTest { assertTrue(absentSession.isEmpty()); final VerificationSession session = new VerificationSession(null, - List.of(VerificationSession.Information.PUSH_CHALLENGE), Collections.emptyList(), true, + List.of(VerificationSession.Information.PUSH_CHALLENGE), Collections.emptyList(), null, null, true, clock.millis(), clock.millis(), Duration.ofMinutes(1).toSeconds()); verificationSessions.insert(sessionId, session).join(); @@ -79,7 +79,7 @@ class VerificationSessionsTest { "inserting with the same key should fail conditional checks"); final VerificationSession updatedSession = new VerificationSession(null, Collections.emptyList(), - List.of(VerificationSession.Information.PUSH_CHALLENGE), true, clock.millis(), clock.millis(), + List.of(VerificationSession.Information.PUSH_CHALLENGE), null, null, true, clock.millis(), clock.millis(), Duration.ofMinutes(2).toSeconds()); verificationSessions.update(sessionId, updatedSession).join();