diff --git a/service/config/sample.yml b/service/config/sample.yml index 8a54b45ab..911478a0b 100644 --- a/service/config/sample.yml +++ b/service/config/sample.yml @@ -246,7 +246,6 @@ recaptcha: secret: unset recaptchaV2: - scoreFloor: 1.0 projectPath: projects/example credentialConfigurationJson: "{ }" # service account configuration for backend authentication diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 9b49d74e8..ac27c85f5 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -476,11 +476,12 @@ public class WhisperServerService extends Application signupCountryCodes = Collections.emptySet(); + + public Set getSignupCountryCodes() { + return signupCountryCodes; + } + + @VisibleForTesting + public void setSignupCountryCodes(Set numbers) { + this.signupCountryCodes = numbers; + } + + public BigDecimal getScoreFloor() { + return scoreFloor; + } +} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicConfiguration.java index 9b2bf9cb8..f5bf0cc89 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicConfiguration.java @@ -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() { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicSignupCaptchaConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicSignupCaptchaConfiguration.java deleted file mode 100644 index 894b7f370..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicSignupCaptchaConfiguration.java +++ /dev/null @@ -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 countryCodes = Collections.emptySet(); - - public Set getCountryCodes() { - return countryCodes; - } - - @VisibleForTesting - public void setCountryCodes(Set numbers) { - this.countryCodes = numbers; - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java index 87787b867..06bd69797 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java @@ -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); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/recaptcha/EnterpriseRecaptchaClient.java b/service/src/main/java/org/whispersystems/textsecuregcm/recaptcha/EnterpriseRecaptchaClient.java index dae3e0076..dc43c7619 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/recaptcha/EnterpriseRecaptchaClient.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/recaptcha/EnterpriseRecaptchaClient.java @@ -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 dynamicConfigurationManager; public EnterpriseRecaptchaClient( - final double scoreFloor, @Nonnull final String projectPath, - @Nonnull final String recaptchaCredentialConfigurationJson) { + @Nonnull final String recaptchaCredentialConfigurationJson, + final DynamicConfigurationManager 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; + } } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicConfigurationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicConfigurationTest.java index 8dfb3b0fd..7b777cc01 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicConfigurationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicConfigurationTest.java @@ -26,10 +26,15 @@ import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; class DynamicConfigurationTest { + private static final String REQUIRED_CONFIG = """ + captcha: + scoreFloor: 1.0 + """; + @Test void testParseExperimentConfig() throws JsonProcessingException { { - final String emptyConfigYaml = "test: true"; + final String emptyConfigYaml = REQUIRED_CONFIG.concat("test: true"); final DynamicConfiguration emptyConfig = DynamicConfigurationManager.parseConfiguration(emptyConfigYaml, DynamicConfiguration.class).orElseThrow(); @@ -37,7 +42,7 @@ class DynamicConfigurationTest { } { - final String experimentConfigYaml = """ + final String experimentConfigYaml = REQUIRED_CONFIG.concat(""" experiments: percentageOnly: enrollmentPercentage: 12 @@ -49,7 +54,7 @@ class DynamicConfigurationTest { uuidsOnly: enrolledUuids: - 71618739-114c-4b1f-bb0d-6478a44eb600 - """; + """); final DynamicConfiguration config = DynamicConfigurationManager.parseConfiguration(experimentConfigYaml, DynamicConfiguration.class).orElseThrow(); @@ -78,7 +83,7 @@ class DynamicConfigurationTest { @Test void testParsePreRegistrationExperiments() throws JsonProcessingException { { - final String emptyConfigYaml = "test: true"; + final String emptyConfigYaml = REQUIRED_CONFIG.concat("test: true"); final DynamicConfiguration emptyConfig = DynamicConfigurationManager.parseConfiguration(emptyConfigYaml, DynamicConfiguration.class).orElseThrow(); @@ -86,7 +91,7 @@ class DynamicConfigurationTest { } { - final String experimentConfigYaml = """ + final String experimentConfigYaml = REQUIRED_CONFIG.concat(""" preRegistrationExperiments: percentageOnly: enrollmentPercentage: 17 @@ -107,7 +112,7 @@ class DynamicConfigurationTest { - +120255551212 excludedCountryCodes: - 47 - """; + """); final DynamicConfiguration config = DynamicConfigurationManager.parseConfiguration(experimentConfigYaml, DynamicConfiguration.class).orElseThrow(); @@ -160,7 +165,7 @@ class DynamicConfigurationTest { @Test void testParseRemoteDeprecationConfig() throws JsonProcessingException { { - final String emptyConfigYaml = "test: true"; + final String emptyConfigYaml = REQUIRED_CONFIG.concat("test: true"); final DynamicConfiguration emptyConfig = DynamicConfigurationManager.parseConfiguration(emptyConfigYaml, DynamicConfiguration.class).orElseThrow(); @@ -168,7 +173,7 @@ class DynamicConfigurationTest { } { - final String remoteDeprecationConfig = """ + final String remoteDeprecationConfig = REQUIRED_CONFIG.concat(""" remoteDeprecation: minimumVersions: IOS: 1.2.3 @@ -178,7 +183,7 @@ class DynamicConfigurationTest { blockedVersions: DESKTOP: - 1.4.0-beta.2 - """; + """); final DynamicConfiguration config = DynamicConfigurationManager.parseConfiguration(remoteDeprecationConfig, DynamicConfiguration.class).orElseThrow(); @@ -199,7 +204,7 @@ class DynamicConfigurationTest { @Test void testParseFeatureFlags() throws JsonProcessingException { { - final String emptyConfigYaml = "test: true"; + final String emptyConfigYaml = REQUIRED_CONFIG.concat("test: true"); final DynamicConfiguration emptyConfig = DynamicConfigurationManager.parseConfiguration(emptyConfigYaml, DynamicConfiguration.class).orElseThrow(); @@ -207,10 +212,10 @@ class DynamicConfigurationTest { } { - final String featureFlagYaml = """ + final String featureFlagYaml = REQUIRED_CONFIG.concat(""" featureFlags: - testFlag - """; + """); final DynamicConfiguration emptyConfig = DynamicConfigurationManager.parseConfiguration(featureFlagYaml, DynamicConfiguration.class).orElseThrow(); @@ -222,7 +227,7 @@ class DynamicConfigurationTest { @Test void testParseTwilioConfiguration() throws JsonProcessingException { { - final String emptyConfigYaml = "test: true"; + final String emptyConfigYaml = REQUIRED_CONFIG.concat("test: true"); final DynamicConfiguration emptyConfig = DynamicConfigurationManager.parseConfiguration(emptyConfigYaml, DynamicConfiguration.class).orElseThrow(); @@ -230,12 +235,12 @@ class DynamicConfigurationTest { } { - final String twilioConfigYaml = """ + final String twilioConfigYaml = REQUIRED_CONFIG.concat(""" twilio: numbers: - 2135551212 - 2135551313 - """; + """); final DynamicTwilioConfiguration config = DynamicConfigurationManager.parseConfiguration(twilioConfigYaml, DynamicConfiguration.class).orElseThrow() @@ -248,7 +253,7 @@ class DynamicConfigurationTest { @Test void testParsePaymentsConfiguration() throws JsonProcessingException { { - final String emptyConfigYaml = "test: true"; + final String emptyConfigYaml = REQUIRED_CONFIG.concat("test: true"); final DynamicConfiguration emptyConfig = DynamicConfigurationManager.parseConfiguration(emptyConfigYaml, DynamicConfiguration.class).orElseThrow(); @@ -256,11 +261,11 @@ class DynamicConfigurationTest { } { - final String paymentsConfigYaml = """ + final String paymentsConfigYaml = REQUIRED_CONFIG.concat(""" payments: disallowedPrefixes: - +44 - """; + """); final DynamicPaymentsConfiguration config = DynamicConfigurationManager.parseConfiguration(paymentsConfigYaml, DynamicConfiguration.class).orElseThrow() @@ -271,34 +276,47 @@ class DynamicConfigurationTest { } @Test - void testParseSignupCaptchaConfiguration() throws JsonProcessingException { + void testParseCaptchaConfiguration() throws JsonProcessingException { { final String emptyConfigYaml = "test: true"; - final DynamicConfiguration emptyConfig = - DynamicConfigurationManager.parseConfiguration(emptyConfigYaml, DynamicConfiguration.class).orElseThrow(); - assertTrue(emptyConfig.getSignupCaptchaConfiguration().getCountryCodes().isEmpty()); + assertTrue(DynamicConfigurationManager.parseConfiguration(emptyConfigYaml, DynamicConfiguration.class).isEmpty(), + "empty config should not validate"); } { - final String signupCaptchaConfig = """ - signupCaptcha: - countryCodes: + final String captchaConfig = """ + captcha: + signupCountryCodes: - 1 + scoreFloor: null """; - final DynamicSignupCaptchaConfiguration config = - DynamicConfigurationManager.parseConfiguration(signupCaptchaConfig, DynamicConfiguration.class).orElseThrow() - .getSignupCaptchaConfiguration(); + assertTrue(DynamicConfigurationManager.parseConfiguration(captchaConfig, DynamicConfiguration.class).isEmpty(), + "score floor must not be null"); + } - assertEquals(Set.of("1"), config.getCountryCodes()); + { + final String captchaConfig = """ + captcha: + signupCountryCodes: + - 1 + scoreFloor: 0.9 + """; + + final DynamicCaptchaConfiguration config = + DynamicConfigurationManager.parseConfiguration(captchaConfig, DynamicConfiguration.class).orElseThrow() + .getCaptchaConfiguration(); + + assertEquals(Set.of("1"), config.getSignupCountryCodes()); + assertEquals(0.9f, config.getScoreFloor().floatValue()); } } @Test void testParseLimits() throws JsonProcessingException { { - final String emptyConfigYaml = "test: true"; + final String emptyConfigYaml = REQUIRED_CONFIG.concat("test: true"); final DynamicConfiguration emptyConfig = DynamicConfigurationManager.parseConfiguration(emptyConfigYaml, DynamicConfiguration.class).orElseThrow(); @@ -307,12 +325,12 @@ class DynamicConfigurationTest { } { - final String limitsConfig = """ + final String limitsConfig = REQUIRED_CONFIG.concat(""" limits: rateLimitReset: bucketSize: 17 leakRatePerMinute: 44 - """; + """); final RateLimitConfiguration resetRateLimitConfiguration = DynamicConfigurationManager.parseConfiguration(limitsConfig, DynamicConfiguration.class).orElseThrow() @@ -326,7 +344,7 @@ class DynamicConfigurationTest { @Test void testParseRateLimitReset() throws JsonProcessingException { { - final String emptyConfigYaml = "test: true"; + final String emptyConfigYaml = REQUIRED_CONFIG.concat("test: true"); final DynamicConfiguration emptyConfig = DynamicConfigurationManager.parseConfiguration(emptyConfigYaml, DynamicConfiguration.class).orElseThrow(); @@ -334,13 +352,13 @@ class DynamicConfigurationTest { } { - final String rateLimitChallengeConfig = """ + final String rateLimitChallengeConfig = REQUIRED_CONFIG.concat(""" rateLimitChallenge: clientSupportedVersions: IOS: 5.1.0 ANDROID: 5.2.0 DESKTOP: 5.0.0 - """; + """); DynamicRateLimitChallengeConfiguration rateLimitChallengeConfiguration = DynamicConfigurationManager.parseConfiguration(rateLimitChallengeConfig, DynamicConfiguration.class).orElseThrow() @@ -357,7 +375,7 @@ class DynamicConfigurationTest { @Test void testParseDirectoryReconciler() throws JsonProcessingException { { - final String emptyConfigYaml = "test: true"; + final String emptyConfigYaml = REQUIRED_CONFIG.concat("test: true"); final DynamicConfiguration emptyConfig = DynamicConfigurationManager.parseConfiguration(emptyConfigYaml, DynamicConfiguration.class).orElseThrow(); @@ -365,10 +383,10 @@ class DynamicConfigurationTest { } { - final String directoryReconcilerConfig = """ + final String directoryReconcilerConfig = REQUIRED_CONFIG.concat(""" directoryReconciler: enabled: false - """; + """); DynamicDirectoryReconcilerConfiguration directoryReconcilerConfiguration = DynamicConfigurationManager.parseConfiguration(directoryReconcilerConfig, DynamicConfiguration.class).orElseThrow() diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManagerTest.java index 062dd20e5..9ddf6c0d4 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamicConfigurationManagerTest.java @@ -1,12 +1,13 @@ package org.whispersystems.textsecuregcm.storage; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.time.Duration; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient; @@ -14,10 +15,15 @@ import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfiguratio import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationResponse; import software.amazon.awssdk.services.appconfigdata.model.StartConfigurationSessionRequest; import software.amazon.awssdk.services.appconfigdata.model.StartConfigurationSessionResponse; -import java.util.concurrent.TimeUnit; class DynamicConfigurationManagerTest { + private static final SdkBytes VALID_CONFIG = SdkBytes.fromUtf8String(""" + test: true + captcha: + scoreFloor: 1.0 + """); + private DynamicConfigurationManager dynamicConfigurationManager; private AppConfigDataClient appConfig; private StartConfigurationSessionRequest startConfigurationSession; @@ -35,7 +41,7 @@ class DynamicConfigurationManagerTest { } @Test - void testGetInitalConfig() { + void testGetInitialConfig() { when(appConfig.startConfigurationSession(startConfigurationSession)) .thenReturn(StartConfigurationSessionResponse.builder() .initialConfigurationToken("initial") @@ -45,7 +51,7 @@ class DynamicConfigurationManagerTest { when(appConfig.getLatestConfiguration(GetLatestConfigurationRequest.builder() .configurationToken("initial").build())) .thenReturn(GetLatestConfigurationResponse.builder() - .configuration(SdkBytes.fromUtf8String("test: true")) + .configuration(VALID_CONFIG) .nextPollConfigurationToken("next").build()); // subsequent config calls will return empty (no update) @@ -55,8 +61,10 @@ class DynamicConfigurationManagerTest { .configuration(SdkBytes.fromUtf8String("")) .nextPollConfigurationToken("next").build()); - dynamicConfigurationManager.start(); - assertThat(dynamicConfigurationManager.getConfiguration()).isNotNull(); + assertTimeoutPreemptively(Duration.ofSeconds(5), () -> { + dynamicConfigurationManager.start(); + assertThat(dynamicConfigurationManager.getConfiguration()).isNotNull(); + }); } @Test @@ -77,7 +85,7 @@ class DynamicConfigurationManagerTest { when(appConfig.getLatestConfiguration(GetLatestConfigurationRequest.builder(). configurationToken("goodconfig").build())) .thenReturn(GetLatestConfigurationResponse.builder() - .configuration(SdkBytes.fromUtf8String("test: true")) + .configuration(VALID_CONFIG) .nextPollConfigurationToken("next").build()); // all subsequent config calls will return an empty config (no update) @@ -86,13 +94,15 @@ class DynamicConfigurationManagerTest { .thenReturn(GetLatestConfigurationResponse.builder() .configuration(SdkBytes.fromUtf8String("")) .nextPollConfigurationToken("next").build()); - dynamicConfigurationManager.start(); - assertThat(dynamicConfigurationManager.getConfiguration()).isNotNull(); + + assertTimeoutPreemptively(Duration.ofSeconds(5), () -> { + dynamicConfigurationManager.start(); + assertThat(dynamicConfigurationManager.getConfiguration()).isNotNull(); + }); } @Test - @Timeout(value=5, unit= TimeUnit.SECONDS) - void testGetConfigMultiple() throws InterruptedException { + void testGetConfigMultiple() { when(appConfig.startConfigurationSession(startConfigurationSession)) .thenReturn(StartConfigurationSessionResponse.builder() .initialConfigurationToken("0") @@ -102,7 +112,7 @@ class DynamicConfigurationManagerTest { when(appConfig.getLatestConfiguration(GetLatestConfigurationRequest.builder(). configurationToken("0").build())) .thenReturn(GetLatestConfigurationResponse.builder() - .configuration(SdkBytes.fromUtf8String("test: true")) + .configuration(VALID_CONFIG) .nextPollConfigurationToken("1").build()); // config update with a real config @@ -112,6 +122,8 @@ class DynamicConfigurationManagerTest { .configuration(SdkBytes.fromUtf8String(""" featureFlags: - testFlag + captcha: + scoreFloor: 1.0 """)) .nextPollConfigurationToken("2").build()); @@ -122,11 +134,16 @@ class DynamicConfigurationManagerTest { .configuration(SdkBytes.fromUtf8String("")) .nextPollConfigurationToken("2").build()); - // we should eventually get the updated config (or the test will timeout) - dynamicConfigurationManager.start(); - while (dynamicConfigurationManager.getConfiguration().getActiveFeatureFlags().isEmpty()) { - Thread.sleep(100); - } - assertThat(dynamicConfigurationManager.getConfiguration().getActiveFeatureFlags()).containsExactly("testFlag"); + // the internal waiting done by dynamic configuration manager catches the InterruptedException used + // by JUnit’s @Timeout, so we use assertTimeoutPreemptively + assertTimeoutPreemptively(Duration.ofSeconds(5), () -> { + // we should eventually get the updated config (or the test will timeout) + dynamicConfigurationManager.start(); + while (dynamicConfigurationManager.getConfiguration().getActiveFeatureFlags().isEmpty()) { + Thread.sleep(100); + } + assertThat(dynamicConfigurationManager.getConfiguration().getActiveFeatureFlags()).containsExactly("testFlag"); + }); + } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java index 6385f5b0b..c0cf27b13 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java @@ -52,7 +52,6 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; -import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials; @@ -61,8 +60,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.configuration.dynamic.DynamicCaptchaConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicSignupCaptchaConfiguration; import org.whispersystems.textsecuregcm.controllers.AccountController; import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException; import org.whispersystems.textsecuregcm.entities.AccountAttributes; @@ -263,9 +262,9 @@ class AccountControllerTest { when(dynamicConfigurationManager.getConfiguration()) .thenReturn(dynamicConfiguration); - DynamicSignupCaptchaConfiguration signupCaptchaConfig = new DynamicSignupCaptchaConfiguration(); + DynamicCaptchaConfiguration signupCaptchaConfig = new DynamicCaptchaConfiguration(); - when(dynamicConfiguration.getSignupCaptchaConfiguration()).thenReturn(signupCaptchaConfig); + when(dynamicConfiguration.getCaptchaConfiguration()).thenReturn(signupCaptchaConfig); } when(abusiveHostRules.getAbusiveHostRulesFor(eq(ABUSIVE_HOST))).thenReturn(Collections.singletonList(new AbusiveHostRule(ABUSIVE_HOST, true, Collections.emptyList()))); when(abusiveHostRules.getAbusiveHostRulesFor(eq(RESTRICTED_HOST))).thenReturn(Collections.singletonList(new AbusiveHostRule(RESTRICTED_HOST, false, Collections.singletonList("+123")))); @@ -1741,9 +1740,9 @@ class AccountControllerTest { when(dynamicConfigurationManager.getConfiguration()) .thenReturn(dynamicConfiguration); - DynamicSignupCaptchaConfiguration signupCaptchaConfig = new DynamicSignupCaptchaConfiguration(); - signupCaptchaConfig.setCountryCodes(countryCodes); - when(dynamicConfiguration.getSignupCaptchaConfiguration()) + DynamicCaptchaConfiguration signupCaptchaConfig = new DynamicCaptchaConfiguration(); + signupCaptchaConfig.setSignupCountryCodes(countryCodes); + when(dynamicConfiguration.getCaptchaConfiguration()) .thenReturn(signupCaptchaConfig); Response response =