Add profile controller

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2017-05-15 14:50:19 -07:00
parent b8fb8a52f1
commit f5aec1c894
7 changed files with 140 additions and 1 deletions

View File

@ -41,6 +41,7 @@ import org.whispersystems.textsecuregcm.controllers.FederationControllerV2;
import org.whispersystems.textsecuregcm.controllers.KeepAliveController;
import org.whispersystems.textsecuregcm.controllers.KeysController;
import org.whispersystems.textsecuregcm.controllers.MessageController;
import org.whispersystems.textsecuregcm.controllers.ProfileController;
import org.whispersystems.textsecuregcm.controllers.ProvisioningController;
import org.whispersystems.textsecuregcm.controllers.ReceiptController;
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
@ -196,6 +197,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
AttachmentController attachmentController = new AttachmentController(rateLimiters, federatedClientManager, urlSigner);
KeysController keysController = new KeysController(rateLimiters, keys, accountsManager, federatedClientManager);
MessageController messageController = new MessageController(rateLimiters, pushSender, receiptSender, accountsManager, messagesManager, federatedClientManager);
ProfileController profileController = new ProfileController(rateLimiters , accountsManager);
environment.jersey().register(new AuthDynamicFeature(new BasicCredentialAuthFilter.Builder<Account>()
.setAuthenticator(deviceAuthenticator)
@ -217,6 +219,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
environment.jersey().register(attachmentController);
environment.jersey().register(keysController);
environment.jersey().register(messageController);
environment.jersey().register(profileController);
///
WebSocketEnvironment webSocketEnvironment = new WebSocketEnvironment(environment, config.getWebSocketConfiguration(), 90000);
@ -224,6 +227,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
webSocketEnvironment.setConnectListener(new AuthenticatedConnectListener(accountsManager, pushSender, receiptSender, messagesManager, pubSubManager));
webSocketEnvironment.jersey().register(new KeepAliveController(pubSubManager));
webSocketEnvironment.jersey().register(messageController);
webSocketEnvironment.jersey().register(profileController);
WebSocketEnvironment provisioningEnvironment = new WebSocketEnvironment(environment, webSocketEnvironment.getRequestLog(), 60000);
provisioningEnvironment.setConnectListener(new ProvisioningConnectListener(pubSubManager));

View File

@ -53,6 +53,9 @@ public class RateLimitsConfiguration {
@JsonProperty
private RateLimitConfiguration turnAllocations = new RateLimitConfiguration(60, 60);
@JsonProperty
private RateLimitConfiguration profile = new RateLimitConfiguration(4320, 3);
public RateLimitConfiguration getAllocateDevice() {
return allocateDevice;
}
@ -97,6 +100,10 @@ public class RateLimitsConfiguration {
return turnAllocations;
}
public RateLimitConfiguration getProfile() {
return profile;
}
public static class RateLimitConfiguration {
@JsonProperty
private int bucketSize;

View File

@ -0,0 +1,50 @@
package org.whispersystems.textsecuregcm.controllers;
import com.codahale.metrics.annotation.Timed;
import com.google.common.base.Optional;
import org.whispersystems.textsecuregcm.entities.Profile;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import javax.ws.rs.GET;
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 io.dropwizard.auth.Auth;
@Path("/v1/profile")
public class ProfileController {
private final RateLimiters rateLimiters;
private final AccountsManager accountsManager;
public ProfileController(RateLimiters rateLimiters, AccountsManager accountsManager) {
this.rateLimiters = rateLimiters;
this.accountsManager = accountsManager;
}
@Timed
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/{number}")
public Profile getProfile(@Auth Account account, @PathParam("number") String number)
throws RateLimitExceededException
{
rateLimiters.getProfileLimiter().validate(account.getNumber());
Optional<Account> accountProfile = accountsManager.get(number);
if (!accountProfile.isPresent()) {
throw new WebApplicationException(Response.status(404).build());
}
return new Profile(accountProfile.get().getIdentityKey());
}
}

View File

@ -0,0 +1,21 @@
package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
public class Profile {
@JsonProperty
private String identityKey;
public Profile() {}
public Profile(String identityKey) {
this.identityKey = identityKey;
}
@VisibleForTesting
public String getIdentityKey() {
return identityKey;
}
}

View File

@ -38,6 +38,8 @@ public class RateLimiters {
private final RateLimiter turnLimiter;
private final RateLimiter profileLimiter;
public RateLimiters(RateLimitsConfiguration config, JedisPool cacheClient) {
this.smsDestinationLimiter = new RateLimiter(cacheClient, "smsDestination",
config.getSmsDestination().getBucketSize(),
@ -82,6 +84,10 @@ public class RateLimiters {
this.turnLimiter = new RateLimiter(cacheClient, "turnAllocate",
config.getTurnAllocations().getBucketSize(),
config.getTurnAllocations().getLeakRatePerMinute());
this.profileLimiter = new RateLimiter(cacheClient, "profile",
config.getProfile().getBucketSize(),
config.getProfile().getLeakRatePerMinute());
}
public RateLimiter getAllocateDeviceLimiter() {
@ -128,4 +134,8 @@ public class RateLimiters {
return turnLimiter;
}
public RateLimiter getProfileLimiter() {
return profileLimiter;
}
}

View File

@ -145,7 +145,11 @@ public class APNSender implements Managed {
return;
}
logger.info("APN Unregister APN ID matches! " + number + ", " + deviceId);
if (registrationId.equals(device.get().getApnId())) {
logger.info("APN Unregister APN ID matches! " + number + ", " + deviceId);
} else if (registrationId.equals(device.get().getVoipApnId())) {
logger.info("APN Unregister VoIP ID matches! " + number + ", " + deviceId);
}
long tokenTimestamp = device.get().getPushTimestamp();

View File

@ -0,0 +1,43 @@
package org.whispersystems.textsecuregcm.tests.controllers;
import com.google.common.base.Optional;
import org.junit.Test;
import org.whispersystems.textsecuregcm.controllers.ProfileController;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.entities.Profile;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
public class ProfileControllerTest {
@Test
public void testProfileGet() throws RateLimitExceededException {
Account requestAccount = mock(Account.class );
Account profileAccount = mock(Account.class );
RateLimiter rateLimiter = mock(RateLimiter.class );
RateLimiters rateLimiters = mock(RateLimiters.class );
AccountsManager accountsManager = mock(AccountsManager.class);
when(rateLimiters.getProfileLimiter()).thenReturn(rateLimiter);
when(requestAccount.getNumber()).thenReturn("foo");
when(profileAccount.getIdentityKey()).thenReturn("bar");
when(accountsManager.get(eq("baz"))).thenReturn(Optional.of(profileAccount));
ProfileController profileController = new ProfileController(rateLimiters, accountsManager);
Profile result = profileController.getProfile(requestAccount, "baz");
assertEquals(result.getIdentityKey(), "bar");
verify(accountsManager, times(1)).get(eq("baz"));
verify(rateLimiters, times(1)).getProfileLimiter();
verify(rateLimiter, times(1)).validate("foo");
}
}