mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-22 08:08:01 +01:00
Add hCaptcha support
This commit is contained in:
committed by
ravi-signal
parent
dcec90fc52
commit
65ad3fe623
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2022 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.captcha;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.whispersystems.textsecuregcm.captcha.CaptchaChecker.SEPARATOR;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.ws.rs.BadRequestException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
public class CaptchaCheckerTest {
|
||||
|
||||
private static final String SITE_KEY = "site-key";
|
||||
private static final String TOKEN = "some-token";
|
||||
private static final String PREFIX = "prefix";
|
||||
private static final String PREFIX_A = "prefix-a";
|
||||
private static final String PREFIX_B = "prefix-b";
|
||||
|
||||
static Stream<Arguments> parseInputToken() {
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
String.join(SEPARATOR, PREFIX, SITE_KEY, TOKEN),
|
||||
TOKEN,
|
||||
SITE_KEY,
|
||||
null),
|
||||
Arguments.of(
|
||||
String.join(SEPARATOR, PREFIX, SITE_KEY, "an-action", TOKEN),
|
||||
TOKEN,
|
||||
SITE_KEY,
|
||||
"an-action"),
|
||||
Arguments.of(
|
||||
String.join(SEPARATOR, PREFIX, SITE_KEY, "an-action", TOKEN, "something-else"),
|
||||
TOKEN + SEPARATOR + "something-else",
|
||||
SITE_KEY,
|
||||
"an-action")
|
||||
);
|
||||
}
|
||||
|
||||
private static CaptchaClient mockClient(final String prefix) throws IOException {
|
||||
final CaptchaClient captchaClient = mock(CaptchaClient.class);
|
||||
when(captchaClient.scheme()).thenReturn(prefix);
|
||||
when(captchaClient.verify(any(), any(), any(), any())).thenReturn(AssessmentResult.invalid());
|
||||
return captchaClient;
|
||||
}
|
||||
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void parseInputToken(final String input, final String expectedToken, final String siteKey,
|
||||
@Nullable final String expectedAction) throws IOException {
|
||||
final CaptchaClient captchaClient = mockClient(PREFIX);
|
||||
new CaptchaChecker(List.of(captchaClient)).verify(input, null);
|
||||
verify(captchaClient, times(1)).verify(eq(siteKey), eq(expectedAction), eq(expectedToken), any());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void scoreString(float score, String expected) {
|
||||
assertThat(AssessmentResult.scoreString(score)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
|
||||
static Stream<Arguments> scoreString() {
|
||||
return Stream.of(
|
||||
Arguments.of(0.3f, "30"),
|
||||
Arguments.of(0.0f, "0"),
|
||||
Arguments.of(0.333f, "30"),
|
||||
Arguments.of(0.29f, "30"),
|
||||
Arguments.of(Float.NaN, "0")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void choose() throws IOException {
|
||||
String ainput = String.join(SEPARATOR, PREFIX_A, SITE_KEY, TOKEN);
|
||||
String binput = String.join(SEPARATOR, PREFIX_B, SITE_KEY, TOKEN);
|
||||
final CaptchaClient a = mockClient(PREFIX_A);
|
||||
final CaptchaClient b = mockClient(PREFIX_B);
|
||||
|
||||
new CaptchaChecker(List.of(a, b)).verify(ainput, null);
|
||||
verify(a, times(1)).verify(any(), any(), any(), any());
|
||||
|
||||
new CaptchaChecker(List.of(a, b)).verify(binput, null);
|
||||
verify(b, times(1)).verify(any(), any(), any(), any());
|
||||
}
|
||||
|
||||
static Stream<Arguments> badToken() {
|
||||
return Stream.of(
|
||||
Arguments.of(String.join(SEPARATOR, "invalid", SITE_KEY, "action", TOKEN)),
|
||||
Arguments.of(String.join(SEPARATOR, PREFIX, TOKEN)),
|
||||
Arguments.of(String.join(SEPARATOR, SITE_KEY, PREFIX, "action", TOKEN))
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
public void badToken(final String input) throws IOException {
|
||||
final CaptchaClient cc = mockClient(PREFIX);
|
||||
assertThrows(BadRequestException.class, () -> new CaptchaChecker(List.of(cc)).verify(input, null));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package org.whispersystems.textsecuregcm.captcha;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicCaptchaConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
|
||||
public class HCaptchaClientTest {
|
||||
|
||||
private static final String SITE_KEY = "site-key";
|
||||
private static final String TOKEN = "token";
|
||||
|
||||
|
||||
static Stream<Arguments> captchaProcessed() {
|
||||
return Stream.of(
|
||||
Arguments.of(true, 0.6f, true),
|
||||
Arguments.of(false, 0.6f, false),
|
||||
Arguments.of(true, 0.4f, false),
|
||||
Arguments.of(false, 0.4f, false)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
public void captchaProcessed(final boolean success, final float score, final boolean expectedResult)
|
||||
throws IOException, InterruptedException {
|
||||
|
||||
final HttpClient client = mockResponder(200, String.format("""
|
||||
{
|
||||
"success": %b,
|
||||
"score": %f,
|
||||
"score-reasons": ["great job doing this captcha"]
|
||||
}
|
||||
""",
|
||||
success, 1 - score)); // hCaptcha scores are inverted compared to recaptcha scores. (low score is good)
|
||||
|
||||
final AssessmentResult result = new HCaptchaClient("fake", client, mockConfig(true, 0.5))
|
||||
.verify(SITE_KEY, "whatever", TOKEN, null);
|
||||
if (!success) {
|
||||
assertThat(result).isEqualTo(AssessmentResult.invalid());
|
||||
} else {
|
||||
assertThat(result)
|
||||
.isEqualTo(new AssessmentResult(expectedResult, AssessmentResult.scoreString(score)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorResponse() throws IOException, InterruptedException {
|
||||
final HttpClient httpClient = mockResponder(503, "");
|
||||
final HCaptchaClient client = new HCaptchaClient("fake", httpClient, mockConfig(true, 0.5));
|
||||
assertThrows(IOException.class, () -> client.verify(SITE_KEY, "whatever", TOKEN, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidScore() throws IOException, InterruptedException {
|
||||
final HttpClient httpClient = mockResponder(200, """
|
||||
{"success" : true, "score": 1.1}
|
||||
""");
|
||||
final HCaptchaClient client = new HCaptchaClient("fake", httpClient, mockConfig(true, 0.5));
|
||||
assertThat(client.verify(SITE_KEY, "whatever", TOKEN, null)).isEqualTo(AssessmentResult.invalid());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void badBody() throws IOException, InterruptedException {
|
||||
final HttpClient httpClient = mockResponder(200, """
|
||||
{"success" : true,
|
||||
""");
|
||||
final HCaptchaClient client = new HCaptchaClient("fake", httpClient, mockConfig(true, 0.5));
|
||||
assertThrows(IOException.class, () -> client.verify(SITE_KEY, "whatever", TOKEN, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disabled() throws IOException {
|
||||
final HCaptchaClient hc = new HCaptchaClient("fake", null, mockConfig(false, 0.5));
|
||||
assertThat(hc.verify(SITE_KEY, null, TOKEN, null)).isEqualTo(AssessmentResult.invalid());
|
||||
}
|
||||
|
||||
private static HttpClient mockResponder(final int statusCode, final String jsonBody)
|
||||
throws IOException, InterruptedException {
|
||||
HttpClient httpClient = mock(HttpClient.class);
|
||||
@SuppressWarnings("unchecked") final HttpResponse<Object> httpResponse = mock(HttpResponse.class);
|
||||
|
||||
when(httpResponse.body()).thenReturn(jsonBody);
|
||||
when(httpResponse.statusCode()).thenReturn(statusCode);
|
||||
|
||||
when(httpClient.send(any(), any())).thenReturn(httpResponse);
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
private static DynamicConfigurationManager<DynamicConfiguration> mockConfig(boolean enabled, double scoreFloor) {
|
||||
final DynamicCaptchaConfiguration config = new DynamicCaptchaConfiguration();
|
||||
config.setAllowHCaptcha(enabled);
|
||||
config.setScoreFloor(BigDecimal.valueOf(scoreFloor));
|
||||
|
||||
@SuppressWarnings("unchecked") final DynamicConfigurationManager<DynamicConfiguration> m = mock(
|
||||
DynamicConfigurationManager.class);
|
||||
final DynamicConfiguration d = mock(DynamicConfiguration.class);
|
||||
when(m.getConfiguration()).thenReturn(d);
|
||||
when(d.getCaptchaConfiguration()).thenReturn(config);
|
||||
return m;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import com.google.i18n.phonenumbers.Phonenumber;
|
||||
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||
import io.dropwizard.testing.junit5.ResourceExtension;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.Duration;
|
||||
@@ -69,6 +70,8 @@ import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
|
||||
import org.whispersystems.textsecuregcm.auth.StoredRegistrationLock;
|
||||
import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
|
||||
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
|
||||
import org.whispersystems.textsecuregcm.captcha.AssessmentResult;
|
||||
import org.whispersystems.textsecuregcm.captcha.CaptchaChecker;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicCaptchaConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
@@ -95,7 +98,6 @@ import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper
|
||||
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
||||
import org.whispersystems.textsecuregcm.push.PushNotification;
|
||||
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
|
||||
import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
|
||||
import org.whispersystems.textsecuregcm.registration.ClientType;
|
||||
import org.whispersystems.textsecuregcm.registration.MessageTransport;
|
||||
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
|
||||
@@ -159,7 +161,7 @@ class AccountControllerTest {
|
||||
private static Account senderRegLockAccount = mock(Account.class);
|
||||
private static Account senderHasStorage = mock(Account.class);
|
||||
private static Account senderTransfer = mock(Account.class);
|
||||
private static RecaptchaClient recaptchaClient = mock(RecaptchaClient.class);
|
||||
private static CaptchaChecker captchaChecker = mock(CaptchaChecker.class);
|
||||
private static PushNotificationManager pushNotificationManager = mock(PushNotificationManager.class);
|
||||
private static ChangeNumberManager changeNumberManager = mock(ChangeNumberManager.class);
|
||||
private static ClientPresenceManager clientPresenceManager = mock(ClientPresenceManager.class);
|
||||
@@ -188,7 +190,7 @@ class AccountControllerTest {
|
||||
dynamicConfigurationManager,
|
||||
turnTokenGenerator,
|
||||
Map.of(TEST_NUMBER, 123456),
|
||||
recaptchaClient,
|
||||
captchaChecker,
|
||||
pushNotificationManager,
|
||||
changeNumberManager,
|
||||
storageCredentialGenerator,
|
||||
@@ -297,10 +299,10 @@ class AccountControllerTest {
|
||||
|
||||
when(dynamicConfiguration.getCaptchaConfiguration()).thenReturn(signupCaptchaConfig);
|
||||
}
|
||||
when(recaptchaClient.verify(eq(INVALID_CAPTCHA_TOKEN), anyString()))
|
||||
.thenReturn(RecaptchaClient.AssessmentResult.invalid());
|
||||
when(recaptchaClient.verify(eq(VALID_CAPTCHA_TOKEN), anyString()))
|
||||
.thenReturn(new RecaptchaClient.AssessmentResult(true, ""));
|
||||
when(captchaChecker.verify(eq(INVALID_CAPTCHA_TOKEN), anyString()))
|
||||
.thenReturn(AssessmentResult.invalid());
|
||||
when(captchaChecker.verify(eq(VALID_CAPTCHA_TOKEN), anyString()))
|
||||
.thenReturn(new AssessmentResult(true, ""));
|
||||
|
||||
doThrow(new RateLimitExceededException(Duration.ZERO)).when(pinLimiter).validate(eq(SENDER_OVER_PIN));
|
||||
|
||||
@@ -328,7 +330,7 @@ class AccountControllerTest {
|
||||
senderRegLockAccount,
|
||||
senderHasStorage,
|
||||
senderTransfer,
|
||||
recaptchaClient,
|
||||
captchaChecker,
|
||||
pushNotificationManager,
|
||||
changeNumberManager,
|
||||
clientPresenceManager);
|
||||
@@ -631,7 +633,7 @@ class AccountControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendWithValidCaptcha() throws NumberParseException {
|
||||
void testSendWithValidCaptcha() throws NumberParseException, IOException {
|
||||
|
||||
when(registrationServiceClient.sendRegistrationCode(any(), any(), any(), any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(new byte[16]));
|
||||
@@ -648,12 +650,12 @@ class AccountControllerTest {
|
||||
|
||||
final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtil.getInstance().parse(SENDER, null);
|
||||
|
||||
verify(recaptchaClient).verify(eq(VALID_CAPTCHA_TOKEN), eq(NICE_HOST));
|
||||
verify(captchaChecker).verify(eq(VALID_CAPTCHA_TOKEN), eq(NICE_HOST));
|
||||
verify(registrationServiceClient).sendRegistrationCode(phoneNumber, MessageTransport.SMS, ClientType.UNKNOWN, null, AccountController.REGISTRATION_RPC_TIMEOUT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendWithInvalidCaptcha() {
|
||||
void testSendWithInvalidCaptcha() throws IOException {
|
||||
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
@@ -665,7 +667,7 @@ class AccountControllerTest {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verify(recaptchaClient).verify(eq(INVALID_CAPTCHA_TOKEN), eq(NICE_HOST));
|
||||
verify(captchaChecker).verify(eq(INVALID_CAPTCHA_TOKEN), eq(NICE_HOST));
|
||||
verifyNoInteractions(registrationServiceClient);
|
||||
}
|
||||
|
||||
@@ -681,7 +683,7 @@ class AccountControllerTest {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verifyNoInteractions(recaptchaClient);
|
||||
verifyNoInteractions(captchaChecker);
|
||||
verifyNoInteractions(registrationServiceClient);
|
||||
}
|
||||
|
||||
@@ -698,7 +700,7 @@ class AccountControllerTest {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verifyNoInteractions(recaptchaClient);
|
||||
verifyNoInteractions(captchaChecker);
|
||||
verifyNoInteractions(registrationServiceClient);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.google.common.net.HttpHeaders;
|
||||
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||
import io.dropwizard.testing.junit5.ResourceExtension;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Set;
|
||||
import javax.ws.rs.client.Entity;
|
||||
@@ -97,7 +98,7 @@ class ChallengeControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandleRecaptcha() throws RateLimitExceededException {
|
||||
void testHandleRecaptcha() throws RateLimitExceededException, IOException {
|
||||
final String recaptchaChallengeJson = """
|
||||
{
|
||||
"type": "recaptcha",
|
||||
@@ -117,7 +118,7 @@ class ChallengeControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandleRecaptchaRateLimited() throws RateLimitExceededException {
|
||||
void testHandleRecaptchaRateLimited() throws RateLimitExceededException, IOException {
|
||||
final String recaptchaChallengeJson = """
|
||||
{
|
||||
"type": "recaptcha",
|
||||
|
||||
@@ -7,19 +7,22 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.whispersystems.textsecuregcm.abuse.RateLimitChallengeListener;
|
||||
import org.whispersystems.textsecuregcm.captcha.AssessmentResult;
|
||||
import org.whispersystems.textsecuregcm.captcha.CaptchaChecker;
|
||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
|
||||
import org.whispersystems.textsecuregcm.captcha.RecaptchaClient;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
|
||||
class RateLimitChallengeManagerTest {
|
||||
|
||||
private PushChallengeManager pushChallengeManager;
|
||||
private RecaptchaClient recaptchaClient;
|
||||
private CaptchaChecker captchaChecker;
|
||||
private DynamicRateLimiters rateLimiters;
|
||||
private RateLimitChallengeListener rateLimitChallengeListener;
|
||||
|
||||
@@ -28,13 +31,13 @@ class RateLimitChallengeManagerTest {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
pushChallengeManager = mock(PushChallengeManager.class);
|
||||
recaptchaClient = mock(RecaptchaClient.class);
|
||||
captchaChecker = mock(CaptchaChecker.class);
|
||||
rateLimiters = mock(DynamicRateLimiters.class);
|
||||
rateLimitChallengeListener = mock(RateLimitChallengeListener.class);
|
||||
|
||||
rateLimitChallengeManager = new RateLimitChallengeManager(
|
||||
pushChallengeManager,
|
||||
recaptchaClient,
|
||||
captchaChecker,
|
||||
rateLimiters);
|
||||
|
||||
rateLimitChallengeManager.addListener(rateLimitChallengeListener);
|
||||
@@ -63,15 +66,15 @@ class RateLimitChallengeManagerTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void answerRecaptchaChallenge(final boolean successfulChallenge) throws RateLimitExceededException {
|
||||
void answerRecaptchaChallenge(final boolean successfulChallenge) throws RateLimitExceededException, IOException {
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getNumber()).thenReturn("+18005551234");
|
||||
when(account.getUuid()).thenReturn(UUID.randomUUID());
|
||||
|
||||
when(recaptchaClient.verify(any(), any()))
|
||||
when(captchaChecker.verify(any(), any()))
|
||||
.thenReturn(successfulChallenge
|
||||
? new RecaptchaClient.AssessmentResult(true, "")
|
||||
: RecaptchaClient.AssessmentResult.invalid());
|
||||
? new AssessmentResult(true, "")
|
||||
: AssessmentResult.invalid());
|
||||
|
||||
when(rateLimiters.getRecaptchaChallengeAttemptLimiter()).thenReturn(mock(RateLimiter.class));
|
||||
when(rateLimiters.getRecaptchaChallengeSuccessLimiter()).thenReturn(mock(RateLimiter.class));
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.recaptcha;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient.SEPARATOR;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.ws.rs.BadRequestException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
class RecaptchaClientTest {
|
||||
|
||||
private static final String PREFIX = RecaptchaClient.V2_PREFIX.substring(0,
|
||||
RecaptchaClient.V2_PREFIX.lastIndexOf(SEPARATOR));
|
||||
private static final String SITE_KEY = "site-key";
|
||||
private static final String TOKEN = "some-token";
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void parseInputToken(final String input, final String expectedToken, final String siteKey,
|
||||
@Nullable final String expectedAction) {
|
||||
|
||||
final String[] parts = RecaptchaClient.parseInputToken(input);
|
||||
|
||||
assertEquals(siteKey, parts[0]);
|
||||
assertEquals(expectedAction, parts[1]);
|
||||
assertEquals(expectedToken, parts[2]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseInputTokenBadRequest() {
|
||||
assertThrows(BadRequestException.class, () -> {
|
||||
RecaptchaClient.parseInputToken(TOKEN);
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void scoreString(float score, String expected) {
|
||||
assertThat(RecaptchaClient.scoreString(score)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
static Stream<Arguments> scoreString() {
|
||||
return Stream.of(
|
||||
Arguments.of(0.3f, "30"),
|
||||
Arguments.of(0.0f, "0"),
|
||||
Arguments.of(0.333f, "33"),
|
||||
Arguments.of(Float.NaN, "0")
|
||||
);
|
||||
}
|
||||
|
||||
static Stream<Arguments> parseInputToken() {
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
String.join(SEPARATOR, SITE_KEY, TOKEN),
|
||||
TOKEN,
|
||||
SITE_KEY,
|
||||
null),
|
||||
Arguments.of(
|
||||
String.join(SEPARATOR, SITE_KEY, "an-action", TOKEN),
|
||||
TOKEN,
|
||||
SITE_KEY,
|
||||
"an-action"),
|
||||
Arguments.of(
|
||||
String.join(SEPARATOR, SITE_KEY, "an-action", TOKEN, "something-else"),
|
||||
TOKEN + SEPARATOR + "something-else",
|
||||
SITE_KEY,
|
||||
"an-action"),
|
||||
Arguments.of(
|
||||
String.join(SEPARATOR, PREFIX, SITE_KEY, TOKEN),
|
||||
TOKEN,
|
||||
SITE_KEY,
|
||||
null),
|
||||
Arguments.of(
|
||||
String.join(SEPARATOR, PREFIX, SITE_KEY, "an-action", TOKEN),
|
||||
TOKEN,
|
||||
SITE_KEY,
|
||||
"an-action"),
|
||||
Arguments.of(
|
||||
String.join(SEPARATOR, PREFIX, SITE_KEY, "an-action", TOKEN, "something-else"),
|
||||
TOKEN + SEPARATOR + "something-else",
|
||||
SITE_KEY,
|
||||
"an-action")
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user