Move score floor to dynamic configuration, add distribution summary

This commit is contained in:
Chris Eager
2022-03-02 15:18:33 -08:00
committed by GitHub
parent 9fc5002619
commit eee6307789
11 changed files with 167 additions and 117 deletions

View File

@@ -476,11 +476,12 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(config.getTurnConfiguration());
LegacyRecaptchaClient legacyRecaptchaClient = new LegacyRecaptchaClient(config.getRecaptchaConfiguration().getSecret());
EnterpriseRecaptchaClient enterpriseRecaptchaClient = new EnterpriseRecaptchaClient(
config.getRecaptchaV2Configuration().getScoreFloor().doubleValue(),
config.getRecaptchaV2Configuration().getProjectPath(),
config.getRecaptchaV2Configuration().getCredentialConfigurationJson());
TransitionalRecaptchaClient transitionalRecaptchaClient = new TransitionalRecaptchaClient(legacyRecaptchaClient, enterpriseRecaptchaClient);
PushChallengeManager pushChallengeManager = new PushChallengeManager(apnSender, gcmSender, pushChallengeDynamoDb);
config.getRecaptchaV2Configuration().getCredentialConfigurationJson(),
dynamicConfigurationManager);
TransitionalRecaptchaClient transitionalRecaptchaClient = new TransitionalRecaptchaClient(legacyRecaptchaClient,
enterpriseRecaptchaClient);
PushChallengeManager pushChallengeManager = new PushChallengeManager(apnSender, gcmSender, pushChallengeDynamoDb);
RateLimitChallengeManager rateLimitChallengeManager = new RateLimitChallengeManager(pushChallengeManager,
transitionalRecaptchaClient, dynamicRateLimiters);
RateLimitChallengeOptionManager rateLimitChallengeOptionManager =

View File

