mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-22 00:08:07 +01:00
Support for getting/setting remote config variables
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.whispersystems.textsecuregcm.entities.UserRemoteConfig;
|
||||
import org.whispersystems.textsecuregcm.entities.UserRemoteConfigList;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.RemoteConfig;
|
||||
import org.whispersystems.textsecuregcm.storage.RemoteConfigsManager;
|
||||
import org.whispersystems.textsecuregcm.util.Conversions;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.DELETE;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.HeaderParam;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.dropwizard.auth.Auth;
|
||||
|
||||
@Path("/v1/config")
|
||||
public class RemoteConfigController {
|
||||
|
||||
private final RemoteConfigsManager remoteConfigsManager;
|
||||
private final List<String> configAuthTokens;
|
||||
|
||||
public RemoteConfigController(RemoteConfigsManager remoteConfigsManager, List<String> configAuthTokens) {
|
||||
this.remoteConfigsManager = remoteConfigsManager;
|
||||
this.configAuthTokens = configAuthTokens;
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public UserRemoteConfigList getAll(@Auth Account account) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA1");
|
||||
byte[] number = account.getNumber().getBytes();
|
||||
|
||||
return new UserRemoteConfigList(remoteConfigsManager.getAll().stream().map(config -> new UserRemoteConfig(config.getName(),
|
||||
isInBucket(digest, number,
|
||||
config.getName().getBytes(),
|
||||
config.getPercentage())))
|
||||
.collect(Collectors.toList()));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Timed
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public void set(@HeaderParam("Config-Token") String configToken, @Valid RemoteConfig config) {
|
||||
if (!isAuthorized(configToken)) {
|
||||
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
remoteConfigsManager.set(config);
|
||||
}
|
||||
|
||||
@Timed
|
||||
@DELETE
|
||||
@Path("/{name}")
|
||||
public void delete(@HeaderParam("Config-Token") String configToken, @PathParam("name") String name) {
|
||||
if (!isAuthorized(configToken)) {
|
||||
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
remoteConfigsManager.delete(name);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static boolean isInBucket(MessageDigest digest, byte[] user, byte[] configName, int configPercentage) {
|
||||
digest.update(user);
|
||||
|
||||
byte[] hash = digest.digest(configName);
|
||||
int bucket = (int)(Math.abs(Conversions.byteArrayToLong(hash)) % 100);
|
||||
|
||||
return bucket < configPercentage;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean isAuthorized(String configToken) {
|
||||
return configAuthTokens.stream().anyMatch(authorized -> MessageDigest.isEqual(authorized.getBytes(), configToken.getBytes()));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user