Parameterize sitekey

This commit is contained in:
Chris Eager
2022-02-28 09:49:31 -08:00
committed by Chris Eager
parent 3a1c716c73
commit 935e268dec
7 changed files with 115 additions and 59 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* Copyright 2013-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm;
@@ -477,7 +477,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
LegacyRecaptchaClient legacyRecaptchaClient = new LegacyRecaptchaClient(config.getRecaptchaConfiguration().getSecret());
EnterpriseRecaptchaClient enterpriseRecaptchaClient = new EnterpriseRecaptchaClient(
config.getRecaptchaV2Configuration().getScoreFloor().doubleValue(),
config.getRecaptchaV2Configuration().getSiteKey(),
config.getRecaptchaV2Configuration().getProjectPath(),
config.getRecaptchaV2Configuration().getCredentialConfigurationJson());
TransitionalRecaptchaClient transitionalRecaptchaClient = new TransitionalRecaptchaClient(legacyRecaptchaClient, enterpriseRecaptchaClient);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2021 Signal Messenger, LLC
* Copyright 2021-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@@ -15,7 +15,6 @@ public class RecaptchaV2Configuration {
private BigDecimal scoreFloor;
private String projectPath;
private String siteKey;
private String credentialConfigurationJson;
@DecimalMin("0")
@@ -30,11 +29,6 @@ public class RecaptchaV2Configuration {
return projectPath;
}
@NotEmpty
public String getSiteKey() {
return siteKey;
}
@NotEmpty
public String getCredentialConfigurationJson() {
return credentialConfigurationJson;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2021 Signal Messenger, LLC
* Copyright 2021-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@@ -9,33 +9,34 @@ import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient;
import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceSettings;
import com.google.common.annotations.VisibleForTesting;
import com.google.recaptchaenterprise.v1.Assessment;
import com.google.recaptchaenterprise.v1.Event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
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;
public class EnterpriseRecaptchaClient implements RecaptchaClient {
@VisibleForTesting
static final String SEPARATOR = ".";
private static final Logger logger = LoggerFactory.getLogger(EnterpriseRecaptchaClient.class);
private final double scoreFloor;
private final String siteKey;
private final String projectPath;
private final RecaptchaEnterpriseServiceClient client;
public EnterpriseRecaptchaClient(
final double scoreFloor,
@Nonnull final String siteKey,
@Nonnull final String projectPath,
@Nonnull final String recaptchaCredentialConfigurationJson) {
try {
this.scoreFloor = scoreFloor;
this.siteKey = Objects.requireNonNull(siteKey);
this.projectPath = Objects.requireNonNull(projectPath);
this.client = RecaptchaEnterpriseServiceClient.create(RecaptchaEnterpriseServiceSettings.newBuilder()
.setCredentialsProvider(FixedCredentialsProvider.create(GoogleCredentials.fromStream(
@@ -46,14 +47,38 @@ public class EnterpriseRecaptchaClient implements RecaptchaClient {
}
}
@Override
public boolean verify(final String token, final String ip) {
return verify(token, ip, null);
/**
* Parses the token and action (if any) from {@code input}. The expected input format is: {@code [action:]token}.
* <p>
* For action to be optional, there is a strong assumption that the token will never contain a {@value SEPARATOR}.
* Observation suggests {@code token} is base-64 encoded. In practice, an action should always be present, but we
* dont need to be strict.
*/
static String[] parseInputToken(final String input) {
String[] keyActionAndToken = input.split("\\" + SEPARATOR, 3);
if (keyActionAndToken.length == 1) {
throw new BadRequestException("too few parts");
}
if (keyActionAndToken.length == 2) {
// there was no ":" delimiter; assume we only have a token
return new String[]{keyActionAndToken[0], null, keyActionAndToken[1]};
}
return keyActionAndToken;
}
public boolean verify(final String token, final String ip, @Nullable final String expectedAction) {
@Override
public boolean verify(final String input, final String ip) {
final String[] parts = parseInputToken(input);
final String sitekey = parts[0];
final String expectedAction = parts[1];
final String token = parts[2];
Event.Builder eventBuilder = Event.newBuilder()
.setSiteKey(siteKey)
.setSiteKey(sitekey)
.setToken(token)
.setUserIpAddress(ip);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2021 Signal Messenger, LLC
* Copyright 2021-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@@ -12,9 +12,7 @@ import javax.annotation.Nonnull;
public class TransitionalRecaptchaClient implements RecaptchaClient {
@VisibleForTesting
static final String SEPARATOR = ".";
@VisibleForTesting
static final String V2_PREFIX = "signal-recaptcha-v2" + SEPARATOR;
static final String V2_PREFIX = "signal-recaptcha-v2" + EnterpriseRecaptchaClient.SEPARATOR;
private final LegacyRecaptchaClient legacyRecaptchaClient;
private final EnterpriseRecaptchaClient enterpriseRecaptchaClient;
@@ -29,28 +27,10 @@ public class TransitionalRecaptchaClient implements RecaptchaClient {
@Override
public boolean verify(@Nonnull final String token, @Nonnull final String ip) {
if (token.startsWith(V2_PREFIX)) {
final String[] actionAndToken = parseV2ActionAndToken(token.substring(V2_PREFIX.length()));
return enterpriseRecaptchaClient.verify(actionAndToken[1], ip, actionAndToken[0]);
return enterpriseRecaptchaClient.verify(token.substring(V2_PREFIX.length()), ip);
} else {
return legacyRecaptchaClient.verify(token, ip);
}
}
/**
* Parses the token and action (if any) from {@code input}. The expected input format is: {@code [action:]token}.
* <p>
* For action to be optional, there is a strong assumption that the token will never contain a {@value SEPARATOR}.
* Observation suggests {@code token} is base-64 encoded. In practice, an action should always be present, but we
* dont need to be strict.
*/
static String[] parseV2ActionAndToken(final String input) {
String[] actionAndToken = input.split("\\" + SEPARATOR, 2);
if (actionAndToken.length == 1) {
// there was no ":" delimiter; assume we only have a token
return new String[]{null, actionAndToken[0]};
}
return actionAndToken;
}
}