Add support for UUID buckets in remote config

This commit is contained in:
Moxie Marlinspike
2020-01-21 14:38:47 -08:00
parent 08a70664f4
commit e4e20c2d25
8 changed files with 135 additions and 31 deletions

View File

@@ -22,9 +22,11 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
import io.dropwizard.testing.junit.ResourceTestRule;
@@ -52,9 +54,22 @@ public class RemoteConfigControllerTest {
@Before
public void setup() throws Exception {
when(remoteConfigsManager.getAll()).thenReturn(new LinkedList<>() {{
add(new RemoteConfig("android.stickers", 25));
add(new RemoteConfig("ios.stickers", 50));
add(new RemoteConfig("always.true", 100));
add(new RemoteConfig("android.stickers", 25, new HashSet<>() {{
add(AuthHelper.DISABLED_UUID);
add(AuthHelper.INVALID_UUID);
}}));
add(new RemoteConfig("ios.stickers", 50, new HashSet<>() {{
}}));
add(new RemoteConfig("always.true", 100, new HashSet<>() {{
}}));
add(new RemoteConfig("only.special", 0, new HashSet<>() {{
add(AuthHelper.VALID_UUID);
}}));
}});
}
@@ -68,13 +83,33 @@ public class RemoteConfigControllerTest {
verify(remoteConfigsManager, times(1)).getAll();
assertThat(configuration.getConfig().size()).isEqualTo(3);
assertThat(configuration.getConfig().size()).isEqualTo(4);
assertThat(configuration.getConfig().get(0).getName()).isEqualTo("android.stickers");
assertThat(configuration.getConfig().get(1).getName()).isEqualTo("ios.stickers");
assertThat(configuration.getConfig().get(2).getName()).isEqualTo("always.true");
assertThat(configuration.getConfig().get(2).isEnabled()).isEqualTo(true);
assertThat(configuration.getConfig().get(3).isEnabled()).isEqualTo(true);
}
@Test
public void testRetrieveConfigNotSpecial() {
UserRemoteConfigList configuration = resources.getJerseyTest()
.target("/v1/config/")
.request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER_TWO, AuthHelper.VALID_PASSWORD_TWO))
.get(UserRemoteConfigList.class);
verify(remoteConfigsManager, times(1)).getAll();
assertThat(configuration.getConfig().size()).isEqualTo(4);
assertThat(configuration.getConfig().get(0).getName()).isEqualTo("android.stickers");
assertThat(configuration.getConfig().get(1).getName()).isEqualTo("ios.stickers");
assertThat(configuration.getConfig().get(2).getName()).isEqualTo("always.true");
assertThat(configuration.getConfig().get(2).isEnabled()).isEqualTo(true);
assertThat(configuration.getConfig().get(3).isEnabled()).isEqualTo(false);
}
@Test
public void testRetrieveConfigUnauthorized() {
Response response = resources.getJerseyTest()
@@ -95,7 +130,7 @@ public class RemoteConfigControllerTest {
.target("/v1/config")
.request()
.header("Config-Token", "foo")
.put(Entity.entity(new RemoteConfig("android.stickers", 88), MediaType.APPLICATION_JSON_TYPE));
.put(Entity.entity(new RemoteConfig("android.stickers", 88, new HashSet<>()), MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(204);
@@ -105,6 +140,7 @@ public class RemoteConfigControllerTest {
assertThat(captor.getValue().getName()).isEqualTo("android.stickers");
assertThat(captor.getValue().getPercentage()).isEqualTo(88);
assertThat(captor.getValue().getUuids()).isEmpty();
}
@Test
@@ -113,7 +149,19 @@ public class RemoteConfigControllerTest {
.target("/v1/config")
.request()
.header("Config-Token", "baz")
.put(Entity.entity(new RemoteConfig("android.stickers", 88), MediaType.APPLICATION_JSON_TYPE));
.put(Entity.entity(new RemoteConfig("android.stickers", 88, new HashSet<>()), MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(401);
verifyNoMoreInteractions(remoteConfigsManager);
}
@Test
public void testSetConfigMissingUnauthorized() {
Response response = resources.getJerseyTest()
.target("/v1/config")
.request()
.put(Entity.entity(new RemoteConfig("android.stickers", 88, new HashSet<>()), MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(401);
@@ -126,7 +174,7 @@ public class RemoteConfigControllerTest {
.target("/v1/config")
.request()
.header("Config-Token", "foo")
.put(Entity.entity(new RemoteConfig("android-stickers", 88), MediaType.APPLICATION_JSON_TYPE));
.put(Entity.entity(new RemoteConfig("android-stickers", 88, new HashSet<>()), MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(422);
@@ -139,7 +187,7 @@ public class RemoteConfigControllerTest {
.target("/v1/config")
.request()
.header("Config-Token", "foo")
.put(Entity.entity(new RemoteConfig("", 88), MediaType.APPLICATION_JSON_TYPE));
.put(Entity.entity(new RemoteConfig("", 88, new HashSet<>()), MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(422);
@@ -186,7 +234,7 @@ public class RemoteConfigControllerTest {
int count = enabledMap.getOrDefault(config.getName(), 0);
int random = new SecureRandom().nextInt(iterations);
if (RemoteConfigController.isInBucket(digest, ("+121322" + String.format("%05d", random)).getBytes(), config.getName().getBytes(), config.getPercentage())) {
if (RemoteConfigController.isInBucket(digest, UUID.randomUUID(), config.getName().getBytes(), config.getPercentage(), new HashSet<>())) {
count++;
}

View File

@@ -12,9 +12,12 @@ import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
import org.whispersystems.textsecuregcm.storage.RemoteConfig;
import org.whispersystems.textsecuregcm.storage.RemoteConfigs;
import org.whispersystems.textsecuregcm.storage.RemoteConfigsManager;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import java.util.HashSet;
import java.util.List;
import io.dropwizard.auth.Auth;
import static org.assertj.core.api.Java6Assertions.assertThat;
public class RemoteConfigsManagerTest {
@@ -33,9 +36,11 @@ public class RemoteConfigsManagerTest {
@Test
public void testUpdate() throws InterruptedException {
remoteConfigs.set(new RemoteConfig("android.stickers", 50));
remoteConfigs.set(new RemoteConfig("ios.stickers", 50));
remoteConfigs.set(new RemoteConfig("ios.stickers", 75));
remoteConfigs.set(new RemoteConfig("android.stickers", 50, new HashSet<>() {{
add(AuthHelper.VALID_UUID);
}}));
remoteConfigs.set(new RemoteConfig("ios.stickers", 50, new HashSet<>()));
remoteConfigs.set(new RemoteConfig("ios.stickers", 75, new HashSet<>()));
Thread.sleep(501);
@@ -44,8 +49,12 @@ public class RemoteConfigsManagerTest {
assertThat(results.size()).isEqualTo(2);
assertThat(results.get(0).getName()).isEqualTo("android.stickers");
assertThat(results.get(0).getPercentage()).isEqualTo(50);
assertThat(results.get(0).getUuids().size()).isEqualTo(1);
assertThat(results.get(0).getUuids().contains(AuthHelper.VALID_UUID)).isTrue();
assertThat(results.get(1).getName()).isEqualTo("ios.stickers");
assertThat(results.get(1).getPercentage()).isEqualTo(75);
assertThat(results.get(1).getUuids()).isEmpty();
}

View File

@@ -11,10 +11,13 @@ import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguratio
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
import org.whispersystems.textsecuregcm.storage.RemoteConfig;
import org.whispersystems.textsecuregcm.storage.RemoteConfigs;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.List;
import io.dropwizard.auth.Auth;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
public class RemoteConfigsTest {
@@ -31,35 +34,51 @@ public class RemoteConfigsTest {
@Test
public void testStore() throws SQLException {
remoteConfigs.set(new RemoteConfig("android.stickers", 50));
remoteConfigs.set(new RemoteConfig("android.stickers", 50, new HashSet<>() {{
add(AuthHelper.VALID_UUID);
add(AuthHelper.VALID_UUID_TWO);
}}));
List<RemoteConfig> configs = remoteConfigs.getAll();
assertThat(configs.size()).isEqualTo(1);
assertThat(configs.get(0).getName()).isEqualTo("android.stickers");
assertThat(configs.get(0).getPercentage()).isEqualTo(50);
assertThat(configs.get(0).getUuids().size()).isEqualTo(2);
assertThat(configs.get(0).getUuids().contains(AuthHelper.VALID_UUID)).isTrue();
assertThat(configs.get(0).getUuids().contains(AuthHelper.VALID_UUID_TWO)).isTrue();
assertThat(configs.get(0).getUuids().contains(AuthHelper.INVALID_UUID)).isFalse();
}
@Test
public void testUpdate() throws SQLException {
remoteConfigs.set(new RemoteConfig("android.stickers", 50));
remoteConfigs.set(new RemoteConfig("ios.stickers", 50));
remoteConfigs.set(new RemoteConfig("ios.stickers", 75));
remoteConfigs.set(new RemoteConfig("android.stickers", 50, new HashSet<>()));
remoteConfigs.set(new RemoteConfig("ios.stickers", 50, new HashSet<>() {{
add(AuthHelper.DISABLED_UUID);
}}));
remoteConfigs.set(new RemoteConfig("ios.stickers", 75, new HashSet<>()));
List<RemoteConfig> configs = remoteConfigs.getAll();
assertThat(configs.size()).isEqualTo(2);
assertThat(configs.get(0).getName()).isEqualTo("android.stickers");
assertThat(configs.get(0).getPercentage()).isEqualTo(50);
assertThat(configs.get(0).getUuids().size()).isEqualTo(0);
assertThat(configs.get(1).getName()).isEqualTo("ios.stickers");
assertThat(configs.get(1).getPercentage()).isEqualTo(75);
assertThat(configs.get(1).getUuids().size()).isEqualTo(0);
}
@Test
public void testDelete() {
remoteConfigs.set(new RemoteConfig("android.stickers", 50));
remoteConfigs.set(new RemoteConfig("ios.stickers", 50));
remoteConfigs.set(new RemoteConfig("ios.stickers", 75));
remoteConfigs.set(new RemoteConfig("android.stickers", 50, new HashSet<>() {{
add(AuthHelper.VALID_UUID);
}}));
remoteConfigs.set(new RemoteConfig("ios.stickers", 50, new HashSet<>()));
remoteConfigs.set(new RemoteConfig("ios.stickers", 75, new HashSet<>()));
remoteConfigs.delete("android.stickers");
List<RemoteConfig> configs = remoteConfigs.getAll();