Remove RecaptchaClient

This commit is contained in:
Chris Eager 2024-02-28 16:51:52 -06:00 committed by Chris Eager
parent 3d32b68bb2
commit a4d4a9c686
8 changed files with 2 additions and 187 deletions

View File

@ -285,10 +285,6 @@ unidentifiedDelivery:
privateKey: secret://unidentifiedDelivery.privateKey
expiresDays: 7
recaptcha:
projectPath: projects/example
credentialConfigurationJson: "{ }" # service account configuration for backend authentication
hCaptcha:
apiKey: secret://hCaptcha.apiKey

View File

@ -466,18 +466,6 @@
<artifactId>google-auth-library-oauth2-http</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-recaptchaenterprise</artifactId>
<exclusions>
<!-- fix dependency convergence from older 1.0.1 -->
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>failureaccess</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.stripe</groupId>
<artifactId>stripe-java</artifactId>

View File

@ -41,7 +41,6 @@ import org.whispersystems.textsecuregcm.configuration.MessageCacheConfiguration;
import org.whispersystems.textsecuregcm.configuration.MonitoredS3ObjectConfiguration;
import org.whispersystems.textsecuregcm.configuration.OneTimeDonationConfiguration;
import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.RecaptchaConfiguration;
import org.whispersystems.textsecuregcm.configuration.RedisClusterConfiguration;
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration;
import org.whispersystems.textsecuregcm.configuration.RegistrationServiceConfiguration;
@ -204,11 +203,6 @@ public class WhisperServerConfiguration extends Configuration {
@JsonProperty
private UnidentifiedDeliveryConfiguration unidentifiedDelivery;
@Valid
@NotNull
@JsonProperty
private RecaptchaConfiguration recaptcha;
@Valid
@NotNull
@JsonProperty
@ -359,10 +353,6 @@ public class WhisperServerConfiguration extends Configuration {
return dynamoDbTables;
}
public RecaptchaConfiguration getRecaptchaConfiguration() {
return recaptcha;
}
public HCaptchaConfiguration getHCaptchaConfiguration() {
return hCaptcha;
}

View File

@ -85,7 +85,6 @@ import org.whispersystems.textsecuregcm.calls.routing.DynamicConfigTurnRouter;
import org.whispersystems.textsecuregcm.calls.routing.TurnCallRouter;
import org.whispersystems.textsecuregcm.captcha.CaptchaChecker;
import org.whispersystems.textsecuregcm.captcha.HCaptchaClient;
import org.whispersystems.textsecuregcm.captcha.RecaptchaClient;
import org.whispersystems.textsecuregcm.captcha.RegistrationCaptchaManager;
import org.whispersystems.textsecuregcm.captcha.ShortCodeExpander;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
@ -625,10 +624,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
"message_byte_limit",
config.getMessageByteLimitCardinalityEstimator().period());
RecaptchaClient recaptchaClient = new RecaptchaClient(
config.getRecaptchaConfiguration().projectPath(),
config.getRecaptchaConfiguration().credentialConfigurationJson(),
dynamicConfigurationManager);
HCaptchaClient hCaptchaClient = new HCaptchaClient(
config.getHCaptchaConfiguration().getApiKey().value(),
hcaptchaRetryExecutor,
@ -638,7 +633,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
HttpClient shortCodeRetrieverHttpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10)).build();
ShortCodeExpander shortCodeRetriever = new ShortCodeExpander(shortCodeRetrieverHttpClient, config.getShortCodeRetrieverConfiguration().baseUrl());
CaptchaChecker captchaChecker = new CaptchaChecker(shortCodeRetriever, List.of(recaptchaClient, hCaptchaClient));
CaptchaChecker captchaChecker = new CaptchaChecker(shortCodeRetriever, List.of(hCaptchaClient));
PushChallengeManager pushChallengeManager = new PushChallengeManager(pushNotificationManager,
pushChallengeDynamoDb);

View File

