parent
8eed2329bc
commit
19a4c7253a
|
@ -25,6 +25,7 @@ import org.whispersystems.textsecuregcm.configuration.RedPhoneConfiguration;
|
|||
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.S3Configuration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TestDeviceConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TurnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.WebsocketConfiguration;
|
||||
|
||||
|
@ -105,6 +106,11 @@ public class WhisperServerConfiguration extends Configuration {
|
|||
@JsonProperty
|
||||
private JerseyClientConfiguration httpClient = new JerseyClientConfiguration();
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private TurnConfiguration turn;
|
||||
|
||||
|
||||
public WebsocketConfiguration getWebsocketConfiguration() {
|
||||
return websocket;
|
||||
|
@ -158,6 +164,10 @@ public class WhisperServerConfiguration extends Configuration {
|
|||
return redphone;
|
||||
}
|
||||
|
||||
public TurnConfiguration getTurnConfiguration() {
|
||||
return turn;
|
||||
}
|
||||
|
||||
public Map<String, Integer> getTestDevices() {
|
||||
Map<String, Integer> results = new HashMap<>();
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package org.whispersystems.textsecuregcm;
|
||||
|
||||
import com.codahale.metrics.SharedMetricRegistries;
|
||||
import com.codahale.metrics.graphite.GraphiteReporter;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
|
@ -33,6 +32,7 @@ import org.whispersystems.dropwizard.simpleauth.AuthValueFactoryProvider;
|
|||
import org.whispersystems.dropwizard.simpleauth.BasicCredentialAuthFilter;
|
||||
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
|
||||
import org.whispersystems.textsecuregcm.auth.FederatedPeerAuthenticator;
|
||||
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
|
||||
import org.whispersystems.textsecuregcm.controllers.AccountController;
|
||||
import org.whispersystems.textsecuregcm.controllers.AttachmentController;
|
||||
import org.whispersystems.textsecuregcm.controllers.DeviceController;
|
||||
|
@ -100,14 +100,12 @@ import javax.servlet.ServletRegistration;
|
|||
import javax.ws.rs.client.Client;
|
||||
import java.security.Security;
|
||||
import java.util.EnumSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.codahale.metrics.MetricRegistry.name;
|
||||
import io.dropwizard.Application;
|
||||
import io.dropwizard.client.JerseyClientBuilder;
|
||||
import io.dropwizard.db.DataSourceFactory;
|
||||
import io.dropwizard.jdbi.DBIFactory;
|
||||
import io.dropwizard.metrics.graphite.GraphiteReporterFactory;
|
||||
import io.dropwizard.setup.Bootstrap;
|
||||
import io.dropwizard.setup.Environment;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
|
@ -190,6 +188,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||
PushSender pushSender = new PushSender(apnFallbackManager, pushServiceClient, websocketSender, config.getPushConfiguration().getQueueSize());
|
||||
ReceiptSender receiptSender = new ReceiptSender(accountsManager, pushSender, federatedClientManager);
|
||||
FeedbackHandler feedbackHandler = new FeedbackHandler(pushServiceClient, accountsManager);
|
||||
TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(config.getTurnConfiguration());
|
||||
Optional<byte[]> authorizationKey = config.getRedphoneConfiguration().getAuthorizationKey();
|
||||
|
||||
environment.lifecycle().manage(apnFallbackManager);
|
||||
|
@ -212,7 +211,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
|||
.buildAuthFilter()));
|
||||
environment.jersey().register(new AuthValueFactoryProvider.Binder());
|
||||
|
||||
environment.jersey().register(new AccountController(pendingAccountsManager, accountsManager, rateLimiters, smsSender, messagesManager, new TimeProvider(), authorizationKey, config.getTestDevices()));
|
||||
environment.jersey().register(new AccountController(pendingAccountsManager, accountsManager, rateLimiters, smsSender, messagesManager, new TimeProvider(), authorizationKey, turnTokenGenerator, config.getTestDevices()));
|
||||
environment.jersey().register(new DeviceController(pendingDevicesManager, accountsManager, messagesManager, rateLimiters));
|
||||
environment.jersey().register(new DirectoryController(rateLimiters, directory));
|
||||
environment.jersey().register(new FederationControllerV1(accountsManager, attachmentController, messageController, keysControllerV1));
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TurnToken {
|
||||
|
||||
@JsonProperty
|
||||
private String username;
|
||||
|
||||
@JsonProperty
|
||||
private String password;
|
||||
|
||||
@JsonProperty
|
||||
private List<String> urls;
|
||||
|
||||
public TurnToken(String username, String password, List<String> urls) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.urls = urls;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import org.whispersystems.textsecuregcm.configuration.TurnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TurnTokenGenerator {
|
||||
|
||||
private final byte[] key;
|
||||
private final List<String> urls;
|
||||
|
||||
public TurnTokenGenerator(TurnConfiguration configuration) {
|
||||
this.key = configuration.getSecret().getBytes();
|
||||
this.urls = configuration.getUris();
|
||||
}
|
||||
|
||||
public TurnToken generate() {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA1");
|
||||
long validUntilSeconds = (System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1)) / 1000;
|
||||
long user = Math.abs(SecureRandom.getInstance("SHA1PRNG").nextInt());
|
||||
String userTime = validUntilSeconds + ":" + user;
|
||||
|
||||
mac.init(new SecretKeySpec(key, "HmacSHA1"));
|
||||
String password = Base64.encodeBytes(mac.doFinal(userTime.getBytes()));
|
||||
|
||||
return new TurnToken(userTime, password, urls);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,6 +50,9 @@ public class RateLimitsConfiguration {
|
|||
@JsonProperty
|
||||
private RateLimitConfiguration verifyDevice = new RateLimitConfiguration(2, 2);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration turnAllocations = new RateLimitConfiguration(60, 60);
|
||||
|
||||
public RateLimitConfiguration getAllocateDevice() {
|
||||
return allocateDevice;
|
||||
}
|
||||
|
@ -90,6 +93,10 @@ public class RateLimitsConfiguration {
|
|||
return verifyNumber;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getTurnAllocations() {
|
||||
return turnAllocations;
|
||||
}
|
||||
|
||||
public static class RateLimitConfiguration {
|
||||
@JsonProperty
|
||||
private int bucketSize;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
public class TurnConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String secret;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
private List<String> uris;
|
||||
|
||||
public List<String> getUris() {
|
||||
return uris;
|
||||
}
|
||||
|
||||
public String getSecret() {
|
||||
return secret;
|
||||
}
|
||||
}
|
|
@ -19,7 +19,6 @@ package org.whispersystems.textsecuregcm.controllers;
|
|||
import com.codahale.metrics.Meter;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.SharedMetricRegistries;
|
||||
import com.codahale.metrics.Timer;
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Optional;
|
||||
|
@ -30,6 +29,8 @@ import org.whispersystems.textsecuregcm.auth.AuthorizationHeader;
|
|||
import org.whispersystems.textsecuregcm.auth.AuthorizationToken;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthorizationTokenGenerator;
|
||||
import org.whispersystems.textsecuregcm.auth.InvalidAuthorizationHeaderException;
|
||||
import org.whispersystems.textsecuregcm.auth.TurnToken;
|
||||
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.entities.ApnRegistrationId;
|
||||
import org.whispersystems.textsecuregcm.entities.GcmRegistrationId;
|
||||
|
@ -45,7 +46,6 @@ import org.whispersystems.textsecuregcm.storage.PendingAccountsManager;
|
|||
import org.whispersystems.textsecuregcm.util.Constants;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
import org.whispersystems.textsecuregcm.util.VerificationCode;
|
||||
import org.whispersystems.textsecuregcm.websocket.WebSocketConnection;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
|
@ -83,6 +83,7 @@ public class AccountController {
|
|||
private final MessagesManager messagesManager;
|
||||
private final TimeProvider timeProvider;
|
||||
private final Optional<AuthorizationTokenGenerator> tokenGenerator;
|
||||
private final TurnTokenGenerator turnTokenGenerator;
|
||||
private final Map<String, Integer> testDevices;
|
||||
|
||||
public AccountController(PendingAccountsManager pendingAccounts,
|
||||
|
@ -92,15 +93,17 @@ public class AccountController {
|
|||
MessagesManager messagesManager,
|
||||
TimeProvider timeProvider,
|
||||
Optional<byte[]> authorizationKey,
|
||||
TurnTokenGenerator turnTokenGenerator,
|
||||
Map<String, Integer> testDevices)
|
||||
{
|
||||
this.pendingAccounts = pendingAccounts;
|
||||
this.accounts = accounts;
|
||||
this.rateLimiters = rateLimiters;
|
||||
this.smsSender = smsSenderFactory;
|
||||
this.messagesManager = messagesManager;
|
||||
this.timeProvider = timeProvider;
|
||||
this.testDevices = testDevices;
|
||||
this.pendingAccounts = pendingAccounts;
|
||||
this.accounts = accounts;
|
||||
this.rateLimiters = rateLimiters;
|
||||
this.smsSender = smsSenderFactory;
|
||||
this.messagesManager = messagesManager;
|
||||
this.timeProvider = timeProvider;
|
||||
this.testDevices = testDevices;
|
||||
this.turnTokenGenerator = turnTokenGenerator;
|
||||
|
||||
if (authorizationKey.isPresent()) {
|
||||
tokenGenerator = Optional.of(new AuthorizationTokenGenerator(authorizationKey.get()));
|
||||
|
@ -232,6 +235,15 @@ public class AccountController {
|
|||
return tokenGenerator.get().generateFor(account.getNumber());
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/turn/")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public TurnToken getTurnToken(@Auth Account account) throws RateLimitExceededException {
|
||||
rateLimiters.getTurnLimiter().validate(account.getNumber());
|
||||
return turnTokenGenerator.generate();
|
||||
}
|
||||
|
||||
@Timed
|
||||
@PUT
|
||||
@Path("/gcm/")
|
||||
|
|
|
@ -36,6 +36,8 @@ public class RateLimiters {
|
|||
private final RateLimiter allocateDeviceLimiter;
|
||||
private final RateLimiter verifyDeviceLimiter;
|
||||
|
||||
private final RateLimiter turnLimiter;
|
||||
|
||||
public RateLimiters(RateLimitsConfiguration config, JedisPool cacheClient) {
|
||||
this.smsDestinationLimiter = new RateLimiter(cacheClient, "smsDestination",
|
||||
config.getSmsDestination().getBucketSize(),
|
||||
|
@ -77,6 +79,9 @@ public class RateLimiters {
|
|||
config.getVerifyDevice().getBucketSize(),
|
||||
config.getVerifyDevice().getLeakRatePerMinute());
|
||||
|
||||
this.turnLimiter = new RateLimiter(cacheClient, "turnAllocate",
|
||||
config.getTurnAllocations().getBucketSize(),
|
||||
config.getTurnAllocations().getLeakRatePerMinute());
|
||||
}
|
||||
|
||||
public RateLimiter getAllocateDeviceLimiter() {
|
||||
|
@ -119,4 +124,8 @@ public class RateLimiters {
|
|||
return verifyLimiter;
|
||||
}
|
||||
|
||||
public RateLimiter getTurnLimiter() {
|
||||
return turnLimiter;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.junit.Before;
|
|||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.dropwizard.simpleauth.AuthValueFactoryProvider;
|
||||
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
|
||||
import org.whispersystems.textsecuregcm.controllers.AccountController;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
|
@ -42,6 +43,7 @@ public class AccountControllerTest {
|
|||
private SmsSender smsSender = mock(SmsSender.class );
|
||||
private MessagesManager storedMessages = mock(MessagesManager.class );
|
||||
private TimeProvider timeProvider = mock(TimeProvider.class );
|
||||
private TurnTokenGenerator turnTokenGenerator = mock(TurnTokenGenerator.class);
|
||||
private static byte[] authorizationKey = decodeHex("3a078586eea8971155f5c1ebd73c8c923cbec1c3ed22a54722e4e88321dc749f");
|
||||
|
||||
@Rule
|
||||
|
@ -57,6 +59,7 @@ public class AccountControllerTest {
|
|||
storedMessages,
|
||||
timeProvider,
|
||||
Optional.of(authorizationKey),
|
||||
turnTokenGenerator,
|
||||
new HashMap<String, Integer>()))
|
||||
.build();
|
||||
|
||||
|
|
Loading…
Reference in New Issue