@@ -5,25 +5,13 @@
package org.whispersystems.textsecuregcm.configuration;
import java.math.BigDecimal;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
public class RecaptchaV2Configuration {
private BigDecimal scoreFloor;
private String projectPath;
private String credentialConfigurationJson;
@DecimalMin("0")
@DecimalMax("1")
@NotNull
public BigDecimal getScoreFloor() {
return scoreFloor;
}
@NotEmpty
public String getProjectPath() {
return projectPath;

View File

@@ -0,0 +1,36 @@
package org.whispersystems.textsecuregcm.configuration.dynamic;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Set;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;
public class DynamicCaptchaConfiguration {
@JsonProperty
@DecimalMin("0")
@DecimalMax("1")
@NotNull
private BigDecimal scoreFloor;
@JsonProperty
@NotNull
private Set<String> signupCountryCodes = Collections.emptySet();
public Set<String> getSignupCountryCodes() {
return signupCountryCodes;
}
@VisibleForTesting
public void setSignupCountryCodes(Set<String> numbers) {
this.signupCountryCodes = numbers;
}
public BigDecimal getScoreFloor() {
return scoreFloor;
}
}

View File

@@ -38,7 +38,8 @@ public class DynamicConfiguration {
private DynamicTwilioConfiguration twilio = new DynamicTwilioConfiguration();
@JsonProperty
private DynamicSignupCaptchaConfiguration signupCaptcha = new DynamicSignupCaptchaConfiguration();
@Valid
private DynamicCaptchaConfiguration captcha = new DynamicCaptchaConfiguration();
@JsonProperty
@Valid
@@ -86,8 +87,8 @@ public class DynamicConfiguration {
this.twilio = twilioConfiguration;
}
public DynamicSignupCaptchaConfiguration getSignupCaptchaConfiguration() {
return signupCaptcha;
public DynamicCaptchaConfiguration getCaptchaConfiguration() {
return captcha;
}
public DynamicRateLimitChallengeConfiguration getRateLimitChallengeConfiguration() {

View File

@@ -1,23 +0,0 @@
package org.whispersystems.textsecuregcm.configuration.dynamic;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import java.util.Collections;
import java.util.Set;
import javax.validation.constraints.NotNull;
public class DynamicSignupCaptchaConfiguration {
@JsonProperty
@NotNull
private Set<String> countryCodes = Collections.emptySet();
public Set<String> getCountryCodes() {
return countryCodes;
}
@VisibleForTesting
public void setCountryCodes(Set<String> numbers) {
this.countryCodes = numbers;
}
}

View File

@@ -61,8 +61,8 @@ import org.whispersystems.textsecuregcm.auth.StoredRegistrationLock;
import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
import org.whispersystems.textsecuregcm.auth.TurnToken;
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicCaptchaConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicSignupCaptchaConfiguration;
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
import org.whispersystems.textsecuregcm.entities.AccountIdentityResponse;
import org.whispersystems.textsecuregcm.entities.ApnRegistrationId;
@@ -770,8 +770,9 @@ public class AccountController {
return new CaptchaRequirement(true, true);
}
DynamicSignupCaptchaConfiguration signupCaptchaConfig = dynamicConfigurationManager.getConfiguration().getSignupCaptchaConfiguration();
if (signupCaptchaConfig.getCountryCodes().contains(countryCode)) {
DynamicCaptchaConfiguration captchaConfig = dynamicConfigurationManager.getConfiguration()
.getCaptchaConfiguration();
if (captchaConfig.getSignupCountryCodes().contains(countryCode)) {
return new CaptchaRequirement(true, false);
}

View File

@@ -5,6 +5,8 @@
package org.whispersystems.textsecuregcm.recaptcha;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient;
@@ -12,36 +14,38 @@ import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceSetting
import com.google.common.annotations.VisibleForTesting;
import com.google.recaptchaenterprise.v1.Assessment;
import com.google.recaptchaenterprise.v1.Event;
import io.micrometer.core.instrument.Metrics;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.ws.rs.BadRequestException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
public class EnterpriseRecaptchaClient implements RecaptchaClient {
@VisibleForTesting
static final String SEPARATOR = ".";
private static final Logger logger = LoggerFactory.getLogger(EnterpriseRecaptchaClient.class);
private static final String SCORE_DISTRIBUTION_NAME = name(EnterpriseRecaptchaClient.class, "scoreDistribution");
private final double scoreFloor;
private final String projectPath;
private final RecaptchaEnterpriseServiceClient client;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
public EnterpriseRecaptchaClient(
final double scoreFloor,
@Nonnull final String projectPath,
@Nonnull final String recaptchaCredentialConfigurationJson) {
@Nonnull final String recaptchaCredentialConfigurationJson,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
try {
this.scoreFloor = scoreFloor;
this.projectPath = Objects.requireNonNull(projectPath);
this.client = RecaptchaEnterpriseServiceClient.create(RecaptchaEnterpriseServiceSettings.newBuilder()
.setCredentialsProvider(FixedCredentialsProvider.create(GoogleCredentials.fromStream(
new ByteArrayInputStream(recaptchaCredentialConfigurationJson.getBytes(StandardCharsets.UTF_8)))))
.build());
this.dynamicConfigurationManager = dynamicConfigurationManager;
} catch (IOException e) {
throw new AssertionError(e);
}
@@ -89,6 +93,15 @@ public class EnterpriseRecaptchaClient implements RecaptchaClient {
final Event event = eventBuilder.build();
final Assessment assessment = client.createAssessment(projectPath, Assessment.newBuilder().setEvent(event).build());
return assessment.getTokenProperties().getValid() && assessment.getRiskAnalysis().getScore() >= scoreFloor;
if (assessment.getTokenProperties().getValid()) {
final float score = assessment.getRiskAnalysis().getScore();
Metrics.summary(SCORE_DISTRIBUTION_NAME).record(score);
return score >= dynamicConfigurationManager.getConfiguration().getCaptchaConfiguration().getScoreFloor()
.floatValue();
} else {
return false;
}
}
}