Add filter-provided captcha score thresholds

This commit is contained in:
Ravi Khadiwala 2023-03-21 17:26:42 -05:00 committed by ravi-signal
parent a8eb27940d
commit ee53260d72
16 changed files with 280 additions and 50 deletions

View File

@ -166,6 +166,7 @@ import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
import org.whispersystems.textsecuregcm.spam.FilterSpam;
import org.whispersystems.textsecuregcm.spam.RateLimitChallengeListener;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider;
import org.whispersystems.textsecuregcm.spam.ScoreThresholdProvider;
import org.whispersystems.textsecuregcm.spam.SpamFilter;
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
import org.whispersystems.textsecuregcm.storage.AccountCleaner;
@ -780,6 +781,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
webSocketEnvironment.jersey().register(controller);
}
WebSocketEnvironment<AuthenticatedAccount> provisioningEnvironment = new WebSocketEnvironment<>(environment,
webSocketEnvironment.getRequestLog(), 60000);
provisioningEnvironment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager));
@ -789,6 +791,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
registerCorsFilter(environment);
registerExceptionMappers(environment, webSocketEnvironment, provisioningEnvironment);
registerProviders(environment, webSocketEnvironment, provisioningEnvironment);
environment.jersey().property(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE);
webSocketEnvironment.jersey().property(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE);
@ -830,6 +833,15 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
GarbageCollectionGauges.registerMetrics();
}
private void registerProviders(Environment environment,
WebSocketEnvironment<AuthenticatedAccount> webSocketEnvironment,
WebSocketEnvironment<AuthenticatedAccount> provisioningEnvironment) {
environment.jersey().register(ScoreThresholdProvider.ScoreThresholdFeature.class);
webSocketEnvironment.jersey().register(ScoreThresholdProvider.ScoreThresholdFeature.class);
provisioningEnvironment.jersey().register(ScoreThresholdProvider.ScoreThresholdFeature.class);
}
private void registerExceptionMappers(Environment environment,
WebSocketEnvironment<AuthenticatedAccount> webSocketEnvironment,
WebSocketEnvironment<AuthenticatedAccount> provisioningEnvironment) {

View File

@ -5,23 +5,111 @@
package org.whispersystems.textsecuregcm.captcha;
/**
* A captcha assessment
*
* @param valid whether the captcha was passed
* @param score string representation of the risk level
*/
public record AssessmentResult(boolean valid, String score) {
import java.util.Objects;
import java.util.Optional;
public static AssessmentResult invalid() {
return new AssessmentResult(false, "");
public class AssessmentResult {
private final boolean solved;
private final float actualScore;
private final float defaultScoreThreshold;
private final String scoreString;
/**
* A captcha assessment
*
* @param solved if false, the captcha was not successfully completed
* @param actualScore float representation of the risk level from [0, 1.0], with 1.0 being the least risky
* @param defaultScoreThreshold the score threshold which the score will be evaluated against by default
* @param scoreString a quantized string representation of the risk level, suitable for use in metrics
*/
private AssessmentResult(boolean solved, float actualScore, float defaultScoreThreshold, final String scoreString) {
this.solved = solved;
this.actualScore = actualScore;
this.defaultScoreThreshold = defaultScoreThreshold;
this.scoreString = scoreString;
}
/**
* Construct an {@link AssessmentResult} from a captcha evaluation score
*
* @param actualScore the score
* @param defaultScoreThreshold the threshold to compare the score against by default
*/
public static AssessmentResult fromScore(float actualScore, float defaultScoreThreshold) {
if (actualScore < 0 || actualScore > 1.0 || defaultScoreThreshold < 0 || defaultScoreThreshold > 1.0) {
throw new IllegalArgumentException("invalid captcha score");
}
return new AssessmentResult(true, actualScore, defaultScoreThreshold, AssessmentResult.scoreString(actualScore));
}
/**
* Construct a captcha assessment that will always be invalid
*/
public static AssessmentResult invalid() {
return new AssessmentResult(false, 0.0f, 0.0f, "");
}
/**
* Construct a captcha assessment that will always be valid
*/
public static AssessmentResult alwaysValid() {
return new AssessmentResult(true, 1.0f, 0.0f, "1.0");
}
/**
* Check if the captcha assessment should be accepted using the default score threshold
*
* @return true if this assessment should be accepted under the default score threshold
*/
public boolean isValid() {
return isValid(Optional.empty());
}
/**
* Check if the captcha assessment should be accepted
*
* @param scoreThreshold the minimum score the assessment requires to pass, uses default if empty
* @return true if the assessment scored higher than the provided scoreThreshold
*/
public boolean isValid(Optional<Float> scoreThreshold) {
if (!solved) {
return false;
}
return this.actualScore >= scoreThreshold.orElse(this.defaultScoreThreshold);
}
public String getScoreString() {
return scoreString;
}
public float getScore() {
return this.actualScore;
}
/**
* Map a captcha score in [0.0, 1.0] to a low cardinality discrete space in [0, 100] suitable for use in metrics
*/
static String scoreString(final float score) {
private static String scoreString(final float score) {
final int x = Math.round(score * 10); // [0, 10]
return Integer.toString(x * 10); // [0, 100] in increments of 10
}
@Override
public boolean equals(final Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
AssessmentResult that = (AssessmentResult) o;
return solved == that.solved && Float.compare(that.actualScore, actualScore) == 0
&& Float.compare(that.defaultScoreThreshold, defaultScoreThreshold) == 0 && Objects.equals(scoreString,
that.scoreString);
}
@Override
public int hashCode() {
return Objects.hash(solved, actualScore, defaultScoreThreshold, scoreString);
}
}

View File

@ -91,8 +91,7 @@ public class CaptchaChecker {
final AssessmentResult result = client.verify(siteKey, parsedAction, token, ip);
Metrics.counter(ASSESSMENTS_COUNTER_NAME,
"action", action,
"valid", String.valueOf(result.valid()),
"score", result.score(),
"score", result.getScoreString(),
"provider", prefix)
.increment();
return result;

View File

@ -33,7 +33,6 @@ public class HCaptchaClient implements CaptchaClient {
private static final String PREFIX = "signal-hcaptcha";
private static final String ASSESSMENT_REASON_COUNTER_NAME = name(HCaptchaClient.class, "assessmentReason");
private static final String INVALID_REASON_COUNTER_NAME = name(HCaptchaClient.class, "invalidReason");
private static final String INVALID_SITEKEY_COUNTER_NAME = name(HCaptchaClient.class, "invalidSiteKey");
private final String apiKey;
private final HttpClient client;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
@ -115,16 +114,15 @@ public class HCaptchaClient implements CaptchaClient {
logger.error("Invalid score {} from hcaptcha response {}", hCaptchaResponse.score, hCaptchaResponse);
return AssessmentResult.invalid();
}
final String scoreString = AssessmentResult.scoreString(score);
final BigDecimal threshold = config.getScoreFloorByAction().getOrDefault(action, config.getScoreFloor());
final AssessmentResult assessmentResult = AssessmentResult.fromScore(score, threshold.floatValue());
for (String reason : hCaptchaResponse.scoreReasons) {
Metrics.counter(ASSESSMENT_REASON_COUNTER_NAME,
"action", action.getActionName(),
"reason", reason,
"score", scoreString).increment();
"score", assessmentResult.getScoreString()).increment();
}
final BigDecimal threshold = config.getScoreFloorByAction().getOrDefault(action, config.getScoreFloor());
return new AssessmentResult(score >= threshold.floatValue(), scoreString);
return assessmentResult;
}
}

View File

@ -113,17 +113,16 @@ public class RecaptchaClient implements CaptchaClient {
if (assessment.getTokenProperties().getValid()) {
final float score = assessment.getRiskAnalysis().getScore();
log.debug("assessment for {} was valid, score: {}", action.getActionName(), score);
final BigDecimal threshold = config.getScoreFloorByAction().getOrDefault(action, config.getScoreFloor());
final AssessmentResult assessmentResult = AssessmentResult.fromScore(score, threshold.floatValue());
for (RiskAnalysis.ClassificationReason reason : assessment.getRiskAnalysis().getReasonsList()) {
Metrics.counter(ASSESSMENT_REASON_COUNTER_NAME,
"action", action.getActionName(),
"score", AssessmentResult.scoreString(score),
"score", assessmentResult.getScoreString(),
"reason", reason.name())
.increment();
}
final BigDecimal threshold = config.getScoreFloorByAction().getOrDefault(action, config.getScoreFloor());
return new AssessmentResult(
score >= threshold.floatValue(),
AssessmentResult.scoreString(score));
return assessmentResult;
} else {
Metrics.counter(INVALID_REASON_COUNTER_NAME,
"action", action.getActionName(),

View File

@ -93,7 +93,9 @@ import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.registration.ClientType;
import org.whispersystems.textsecuregcm.registration.MessageTransport;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
import org.whispersystems.textsecuregcm.spam.Extract;
import org.whispersystems.textsecuregcm.spam.FilterSpam;
import org.whispersystems.textsecuregcm.spam.ScoreThreshold;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.ChangeNumberManager;
@ -255,14 +257,15 @@ public class AccountController {
@Path("/{transport}/code/{number}")
@FilterSpam
@Produces(MediaType.APPLICATION_JSON)
public Response createAccount(@PathParam("transport") String transport,
@PathParam("number") String number,
@HeaderParam(HttpHeaders.X_FORWARDED_FOR) String forwardedFor,
@HeaderParam(HttpHeaders.USER_AGENT) String userAgent,
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) Optional<String> acceptLanguage,
@QueryParam("client") Optional<String> client,
@QueryParam("captcha") Optional<String> captcha,
@QueryParam("challenge") Optional<String> pushChallenge)
public Response createAccount(@PathParam("transport") String transport,
@PathParam("number") String number,
@HeaderParam(HttpHeaders.X_FORWARDED_FOR) String forwardedFor,
@HeaderParam(HttpHeaders.USER_AGENT) String userAgent,
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) Optional<String> acceptLanguage,
@QueryParam("client") Optional<String> client,
@QueryParam("captcha") Optional<String> captcha,
@QueryParam("challenge") Optional<String> pushChallenge,
@Extract ScoreThreshold captchaScoreThreshold)
throws RateLimitExceededException, ImpossiblePhoneNumberException, NonNormalizedPhoneNumberException, IOException {
Util.requireNormalizedNumber(number);
@ -278,12 +281,12 @@ public class AccountController {
assessmentResult.ifPresent(result ->
Metrics.counter(CAPTCHA_ATTEMPT_COUNTER_NAME, Tags.of(
Tag.of("success", String.valueOf(result.valid())),
Tag.of("success", String.valueOf(result.isValid(captchaScoreThreshold.getScoreThreshold()))),
UserAgentTagUtil.getPlatformTag(userAgent),
Tag.of(COUNTRY_CODE_TAG_NAME, countryCode),
Tag.of(REGION_TAG_NAME, region),
Tag.of(REGION_CODE_TAG_NAME, region),
Tag.of(SCORE_TAG_NAME, result.score())))
Tag.of(SCORE_TAG_NAME, result.getScoreString())))
.increment());
final boolean pushChallengeMatch = pushChallengeMatches(number, pushChallenge, maybeStoredVerificationCode);
@ -293,7 +296,7 @@ public class AccountController {
}
final boolean requiresCaptcha = assessmentResult
.map(result -> !result.valid())
.map(result -> !result.isValid(captchaScoreThreshold.getScoreThreshold()))
.orElseGet(
() -> registrationCaptchaManager.requiresCaptcha(number, forwardedFor, sourceHost, pushChallengeMatch));

View File

@ -75,8 +75,10 @@ import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceException;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceSenderException;
import org.whispersystems.textsecuregcm.registration.VerificationSession;
import org.whispersystems.textsecuregcm.spam.Extract;
import org.whispersystems.textsecuregcm.spam.FilterSpam;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.spam.ScoreThreshold;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
import org.whispersystems.textsecuregcm.util.ExceptionUtils;
@ -89,7 +91,6 @@ import org.whispersystems.textsecuregcm.util.Util;
public class VerificationController {
private static final Logger logger = LoggerFactory.getLogger(VerificationController.class);
private static final Duration REGISTRATION_RPC_TIMEOUT = Duration.ofSeconds(15);
private static final Duration DYNAMODB_TIMEOUT = Duration.ofSeconds(5);
@ -195,7 +196,8 @@ public class VerificationController {
public VerificationSessionResponse updateSession(@PathParam("sessionId") final String encodedSessionId,
@HeaderParam(com.google.common.net.HttpHeaders.X_FORWARDED_FOR) String forwardedFor,
@HeaderParam(HttpHeaders.USER_AGENT) final String userAgent,
@NotNull @Valid final UpdateVerificationSessionRequest updateVerificationSessionRequest) {
@NotNull @Valid final UpdateVerificationSessionRequest updateVerificationSessionRequest,
@NotNull @Extract final ScoreThreshold captchaScoreThreshold) {
final String sourceHost = HeaderUtils.getMostRecentProxy(forwardedFor).orElseThrow();
@ -213,7 +215,7 @@ public class VerificationController {
verificationSession);
verificationSession = handleCaptcha(sourceHost, updateVerificationSessionRequest, registrationServiceSession,
verificationSession, userAgent);
verificationSession, userAgent, captchaScoreThreshold.getScoreThreshold());
} catch (final RateLimitExceededException e) {
final Response response = buildResponseForRateLimitExceeded(verificationSession, registrationServiceSession,
@ -351,11 +353,13 @@ public class VerificationController {
* @throws ForbiddenException if assessment is not valid.
* @throws RateLimitExceededException if too many captchas have been submitted
*/
private VerificationSession handleCaptcha(final String sourceHost,
private VerificationSession handleCaptcha(
final String sourceHost,
final UpdateVerificationSessionRequest updateVerificationSessionRequest,
final RegistrationServiceSession registrationServiceSession,
VerificationSession verificationSession,
final String userAgent) throws RateLimitExceededException {
final String userAgent,
final Optional<Float> captchaScoreThreshold) throws RateLimitExceededException {
if (updateVerificationSessionRequest.captcha() == null) {
return verificationSession;
@ -366,23 +370,24 @@ public class VerificationController {
final AssessmentResult assessmentResult;
try {
assessmentResult = registrationCaptchaManager.assessCaptcha(
Optional.of(updateVerificationSessionRequest.captcha()), sourceHost)
.orElseThrow(() -> new ServerErrorException(Response.Status.INTERNAL_SERVER_ERROR));
Metrics.counter(CAPTCHA_ATTEMPT_COUNTER_NAME, Tags.of(
Tag.of(SUCCESS_TAG_NAME, String.valueOf(assessmentResult.valid())),
Tag.of(SUCCESS_TAG_NAME, String.valueOf(assessmentResult.isValid(captchaScoreThreshold))),
UserAgentTagUtil.getPlatformTag(userAgent),
Tag.of(COUNTRY_CODE_TAG_NAME, Util.getCountryCode(registrationServiceSession.number())),
Tag.of(REGION_CODE_TAG_NAME, Util.getRegion(registrationServiceSession.number())),
Tag.of(SCORE_TAG_NAME, assessmentResult.score())))
Tag.of(SCORE_TAG_NAME, assessmentResult.getScoreString())))
.increment();
} catch (IOException e) {
throw new ServerErrorException(Response.Status.SERVICE_UNAVAILABLE);
}
if (assessmentResult.valid()) {
if (assessmentResult.isValid(captchaScoreThreshold)) {
final List<VerificationSession.Information> submittedInformation = new ArrayList<>(
verificationSession.submittedInformation());
submittedInformation.add(VerificationSession.Information.CAPTCHA);

View File

@ -68,7 +68,7 @@ public class RateLimitChallengeManager {
rateLimiters.getRecaptchaChallengeAttemptLimiter().validate(account.getUuid());
final boolean challengeSuccess = captchaChecker.verify(Action.CHALLENGE, captcha, mostRecentProxyIp).valid();
final boolean challengeSuccess = captchaChecker.verify(Action.CHALLENGE, captcha, mostRecentProxyIp).isValid();
final Tags tags = Tags.of(
Tag.of(SOURCE_COUNTRY_TAG_NAME, Util.getCountryCode(account.getNumber())),

View File

@ -0,0 +1,20 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.spam;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that a parameter should be parsed from a {@link org.glassfish.jersey.server.ContainerRequest}
*/
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Extract {
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2023 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 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.
*/
public class ScoreThreshold {
private static final Logger logger = LoggerFactory.getLogger(ScoreThreshold.class);
public static final String PROPERTY_NAME = "scoreThreshold";
/**
* A score threshold in the range [0, 1.0]
*/
private final Optional<Float> scoreThreshold;
/**
* Extract an optional score threshold parameter provided by an upstream request filter
*/
public ScoreThreshold(final ContainerRequest containerRequest) {
this.scoreThreshold = Optional
.ofNullable(containerRequest.getProperty(PROPERTY_NAME))
.flatMap(obj -> {
if (obj instanceof Float f) {
return Optional.of(f);
}
logger.warn("invalid format for filter provided score threshold {}", obj);
return Optional.empty();
});
}
public Optional<Float> getScoreThreshold() {
return this.scoreThreshold;
}
}

View File

@ -0,0 +1,55 @@
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 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 {
/**
* Configures the ScoreThresholdProvider
*/
public static class ScoreThresholdFeature implements Feature {
@Override
public boolean configure(FeatureContext context) {
context.register(new AbstractBinder() {
@Override
protected void configure() {
bind(ScoreThresholdProvider.class)
.to(ValueParamProvider.class)
.in(Singleton.class);
}
});
return true;
}
}
@Override
public Function<ContainerRequest, ?> getValueProvider(final Parameter parameter) {
if (parameter.getRawType().equals(ScoreThreshold.class)
&& parameter.isAnnotationPresent(Extract.class)) {
return ScoreThreshold::new;
}
return null;
}
@Override
public PriorityType getPriority() {
return Priority.HIGH;
}
}

View File

@ -84,7 +84,7 @@ public class CaptchaCheckerTest {
@ParameterizedTest
@MethodSource
void scoreString(float score, String expected) {
assertThat(AssessmentResult.scoreString(score)).isEqualTo(expected);
assertThat(AssessmentResult.fromScore(score, 0.0f).getScoreString()).isEqualTo(expected);
}

View File

@ -58,8 +58,7 @@ public class HCaptchaClientTest {
if (!success) {
assertThat(result).isEqualTo(AssessmentResult.invalid());
} else {
assertThat(result)
.isEqualTo(new AssessmentResult(expectedResult, AssessmentResult.scoreString(score)));
assertThat(result.isValid()).isEqualTo(expectedResult);
}
}

View File

@ -109,6 +109,8 @@ import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.registration.ClientType;
import org.whispersystems.textsecuregcm.registration.MessageTransport;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
import org.whispersystems.textsecuregcm.spam.Extract;
import org.whispersystems.textsecuregcm.spam.ScoreThresholdProvider;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.ChangeNumberManager;
@ -215,6 +217,7 @@ class AccountControllerTest {
.addProvider(new ImpossiblePhoneNumberExceptionMapper())
.addProvider(new NonNormalizedPhoneNumberExceptionMapper())
.addProvider(new RateLimitByIpFilter(rateLimiters))
.addProvider(ScoreThresholdProvider.ScoreThresholdFeature.class)
.setMapper(SystemMapper.jsonMapper())
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new AccountController(pendingAccountsManager,
@ -349,7 +352,7 @@ class AccountControllerTest {
when(captchaChecker.verify(eq(Action.REGISTRATION), eq(INVALID_CAPTCHA_TOKEN), anyString()))
.thenReturn(AssessmentResult.invalid());
when(captchaChecker.verify(eq(Action.REGISTRATION), eq(VALID_CAPTCHA_TOKEN), anyString()))
.thenReturn(new AssessmentResult(true, ""));
.thenReturn(AssessmentResult.alwaysValid());
doThrow(new RateLimitExceededException(Duration.ZERO, true)).when(pinLimiter).validate(eq(SENDER_OVER_PIN));
@ -690,6 +693,7 @@ class AccountControllerTest {
final Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", number))
.register(Extract.class)
.queryParam("challenge", "1234-push")
.request()
.header(HttpHeaders.X_FORWARDED_FOR, NICE_HOST)

View File

@ -69,6 +69,7 @@ import org.whispersystems.textsecuregcm.registration.RegistrationServiceSenderEx
import org.whispersystems.textsecuregcm.registration.VerificationSession;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.spam.ScoreThresholdProvider;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
import org.whispersystems.textsecuregcm.util.SystemMapper;
@ -100,6 +101,7 @@ class VerificationControllerTest {
.addProvider(new ImpossiblePhoneNumberExceptionMapper())
.addProvider(new NonNormalizedPhoneNumberExceptionMapper())
.addProvider(new RegistrationServiceSenderExceptionMapper())
.addProvider(ScoreThresholdProvider.ScoreThresholdFeature.class)
.setMapper(SystemMapper.jsonMapper())
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(
@ -621,7 +623,7 @@ class VerificationControllerTest {
registrationServiceSession.expiration()))));
when(registrationCaptchaManager.assessCaptcha(any(), any()))
.thenReturn(Optional.of(new AssessmentResult(true, "1")));
.thenReturn(Optional.of(AssessmentResult.alwaysValid()));
when(verificationSessionManager.update(any(), any()))
.thenReturn(CompletableFuture.completedFuture(null));
@ -669,7 +671,7 @@ class VerificationControllerTest {
registrationServiceSession.expiration()))));
when(registrationCaptchaManager.assessCaptcha(any(), any()))
.thenReturn(Optional.of(new AssessmentResult(true, "1")));
.thenReturn(Optional.of(AssessmentResult.alwaysValid()));
when(verificationSessionManager.update(any(), any()))
.thenReturn(CompletableFuture.completedFuture(null));

View File

@ -78,7 +78,7 @@ class RateLimitChallengeManagerTest {
when(captchaChecker.verify(eq(Action.CHALLENGE), any(), any()))
.thenReturn(successfulChallenge
? new AssessmentResult(true, "")
? AssessmentResult.alwaysValid()
: AssessmentResult.invalid());
when(rateLimiters.getRecaptchaChallengeAttemptLimiter()).thenReturn(mock(RateLimiter.class));