Create global remote config controllable in the signal server configuration (#127)
* Add global config controller through file rather than database * Do no permit attempting to set or delete global config entries
This commit is contained in:
parent
b14a8ff2fd
commit
b97158bf7b
|
@ -127,3 +127,11 @@ micrometer: # Micrometer metrics config
|
|||
- uri: "https://metrics.example.com/"
|
||||
- apiKey:
|
||||
- accountId:
|
||||
|
||||
remoteConfig:
|
||||
authorizedTokens:
|
||||
- # 1st authorized token
|
||||
- # 2nd authorized token
|
||||
- # ...
|
||||
- # Nth authorized token
|
||||
globalConfig: # keys and values that are given to clients on GET /v1/config
|
||||
|
|
|
@ -417,7 +417,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||
MessageController messageController = new MessageController(rateLimiters, pushSender, receiptSender, accountsManager, messagesManager, apnFallbackManager);
|
||||
ProfileController profileController = new ProfileController(rateLimiters, accountsManager, profilesManager, usernamesManager, cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner, config.getCdnConfiguration().getBucket(), zkProfileOperations, isZkEnabled);
|
||||
StickerController stickerController = new StickerController(rateLimiters, config.getCdnConfiguration().getAccessKey(), config.getCdnConfiguration().getAccessSecret(), config.getCdnConfiguration().getRegion(), config.getCdnConfiguration().getBucket());
|
||||
RemoteConfigController remoteConfigController = new RemoteConfigController(remoteConfigsManager, config.getRemoteConfigConfiguration().getAuthorizedTokens());
|
||||
RemoteConfigController remoteConfigController = new RemoteConfigController(remoteConfigsManager, config.getRemoteConfigConfiguration().getAuthorizedTokens(), config.getRemoteConfigConfiguration().getGlobalConfig());
|
||||
|
||||
AuthFilter<BasicCredentials, Account> accountAuthFilter = new BasicCredentialAuthFilter.Builder<Account>().setAuthenticator(accountAuthenticator).buildAuthFilter ();
|
||||
AuthFilter<BasicCredentials, DisabledPermittedAccount> disabledPermittedAccountAuthFilter = new BasicCredentialAuthFilter.Builder<DisabledPermittedAccount>().setAuthenticator(disabledPermittedAccountAuthenticator).buildAuthFilter();
|
||||
|
|
|
@ -3,8 +3,10 @@ package org.whispersystems.textsecuregcm.configuration;
|
|||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class RemoteConfigConfiguration {
|
||||
|
||||
|
@ -12,7 +14,15 @@ public class RemoteConfigConfiguration {
|
|||
@NotNull
|
||||
private List<String> authorizedTokens = new LinkedList<>();
|
||||
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private Map<String, String> globalConfig = new HashMap<>();
|
||||
|
||||
public List<String> getAuthorizedTokens() {
|
||||
return authorizedTokens;
|
||||
}
|
||||
|
||||
public Map<String, String> getGlobalConfig() {
|
||||
return globalConfig;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,19 +27,23 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Path("/v1/config")
|
||||
public class RemoteConfigController {
|
||||
|
||||
private final RemoteConfigsManager remoteConfigsManager;
|
||||
private final List<String> configAuthTokens;
|
||||
private final Map<String, String> globalConfig;
|
||||
|
||||
public RemoteConfigController(RemoteConfigsManager remoteConfigsManager, List<String> configAuthTokens) {
|
||||
public RemoteConfigController(RemoteConfigsManager remoteConfigsManager, List<String> configAuthTokens, Map<String, String> globalConfig) {
|
||||
this.remoteConfigsManager = remoteConfigsManager;
|
||||
this.configAuthTokens = configAuthTokens;
|
||||
this.configAuthTokens = configAuthTokens;
|
||||
this.globalConfig = globalConfig;
|
||||
}
|
||||
|
||||
@Timed
|
||||
|
@ -50,11 +54,12 @@ public class RemoteConfigController {
|
|||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA1");
|
||||
|
||||
return new UserRemoteConfigList(remoteConfigsManager.getAll().stream().map(config -> {
|
||||
final Stream<UserRemoteConfig> globalConfigStream = globalConfig.entrySet().stream().map(entry -> new UserRemoteConfig("g." + entry.getKey(), true, entry.getValue()));
|
||||
return new UserRemoteConfigList(Stream.concat(remoteConfigsManager.getAll().stream().map(config -> {
|
||||
final byte[] hashKey = config.getHashKey() != null ? config.getHashKey().getBytes(StandardCharsets.UTF_8) : config.getName().getBytes(StandardCharsets.UTF_8);
|
||||
boolean inBucket = isInBucket(digest, account.getUuid(), hashKey, config.getPercentage(), config.getUuids());
|
||||
return new UserRemoteConfig(config.getName(), inBucket, inBucket ? config.getValue() : config.getDefaultValue());
|
||||
}).collect(Collectors.toList()));
|
||||
}), globalConfigStream).collect(Collectors.toList()));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
@ -69,6 +74,10 @@ public class RemoteConfigController {
|
|||
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if (config.getName().startsWith("g.")) {
|
||||
throw new WebApplicationException(Response.Status.FORBIDDEN);
|
||||
}
|
||||
|
||||
remoteConfigsManager.set(config);
|
||||
}
|
||||
|
||||
|
@ -80,6 +89,10 @@ public class RemoteConfigController {
|
|||
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if (name.startsWith("g.")) {
|
||||
throw new WebApplicationException(Response.Status.FORBIDDEN);
|
||||
}
|
||||
|
||||
remoteConfigsManager.delete(name);
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ public class RemoteConfigControllerTest {
|
|||
.addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(ImmutableSet.of(Account.class, DisabledPermittedAccount.class)))
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addProvider(new DeviceLimitExceededExceptionMapper())
|
||||
.addResource(new RemoteConfigController(remoteConfigsManager, remoteConfigsAuth))
|
||||
.addResource(new RemoteConfigController(remoteConfigsManager, remoteConfigsAuth, Map.of("maxGroupSize", "42")))
|
||||
.build();
|
||||
|
||||
|
||||
|
@ -79,7 +79,7 @@ public class RemoteConfigControllerTest {
|
|||
|
||||
verify(remoteConfigsManager, times(1)).getAll();
|
||||
|
||||
assertThat(configuration.getConfig()).hasSize(10);
|
||||
assertThat(configuration.getConfig()).hasSize(11);
|
||||
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");
|
||||
|
@ -100,6 +100,7 @@ public class RemoteConfigControllerTest {
|
|||
assertThat(configuration.getConfig().get(7).getName()).isEqualTo("linked.config.0");
|
||||
assertThat(configuration.getConfig().get(8).getName()).isEqualTo("linked.config.1");
|
||||
assertThat(configuration.getConfig().get(9).getName()).isEqualTo("unlinked.config");
|
||||
assertThat(configuration.getConfig().get(10).getName()).isEqualTo("g.maxGroupSize");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -112,7 +113,7 @@ public class RemoteConfigControllerTest {
|
|||
|
||||
verify(remoteConfigsManager, times(1)).getAll();
|
||||
|
||||
assertThat(configuration.getConfig()).hasSize(10);
|
||||
assertThat(configuration.getConfig()).hasSize(11);
|
||||
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");
|
||||
|
@ -133,6 +134,7 @@ public class RemoteConfigControllerTest {
|
|||
assertThat(configuration.getConfig().get(7).getName()).isEqualTo("linked.config.0");
|
||||
assertThat(configuration.getConfig().get(8).getName()).isEqualTo("linked.config.1");
|
||||
assertThat(configuration.getConfig().get(9).getName()).isEqualTo("unlinked.config");
|
||||
assertThat(configuration.getConfig().get(10).getName()).isEqualTo("g.maxGroupSize");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -140,7 +142,7 @@ public class RemoteConfigControllerTest {
|
|||
boolean allUnlinkedConfigsMatched = true;
|
||||
for (AuthHelper.TestAccount testAccount : AuthHelper.TEST_ACCOUNTS) {
|
||||
UserRemoteConfigList configuration = resources.getJerseyTest().target("/v1/config/").request().header("Authorization", testAccount.getAuthHeader()).get(UserRemoteConfigList.class);
|
||||
assertThat(configuration.getConfig()).hasSize(10);
|
||||
assertThat(configuration.getConfig()).hasSize(11);
|
||||
|
||||
final UserRemoteConfig linkedConfig0 = configuration.getConfig().get(7);
|
||||
assertThat(linkedConfig0.getName()).isEqualTo("linked.config.0");
|
||||
|
@ -297,6 +299,16 @@ public class RemoteConfigControllerTest {
|
|||
verifyNoMoreInteractions(remoteConfigsManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetGlobalConfig() {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v1/config")
|
||||
.request()
|
||||
.header("Config-Token", "foo")
|
||||
.put(Entity.entity(new RemoteConfig("g.maxGroupSize", 88, Set.of(), "FALSE", "TRUE", null), MediaType.APPLICATION_JSON_TYPE));
|
||||
assertThat(response.getStatus()).isEqualTo(403);
|
||||
verifyNoMoreInteractions(remoteConfigsManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete() {
|
||||
|
@ -325,6 +337,17 @@ public class RemoteConfigControllerTest {
|
|||
verifyNoMoreInteractions(remoteConfigsManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteGlobalConfig() {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v1/config/g.maxGroupSize")
|
||||
.request()
|
||||
.header("Config-Token", "foo")
|
||||
.delete();
|
||||
assertThat(response.getStatus()).isEqualTo(403);
|
||||
verifyNoMoreInteractions(remoteConfigsManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMath() throws NoSuchAlgorithmException {
|
||||
List<RemoteConfig> remoteConfigList = remoteConfigsManager.getAll();
|
||||
|
|
Loading…
Reference in New Issue