Support different v2 captcha actions
This commit is contained in:
parent
7ded802df4
commit
f3457502a6
|
@ -11,13 +11,14 @@ 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 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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class EnterpriseRecaptchaClient implements RecaptchaClient {
|
||||
private static final Logger logger = LoggerFactory.getLogger(EnterpriseRecaptchaClient.class);
|
||||
|
@ -47,12 +48,20 @@ public class EnterpriseRecaptchaClient implements RecaptchaClient {
|
|||
|
||||
@Override
|
||||
public boolean verify(final String token, final String ip) {
|
||||
Event event = Event.newBuilder()
|
||||
.setExpectedAction("challenge")
|
||||
return verify(token, ip, null);
|
||||
}
|
||||
|
||||
public boolean verify(final String token, final String ip, @Nullable final String expectedAction) {
|
||||
Event.Builder eventBuilder = Event.newBuilder()
|
||||
.setSiteKey(siteKey)
|
||||
.setToken(token)
|
||||
.setUserIpAddress(ip)
|
||||
.build();
|
||||
.setUserIpAddress(ip);
|
||||
|
||||
if (expectedAction != null) {
|
||||
eventBuilder.setExpectedAction(expectedAction);
|
||||
}
|
||||
|
||||
final Event event = eventBuilder.build();
|
||||
final Assessment assessment = client.createAssessment(projectPath, Assessment.newBuilder().setEvent(event).build());
|
||||
|
||||
return assessment.getTokenProperties().getValid() && assessment.getRiskAnalysis().getScore() >= scoreFloor;
|
||||
|
|
|
@ -5,12 +5,16 @@
|
|||
|
||||
package org.whispersystems.textsecuregcm.recaptcha;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class TransitionalRecaptchaClient implements RecaptchaClient {
|
||||
|
||||
private static final String V2_PREFIX = "signal-recaptcha-v2:";
|
||||
@VisibleForTesting
|
||||
static final String SEPARATOR = ".";
|
||||
@VisibleForTesting
|
||||
static final String V2_PREFIX = "signal-recaptcha-v2" + SEPARATOR;
|
||||
|
||||
private final LegacyRecaptchaClient legacyRecaptchaClient;
|
||||
private final EnterpriseRecaptchaClient enterpriseRecaptchaClient;
|
||||
|
@ -25,9 +29,28 @@ public class TransitionalRecaptchaClient implements RecaptchaClient {
|
|||
@Override
|
||||
public boolean verify(@Nonnull final String token, @Nonnull final String ip) {
|
||||
if (token.startsWith(V2_PREFIX)) {
|
||||
return enterpriseRecaptchaClient.verify(token.substring(V2_PREFIX.length()), ip);
|
||||
final String[] actionAndToken = parseV2ActionAndToken(token.substring(V2_PREFIX.length()));
|
||||
return enterpriseRecaptchaClient.verify(actionAndToken[1], ip, actionAndToken[0]);
|
||||
} 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
|
||||
* don’t 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright 2022 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.recaptcha;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.whispersystems.textsecuregcm.recaptcha.TransitionalRecaptchaClient.SEPARATOR;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
class TransitionalRecaptchaClientTest {
|
||||
|
||||
private TransitionalRecaptchaClient transitionalRecaptchaClient;
|
||||
private EnterpriseRecaptchaClient enterpriseRecaptchaClient;
|
||||
private LegacyRecaptchaClient legacyRecaptchaClient;
|
||||
|
||||
private static final String PREFIX = TransitionalRecaptchaClient.V2_PREFIX.substring(0,
|
||||
TransitionalRecaptchaClient.V2_PREFIX.lastIndexOf(SEPARATOR));
|
||||
private static final String TOKEN = "some-token";
|
||||
private static final String IP_ADDRESS = "127.0.0.1";
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
enterpriseRecaptchaClient = mock(EnterpriseRecaptchaClient.class);
|
||||
legacyRecaptchaClient = mock(LegacyRecaptchaClient.class);
|
||||
transitionalRecaptchaClient = new TransitionalRecaptchaClient(legacyRecaptchaClient, enterpriseRecaptchaClient);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void testVerify(final String inputToken, final boolean expectLegacy, final String expectedToken,
|
||||
final String expectedAction) {
|
||||
|
||||
transitionalRecaptchaClient.verify(inputToken, IP_ADDRESS);
|
||||
|
||||
if (expectLegacy) {
|
||||
verifyNoInteractions(enterpriseRecaptchaClient);
|
||||
verify(legacyRecaptchaClient).verify(expectedToken, IP_ADDRESS);
|
||||
} else {
|
||||
verifyNoInteractions(legacyRecaptchaClient);
|
||||
verify(enterpriseRecaptchaClient).verify(expectedToken, IP_ADDRESS, expectedAction);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static Stream<Arguments> testVerify() {
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
TOKEN,
|
||||
true,
|
||||
TOKEN,
|
||||
null),
|
||||
Arguments.of(
|
||||
String.join(SEPARATOR, PREFIX, TOKEN),
|
||||
false,
|
||||
TOKEN,
|
||||
null),
|
||||
Arguments.of(
|
||||
String.join(SEPARATOR, PREFIX, "an-action", TOKEN),
|
||||
false,
|
||||
TOKEN,
|
||||
"an-action"),
|
||||
Arguments.of(
|
||||
String.join(SEPARATOR, PREFIX, "an-action", TOKEN, "something-else"),
|
||||
false,
|
||||
TOKEN + SEPARATOR + "something-else",
|
||||
"an-action")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue