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

@@ -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);