@ -23,7 +23,7 @@ import org.slf4j.LoggerFactory;
public class CaptchaChecker {
private static final Logger logger = LoggerFactory.getLogger(CaptchaChecker.class);
private static final String INVALID_SITEKEY_COUNTER_NAME = name(CaptchaChecker.class, "invalidSiteKey");
private static final String ASSESSMENTS_COUNTER_NAME = name(RecaptchaClient.class, "assessments");
private static final String ASSESSMENTS_COUNTER_NAME = name(CaptchaChecker.class, "assessments");
private static final String INVALID_ACTION_COUNTER_NAME = name(CaptchaChecker.class, "invalidActions");
@VisibleForTesting

View File

@ -1,134 +0,0 @@
/*
* Copyright 2021-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.captcha;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.api.gax.rpc.ApiException;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient;
import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceSettings;
import com.google.recaptchaenterprise.v1.Assessment;
import com.google.recaptchaenterprise.v1.Event;
import com.google.recaptchaenterprise.v1.RiskAnalysis;
import io.micrometer.core.instrument.Metrics;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nonnull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicCaptchaConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
public class RecaptchaClient implements CaptchaClient {
private static final Logger log = LoggerFactory.getLogger(RecaptchaClient.class);
private static final String V2_PREFIX = "signal-recaptcha-v2";
private static final String INVALID_REASON_COUNTER_NAME = name(RecaptchaClient.class, "invalidReason");
private static final String INVALID_SITEKEY_COUNTER_NAME = name(RecaptchaClient.class, "invalidSiteKey");
private static final String ASSESSMENT_REASON_COUNTER_NAME = name(RecaptchaClient.class, "assessmentReason");
private final String projectPath;
private final RecaptchaEnterpriseServiceClient client;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
public RecaptchaClient(
@Nonnull final String projectPath,
@Nonnull final String recaptchaCredentialConfigurationJson,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
try {
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);
}
}
@Override
public String scheme() {
return V2_PREFIX;
}
@Override
public Set<String> validSiteKeys(final Action action) {
final DynamicCaptchaConfiguration config = dynamicConfigurationManager.getConfiguration().getCaptchaConfiguration();
if (!config.isAllowRecaptcha()) {
log.warn("Received request to verify a recaptcha, but recaptcha is not enabled");
return Collections.emptySet();
}
return Optional
.ofNullable(config.getRecaptchaSiteKeys().get(action))
.orElse(Collections.emptySet());
}
@Override
public org.whispersystems.textsecuregcm.captcha.AssessmentResult verify(
final String sitekey,
final Action action,
final String token,
final String ip) throws IOException {
final DynamicCaptchaConfiguration config = dynamicConfigurationManager.getConfiguration().getCaptchaConfiguration();
final Set<String> allowedSiteKeys = config.getRecaptchaSiteKeys().get(action);
if (allowedSiteKeys != null && !allowedSiteKeys.contains(sitekey)) {
log.info("invalid recaptcha sitekey {}, action={}, token={}", action, token);
Metrics.counter(INVALID_SITEKEY_COUNTER_NAME, "action", action.getActionName()).increment();
return AssessmentResult.invalid();
}
Event.Builder eventBuilder = Event.newBuilder()
.setSiteKey(sitekey)
.setToken(token)
.setUserIpAddress(ip);
if (action != null) {
eventBuilder.setExpectedAction(action.getActionName());
}
final Event event = eventBuilder.build();
final Assessment assessment;
try {
assessment = client.createAssessment(projectPath, Assessment.newBuilder().setEvent(event).build());
} catch (ApiException e) {
throw new IOException(e);
}
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.getScoreString(),
"reason", reason.name())
.increment();
}
return assessmentResult;
} else {
Metrics.counter(INVALID_REASON_COUNTER_NAME,
"action", action.getActionName(),
"reason", assessment.getTokenProperties().getInvalidReason().name())
.increment();
return AssessmentResult.invalid();
}
}
}

View File

@ -1,12 +0,0 @@
/*
* Copyright 2021-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import javax.validation.constraints.NotEmpty;
public record RecaptchaConfiguration(@NotEmpty String projectPath, @NotEmpty String credentialConfigurationJson) {
}

View File

@ -10,7 +10,6 @@ import com.google.common.annotations.VisibleForTesting;
import org.whispersystems.textsecuregcm.captcha.Action;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.validation.constraints.DecimalMax;
@ -28,9 +27,6 @@ public class DynamicCaptchaConfiguration {
@JsonProperty
private boolean allowHCaptcha = false;
@JsonProperty
private boolean allowRecaptcha = true;
@JsonProperty
@NotNull
private Map<Action, Set<String>> hCaptchaSiteKeys = Collections.emptyMap();
@ -51,10 +47,6 @@ public class DynamicCaptchaConfiguration {
return allowHCaptcha;
}
public boolean isAllowRecaptcha() {
return allowRecaptcha;
}
public Map<Action, BigDecimal> getScoreFloorByAction() {
return scoreFloorByAction;
}