Transparent data controller
This commit is contained in:
parent
ea38645493
commit
6ce686ab9c
|
@ -141,6 +141,8 @@ public class WhisperServerConfiguration extends Configuration {
|
|||
@JsonProperty
|
||||
private VoiceVerificationConfiguration voiceVerification;
|
||||
|
||||
private Map<String, String> transparentDataIndex = new HashMap<>();
|
||||
|
||||
|
||||
public VoiceVerificationConfiguration getVoiceVerificationConfiguration() {
|
||||
return voiceVerification;
|
||||
|
@ -244,4 +246,8 @@ public class WhisperServerConfiguration extends Configuration {
|
|||
return results;
|
||||
}
|
||||
|
||||
public Map<String, String> getTransparentDataIndex() {
|
||||
return transparentDataIndex;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.whispersystems.textsecuregcm.controllers.MessageController;
|
|||
import org.whispersystems.textsecuregcm.controllers.ProfileController;
|
||||
import org.whispersystems.textsecuregcm.controllers.ProvisioningController;
|
||||
import org.whispersystems.textsecuregcm.controllers.VoiceVerificationController;
|
||||
import org.whispersystems.textsecuregcm.controllers.TransparentDataController;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.liquibase.NameableMigrationsBundle;
|
||||
import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper;
|
||||
|
@ -225,6 +226,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||
environment.jersey().register(new ProvisioningController(rateLimiters, pushSender));
|
||||
environment.jersey().register(new CertificateController(new CertificateGenerator(config.getDeliveryCertificate().getCertificate(), config.getDeliveryCertificate().getPrivateKey(), config.getDeliveryCertificate().getExpiresDays())));
|
||||
environment.jersey().register(new VoiceVerificationController(config.getVoiceVerificationConfiguration().getUrl(), config.getVoiceVerificationConfiguration().getLocales()));
|
||||
environment.jersey().register(new TransparentDataController(accountsManager, config.getTransparentDataIndex()));
|
||||
environment.jersey().register(attachmentController);
|
||||
environment.jersey().register(keysController);
|
||||
environment.jersey().register(messageController);
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.PublicAccount;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Path("/v1/transparency/")
|
||||
public class TransparentDataController {
|
||||
|
||||
private final AccountsManager accountsManager;
|
||||
private final Map<String, String> transparentDataIndex;
|
||||
|
||||
public TransparentDataController(AccountsManager accountsManager,
|
||||
Map<String, String> transparentDataIndex)
|
||||
{
|
||||
this.accountsManager = accountsManager;
|
||||
this.transparentDataIndex = transparentDataIndex;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/account/{id}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Optional<PublicAccount> getAccount(@PathParam("id") String id) {
|
||||
String index = transparentDataIndex.get(id);
|
||||
|
||||
if (index != null) {
|
||||
return accountsManager.get(index).map(PublicAccount::new);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -30,9 +30,9 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
public class Account implements Principal {
|
||||
|
||||
public static final int MEMCACHE_VERION = 5;
|
||||
static final int MEMCACHE_VERION = 5;
|
||||
|
||||
@JsonProperty
|
||||
@JsonIgnore
|
||||
private String number;
|
||||
|
||||
@JsonProperty
|
||||
|
|
|
@ -111,7 +111,7 @@ public abstract class Accounts {
|
|||
{
|
||||
try {
|
||||
Account account = mapper.readValue(resultSet.getString(DATA), Account.class);
|
||||
// account.setId(resultSet.getLong(ID));
|
||||
account.setNumber(resultSet.getString(NUMBER));
|
||||
|
||||
return account;
|
||||
} catch (IOException e) {
|
||||
|
|
|
@ -136,8 +136,14 @@ public class AccountsManager {
|
|||
{
|
||||
String json = jedis.get(getKey(number));
|
||||
|
||||
if (json != null) return Optional.of(mapper.readValue(json, Account.class));
|
||||
else return Optional.empty();
|
||||
if (json != null) {
|
||||
Account account = mapper.readValue(json, Account.class);
|
||||
account.setNumber(number);
|
||||
|
||||
return Optional.of(account);
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
} catch (IOException e) {
|
||||
logger.warn("AccountsManager", "Deserialization error", e);
|
||||
return Optional.empty();
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
public class PublicAccount extends Account {
|
||||
|
||||
public PublicAccount() {}
|
||||
|
||||
public PublicAccount(Account account) {
|
||||
setIdentityKey(account.getIdentityKey());
|
||||
setUnidentifiedAccessKey(account.getUnidentifiedAccessKey().orElse(null));
|
||||
setUnrestrictedUnidentifiedAccess(account.isUnrestrictedUnidentifiedAccess());
|
||||
setAvatar(account.getAvatar());
|
||||
setProfileName(account.getProfileName());
|
||||
setPin("******");
|
||||
|
||||
account.getDevices().forEach(this::addDevice);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.controllers.TransparentDataController;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.PublicAccount;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.dropwizard.auth.AuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit.ResourceTestRule;
|
||||
import static junit.framework.TestCase.*;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.asJson;
|
||||
import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.jsonFixture;
|
||||
|
||||
public class TransparentDataControllerTest {
|
||||
|
||||
private final AccountsManager accountsManager = mock(AccountsManager.class);
|
||||
private final Map<String, String> indexMap = new HashMap<>();
|
||||
|
||||
@Rule
|
||||
public final ResourceTestRule resources = ResourceTestRule.builder()
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
.addProvider(new AuthValueFactoryProvider.Binder<>(Account.class))
|
||||
.addProvider(new RateLimitExceededExceptionMapper())
|
||||
.setMapper(SystemMapper.getMapper())
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new TransparentDataController(accountsManager, indexMap))
|
||||
.build();
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
Account accountOne = new Account("+14151231111", Collections.singleton(new Device(1, "foo", "bar", "salt", "keykey", "gcm-id", "apn-id", "voipapn-id", true, 1234, new SignedPreKey(5, "public-signed", "signtture-signed"), 31337, 31336, "CoolClient", true)), new byte[16]);
|
||||
Account accountTwo = new Account("+14151232222", Collections.singleton(new Device(1, "2foo", "2bar", "2salt", "2keykey", "2gcm-id", "2apn-id", "2voipapn-id", true, 1234, new SignedPreKey(5, "public-signed", "signtture-signed"), 31337, 31336, "CoolClient", true)), new byte[16]);
|
||||
|
||||
accountOne.setProfileName("OneProfileName");
|
||||
accountOne.setIdentityKey("identity_key_value");
|
||||
accountTwo.setProfileName("TwoProfileName");
|
||||
accountTwo.setIdentityKey("different_identity_key_value");
|
||||
|
||||
|
||||
indexMap.put("1", "+14151231111");
|
||||
indexMap.put("2", "+14151232222");
|
||||
|
||||
when(accountsManager.get(eq("+14151231111"))).thenReturn(Optional.of(accountOne));
|
||||
when(accountsManager.get(eq("+14151232222"))).thenReturn(Optional.of(accountTwo));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAccountOne() throws IOException {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target(String.format("/v1/transparency/account/%s", "1"))
|
||||
.request()
|
||||
.get();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
Account result = response.readEntity(PublicAccount.class);
|
||||
|
||||
assertTrue(result.getPin().isPresent());
|
||||
assertEquals("******", result.getPin().get());
|
||||
assertNull(result.getNumber());
|
||||
assertEquals("OneProfileName", result.getProfileName());
|
||||
|
||||
assertThat("Account serialization works",
|
||||
asJson(result),
|
||||
is(equalTo(jsonFixture("fixtures/transparent_account.json"))));
|
||||
|
||||
verify(accountsManager, times(1)).get(eq("+14151231111"));
|
||||
verifyNoMoreInteractions(accountsManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAccountTwo() throws IOException {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target(String.format("/v1/transparency/account/%s", "2"))
|
||||
.request()
|
||||
.get();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
Account result = response.readEntity(PublicAccount.class);
|
||||
|
||||
assertTrue(result.getPin().isPresent());
|
||||
assertEquals("******", result.getPin().get());
|
||||
assertNull(result.getNumber());
|
||||
assertEquals("TwoProfileName", result.getProfileName());
|
||||
|
||||
assertThat("Account serialization works 2",
|
||||
asJson(result),
|
||||
is(equalTo(jsonFixture("fixtures/transparent_account2.json"))));
|
||||
|
||||
verify(accountsManager, times(1)).get(eq("+14151232222"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAccountMissing() {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target(String.format("/v1/transparency/account/%s", "3"))
|
||||
.request()
|
||||
.get();
|
||||
|
||||
assertEquals(404, response.getStatus());
|
||||
verifyNoMoreInteractions(accountsManager);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.PublicAccount;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertNull;
|
||||
|
||||
public class PublicAccountTest {
|
||||
|
||||
@Test
|
||||
public void testPinSanitation() throws IOException {
|
||||
Set<Device> devices = Collections.singleton(new Device(1, "foo", "bar", "12345", null, "gcm-1234", null, null, true, 1234, new SignedPreKey(1, "public-foo", "signature-foo"), 31337, 31336, "Android4Life", true));
|
||||
Account account = new Account("+14151231234", devices, new byte[16]);
|
||||
account.setPin("123456");
|
||||
|
||||
PublicAccount publicAccount = new PublicAccount(account);
|
||||
|
||||
String serialized = SystemMapper.getMapper().writeValueAsString(publicAccount);
|
||||
JsonNode result = SystemMapper.getMapper().readTree(serialized);
|
||||
|
||||
assertEquals("******", result.get("pin").textValue());
|
||||
assertNull(result.get("number"));
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -5,13 +5,15 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
|||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static io.dropwizard.testing.FixtureHelpers.fixture;
|
||||
|
||||
public class JsonHelpers {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private static final ObjectMapper objectMapper = SystemMapper.getMapper();
|
||||
|
||||
public static String asJson(Object object) throws JsonProcessingException {
|
||||
return objectMapper.writeValueAsString(object);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"devices":[{"id":1,"name":"foo","authToken":"bar","salt":"salt","signalingKey":"keykey","gcmId":"gcm-id","apnId":"apn-id","voipApnId":"voipapn-id","pushTimestamp":0,"fetchesMessages":true,"registrationId":1234,"signedPreKey":{"keyId":5,"publicKey":"public-signed","signature":"signtture-signed"},"lastSeen":31337,"created":31336,"userAgent":"CoolClient","unauthenticatedDelivery":true}],"identityKey":"identity_key_value","name":"OneProfileName","avatar":null,"avatarDigest":null,"pin":"******","uak":"AAAAAAAAAAAAAAAAAAAAAA==","uua":false}
|
|
@ -0,0 +1 @@
|
|||
{"devices":[{"id":1,"name":"2foo","authToken":"2bar","salt":"2salt","signalingKey":"2keykey","gcmId":"2gcm-id","apnId":"2apn-id","voipApnId":"2voipapn-id","pushTimestamp":0,"fetchesMessages":true,"registrationId":1234,"signedPreKey":{"keyId":5,"publicKey":"public-signed","signature":"signtture-signed"},"lastSeen":31337,"created":31336,"userAgent":"CoolClient","unauthenticatedDelivery":true}],"identityKey":"different_identity_key_value","name":"TwoProfileName","avatar":null,"avatarDigest":null,"pin":"******","uak":"AAAAAAAAAAAAAAAAAAAAAA==","uua":false}
|
Loading…
Reference in New Issue