mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-25 12:58:04 +01:00
Squashed History
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.yammer.dropwizard.config.Configuration;
|
||||
import com.yammer.dropwizard.db.DatabaseConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.FederationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.GcmConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.GraphiteConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.MemcacheConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.NexmoConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.S3Configuration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
public class WhisperServerConfiguration extends Configuration {
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private TwilioConfiguration twilio;
|
||||
|
||||
@JsonProperty
|
||||
private NexmoConfiguration nexmo = new NexmoConfiguration();
|
||||
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private GcmConfiguration gcm;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private S3Configuration s3;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private MemcacheConfiguration memcache;
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private RedisConfiguration redis;
|
||||
|
||||
@JsonProperty
|
||||
private ApnConfiguration apn = new ApnConfiguration();
|
||||
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private FederationConfiguration federation = new FederationConfiguration();
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private DatabaseConfiguration database = new DatabaseConfiguration();
|
||||
|
||||
@Valid
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private RateLimitsConfiguration limits = new RateLimitsConfiguration();
|
||||
|
||||
@Valid
|
||||
@JsonProperty
|
||||
private GraphiteConfiguration graphite = new GraphiteConfiguration();
|
||||
|
||||
public TwilioConfiguration getTwilioConfiguration() {
|
||||
return twilio;
|
||||
}
|
||||
|
||||
public NexmoConfiguration getNexmoConfiguration() {
|
||||
return nexmo;
|
||||
}
|
||||
|
||||
public GcmConfiguration getGcmConfiguration() {
|
||||
return gcm;
|
||||
}
|
||||
|
||||
public ApnConfiguration getApnConfiguration() {
|
||||
return apn;
|
||||
}
|
||||
|
||||
public S3Configuration getS3Configuration() {
|
||||
return s3;
|
||||
}
|
||||
|
||||
public MemcacheConfiguration getMemcacheConfiguration() {
|
||||
return memcache;
|
||||
}
|
||||
|
||||
public RedisConfiguration getRedisConfiguration() {
|
||||
return redis;
|
||||
}
|
||||
|
||||
public DatabaseConfiguration getDatabaseConfiguration() {
|
||||
return database;
|
||||
}
|
||||
|
||||
public RateLimitsConfiguration getLimitsConfiguration() {
|
||||
return limits;
|
||||
}
|
||||
|
||||
public FederationConfiguration getFederationConfiguration() {
|
||||
return federation;
|
||||
}
|
||||
|
||||
public GraphiteConfiguration getGraphiteConfiguration() {
|
||||
return graphite;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm;
|
||||
|
||||
import com.yammer.dropwizard.Service;
|
||||
import com.yammer.dropwizard.config.Bootstrap;
|
||||
import com.yammer.dropwizard.config.Environment;
|
||||
import com.yammer.dropwizard.db.DatabaseConfiguration;
|
||||
import com.yammer.dropwizard.jdbi.DBIFactory;
|
||||
import com.yammer.dropwizard.migrations.MigrationsBundle;
|
||||
import com.yammer.metrics.reporting.GraphiteReporter;
|
||||
import net.spy.memcached.MemcachedClient;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.skife.jdbi.v2.DBI;
|
||||
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
|
||||
import org.whispersystems.textsecuregcm.auth.FederatedPeerAuthenticator;
|
||||
import org.whispersystems.textsecuregcm.auth.MultiBasicAuthProvider;
|
||||
import org.whispersystems.textsecuregcm.controllers.AccountController;
|
||||
import org.whispersystems.textsecuregcm.controllers.AttachmentController;
|
||||
import org.whispersystems.textsecuregcm.controllers.DirectoryController;
|
||||
import org.whispersystems.textsecuregcm.controllers.FederationController;
|
||||
import org.whispersystems.textsecuregcm.controllers.KeysController;
|
||||
import org.whispersystems.textsecuregcm.controllers.MessageController;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.mappers.IOExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.providers.MemcacheHealthCheck;
|
||||
import org.whispersystems.textsecuregcm.providers.MemcachedClientFactory;
|
||||
import org.whispersystems.textsecuregcm.providers.RedisClientFactory;
|
||||
import org.whispersystems.textsecuregcm.providers.RedisHealthCheck;
|
||||
import org.whispersystems.textsecuregcm.push.PushSender;
|
||||
import org.whispersystems.textsecuregcm.sms.SenderFactory;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Accounts;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||
import org.whispersystems.textsecuregcm.storage.PendingAccounts;
|
||||
import org.whispersystems.textsecuregcm.storage.PendingAccountsManager;
|
||||
import org.whispersystems.textsecuregcm.util.UrlSigner;
|
||||
import org.whispersystems.textsecuregcm.workers.DirectoryCommand;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import redis.clients.jedis.JedisPool;
|
||||
|
||||
public class WhisperServerService extends Service<WhisperServerConfiguration> {
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(Bootstrap<WhisperServerConfiguration> bootstrap) {
|
||||
bootstrap.setName("whisper-server");
|
||||
bootstrap.addCommand(new DirectoryCommand());
|
||||
bootstrap.addBundle(new MigrationsBundle<WhisperServerConfiguration>() {
|
||||
@Override
|
||||
public DatabaseConfiguration getDatabaseConfiguration(WhisperServerConfiguration configuration) {
|
||||
return configuration.getDatabaseConfiguration();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(WhisperServerConfiguration config, Environment environment)
|
||||
throws Exception
|
||||
{
|
||||
DBIFactory dbiFactory = new DBIFactory();
|
||||
DBI jdbi = dbiFactory.build(environment, config.getDatabaseConfiguration(), "postgresql");
|
||||
|
||||
Accounts accounts = jdbi.onDemand(Accounts.class);
|
||||
PendingAccounts pendingAccounts = jdbi.onDemand(PendingAccounts.class);
|
||||
Keys keys = jdbi.onDemand(Keys.class);
|
||||
|
||||
MemcachedClient memcachedClient = new MemcachedClientFactory(config.getMemcacheConfiguration()).getClient();
|
||||
JedisPool redisClient = new RedisClientFactory(config.getRedisConfiguration()).getRedisClientPool();
|
||||
|
||||
DirectoryManager directory = new DirectoryManager(redisClient);
|
||||
PendingAccountsManager pendingAccountsManager = new PendingAccountsManager(pendingAccounts, memcachedClient);
|
||||
AccountsManager accountsManager = new AccountsManager(accounts, directory, memcachedClient);
|
||||
AccountAuthenticator accountAuthenticator = new AccountAuthenticator(accountsManager);
|
||||
FederatedClientManager federatedClientManager = new FederatedClientManager(config.getFederationConfiguration());
|
||||
RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), memcachedClient);
|
||||
SenderFactory senderFactory = new SenderFactory(config.getTwilioConfiguration(), config.getNexmoConfiguration());
|
||||
UrlSigner urlSigner = new UrlSigner(config.getS3Configuration());
|
||||
PushSender pushSender = new PushSender(config.getGcmConfiguration(),
|
||||
config.getApnConfiguration(),
|
||||
accountsManager, directory);
|
||||
|
||||
environment.addProvider(new MultiBasicAuthProvider<>(new FederatedPeerAuthenticator(config.getFederationConfiguration()),
|
||||
FederatedPeer.class,
|
||||
accountAuthenticator,
|
||||
Account.class, "WhisperServer"));
|
||||
|
||||
environment.addResource(new AccountController(pendingAccountsManager, accountsManager, rateLimiters, senderFactory));
|
||||
environment.addResource(new DirectoryController(rateLimiters, directory));
|
||||
environment.addResource(new AttachmentController(rateLimiters, federatedClientManager, urlSigner));
|
||||
environment.addResource(new KeysController(rateLimiters, keys, federatedClientManager));
|
||||
environment.addResource(new FederationController(keys, accountsManager, pushSender, urlSigner));
|
||||
|
||||
environment.addServlet(new MessageController(rateLimiters, accountAuthenticator,
|
||||
pushSender, federatedClientManager),
|
||||
MessageController.PATH);
|
||||
|
||||
environment.addHealthCheck(new RedisHealthCheck(redisClient));
|
||||
environment.addHealthCheck(new MemcacheHealthCheck(memcachedClient));
|
||||
|
||||
environment.addProvider(new IOExceptionMapper());
|
||||
environment.addProvider(new RateLimitExceededExceptionMapper());
|
||||
|
||||
if (config.getGraphiteConfiguration().isEnabled()) {
|
||||
GraphiteReporter.enable(15, TimeUnit.SECONDS,
|
||||
config.getGraphiteConfiguration().getHost(),
|
||||
config.getGraphiteConfiguration().getPort());
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
new WhisperServerService().run(args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.yammer.dropwizard.auth.AuthenticationException;
|
||||
import com.yammer.dropwizard.auth.Authenticator;
|
||||
import com.yammer.dropwizard.auth.basic.BasicCredentials;
|
||||
import com.yammer.metrics.Metrics;
|
||||
import com.yammer.metrics.core.Meter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AccountAuthenticator implements Authenticator<BasicCredentials, Account> {
|
||||
|
||||
private final Meter authenticationFailedMeter = Metrics.newMeter(AccountAuthenticator.class,
|
||||
"authentication", "failed",
|
||||
TimeUnit.MINUTES);
|
||||
|
||||
private final Meter authenticationSucceededMeter = Metrics.newMeter(AccountAuthenticator.class,
|
||||
"authentication", "succeeded",
|
||||
TimeUnit.MINUTES);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AccountAuthenticator.class);
|
||||
|
||||
private final AccountsManager accountsManager;
|
||||
|
||||
public AccountAuthenticator(AccountsManager accountsManager) {
|
||||
this.accountsManager = accountsManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Account> authenticate(BasicCredentials basicCredentials)
|
||||
throws AuthenticationException
|
||||
{
|
||||
Optional<Account> account = accountsManager.get(basicCredentials.getUsername());
|
||||
|
||||
if (!account.isPresent()) {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
if (account.get().getAuthenticationCredentials().verify(basicCredentials.getPassword())) {
|
||||
authenticationSucceededMeter.mark();
|
||||
return account;
|
||||
}
|
||||
|
||||
authenticationFailedMeter.mark();
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class AuthenticationCredentials {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AuthenticationCredentials.class);
|
||||
|
||||
private final String hashedAuthenticationToken;
|
||||
private final String salt;
|
||||
|
||||
public AuthenticationCredentials(String hashedAuthenticationToken, String salt) {
|
||||
this.hashedAuthenticationToken = hashedAuthenticationToken;
|
||||
this.salt = salt;
|
||||
}
|
||||
|
||||
public AuthenticationCredentials(String authenticationToken) {
|
||||
try {
|
||||
this.salt = Math.abs(SecureRandom.getInstance("SHA1PRNG").nextInt()) + "";
|
||||
this.hashedAuthenticationToken = getHashedValue(salt, authenticationToken);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getHashedAuthenticationToken() {
|
||||
return hashedAuthenticationToken;
|
||||
}
|
||||
|
||||
public String getSalt() {
|
||||
return salt;
|
||||
}
|
||||
|
||||
public boolean verify(String authenticationToken) {
|
||||
String theirValue = getHashedValue(salt, authenticationToken);
|
||||
|
||||
logger.debug("Comparing: " + theirValue + " , " + this.hashedAuthenticationToken);
|
||||
|
||||
return theirValue.equals(this.hashedAuthenticationToken);
|
||||
}
|
||||
|
||||
private static String getHashedValue(String salt, String token) {
|
||||
Logger logger = LoggerFactory.getLogger(AuthenticationCredentials.class);
|
||||
logger.debug("Getting hashed token: " + salt + " , " + token);
|
||||
|
||||
try {
|
||||
return new String(Hex.encodeHex(MessageDigest.getInstance("SHA1").digest((salt + token).getBytes("UTF-8"))));
|
||||
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class AuthorizationHeader {
|
||||
|
||||
private final String user;
|
||||
private final String password;
|
||||
|
||||
public AuthorizationHeader(String header) throws InvalidAuthorizationHeaderException {
|
||||
try {
|
||||
if (header == null) {
|
||||
throw new InvalidAuthorizationHeaderException("Null header");
|
||||
}
|
||||
|
||||
String[] headerParts = header.split(" ");
|
||||
|
||||
if (headerParts == null || headerParts.length < 2) {
|
||||
throw new InvalidAuthorizationHeaderException("Invalid authorization header: " + header);
|
||||
}
|
||||
|
||||
if (!"Basic".equals(headerParts[0])) {
|
||||
throw new InvalidAuthorizationHeaderException("Unsupported authorization method: " + headerParts[0]);
|
||||
}
|
||||
|
||||
String concatenatedValues = new String(Base64.decode(headerParts[1]));
|
||||
|
||||
if (Util.isEmpty(concatenatedValues)) {
|
||||
throw new InvalidAuthorizationHeaderException("Bad decoded value: " + concatenatedValues);
|
||||
}
|
||||
|
||||
String[] credentialParts = concatenatedValues.split(":");
|
||||
|
||||
if (credentialParts == null || credentialParts.length < 2) {
|
||||
throw new InvalidAuthorizationHeaderException("Badly formated credentials: " + concatenatedValues);
|
||||
}
|
||||
|
||||
this.user = credentialParts[0];
|
||||
this.password = credentialParts[1];
|
||||
|
||||
} catch (IOException ioe) {
|
||||
throw new InvalidAuthorizationHeaderException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.yammer.dropwizard.auth.AuthenticationException;
|
||||
import com.yammer.dropwizard.auth.Authenticator;
|
||||
import com.yammer.dropwizard.auth.basic.BasicCredentials;
|
||||
import com.yammer.metrics.Metrics;
|
||||
import com.yammer.metrics.core.Meter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.configuration.FederationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
public class FederatedPeerAuthenticator implements Authenticator<BasicCredentials, FederatedPeer> {
|
||||
|
||||
private final Meter authenticationFailedMeter = Metrics.newMeter(FederatedPeerAuthenticator.class,
|
||||
"authentication", "failed",
|
||||
TimeUnit.MINUTES);
|
||||
|
||||
private final Meter authenticationSucceededMeter = Metrics.newMeter(FederatedPeerAuthenticator.class,
|
||||
"authentication", "succeeded",
|
||||
TimeUnit.MINUTES);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(FederatedPeerAuthenticator.class);
|
||||
|
||||
private final List<FederatedPeer> peers;
|
||||
|
||||
public FederatedPeerAuthenticator(FederationConfiguration config) {
|
||||
this.peers = config.getPeers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<FederatedPeer> authenticate(BasicCredentials basicCredentials)
|
||||
throws AuthenticationException
|
||||
{
|
||||
|
||||
if (peers == null) {
|
||||
authenticationFailedMeter.mark();
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
for (FederatedPeer peer : peers) {
|
||||
if (basicCredentials.getUsername().equals(peer.getName()) &&
|
||||
basicCredentials.getPassword().equals(peer.getAuthenticationToken()))
|
||||
{
|
||||
authenticationSucceededMeter.mark();
|
||||
return Optional.of(peer);
|
||||
}
|
||||
}
|
||||
|
||||
authenticationFailedMeter.mark();
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
|
||||
public class InvalidAuthorizationHeaderException extends Exception {
|
||||
public InvalidAuthorizationHeaderException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
public InvalidAuthorizationHeaderException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import com.sun.jersey.api.model.Parameter;
|
||||
import com.sun.jersey.core.spi.component.ComponentContext;
|
||||
import com.sun.jersey.core.spi.component.ComponentScope;
|
||||
import com.sun.jersey.spi.inject.Injectable;
|
||||
import com.sun.jersey.spi.inject.InjectableProvider;
|
||||
import com.yammer.dropwizard.auth.Auth;
|
||||
import com.yammer.dropwizard.auth.Authenticator;
|
||||
import com.yammer.dropwizard.auth.basic.BasicAuthProvider;
|
||||
import com.yammer.dropwizard.auth.basic.BasicCredentials;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MultiBasicAuthProvider<T1,T2> implements InjectableProvider<Auth, Parameter> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MultiBasicAuthProvider.class);
|
||||
|
||||
private final BasicAuthProvider<T1> provider1;
|
||||
private final BasicAuthProvider<T2> provider2;
|
||||
|
||||
private final Class<?> clazz1;
|
||||
private final Class<?> clazz2;
|
||||
|
||||
public MultiBasicAuthProvider(Authenticator<BasicCredentials, T1> authenticator1,
|
||||
Class<?> clazz1,
|
||||
Authenticator<BasicCredentials, T2> authenticator2,
|
||||
Class<?> clazz2,
|
||||
String realm)
|
||||
{
|
||||
this.provider1 = new BasicAuthProvider<T1>(authenticator1, realm);
|
||||
this.provider2 = new BasicAuthProvider<T2>(authenticator2, realm);
|
||||
this.clazz1 = clazz1;
|
||||
this.clazz2 = clazz2;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ComponentScope getScope() {
|
||||
return ComponentScope.PerRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Injectable<?> getInjectable(ComponentContext componentContext,
|
||||
Auth auth, Parameter parameter)
|
||||
{
|
||||
if (parameter.getParameterClass().equals(clazz1)) {
|
||||
return this.provider1.getInjectable(componentContext, auth, parameter);
|
||||
} else {
|
||||
return this.provider2.getInjectable(componentContext, auth, parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
|
||||
public class ApnConfiguration {
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String certificate;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String key;
|
||||
|
||||
public String getCertificate() {
|
||||
return certificate;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class FederationConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
private List<FederatedPeer> peers;
|
||||
|
||||
@JsonProperty
|
||||
private String name;
|
||||
|
||||
@JsonProperty
|
||||
private String herokuPeers;
|
||||
|
||||
public List<FederatedPeer> getPeers() {
|
||||
if (peers != null) {
|
||||
return peers;
|
||||
}
|
||||
|
||||
if (herokuPeers != null) {
|
||||
List<FederatedPeer> peers = new LinkedList<>();
|
||||
JsonElement root = new JsonParser().parse(herokuPeers);
|
||||
JsonArray peerElements = root.getAsJsonArray();
|
||||
|
||||
for (JsonElement peer : peerElements) {
|
||||
String name = peer.getAsJsonObject().get("name").getAsString();
|
||||
String url = peer.getAsJsonObject().get("url").getAsString();
|
||||
String authenticationToken = peer.getAsJsonObject().get("authenticationToken").getAsString();
|
||||
String certificate = peer.getAsJsonObject().get("certificate").getAsString();
|
||||
|
||||
peers.add(new FederatedPeer(name, url, authenticationToken, certificate));
|
||||
}
|
||||
|
||||
return peers;
|
||||
}
|
||||
|
||||
return peers;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
public class GcmConfiguration {
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String apiKey;
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class GraphiteConfiguration {
|
||||
@JsonProperty
|
||||
private String host;
|
||||
|
||||
@JsonProperty
|
||||
private int port;
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return host != null && port != 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
public class MemcacheConfiguration {
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String servers;
|
||||
|
||||
@JsonProperty
|
||||
private String user;
|
||||
|
||||
@JsonProperty
|
||||
private String password;
|
||||
|
||||
|
||||
public String getServers() {
|
||||
return servers;
|
||||
}
|
||||
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class NexmoConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
private String apiKey;
|
||||
|
||||
@JsonProperty
|
||||
private String apiSecret;
|
||||
|
||||
@JsonProperty
|
||||
private String number;
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public String getApiSecret() {
|
||||
return apiSecret;
|
||||
}
|
||||
|
||||
public String getNumber() {
|
||||
return number;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class RateLimitsConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration smsDestination = new RateLimitConfiguration(2, 2);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration voiceDestination = new RateLimitConfiguration(2, 1.0 / 2.0);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration verifyNumber = new RateLimitConfiguration(2, 2);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration attachments = new RateLimitConfiguration(50, 50);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration contactQueries = new RateLimitConfiguration(50000, 50000);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration prekeys = new RateLimitConfiguration(3, 1.0 / 10.0);
|
||||
|
||||
@JsonProperty
|
||||
private RateLimitConfiguration messages = new RateLimitConfiguration(60, 60);
|
||||
|
||||
public RateLimitConfiguration getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getPreKeys() {
|
||||
return prekeys;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getContactQueries() {
|
||||
return contactQueries;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getAttachments() {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getSmsDestination() {
|
||||
return smsDestination;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getVoiceDestination() {
|
||||
return voiceDestination;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration getVerifyNumber() {
|
||||
return verifyNumber;
|
||||
}
|
||||
|
||||
public static class RateLimitConfiguration {
|
||||
@JsonProperty
|
||||
private int bucketSize;
|
||||
|
||||
@JsonProperty
|
||||
private double leakRatePerMinute;
|
||||
|
||||
public RateLimitConfiguration(int bucketSize, double leakRatePerMinute) {
|
||||
this.bucketSize = bucketSize;
|
||||
this.leakRatePerMinute = leakRatePerMinute;
|
||||
}
|
||||
|
||||
public RateLimitConfiguration() {}
|
||||
|
||||
public int getBucketSize() {
|
||||
return bucketSize;
|
||||
}
|
||||
|
||||
public double getLeakRatePerMinute() {
|
||||
return leakRatePerMinute;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
public class RedisConfiguration {
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String url;
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
public class S3Configuration {
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String accessKey;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String accessSecret;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String attachmentsBucket;
|
||||
|
||||
public String getAccessKey() {
|
||||
return accessKey;
|
||||
}
|
||||
|
||||
public String getAccessSecret() {
|
||||
return accessSecret;
|
||||
}
|
||||
|
||||
public String getAttachmentsBucket() {
|
||||
return attachmentsBucket;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
public class TwilioConfiguration {
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String accountId;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String accountToken;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String number;
|
||||
|
||||
public String getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public String getAccountToken() {
|
||||
return accountToken;
|
||||
}
|
||||
|
||||
public String getNumber() {
|
||||
return number;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.yammer.dropwizard.auth.Auth;
|
||||
import com.yammer.metrics.annotation.Timed;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthorizationHeader;
|
||||
import org.whispersystems.textsecuregcm.auth.InvalidAuthorizationHeaderException;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.entities.ApnRegistrationId;
|
||||
import org.whispersystems.textsecuregcm.entities.GcmRegistrationId;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.sms.SenderFactory;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.PendingAccountsManager;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
import org.whispersystems.textsecuregcm.util.VerificationCode;
|
||||
|
||||
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.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
@Path("/v1/accounts")
|
||||
public class AccountController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AccountController.class);
|
||||
|
||||
private final PendingAccountsManager pendingAccounts;
|
||||
private final AccountsManager accounts;
|
||||
private final RateLimiters rateLimiters;
|
||||
private final SenderFactory senderFactory;
|
||||
|
||||
public AccountController(PendingAccountsManager pendingAccounts,
|
||||
AccountsManager accounts,
|
||||
RateLimiters rateLimiters,
|
||||
SenderFactory smsSenderFactory)
|
||||
{
|
||||
this.pendingAccounts = pendingAccounts;
|
||||
this.accounts = accounts;
|
||||
this.rateLimiters = rateLimiters;
|
||||
this.senderFactory = smsSenderFactory;
|
||||
}
|
||||
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/{transport}/code/{number}")
|
||||
public Response createAccount(@PathParam("transport") String transport,
|
||||
@PathParam("number") String number)
|
||||
throws IOException, RateLimitExceededException
|
||||
{
|
||||
if (!Util.isValidNumber(number)) {
|
||||
logger.debug("Invalid number: " + number);
|
||||
throw new WebApplicationException(Response.status(400).build());
|
||||
}
|
||||
|
||||
switch (transport) {
|
||||
case "sms":
|
||||
rateLimiters.getSmsDestinationLimiter().validate(number);
|
||||
break;
|
||||
case "voice":
|
||||
rateLimiters.getVoiceDestinationLimiter().validate(number);
|
||||
break;
|
||||
default:
|
||||
throw new WebApplicationException(Response.status(415).build());
|
||||
}
|
||||
|
||||
VerificationCode verificationCode = generateVerificationCode();
|
||||
pendingAccounts.store(number, verificationCode.getVerificationCode());
|
||||
|
||||
if (transport.equals("sms")) {
|
||||
senderFactory.getSmsSender(number).deliverSmsVerification(number, verificationCode.getVerificationCodeDisplay());
|
||||
} else if (transport.equals("voice")) {
|
||||
senderFactory.getVoxSender(number).deliverVoxVerification(number, verificationCode.getVerificationCodeSpeech());
|
||||
}
|
||||
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@Timed
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Path("/code/{verification_code}")
|
||||
public void verifyAccount(@PathParam("verification_code") String verificationCode,
|
||||
@HeaderParam("Authorization") String authorizationHeader,
|
||||
@Valid AccountAttributes accountAttributes)
|
||||
throws RateLimitExceededException
|
||||
{
|
||||
try {
|
||||
AuthorizationHeader header = new AuthorizationHeader(authorizationHeader);
|
||||
String number = header.getUserName();
|
||||
String password = header.getPassword();
|
||||
|
||||
rateLimiters.getVerifyLimiter().validate(number);
|
||||
|
||||
Optional<String> storedVerificationCode = pendingAccounts.getCodeForNumber(number);
|
||||
|
||||
if (!storedVerificationCode.isPresent() ||
|
||||
!verificationCode.equals(storedVerificationCode.get()))
|
||||
{
|
||||
throw new WebApplicationException(Response.status(403).build());
|
||||
}
|
||||
|
||||
Account account = new Account();
|
||||
account.setNumber(number);
|
||||
account.setAuthenticationCredentials(new AuthenticationCredentials(password));
|
||||
account.setSignalingKey(accountAttributes.getSignalingKey());
|
||||
account.setSupportsSms(accountAttributes.getSupportsSms());
|
||||
|
||||
accounts.create(account);
|
||||
logger.debug("Stored account...");
|
||||
|
||||
} catch (InvalidAuthorizationHeaderException e) {
|
||||
logger.info("Bad Authorization Header", e);
|
||||
throw new WebApplicationException(Response.status(401).build());
|
||||
}
|
||||
}
|
||||
|
||||
@Timed
|
||||
@PUT
|
||||
@Path("/gcm/")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void setGcmRegistrationId(@Auth Account account, @Valid GcmRegistrationId registrationId) {
|
||||
account.setApnRegistrationId(null);
|
||||
account.setGcmRegistrationId(registrationId.getGcmRegistrationId());
|
||||
accounts.update(account);
|
||||
}
|
||||
|
||||
@Timed
|
||||
@DELETE
|
||||
@Path("/gcm/")
|
||||
public void deleteGcmRegistrationId(@Auth Account account) {
|
||||
account.setGcmRegistrationId(null);
|
||||
accounts.update(account);
|
||||
}
|
||||
|
||||
@Timed
|
||||
@PUT
|
||||
@Path("/apn/")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void setApnRegistrationId(@Auth Account account, @Valid ApnRegistrationId registrationId) {
|
||||
account.setApnRegistrationId(registrationId.getApnRegistrationId());
|
||||
account.setGcmRegistrationId(null);
|
||||
accounts.update(account);
|
||||
}
|
||||
|
||||
@Timed
|
||||
@DELETE
|
||||
@Path("/apn/")
|
||||
public void deleteApnRegistrationId(@Auth Account account) {
|
||||
account.setApnRegistrationId(null);
|
||||
accounts.update(account);
|
||||
}
|
||||
|
||||
private VerificationCode generateVerificationCode() {
|
||||
try {
|
||||
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
||||
int randomInt = 100000 + random.nextInt(900000);
|
||||
return new VerificationCode(randomInt);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
import com.amazonaws.HttpMethod;
|
||||
import com.yammer.dropwizard.auth.Auth;
|
||||
import com.yammer.metrics.annotation.Timed;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.entities.AttachmentDescriptor;
|
||||
import org.whispersystems.textsecuregcm.entities.AttachmentUri;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||
import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.util.Conversions;
|
||||
import org.whispersystems.textsecuregcm.util.UrlSigner;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
|
||||
@Path("/v1/attachments")
|
||||
public class AttachmentController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AttachmentController.class);
|
||||
|
||||
private final RateLimiters rateLimiters;
|
||||
private final FederatedClientManager federatedClientManager;
|
||||
private final UrlSigner urlSigner;
|
||||
|
||||
public AttachmentController(RateLimiters rateLimiters,
|
||||
FederatedClientManager federatedClientManager,
|
||||
UrlSigner urlSigner)
|
||||
{
|
||||
this.rateLimiters = rateLimiters;
|
||||
this.federatedClientManager = federatedClientManager;
|
||||
this.urlSigner = urlSigner;
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response allocateAttachment(@Auth Account account) throws RateLimitExceededException {
|
||||
rateLimiters.getAttachmentLimiter().validate(account.getNumber());
|
||||
|
||||
long attachmentId = generateAttachmentId();
|
||||
URL url = urlSigner.getPreSignedUrl(attachmentId, HttpMethod.PUT);
|
||||
AttachmentDescriptor descriptor = new AttachmentDescriptor(attachmentId, url.toExternalForm());
|
||||
|
||||
return Response.ok().entity(descriptor).build();
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Path("/{attachmentId}")
|
||||
public Response redirectToAttachment(@Auth Account account,
|
||||
@PathParam("attachmentId") long attachmentId,
|
||||
@QueryParam("relay") String relay)
|
||||
{
|
||||
try {
|
||||
URL url;
|
||||
|
||||
if (relay == null) url = urlSigner.getPreSignedUrl(attachmentId, HttpMethod.GET);
|
||||
else url = federatedClientManager.getClient(relay).getSignedAttachmentUri(attachmentId);
|
||||
|
||||
return Response.ok().entity(new AttachmentUri(url)).build();
|
||||
} catch (IOException e) {
|
||||
logger.warn("No conectivity", e);
|
||||
return Response.status(500).build();
|
||||
} catch (NoSuchPeerException e) {
|
||||
logger.info("No such peer: " + relay);
|
||||
return Response.status(404).build();
|
||||
}
|
||||
}
|
||||
|
||||
private long generateAttachmentId() {
|
||||
try {
|
||||
byte[] attachmentBytes = new byte[8];
|
||||
SecureRandom.getInstance("SHA1PRNG").nextBytes(attachmentBytes);
|
||||
|
||||
attachmentBytes[0] = (byte)(attachmentBytes[0] & 0x7F);
|
||||
return Conversions.byteArrayToLong(attachmentBytes);
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
throw new AssertionError(nsae);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.yammer.dropwizard.auth.Auth;
|
||||
import com.yammer.metrics.annotation.Metered;
|
||||
import com.yammer.metrics.annotation.Timed;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
||||
import org.whispersystems.textsecuregcm.entities.ClientContactTokens;
|
||||
import org.whispersystems.textsecuregcm.entities.ClientContacts;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryManager;
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
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.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Path("/v1/directory")
|
||||
public class DirectoryController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DirectoryController.class);
|
||||
|
||||
private final RateLimiters rateLimiters;
|
||||
private final DirectoryManager directory;
|
||||
|
||||
public DirectoryController(RateLimiters rateLimiters, DirectoryManager directory) {
|
||||
this.directory = directory;
|
||||
this.rateLimiters = rateLimiters;
|
||||
}
|
||||
|
||||
@Timed()
|
||||
@GET
|
||||
@Path("/{token}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response getTokenPresence(@Auth Account account, @PathParam("token") String token)
|
||||
throws RateLimitExceededException
|
||||
{
|
||||
rateLimiters.getContactsLimiter().validate(account.getNumber());
|
||||
|
||||
try {
|
||||
Optional<ClientContact> contact = directory.get(Base64.decodeWithoutPadding(token));
|
||||
|
||||
if (contact.isPresent()) return Response.ok().entity(contact.get()).build();
|
||||
else return Response.status(404).build();
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.info("Bad token", e);
|
||||
return Response.status(404).build();
|
||||
}
|
||||
}
|
||||
|
||||
@Timed()
|
||||
@PUT
|
||||
@Path("/tokens")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public ClientContacts getContactIntersection(@Auth Account account, @Valid ClientContactTokens contacts)
|
||||
throws RateLimitExceededException
|
||||
{
|
||||
rateLimiters.getContactsLimiter().validate(account.getNumber(), contacts.getContacts().size());
|
||||
|
||||
try {
|
||||
List<byte[]> tokens = new LinkedList<>();
|
||||
|
||||
for (String encodedContact : contacts.getContacts()) {
|
||||
tokens.add(Base64.decodeWithoutPadding(encodedContact));
|
||||
}
|
||||
|
||||
List<ClientContact> intersection = directory.get(tokens);
|
||||
return new ClientContacts(intersection);
|
||||
} catch (IOException e) {
|
||||
logger.info("Bad token", e);
|
||||
throw new WebApplicationException(Response.status(400).build());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
import com.amazonaws.HttpMethod;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import com.yammer.dropwizard.auth.Auth;
|
||||
import com.yammer.metrics.annotation.Timed;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountCount;
|
||||
import org.whispersystems.textsecuregcm.entities.AttachmentUri;
|
||||
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
||||
import org.whispersystems.textsecuregcm.entities.ClientContacts;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.RelayMessage;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
||||
import org.whispersystems.textsecuregcm.push.PushSender;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||
import org.whispersystems.textsecuregcm.util.UrlSigner;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
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.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Path("/v1/federation")
|
||||
public class FederationController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(FederationController.class);
|
||||
|
||||
private static final int ACCOUNT_CHUNK_SIZE = 10000;
|
||||
|
||||
private final PushSender pushSender;
|
||||
private final Keys keys;
|
||||
private final AccountsManager accounts;
|
||||
private final UrlSigner urlSigner;
|
||||
|
||||
public FederationController(Keys keys, AccountsManager accounts, PushSender pushSender, UrlSigner urlSigner) {
|
||||
this.keys = keys;
|
||||
this.accounts = accounts;
|
||||
this.pushSender = pushSender;
|
||||
this.urlSigner = urlSigner;
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/attachment/{attachmentId}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public AttachmentUri getSignedAttachmentUri(@Auth FederatedPeer peer,
|
||||
@PathParam("attachmentId") long attachmentId)
|
||||
{
|
||||
URL url = urlSigner.getPreSignedUrl(attachmentId, HttpMethod.GET);
|
||||
return new AttachmentUri(url);
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/key/{number}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public PreKey getKey(@Auth FederatedPeer peer,
|
||||
@PathParam("number") String number)
|
||||
{
|
||||
PreKey preKey = keys.get(number);
|
||||
|
||||
if (preKey == null) {
|
||||
throw new WebApplicationException(Response.status(404).build());
|
||||
}
|
||||
|
||||
return preKey;
|
||||
}
|
||||
|
||||
@Timed
|
||||
@PUT
|
||||
@Path("/message")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void relayMessage(@Auth FederatedPeer peer, @Valid RelayMessage message)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
OutgoingMessageSignal signal = OutgoingMessageSignal.parseFrom(message.getOutgoingMessageSignal())
|
||||
.toBuilder()
|
||||
.setRelay(peer.getName())
|
||||
.build();
|
||||
|
||||
pushSender.sendMessage(message.getDestination(), signal);
|
||||
} catch (InvalidProtocolBufferException ipe) {
|
||||
logger.warn("ProtoBuf", ipe);
|
||||
throw new WebApplicationException(Response.status(400).build());
|
||||
} catch (NoSuchUserException e) {
|
||||
logger.debug("No User", e);
|
||||
throw new WebApplicationException(Response.status(404).build());
|
||||
}
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/user_count")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public AccountCount getUserCount(@Auth FederatedPeer peer) {
|
||||
return new AccountCount((int)accounts.getCount());
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/user_tokens/{offset}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public ClientContacts getUserTokens(@Auth FederatedPeer peer,
|
||||
@PathParam("offset") int offset)
|
||||
{
|
||||
List<Account> accountList = accounts.getAll(offset, ACCOUNT_CHUNK_SIZE);
|
||||
List<ClientContact> clientContacts = new LinkedList<>();
|
||||
|
||||
for (Account account : accountList) {
|
||||
byte[] token = Util.getContactToken(account.getNumber());
|
||||
ClientContact clientContact = new ClientContact(token, null, account.getSupportsSms());
|
||||
|
||||
if (Util.isEmpty(account.getApnRegistrationId()) &&
|
||||
Util.isEmpty(account.getGcmRegistrationId()))
|
||||
{
|
||||
clientContact.setInactive(true);
|
||||
}
|
||||
|
||||
clientContacts.add(clientContact);
|
||||
}
|
||||
|
||||
return new ClientContacts(clientContacts);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
import com.yammer.dropwizard.auth.Auth;
|
||||
import com.yammer.metrics.annotation.Timed;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyList;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||
import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.PUT;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
@Path("/v1/keys")
|
||||
public class KeysController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AccountController.class);
|
||||
|
||||
private final RateLimiters rateLimiters;
|
||||
private final Keys keys;
|
||||
private final FederatedClientManager federatedClientManager;
|
||||
|
||||
public KeysController(RateLimiters rateLimiters, Keys keys,
|
||||
FederatedClientManager federatedClientManager)
|
||||
{
|
||||
this.rateLimiters = rateLimiters;
|
||||
this.keys = keys;
|
||||
this.federatedClientManager = federatedClientManager;
|
||||
}
|
||||
|
||||
@Timed
|
||||
@PUT
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void setKeys(@Auth Account account, @Valid PreKeyList preKeys) {
|
||||
keys.store(account.getNumber(), preKeys.getLastResortKey(), preKeys.getKeys());
|
||||
}
|
||||
|
||||
@Timed
|
||||
@GET
|
||||
@Path("/{number}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public PreKey get(@Auth Account account,
|
||||
@PathParam("number") String number,
|
||||
@QueryParam("relay") String relay)
|
||||
throws RateLimitExceededException
|
||||
{
|
||||
rateLimiters.getPreKeysLimiter().validate(account.getNumber() + "__" + number);
|
||||
|
||||
try {
|
||||
PreKey key;
|
||||
|
||||
if (relay == null) key = keys.get(number);
|
||||
else key = federatedClientManager.getClient(relay).getKey(number);
|
||||
|
||||
if (key == null) throw new WebApplicationException(Response.status(404).build());
|
||||
else return key;
|
||||
} catch (NoSuchPeerException e) {
|
||||
logger.info("No peer: " + relay);
|
||||
throw new WebApplicationException(Response.status(404).build());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.yammer.dropwizard.auth.AuthenticationException;
|
||||
import com.yammer.dropwizard.auth.basic.BasicCredentials;
|
||||
import com.yammer.metrics.Metrics;
|
||||
import com.yammer.metrics.core.Meter;
|
||||
import com.yammer.metrics.core.Timer;
|
||||
import com.yammer.metrics.core.TimerContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthorizationHeader;
|
||||
import org.whispersystems.textsecuregcm.auth.InvalidAuthorizationHeaderException;
|
||||
import org.whispersystems.textsecuregcm.entities.IncomingMessage;
|
||||
import org.whispersystems.textsecuregcm.entities.IncomingMessageList;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageResponse;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedClient;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||
import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.push.PushSender;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
import org.whispersystems.textsecuregcm.util.IterablePair;
|
||||
import org.whispersystems.textsecuregcm.util.IterablePair.Pair;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MessageController extends HttpServlet {
|
||||
|
||||
public static final String PATH = "/v1/messages/";
|
||||
|
||||
private final Meter successMeter = Metrics.newMeter(MessageController.class, "deliver_message", "success", TimeUnit.MINUTES);
|
||||
private final Meter failureMeter = Metrics.newMeter(MessageController.class, "deliver_message", "failure", TimeUnit.MINUTES);
|
||||
private final Timer timer = Metrics.newTimer(MessageController.class, "deliver_message_time", TimeUnit.MILLISECONDS, TimeUnit.MINUTES);
|
||||
private final Logger logger = LoggerFactory.getLogger(MessageController.class);
|
||||
|
||||
private final RateLimiters rateLimiters;
|
||||
private final AccountAuthenticator accountAuthenticator;
|
||||
private final PushSender pushSender;
|
||||
private final FederatedClientManager federatedClientManager;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final ExecutorService executor;
|
||||
|
||||
public MessageController(RateLimiters rateLimiters,
|
||||
AccountAuthenticator accountAuthenticator,
|
||||
PushSender pushSender,
|
||||
FederatedClientManager federatedClientManager)
|
||||
{
|
||||
this.rateLimiters = rateLimiters;
|
||||
this.accountAuthenticator = accountAuthenticator;
|
||||
this.pushSender = pushSender;
|
||||
this.federatedClientManager = federatedClientManager;
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.executor = Executors.newFixedThreadPool(10);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
|
||||
TimerContext timerContext = timer.time();
|
||||
|
||||
try {
|
||||
Account sender = authenticate(req);
|
||||
IncomingMessageList messages = parseIncomingMessages(req);
|
||||
|
||||
rateLimiters.getMessagesLimiter().validate(sender.getNumber());
|
||||
|
||||
List<IncomingMessage> incomingMessages = messages.getMessages();
|
||||
List<OutgoingMessageSignal> outgoingMessages = getOutgoingMessageSignals(sender.getNumber(),
|
||||
incomingMessages);
|
||||
|
||||
IterablePair<IncomingMessage, OutgoingMessageSignal> listPair = new IterablePair<>(incomingMessages,
|
||||
outgoingMessages);
|
||||
|
||||
handleAsyncDelivery(timerContext, req.startAsync(), listPair);
|
||||
} catch (AuthenticationException e) {
|
||||
failureMeter.mark();
|
||||
timerContext.stop();
|
||||
resp.setStatus(401);
|
||||
} catch (ValidationException e) {
|
||||
failureMeter.mark();
|
||||
timerContext.stop();
|
||||
resp.setStatus(415);
|
||||
} catch (IOException e) {
|
||||
logger.warn("IOE", e);
|
||||
failureMeter.mark();
|
||||
timerContext.stop();
|
||||
resp.setStatus(501);
|
||||
} catch (RateLimitExceededException e) {
|
||||
timerContext.stop();
|
||||
failureMeter.mark();
|
||||
resp.setStatus(413);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleAsyncDelivery(final TimerContext timerContext,
|
||||
final AsyncContext context,
|
||||
final IterablePair<IncomingMessage, OutgoingMessageSignal> listPair)
|
||||
{
|
||||
executor.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
List<String> success = new LinkedList<>();
|
||||
List<String> failure = new LinkedList<>();
|
||||
HttpServletResponse response = (HttpServletResponse) context.getResponse();
|
||||
|
||||
try {
|
||||
for (Pair<IncomingMessage, OutgoingMessageSignal> messagePair : listPair) {
|
||||
String destination = messagePair.first().getDestination();
|
||||
String relay = messagePair.first().getRelay();
|
||||
|
||||
try {
|
||||
if (Util.isEmpty(relay)) sendLocalMessage(destination, messagePair.second());
|
||||
else sendRelayMessage(relay, destination, messagePair.second());
|
||||
success.add(destination);
|
||||
} catch (NoSuchUserException e) {
|
||||
logger.debug("No such user", e);
|
||||
failure.add(destination);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] responseData = serializeResponse(new MessageResponse(success, failure));
|
||||
response.setContentLength(responseData.length);
|
||||
response.getOutputStream().write(responseData);
|
||||
context.complete();
|
||||
successMeter.mark();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Async Handler", e);
|
||||
failureMeter.mark();
|
||||
response.setStatus(501);
|
||||
context.complete();
|
||||
}
|
||||
|
||||
timerContext.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void sendLocalMessage(String destination, OutgoingMessageSignal outgoingMessage)
|
||||
throws IOException, NoSuchUserException
|
||||
{
|
||||
pushSender.sendMessage(destination, outgoingMessage);
|
||||
}
|
||||
|
||||
private void sendRelayMessage(String relay, String destination, OutgoingMessageSignal outgoingMessage)
|
||||
throws IOException, NoSuchUserException
|
||||
{
|
||||
try {
|
||||
FederatedClient client = federatedClientManager.getClient(relay);
|
||||
client.sendMessage(destination, outgoingMessage);
|
||||
} catch (NoSuchPeerException e) {
|
||||
logger.info("No such peer", e);
|
||||
throw new NoSuchUserException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<OutgoingMessageSignal> getOutgoingMessageSignals(String number,
|
||||
List<IncomingMessage> incomingMessages)
|
||||
{
|
||||
List<OutgoingMessageSignal> outgoingMessages = new LinkedList<>();
|
||||
|
||||
for (IncomingMessage incoming : incomingMessages) {
|
||||
OutgoingMessageSignal.Builder outgoingMessage = OutgoingMessageSignal.newBuilder();
|
||||
outgoingMessage.setType(incoming.getType());
|
||||
outgoingMessage.setSource(number);
|
||||
|
||||
byte[] messageBody = getMessageBody(incoming);
|
||||
|
||||
if (messageBody != null) {
|
||||
outgoingMessage.setMessage(ByteString.copyFrom(messageBody));
|
||||
}
|
||||
|
||||
outgoingMessage.setTimestamp(System.currentTimeMillis());
|
||||
|
||||
int index = 0;
|
||||
|
||||
for (IncomingMessage sub : incomingMessages) {
|
||||
if (sub != incoming) {
|
||||
outgoingMessage.setDestinations(index++, sub.getDestination());
|
||||
}
|
||||
}
|
||||
|
||||
outgoingMessages.add(outgoingMessage.build());
|
||||
}
|
||||
|
||||
return outgoingMessages;
|
||||
}
|
||||
|
||||
private byte[] getMessageBody(IncomingMessage message) {
|
||||
try {
|
||||
return Base64.decode(message.getBody());
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] serializeResponse(MessageResponse response) throws IOException {
|
||||
try {
|
||||
return objectMapper.writeValueAsBytes(response);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private IncomingMessageList parseIncomingMessages(HttpServletRequest request)
|
||||
throws IOException, ValidationException
|
||||
{
|
||||
BufferedReader reader = request.getReader();
|
||||
StringBuilder content = new StringBuilder();
|
||||
String line;
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
content.append(line);
|
||||
}
|
||||
|
||||
IncomingMessageList messages = objectMapper.readValue(content.toString(),
|
||||
IncomingMessageList.class);
|
||||
|
||||
if (messages.getMessages() == null) {
|
||||
throw new ValidationException();
|
||||
}
|
||||
|
||||
for (IncomingMessage message : messages.getMessages()) {
|
||||
if (message.getBody() == null) throw new ValidationException();
|
||||
if (message.getDestination() == null) throw new ValidationException();
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
private Account authenticate(HttpServletRequest request) throws AuthenticationException {
|
||||
try {
|
||||
AuthorizationHeader authorizationHeader = new AuthorizationHeader(request.getHeader("Authorization"));
|
||||
BasicCredentials credentials = new BasicCredentials(authorizationHeader.getUserName(),
|
||||
authorizationHeader.getPassword() );
|
||||
|
||||
Optional<Account> account = accountAuthenticator.authenticate(credentials);
|
||||
|
||||
if (account.isPresent()) return account.get();
|
||||
else throw new AuthenticationException("Bad credentials");
|
||||
} catch (InvalidAuthorizationHeaderException e) {
|
||||
throw new AuthenticationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// @Timed
|
||||
// @POST
|
||||
// @Consumes(MediaType.APPLICATION_JSON)
|
||||
// @Produces(MediaType.APPLICATION_JSON)
|
||||
// public MessageResponse sendMessage(@Auth Account sender, IncomingMessageList messages)
|
||||
// throws IOException
|
||||
// {
|
||||
// List<String> success = new LinkedList<>();
|
||||
// List<String> failure = new LinkedList<>();
|
||||
// List<IncomingMessage> incomingMessages = messages.getMessages();
|
||||
// List<OutgoingMessageSignal> outgoingMessages = getOutgoingMessageSignals(sender.getNumber(), incomingMessages);
|
||||
//
|
||||
// IterablePair<IncomingMessage, OutgoingMessageSignal> listPair = new IterablePair<>(incomingMessages, outgoingMessages);
|
||||
//
|
||||
// for (Pair<IncomingMessage, OutgoingMessageSignal> messagePair : listPair) {
|
||||
// String destination = messagePair.first().getDestination();
|
||||
// String relay = messagePair.first().getRelay();
|
||||
//
|
||||
// try {
|
||||
// if (Util.isEmpty(relay)) sendLocalMessage(destination, messagePair.second());
|
||||
// else sendRelayMessage(relay, destination, messagePair.second());
|
||||
// success.add(destination);
|
||||
// } catch (NoSuchUserException e) {
|
||||
// logger.debug("No such user", e);
|
||||
// failure.add(destination);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return new MessageResponse(success, failure);
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class NoSuchUserException extends Exception {
|
||||
|
||||
private List<String> missing;
|
||||
|
||||
public NoSuchUserException(String user) {
|
||||
super(user);
|
||||
missing = new LinkedList<String>();
|
||||
missing.add(user);
|
||||
}
|
||||
|
||||
public NoSuchUserException(List<String> missing) {
|
||||
this.missing = missing;
|
||||
}
|
||||
|
||||
public NoSuchUserException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public List<String> getMissing() {
|
||||
return missing;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
public class RateLimitExceededException extends Exception {
|
||||
public RateLimitExceededException(String number) {
|
||||
super(number);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
|
||||
public class ValidationException extends Exception {
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
public class AccountAttributes {
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String signalingKey;
|
||||
|
||||
@JsonProperty
|
||||
private boolean supportsSms;
|
||||
|
||||
public AccountAttributes() {}
|
||||
|
||||
public AccountAttributes(String signalingKey, boolean supportsSms) {
|
||||
this.signalingKey = signalingKey;
|
||||
this.supportsSms = supportsSms;
|
||||
}
|
||||
|
||||
public String getSignalingKey() {
|
||||
return signalingKey;
|
||||
}
|
||||
|
||||
public boolean getSupportsSms() {
|
||||
return supportsSms;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class AccountCount {
|
||||
|
||||
@JsonProperty
|
||||
private int count;
|
||||
|
||||
public AccountCount(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public AccountCount() {}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
public class ApnRegistrationId {
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String apnRegistrationId;
|
||||
|
||||
public String getApnRegistrationId() {
|
||||
return apnRegistrationId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class AttachmentDescriptor {
|
||||
|
||||
@JsonProperty
|
||||
private long id;
|
||||
|
||||
@JsonProperty
|
||||
private String location;
|
||||
|
||||
public AttachmentDescriptor(long id, String location) {
|
||||
this.id = id;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public AttachmentDescriptor() {}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
|
||||
public class AttachmentUri {
|
||||
|
||||
@JsonProperty
|
||||
private String location;
|
||||
|
||||
public AttachmentUri(URL uri) {
|
||||
this.location = uri.toString();
|
||||
}
|
||||
|
||||
public AttachmentUri() {}
|
||||
|
||||
public URL getLocation() throws MalformedURLException {
|
||||
return URI.create(location).toURL();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.google.gson.Gson;
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
||||
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
import java.util.Arrays;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
||||
public class ClientContact {
|
||||
|
||||
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
|
||||
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
|
||||
@JsonProperty
|
||||
private byte[] token;
|
||||
|
||||
private String relay;
|
||||
private boolean inactive;
|
||||
private boolean supportsSms;
|
||||
|
||||
public ClientContact(byte[] token, String relay, boolean supportsSms) {
|
||||
this.token = token;
|
||||
this.relay = relay;
|
||||
this.supportsSms = supportsSms;
|
||||
}
|
||||
|
||||
public ClientContact() {}
|
||||
|
||||
public byte[] getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public String getRelay() {
|
||||
return relay;
|
||||
}
|
||||
|
||||
public void setRelay(String relay) {
|
||||
this.relay = relay;
|
||||
}
|
||||
|
||||
public boolean isSupportsSms() {
|
||||
return supportsSms;
|
||||
}
|
||||
|
||||
public boolean isInactive() {
|
||||
return inactive;
|
||||
}
|
||||
|
||||
public void setInactive(boolean inactive) {
|
||||
this.inactive = inactive;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return new Gson().toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other == null) return false;
|
||||
if (!(other instanceof ClientContact)) return false;
|
||||
|
||||
ClientContact that = (ClientContact)other;
|
||||
|
||||
return
|
||||
Arrays.equals(this.token, that.token) &&
|
||||
this.supportsSms == that.supportsSms &&
|
||||
this.inactive == that.inactive &&
|
||||
(this.relay == null ? (that.relay == null) : this.relay.equals(that.relay));
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(this.token);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
public class ClientContactTokens {
|
||||
|
||||
@NotNull
|
||||
@JsonProperty
|
||||
private List<String> contacts;
|
||||
|
||||
public List<String> getContacts() {
|
||||
return contacts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class ClientContacts {
|
||||
|
||||
@JsonProperty
|
||||
private List<ClientContact> contacts;
|
||||
|
||||
public ClientContacts(List<ClientContact> contacts) {
|
||||
if (contacts != null) this.contacts = contacts;
|
||||
else this.contacts = new LinkedList<>();
|
||||
}
|
||||
|
||||
public ClientContacts() {
|
||||
this.contacts = new LinkedList<>();
|
||||
}
|
||||
|
||||
public List<ClientContact> getContacts() {
|
||||
return contacts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal;
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class EncryptedOutgoingMessage {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EncryptedOutgoingMessage.class);
|
||||
|
||||
private static final byte[] VERSION = new byte[]{0x01};
|
||||
private static final int CIPHER_KEY_SIZE = 32;
|
||||
private static final int MAC_KEY_SIZE = 20;
|
||||
private static final int MAC_SIZE = 10;
|
||||
|
||||
private final OutgoingMessageSignal outgoingMessage;
|
||||
private final String signalingKey;
|
||||
|
||||
public EncryptedOutgoingMessage(OutgoingMessageSignal outgoingMessage,
|
||||
String signalingKey)
|
||||
{
|
||||
this.outgoingMessage = outgoingMessage;
|
||||
this.signalingKey = signalingKey;
|
||||
}
|
||||
|
||||
public String serialize() throws IOException {
|
||||
byte[] plaintext = outgoingMessage.toByteArray();
|
||||
SecretKeySpec cipherKey = getCipherKey (signalingKey);
|
||||
SecretKeySpec macKey = getMacKey(signalingKey);
|
||||
byte[] ciphertext = getCiphertext(plaintext, cipherKey, macKey);
|
||||
|
||||
return Base64.encodeBytes(ciphertext);
|
||||
}
|
||||
|
||||
private byte[] getCiphertext(byte[] plaintext, SecretKeySpec cipherKey, SecretKeySpec macKey)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, cipherKey);
|
||||
|
||||
Mac hmac = Mac.getInstance("HmacSHA256");
|
||||
hmac.init(macKey);
|
||||
|
||||
hmac.update(VERSION);
|
||||
|
||||
byte[] ivBytes = cipher.getIV();
|
||||
hmac.update(ivBytes);
|
||||
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
byte[] mac = hmac.doFinal(ciphertext);
|
||||
byte[] truncatedMac = new byte[MAC_SIZE];
|
||||
System.arraycopy(mac, 0, truncatedMac, 0, truncatedMac.length);
|
||||
|
||||
return Util.combine(VERSION, ivBytes, ciphertext, truncatedMac);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
logger.warn("Invalid Key", e);
|
||||
throw new IOException("Invalid key!");
|
||||
}
|
||||
}
|
||||
|
||||
private SecretKeySpec getCipherKey(String signalingKey) throws IOException {
|
||||
byte[] signalingKeyBytes = Base64.decode(signalingKey);
|
||||
byte[] cipherKey = new byte[CIPHER_KEY_SIZE];
|
||||
|
||||
if (signalingKeyBytes.length < CIPHER_KEY_SIZE)
|
||||
throw new IOException("Signaling key too short!");
|
||||
|
||||
System.arraycopy(signalingKeyBytes, 0, cipherKey, 0, cipherKey.length);
|
||||
return new SecretKeySpec(cipherKey, "AES");
|
||||
}
|
||||
|
||||
private SecretKeySpec getMacKey(String signalingKey) throws IOException {
|
||||
byte[] signalingKeyBytes = Base64.decode(signalingKey);
|
||||
byte[] macKey = new byte[MAC_KEY_SIZE];
|
||||
|
||||
if (signalingKeyBytes.length < CIPHER_KEY_SIZE + MAC_KEY_SIZE)
|
||||
throw new IOException(("Signaling key too short!"));
|
||||
|
||||
System.arraycopy(signalingKeyBytes, CIPHER_KEY_SIZE, macKey, 0, macKey.length);
|
||||
|
||||
return new SecretKeySpec(macKey, "HmacSHA256");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
public class GcmRegistrationId {
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String gcmRegistrationId;
|
||||
|
||||
public String getGcmRegistrationId() {
|
||||
return gcmRegistrationId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
public class IncomingMessage {
|
||||
|
||||
@JsonProperty
|
||||
private int type;
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String destination;
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String body;
|
||||
|
||||
@JsonProperty
|
||||
private String relay;
|
||||
|
||||
@JsonProperty
|
||||
private long timestamp;
|
||||
|
||||
public String getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getRelay() {
|
||||
return relay;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
public class IncomingMessageList {
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Valid
|
||||
private List<IncomingMessage> messages;
|
||||
|
||||
public IncomingMessageList() {}
|
||||
|
||||
public List<IncomingMessage> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,809 @@
|
||||
// Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
// source: OutgoingMessageSignal.proto
|
||||
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
public final class MessageProtos {
|
||||
private MessageProtos() {}
|
||||
public static void registerAllExtensions(
|
||||
com.google.protobuf.ExtensionRegistry registry) {
|
||||
}
|
||||
public interface OutgoingMessageSignalOrBuilder
|
||||
extends com.google.protobuf.MessageOrBuilder {
|
||||
|
||||
// optional uint32 type = 1;
|
||||
boolean hasType();
|
||||
int getType();
|
||||
|
||||
// optional string source = 2;
|
||||
boolean hasSource();
|
||||
String getSource();
|
||||
|
||||
// optional string relay = 3;
|
||||
boolean hasRelay();
|
||||
String getRelay();
|
||||
|
||||
// repeated string destinations = 4;
|
||||
java.util.List<String> getDestinationsList();
|
||||
int getDestinationsCount();
|
||||
String getDestinations(int index);
|
||||
|
||||
// optional uint64 timestamp = 5;
|
||||
boolean hasTimestamp();
|
||||
long getTimestamp();
|
||||
|
||||
// optional bytes message = 6;
|
||||
boolean hasMessage();
|
||||
com.google.protobuf.ByteString getMessage();
|
||||
}
|
||||
public static final class OutgoingMessageSignal extends
|
||||
com.google.protobuf.GeneratedMessage
|
||||
implements OutgoingMessageSignalOrBuilder {
|
||||
// Use OutgoingMessageSignal.newBuilder() to construct.
|
||||
private OutgoingMessageSignal(Builder builder) {
|
||||
super(builder);
|
||||
}
|
||||
private OutgoingMessageSignal(boolean noInit) {}
|
||||
|
||||
private static final OutgoingMessageSignal defaultInstance;
|
||||
public static OutgoingMessageSignal getDefaultInstance() {
|
||||
return defaultInstance;
|
||||
}
|
||||
|
||||
public OutgoingMessageSignal getDefaultInstanceForType() {
|
||||
return defaultInstance;
|
||||
}
|
||||
|
||||
public static final com.google.protobuf.Descriptors.Descriptor
|
||||
getDescriptor() {
|
||||
return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_OutgoingMessageSignal_descriptor;
|
||||
}
|
||||
|
||||
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
|
||||
internalGetFieldAccessorTable() {
|
||||
return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_OutgoingMessageSignal_fieldAccessorTable;
|
||||
}
|
||||
|
||||
private int bitField0_;
|
||||
// optional uint32 type = 1;
|
||||
public static final int TYPE_FIELD_NUMBER = 1;
|
||||
private int type_;
|
||||
public boolean hasType() {
|
||||
return ((bitField0_ & 0x00000001) == 0x00000001);
|
||||
}
|
||||
public int getType() {
|
||||
return type_;
|
||||
}
|
||||
|
||||
// optional string source = 2;
|
||||
public static final int SOURCE_FIELD_NUMBER = 2;
|
||||
private java.lang.Object source_;
|
||||
public boolean hasSource() {
|
||||
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||
}
|
||||
public String getSource() {
|
||||
java.lang.Object ref = source_;
|
||||
if (ref instanceof String) {
|
||||
return (String) ref;
|
||||
} else {
|
||||
com.google.protobuf.ByteString bs =
|
||||
(com.google.protobuf.ByteString) ref;
|
||||
String s = bs.toStringUtf8();
|
||||
if (com.google.protobuf.Internal.isValidUtf8(bs)) {
|
||||
source_ = s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
private com.google.protobuf.ByteString getSourceBytes() {
|
||||
java.lang.Object ref = source_;
|
||||
if (ref instanceof String) {
|
||||
com.google.protobuf.ByteString b =
|
||||
com.google.protobuf.ByteString.copyFromUtf8((String) ref);
|
||||
source_ = b;
|
||||
return b;
|
||||
} else {
|
||||
return (com.google.protobuf.ByteString) ref;
|
||||
}
|
||||
}
|
||||
|
||||
// optional string relay = 3;
|
||||
public static final int RELAY_FIELD_NUMBER = 3;
|
||||
private java.lang.Object relay_;
|
||||
public boolean hasRelay() {
|
||||
return ((bitField0_ & 0x00000004) == 0x00000004);
|
||||
}
|
||||
public String getRelay() {
|
||||
java.lang.Object ref = relay_;
|
||||
if (ref instanceof String) {
|
||||
return (String) ref;
|
||||
} else {
|
||||
com.google.protobuf.ByteString bs =
|
||||
(com.google.protobuf.ByteString) ref;
|
||||
String s = bs.toStringUtf8();
|
||||
if (com.google.protobuf.Internal.isValidUtf8(bs)) {
|
||||
relay_ = s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
private com.google.protobuf.ByteString getRelayBytes() {
|
||||
java.lang.Object ref = relay_;
|
||||
if (ref instanceof String) {
|
||||
com.google.protobuf.ByteString b =
|
||||
com.google.protobuf.ByteString.copyFromUtf8((String) ref);
|
||||
relay_ = b;
|
||||
return b;
|
||||
} else {
|
||||
return (com.google.protobuf.ByteString) ref;
|
||||
}
|
||||
}
|
||||
|
||||
// repeated string destinations = 4;
|
||||
public static final int DESTINATIONS_FIELD_NUMBER = 4;
|
||||
private com.google.protobuf.LazyStringList destinations_;
|
||||
public java.util.List<String>
|
||||
getDestinationsList() {
|
||||
return destinations_;
|
||||
}
|
||||
public int getDestinationsCount() {
|
||||
return destinations_.size();
|
||||
}
|
||||
public String getDestinations(int index) {
|
||||
return destinations_.get(index);
|
||||
}
|
||||
|
||||
// optional uint64 timestamp = 5;
|
||||
public static final int TIMESTAMP_FIELD_NUMBER = 5;
|
||||
private long timestamp_;
|
||||
public boolean hasTimestamp() {
|
||||
return ((bitField0_ & 0x00000008) == 0x00000008);
|
||||
}
|
||||
public long getTimestamp() {
|
||||
return timestamp_;
|
||||
}
|
||||
|
||||
// optional bytes message = 6;
|
||||
public static final int MESSAGE_FIELD_NUMBER = 6;
|
||||
private com.google.protobuf.ByteString message_;
|
||||
public boolean hasMessage() {
|
||||
return ((bitField0_ & 0x00000010) == 0x00000010);
|
||||
}
|
||||
public com.google.protobuf.ByteString getMessage() {
|
||||
return message_;
|
||||
}
|
||||
|
||||
private void initFields() {
|
||||
type_ = 0;
|
||||
source_ = "";
|
||||
relay_ = "";
|
||||
destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY;
|
||||
timestamp_ = 0L;
|
||||
message_ = com.google.protobuf.ByteString.EMPTY;
|
||||
}
|
||||
private byte memoizedIsInitialized = -1;
|
||||
public final boolean isInitialized() {
|
||||
byte isInitialized = memoizedIsInitialized;
|
||||
if (isInitialized != -1) return isInitialized == 1;
|
||||
|
||||
memoizedIsInitialized = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void writeTo(com.google.protobuf.CodedOutputStream output)
|
||||
throws java.io.IOException {
|
||||
getSerializedSize();
|
||||
if (((bitField0_ & 0x00000001) == 0x00000001)) {
|
||||
output.writeUInt32(1, type_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
output.writeBytes(2, getSourceBytes());
|
||||
}
|
||||
if (((bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
output.writeBytes(3, getRelayBytes());
|
||||
}
|
||||
for (int i = 0; i < destinations_.size(); i++) {
|
||||
output.writeBytes(4, destinations_.getByteString(i));
|
||||
}
|
||||
if (((bitField0_ & 0x00000008) == 0x00000008)) {
|
||||
output.writeUInt64(5, timestamp_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000010) == 0x00000010)) {
|
||||
output.writeBytes(6, message_);
|
||||
}
|
||||
getUnknownFields().writeTo(output);
|
||||
}
|
||||
|
||||
private int memoizedSerializedSize = -1;
|
||||
public int getSerializedSize() {
|
||||
int size = memoizedSerializedSize;
|
||||
if (size != -1) return size;
|
||||
|
||||
size = 0;
|
||||
if (((bitField0_ & 0x00000001) == 0x00000001)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeUInt32Size(1, type_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeBytesSize(2, getSourceBytes());
|
||||
}
|
||||
if (((bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeBytesSize(3, getRelayBytes());
|
||||
}
|
||||
{
|
||||
int dataSize = 0;
|
||||
for (int i = 0; i < destinations_.size(); i++) {
|
||||
dataSize += com.google.protobuf.CodedOutputStream
|
||||
.computeBytesSizeNoTag(destinations_.getByteString(i));
|
||||
}
|
||||
size += dataSize;
|
||||
size += 1 * getDestinationsList().size();
|
||||
}
|
||||
if (((bitField0_ & 0x00000008) == 0x00000008)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeUInt64Size(5, timestamp_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000010) == 0x00000010)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeBytesSize(6, message_);
|
||||
}
|
||||
size += getUnknownFields().getSerializedSize();
|
||||
memoizedSerializedSize = size;
|
||||
return size;
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
@java.lang.Override
|
||||
protected java.lang.Object writeReplace()
|
||||
throws java.io.ObjectStreamException {
|
||||
return super.writeReplace();
|
||||
}
|
||||
|
||||
public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom(
|
||||
com.google.protobuf.ByteString data)
|
||||
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||
return newBuilder().mergeFrom(data).buildParsed();
|
||||
}
|
||||
public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom(
|
||||
com.google.protobuf.ByteString data,
|
||||
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||
return newBuilder().mergeFrom(data, extensionRegistry)
|
||||
.buildParsed();
|
||||
}
|
||||
public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom(byte[] data)
|
||||
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||
return newBuilder().mergeFrom(data).buildParsed();
|
||||
}
|
||||
public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom(
|
||||
byte[] data,
|
||||
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||
return newBuilder().mergeFrom(data, extensionRegistry)
|
||||
.buildParsed();
|
||||
}
|
||||
public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom(java.io.InputStream input)
|
||||
throws java.io.IOException {
|
||||
return newBuilder().mergeFrom(input).buildParsed();
|
||||
}
|
||||
public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom(
|
||||
java.io.InputStream input,
|
||||
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||
throws java.io.IOException {
|
||||
return newBuilder().mergeFrom(input, extensionRegistry)
|
||||
.buildParsed();
|
||||
}
|
||||
public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseDelimitedFrom(java.io.InputStream input)
|
||||
throws java.io.IOException {
|
||||
Builder builder = newBuilder();
|
||||
if (builder.mergeDelimitedFrom(input)) {
|
||||
return builder.buildParsed();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseDelimitedFrom(
|
||||
java.io.InputStream input,
|
||||
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||
throws java.io.IOException {
|
||||
Builder builder = newBuilder();
|
||||
if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
|
||||
return builder.buildParsed();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom(
|
||||
com.google.protobuf.CodedInputStream input)
|
||||
throws java.io.IOException {
|
||||
return newBuilder().mergeFrom(input).buildParsed();
|
||||
}
|
||||
public static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal parseFrom(
|
||||
com.google.protobuf.CodedInputStream input,
|
||||
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||
throws java.io.IOException {
|
||||
return newBuilder().mergeFrom(input, extensionRegistry)
|
||||
.buildParsed();
|
||||
}
|
||||
|
||||
public static Builder newBuilder() { return Builder.create(); }
|
||||
public Builder newBuilderForType() { return newBuilder(); }
|
||||
public static Builder newBuilder(org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal prototype) {
|
||||
return newBuilder().mergeFrom(prototype);
|
||||
}
|
||||
public Builder toBuilder() { return newBuilder(this); }
|
||||
|
||||
@java.lang.Override
|
||||
protected Builder newBuilderForType(
|
||||
com.google.protobuf.GeneratedMessage.BuilderParent parent) {
|
||||
Builder builder = new Builder(parent);
|
||||
return builder;
|
||||
}
|
||||
public static final class Builder extends
|
||||
com.google.protobuf.GeneratedMessage.Builder<Builder>
|
||||
implements org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignalOrBuilder {
|
||||
public static final com.google.protobuf.Descriptors.Descriptor
|
||||
getDescriptor() {
|
||||
return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_OutgoingMessageSignal_descriptor;
|
||||
}
|
||||
|
||||
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
|
||||
internalGetFieldAccessorTable() {
|
||||
return org.whispersystems.textsecuregcm.entities.MessageProtos.internal_static_textsecure_OutgoingMessageSignal_fieldAccessorTable;
|
||||
}
|
||||
|
||||
// Construct using org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.newBuilder()
|
||||
private Builder() {
|
||||
maybeForceBuilderInitialization();
|
||||
}
|
||||
|
||||
private Builder(BuilderParent parent) {
|
||||
super(parent);
|
||||
maybeForceBuilderInitialization();
|
||||
}
|
||||
private void maybeForceBuilderInitialization() {
|
||||
if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
|
||||
}
|
||||
}
|
||||
private static Builder create() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public Builder clear() {
|
||||
super.clear();
|
||||
type_ = 0;
|
||||
bitField0_ = (bitField0_ & ~0x00000001);
|
||||
source_ = "";
|
||||
bitField0_ = (bitField0_ & ~0x00000002);
|
||||
relay_ = "";
|
||||
bitField0_ = (bitField0_ & ~0x00000004);
|
||||
destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY;
|
||||
bitField0_ = (bitField0_ & ~0x00000008);
|
||||
timestamp_ = 0L;
|
||||
bitField0_ = (bitField0_ & ~0x00000010);
|
||||
message_ = com.google.protobuf.ByteString.EMPTY;
|
||||
bitField0_ = (bitField0_ & ~0x00000020);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder clone() {
|
||||
return create().mergeFrom(buildPartial());
|
||||
}
|
||||
|
||||
public com.google.protobuf.Descriptors.Descriptor
|
||||
getDescriptorForType() {
|
||||
return org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.getDescriptor();
|
||||
}
|
||||
|
||||
public org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal getDefaultInstanceForType() {
|
||||
return org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.getDefaultInstance();
|
||||
}
|
||||
|
||||
public org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal build() {
|
||||
org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal result = buildPartial();
|
||||
if (!result.isInitialized()) {
|
||||
throw newUninitializedMessageException(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal buildParsed()
|
||||
throws com.google.protobuf.InvalidProtocolBufferException {
|
||||
org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal result = buildPartial();
|
||||
if (!result.isInitialized()) {
|
||||
throw newUninitializedMessageException(
|
||||
result).asInvalidProtocolBufferException();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal buildPartial() {
|
||||
org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal result = new org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal(this);
|
||||
int from_bitField0_ = bitField0_;
|
||||
int to_bitField0_ = 0;
|
||||
if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
|
||||
to_bitField0_ |= 0x00000001;
|
||||
}
|
||||
result.type_ = type_;
|
||||
if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
to_bitField0_ |= 0x00000002;
|
||||
}
|
||||
result.source_ = source_;
|
||||
if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
to_bitField0_ |= 0x00000004;
|
||||
}
|
||||
result.relay_ = relay_;
|
||||
if (((bitField0_ & 0x00000008) == 0x00000008)) {
|
||||
destinations_ = new com.google.protobuf.UnmodifiableLazyStringList(
|
||||
destinations_);
|
||||
bitField0_ = (bitField0_ & ~0x00000008);
|
||||
}
|
||||
result.destinations_ = destinations_;
|
||||
if (((from_bitField0_ & 0x00000010) == 0x00000010)) {
|
||||
to_bitField0_ |= 0x00000008;
|
||||
}
|
||||
result.timestamp_ = timestamp_;
|
||||
if (((from_bitField0_ & 0x00000020) == 0x00000020)) {
|
||||
to_bitField0_ |= 0x00000010;
|
||||
}
|
||||
result.message_ = message_;
|
||||
result.bitField0_ = to_bitField0_;
|
||||
onBuilt();
|
||||
return result;
|
||||
}
|
||||
|
||||
public Builder mergeFrom(com.google.protobuf.Message other) {
|
||||
if (other instanceof org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal) {
|
||||
return mergeFrom((org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal)other);
|
||||
} else {
|
||||
super.mergeFrom(other);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public Builder mergeFrom(org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal other) {
|
||||
if (other == org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.getDefaultInstance()) return this;
|
||||
if (other.hasType()) {
|
||||
setType(other.getType());
|
||||
}
|
||||
if (other.hasSource()) {
|
||||
setSource(other.getSource());
|
||||
}
|
||||
if (other.hasRelay()) {
|
||||
setRelay(other.getRelay());
|
||||
}
|
||||
if (!other.destinations_.isEmpty()) {
|
||||
if (destinations_.isEmpty()) {
|
||||
destinations_ = other.destinations_;
|
||||
bitField0_ = (bitField0_ & ~0x00000008);
|
||||
} else {
|
||||
ensureDestinationsIsMutable();
|
||||
destinations_.addAll(other.destinations_);
|
||||
}
|
||||
onChanged();
|
||||
}
|
||||
if (other.hasTimestamp()) {
|
||||
setTimestamp(other.getTimestamp());
|
||||
}
|
||||
if (other.hasMessage()) {
|
||||
setMessage(other.getMessage());
|
||||
}
|
||||
this.mergeUnknownFields(other.getUnknownFields());
|
||||
return this;
|
||||
}
|
||||
|
||||
public final boolean isInitialized() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Builder mergeFrom(
|
||||
com.google.protobuf.CodedInputStream input,
|
||||
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
|
||||
throws java.io.IOException {
|
||||
com.google.protobuf.UnknownFieldSet.Builder unknownFields =
|
||||
com.google.protobuf.UnknownFieldSet.newBuilder(
|
||||
this.getUnknownFields());
|
||||
while (true) {
|
||||
int tag = input.readTag();
|
||||
switch (tag) {
|
||||
case 0:
|
||||
this.setUnknownFields(unknownFields.build());
|
||||
onChanged();
|
||||
return this;
|
||||
default: {
|
||||
if (!parseUnknownField(input, unknownFields,
|
||||
extensionRegistry, tag)) {
|
||||
this.setUnknownFields(unknownFields.build());
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 8: {
|
||||
bitField0_ |= 0x00000001;
|
||||
type_ = input.readUInt32();
|
||||
break;
|
||||
}
|
||||
case 18: {
|
||||
bitField0_ |= 0x00000002;
|
||||
source_ = input.readBytes();
|
||||
break;
|
||||
}
|
||||
case 26: {
|
||||
bitField0_ |= 0x00000004;
|
||||
relay_ = input.readBytes();
|
||||
break;
|
||||
}
|
||||
case 34: {
|
||||
ensureDestinationsIsMutable();
|
||||
destinations_.add(input.readBytes());
|
||||
break;
|
||||
}
|
||||
case 40: {
|
||||
bitField0_ |= 0x00000010;
|
||||
timestamp_ = input.readUInt64();
|
||||
break;
|
||||
}
|
||||
case 50: {
|
||||
bitField0_ |= 0x00000020;
|
||||
message_ = input.readBytes();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int bitField0_;
|
||||
|
||||
// optional uint32 type = 1;
|
||||
private int type_ ;
|
||||
public boolean hasType() {
|
||||
return ((bitField0_ & 0x00000001) == 0x00000001);
|
||||
}
|
||||
public int getType() {
|
||||
return type_;
|
||||
}
|
||||
public Builder setType(int value) {
|
||||
bitField0_ |= 0x00000001;
|
||||
type_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearType() {
|
||||
bitField0_ = (bitField0_ & ~0x00000001);
|
||||
type_ = 0;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
|
||||
// optional string source = 2;
|
||||
private java.lang.Object source_ = "";
|
||||
public boolean hasSource() {
|
||||
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||
}
|
||||
public String getSource() {
|
||||
java.lang.Object ref = source_;
|
||||
if (!(ref instanceof String)) {
|
||||
String s = ((com.google.protobuf.ByteString) ref).toStringUtf8();
|
||||
source_ = s;
|
||||
return s;
|
||||
} else {
|
||||
return (String) ref;
|
||||
}
|
||||
}
|
||||
public Builder setSource(String value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
bitField0_ |= 0x00000002;
|
||||
source_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearSource() {
|
||||
bitField0_ = (bitField0_ & ~0x00000002);
|
||||
source_ = getDefaultInstance().getSource();
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
void setSource(com.google.protobuf.ByteString value) {
|
||||
bitField0_ |= 0x00000002;
|
||||
source_ = value;
|
||||
onChanged();
|
||||
}
|
||||
|
||||
// optional string relay = 3;
|
||||
private java.lang.Object relay_ = "";
|
||||
public boolean hasRelay() {
|
||||
return ((bitField0_ & 0x00000004) == 0x00000004);
|
||||
}
|
||||
public String getRelay() {
|
||||
java.lang.Object ref = relay_;
|
||||
if (!(ref instanceof String)) {
|
||||
String s = ((com.google.protobuf.ByteString) ref).toStringUtf8();
|
||||
relay_ = s;
|
||||
return s;
|
||||
} else {
|
||||
return (String) ref;
|
||||
}
|
||||
}
|
||||
public Builder setRelay(String value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
bitField0_ |= 0x00000004;
|
||||
relay_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearRelay() {
|
||||
bitField0_ = (bitField0_ & ~0x00000004);
|
||||
relay_ = getDefaultInstance().getRelay();
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
void setRelay(com.google.protobuf.ByteString value) {
|
||||
bitField0_ |= 0x00000004;
|
||||
relay_ = value;
|
||||
onChanged();
|
||||
}
|
||||
|
||||
// repeated string destinations = 4;
|
||||
private com.google.protobuf.LazyStringList destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY;
|
||||
private void ensureDestinationsIsMutable() {
|
||||
if (!((bitField0_ & 0x00000008) == 0x00000008)) {
|
||||
destinations_ = new com.google.protobuf.LazyStringArrayList(destinations_);
|
||||
bitField0_ |= 0x00000008;
|
||||
}
|
||||
}
|
||||
public java.util.List<String>
|
||||
getDestinationsList() {
|
||||
return java.util.Collections.unmodifiableList(destinations_);
|
||||
}
|
||||
public int getDestinationsCount() {
|
||||
return destinations_.size();
|
||||
}
|
||||
public String getDestinations(int index) {
|
||||
return destinations_.get(index);
|
||||
}
|
||||
public Builder setDestinations(
|
||||
int index, String value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
ensureDestinationsIsMutable();
|
||||
destinations_.set(index, value);
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder addDestinations(String value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
ensureDestinationsIsMutable();
|
||||
destinations_.add(value);
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder addAllDestinations(
|
||||
java.lang.Iterable<String> values) {
|
||||
ensureDestinationsIsMutable();
|
||||
super.addAll(values, destinations_);
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearDestinations() {
|
||||
destinations_ = com.google.protobuf.LazyStringArrayList.EMPTY;
|
||||
bitField0_ = (bitField0_ & ~0x00000008);
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
void addDestinations(com.google.protobuf.ByteString value) {
|
||||
ensureDestinationsIsMutable();
|
||||
destinations_.add(value);
|
||||
onChanged();
|
||||
}
|
||||
|
||||
// optional uint64 timestamp = 5;
|
||||
private long timestamp_ ;
|
||||
public boolean hasTimestamp() {
|
||||
return ((bitField0_ & 0x00000010) == 0x00000010);
|
||||
}
|
||||
public long getTimestamp() {
|
||||
return timestamp_;
|
||||
}
|
||||
public Builder setTimestamp(long value) {
|
||||
bitField0_ |= 0x00000010;
|
||||
timestamp_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearTimestamp() {
|
||||
bitField0_ = (bitField0_ & ~0x00000010);
|
||||
timestamp_ = 0L;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
|
||||
// optional bytes message = 6;
|
||||
private com.google.protobuf.ByteString message_ = com.google.protobuf.ByteString.EMPTY;
|
||||
public boolean hasMessage() {
|
||||
return ((bitField0_ & 0x00000020) == 0x00000020);
|
||||
}
|
||||
public com.google.protobuf.ByteString getMessage() {
|
||||
return message_;
|
||||
}
|
||||
public Builder setMessage(com.google.protobuf.ByteString value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
bitField0_ |= 0x00000020;
|
||||
message_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearMessage() {
|
||||
bitField0_ = (bitField0_ & ~0x00000020);
|
||||
message_ = getDefaultInstance().getMessage();
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
|
||||
// @@protoc_insertion_point(builder_scope:textsecure.OutgoingMessageSignal)
|
||||
}
|
||||
|
||||
static {
|
||||
defaultInstance = new OutgoingMessageSignal(true);
|
||||
defaultInstance.initFields();
|
||||
}
|
||||
|
||||
// @@protoc_insertion_point(class_scope:textsecure.OutgoingMessageSignal)
|
||||
}
|
||||
|
||||
private static com.google.protobuf.Descriptors.Descriptor
|
||||
internal_static_textsecure_OutgoingMessageSignal_descriptor;
|
||||
private static
|
||||
com.google.protobuf.GeneratedMessage.FieldAccessorTable
|
||||
internal_static_textsecure_OutgoingMessageSignal_fieldAccessorTable;
|
||||
|
||||
public static com.google.protobuf.Descriptors.FileDescriptor
|
||||
getDescriptor() {
|
||||
return descriptor;
|
||||
}
|
||||
private static com.google.protobuf.Descriptors.FileDescriptor
|
||||
descriptor;
|
||||
static {
|
||||
java.lang.String[] descriptorData = {
|
||||
"\n\033OutgoingMessageSignal.proto\022\ntextsecur" +
|
||||
"e\"~\n\025OutgoingMessageSignal\022\014\n\004type\030\001 \001(\r" +
|
||||
"\022\016\n\006source\030\002 \001(\t\022\r\n\005relay\030\003 \001(\t\022\024\n\014desti" +
|
||||
"nations\030\004 \003(\t\022\021\n\ttimestamp\030\005 \001(\004\022\017\n\007mess" +
|
||||
"age\030\006 \001(\014B:\n)org.whispersystems.textsecu" +
|
||||
"regcm.entitiesB\rMessageProtos"
|
||||
};
|
||||
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
||||
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
||||
public com.google.protobuf.ExtensionRegistry assignDescriptors(
|
||||
com.google.protobuf.Descriptors.FileDescriptor root) {
|
||||
descriptor = root;
|
||||
internal_static_textsecure_OutgoingMessageSignal_descriptor =
|
||||
getDescriptor().getMessageTypes().get(0);
|
||||
internal_static_textsecure_OutgoingMessageSignal_fieldAccessorTable = new
|
||||
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
|
||||
internal_static_textsecure_OutgoingMessageSignal_descriptor,
|
||||
new java.lang.String[] { "Type", "Source", "Relay", "Destinations", "Timestamp", "Message", },
|
||||
org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.class,
|
||||
org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal.Builder.class);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
com.google.protobuf.Descriptors.FileDescriptor
|
||||
.internalBuildGeneratedFileFrom(descriptorData,
|
||||
new com.google.protobuf.Descriptors.FileDescriptor[] {
|
||||
}, assigner);
|
||||
}
|
||||
|
||||
// @@protoc_insertion_point(outer_class_scope)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MessageResponse {
|
||||
private List<String> success;
|
||||
private List<String> failure;
|
||||
|
||||
public MessageResponse(List<String> success, List<String> failure) {
|
||||
this.success = success;
|
||||
this.failure = failure;
|
||||
}
|
||||
|
||||
public MessageResponse() {}
|
||||
|
||||
public List<String> getSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public List<String> getFailure() {
|
||||
return failure;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
import java.io.Serializable;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
||||
public class PreKey {
|
||||
|
||||
@JsonIgnore
|
||||
private long id;
|
||||
|
||||
@JsonIgnore
|
||||
private String number;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
private long keyId;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
private String publicKey;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
private String identityKey;
|
||||
|
||||
@JsonProperty
|
||||
private boolean lastResort;
|
||||
|
||||
public PreKey() {}
|
||||
|
||||
public PreKey(long id, String number, long keyId,
|
||||
String publicKey, String identityKey,
|
||||
boolean lastResort)
|
||||
{
|
||||
this.id = id;
|
||||
this.number = number;
|
||||
this.keyId = keyId;
|
||||
this.publicKey = publicKey;
|
||||
this.identityKey = identityKey;
|
||||
this.lastResort = lastResort;
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
public String getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(String number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public void setPublicKey(String publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public long getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public void setKeyId(long keyId) {
|
||||
this.keyId = keyId;
|
||||
}
|
||||
|
||||
public String getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
public void setIdentityKey(String identityKey) {
|
||||
this.identityKey = identityKey;
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
public boolean isLastResort() {
|
||||
return lastResort;
|
||||
}
|
||||
|
||||
public void setLastResort(boolean lastResort) {
|
||||
this.lastResort = lastResort;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
public class PreKeyList {
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
private PreKey lastResortKey;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@Valid
|
||||
private List<PreKey> keys;
|
||||
|
||||
public List<PreKey> getKeys() {
|
||||
return keys;
|
||||
}
|
||||
|
||||
public PreKey getLastResortKey() {
|
||||
return lastResortKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.entities;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
public class RelayMessage {
|
||||
|
||||
@JsonProperty
|
||||
@NotEmpty
|
||||
private String destination;
|
||||
|
||||
@JsonProperty
|
||||
@NotNull
|
||||
@JsonSerialize(using = ByteArrayAdapter.Serializing.class)
|
||||
@JsonDeserialize(using = ByteArrayAdapter.Deserializing.class)
|
||||
private byte[] outgoingMessageSignal;
|
||||
|
||||
public RelayMessage() {}
|
||||
|
||||
public RelayMessage(String destination, byte[] outgoingMessageSignal) {
|
||||
this.destination = destination;
|
||||
this.outgoingMessageSignal = outgoingMessageSignal;
|
||||
}
|
||||
|
||||
public String getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
public byte[] getOutgoingMessageSignal() {
|
||||
return outgoingMessageSignal;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.federation;
|
||||
|
||||
|
||||
import com.sun.jersey.api.client.Client;
|
||||
import com.sun.jersey.api.client.ClientHandlerException;
|
||||
import com.sun.jersey.api.client.ClientResponse;
|
||||
import com.sun.jersey.api.client.UniformInterfaceException;
|
||||
import com.sun.jersey.api.client.WebResource;
|
||||
import com.sun.jersey.api.client.config.ClientConfig;
|
||||
import com.sun.jersey.api.client.config.DefaultClientConfig;
|
||||
import com.sun.jersey.api.json.JSONConfiguration;
|
||||
import com.sun.jersey.client.urlconnection.HTTPSProperties;
|
||||
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||
import org.bouncycastle.openssl.PEMReader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.controllers.NoSuchUserException;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountCount;
|
||||
import org.whispersystems.textsecuregcm.entities.AttachmentUri;
|
||||
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
||||
import org.whispersystems.textsecuregcm.entities.ClientContacts;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.RelayMessage;
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
public class FederatedClient {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(FederatedClient.class);
|
||||
|
||||
private static final String USER_COUNT_PATH = "/v1/federation/user_count";
|
||||
private static final String USER_TOKENS_PATH = "/v1/federation/user_tokens/%d";
|
||||
private static final String RELAY_MESSAGE_PATH = "/v1/federation/message";
|
||||
private static final String PREKEY_PATH = "/v1/federation/key/%s";
|
||||
private static final String ATTACHMENT_URI_PATH = "/v1/federation/attachment/%d";
|
||||
|
||||
private final FederatedPeer peer;
|
||||
private final Client client;
|
||||
private final String authorizationHeader;
|
||||
|
||||
public FederatedClient(String federationName, FederatedPeer peer)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
this.client = Client.create(getClientConfig(peer));
|
||||
this.peer = peer;
|
||||
this.authorizationHeader = getAuthorizationHeader(federationName, peer);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (KeyStoreException | KeyManagementException | CertificateException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public URL getSignedAttachmentUri(long attachmentId) throws IOException {
|
||||
try {
|
||||
WebResource resource = client.resource(peer.getUrl())
|
||||
.path(String.format(ATTACHMENT_URI_PATH, attachmentId));
|
||||
|
||||
return resource.accept(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", authorizationHeader)
|
||||
.get(AttachmentUri.class)
|
||||
.getLocation();
|
||||
} catch (UniformInterfaceException | ClientHandlerException e) {
|
||||
logger.warn("Bad URI", e);
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public PreKey getKey(String destination) {
|
||||
try {
|
||||
WebResource resource = client.resource(peer.getUrl()).path(String.format(PREKEY_PATH, destination));
|
||||
return resource.accept(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", authorizationHeader)
|
||||
.get(PreKey.class);
|
||||
} catch (UniformInterfaceException | ClientHandlerException e) {
|
||||
logger.warn("PreKey", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int getUserCount() {
|
||||
try {
|
||||
WebResource resource = client.resource(peer.getUrl()).path(USER_COUNT_PATH);
|
||||
AccountCount count = resource.accept(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", authorizationHeader)
|
||||
.get(AccountCount.class);
|
||||
|
||||
return count.getCount();
|
||||
} catch (UniformInterfaceException | ClientHandlerException e) {
|
||||
logger.warn("User Count", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public List<ClientContact> getUserTokens(int offset) {
|
||||
try {
|
||||
WebResource resource = client.resource(peer.getUrl()).path(String.format(USER_TOKENS_PATH, offset));
|
||||
ClientContacts contacts = resource.accept(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", authorizationHeader)
|
||||
.get(ClientContacts.class);
|
||||
|
||||
return contacts.getContacts();
|
||||
} catch (UniformInterfaceException | ClientHandlerException e) {
|
||||
logger.warn("User Tokens", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMessage(String destination, OutgoingMessageSignal message)
|
||||
throws IOException, NoSuchUserException
|
||||
{
|
||||
try {
|
||||
WebResource resource = client.resource(peer.getUrl()).path(RELAY_MESSAGE_PATH);
|
||||
ClientResponse response = resource.type(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", authorizationHeader)
|
||||
.entity(new RelayMessage(destination, message.toByteArray()))
|
||||
.put(ClientResponse.class);
|
||||
|
||||
if (response.getStatus() == 404) {
|
||||
throw new NoSuchUserException("No remote user: " + destination);
|
||||
}
|
||||
|
||||
if (response.getStatus() != 200 && response.getStatus() != 204) {
|
||||
throw new IOException("Bad response: " + response.getStatus());
|
||||
}
|
||||
} catch (UniformInterfaceException | ClientHandlerException e) {
|
||||
logger.warn("sendMessage", e);
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getAuthorizationHeader(String federationName, FederatedPeer peer) {
|
||||
return "Basic " + Base64.encodeBytes((federationName + ":" + peer.getAuthenticationToken()).getBytes());
|
||||
}
|
||||
|
||||
private ClientConfig getClientConfig(FederatedPeer peer)
|
||||
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, CertificateException
|
||||
{
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
|
||||
trustManagerFactory.init(initializeTrustStore(peer.getName(), peer.getCertificate()));
|
||||
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, trustManagerFactory.getTrustManagers(), SecureRandom.getInstance("SHA1PRNG"));
|
||||
|
||||
ClientConfig config = new DefaultClientConfig();
|
||||
config.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES,
|
||||
new HTTPSProperties(new StrictHostnameVerifier(), sslContext));
|
||||
config.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private KeyStore initializeTrustStore(String name, String pemCertificate)
|
||||
throws CertificateException
|
||||
{
|
||||
try {
|
||||
PEMReader reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(pemCertificate.getBytes())));
|
||||
X509Certificate certificate = (X509Certificate) reader.readObject();
|
||||
|
||||
if (certificate == null) {
|
||||
throw new CertificateException("No certificate found in parsing!");
|
||||
}
|
||||
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
keyStore.load(null);
|
||||
keyStore.setCertificateEntry(name, certificate);
|
||||
|
||||
return keyStore;
|
||||
} catch (IOException | KeyStoreException e) {
|
||||
throw new CertificateException(e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getPeerName() {
|
||||
return peer.getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.federation;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.configuration.FederationConfiguration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class FederatedClientManager {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(FederatedClientManager.class);
|
||||
|
||||
private final HashMap<String, FederatedClient> clients = new HashMap<>();
|
||||
|
||||
public FederatedClientManager(FederationConfiguration federationConfig)
|
||||
throws IOException
|
||||
{
|
||||
List<FederatedPeer> peers = federationConfig.getPeers();
|
||||
String identity = federationConfig.getName();
|
||||
|
||||
if (peers != null) {
|
||||
for (FederatedPeer peer : peers) {
|
||||
logger.info("Adding peer: " + peer.getName());
|
||||
clients.put(peer.getName(), new FederatedClient(identity, peer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FederatedClient getClient(String name) throws NoSuchPeerException {
|
||||
FederatedClient client = clients.get(name);
|
||||
|
||||
if (client == null) {
|
||||
throw new NoSuchPeerException(name);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
public List<FederatedClient> getClients() {
|
||||
return new LinkedList<>(clients.values());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.federation;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
public class FederatedPeer {
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String name;
|
||||
|
||||
@NotEmpty
|
||||
@URL
|
||||
@JsonProperty
|
||||
private String url;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String authenticationToken;
|
||||
|
||||
@NotEmpty
|
||||
@JsonProperty
|
||||
private String certificate;
|
||||
|
||||
public FederatedPeer() {}
|
||||
|
||||
public FederatedPeer(String name, String url, String authenticationToken, String certificate) {
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
this.authenticationToken = authenticationToken;
|
||||
this.certificate = certificate;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getAuthenticationToken() {
|
||||
return authenticationToken;
|
||||
}
|
||||
|
||||
public String getCertificate() {
|
||||
return certificate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.federation;
|
||||
|
||||
|
||||
public class NoSuchPeerException extends Exception {
|
||||
public NoSuchPeerException(String name) {
|
||||
super(name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.limits;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class LeakyBucket implements Serializable {
|
||||
|
||||
private final int bucketSize;
|
||||
private final double leakRatePerMillis;
|
||||
|
||||
private int spaceRemaining;
|
||||
private long lastUpdateTimeMillis;
|
||||
|
||||
public LeakyBucket(int bucketSize, double leakRatePerMillis) {
|
||||
this.bucketSize = bucketSize;
|
||||
this.leakRatePerMillis = leakRatePerMillis;
|
||||
this.spaceRemaining = bucketSize;
|
||||
this.lastUpdateTimeMillis = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public boolean add(int amount) {
|
||||
this.spaceRemaining = getUpdatedSpaceRemaining();
|
||||
|
||||
if (this.spaceRemaining >= amount) {
|
||||
this.spaceRemaining -= amount;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private int getUpdatedSpaceRemaining() {
|
||||
long elapsedTime = System.currentTimeMillis() - this.lastUpdateTimeMillis;
|
||||
|
||||
return Math.min(this.bucketSize,
|
||||
(int)Math.floor(this.spaceRemaining + (elapsedTime * this.leakRatePerMillis)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.limits;
|
||||
|
||||
import com.yammer.metrics.Metrics;
|
||||
import com.yammer.metrics.core.Meter;
|
||||
import net.spy.memcached.MemcachedClient;
|
||||
|
||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RateLimiter {
|
||||
|
||||
private final Meter meter;
|
||||
private final MemcachedClient memcachedClient;
|
||||
private final String name;
|
||||
private final int bucketSize;
|
||||
private final double leakRatePerMillis;
|
||||
|
||||
public RateLimiter(MemcachedClient memcachedClient, String name,
|
||||
int bucketSize, double leakRatePerMinute)
|
||||
{
|
||||
this.meter = Metrics.newMeter(RateLimiter.class, name, "exceeded", TimeUnit.MINUTES);
|
||||
this.memcachedClient = memcachedClient;
|
||||
this.name = name;
|
||||
this.bucketSize = bucketSize;
|
||||
this.leakRatePerMillis = leakRatePerMinute / (60.0 * 1000.0);
|
||||
}
|
||||
|
||||
public void validate(String key, int amount) throws RateLimitExceededException {
|
||||
LeakyBucket bucket = getBucket(key);
|
||||
|
||||
if (bucket.add(amount)) {
|
||||
setBucket(key, bucket);
|
||||
} else {
|
||||
meter.mark();
|
||||
throw new RateLimitExceededException(key + " , " + amount);
|
||||
}
|
||||
}
|
||||
|
||||
public void validate(String key) throws RateLimitExceededException {
|
||||
validate(key, 1);
|
||||
}
|
||||
|
||||
private void setBucket(String key, LeakyBucket bucket) {
|
||||
memcachedClient.set(getBucketName(key),
|
||||
(int)Math.ceil((bucketSize / leakRatePerMillis) / 1000), bucket);
|
||||
}
|
||||
|
||||
private LeakyBucket getBucket(String key) {
|
||||
LeakyBucket bucket = (LeakyBucket)memcachedClient.get(getBucketName(key));
|
||||
|
||||
if (bucket == null) {
|
||||
return new LeakyBucket(bucketSize, leakRatePerMillis);
|
||||
} else {
|
||||
return bucket;
|
||||
}
|
||||
}
|
||||
|
||||
private String getBucketName(String key) {
|
||||
return LeakyBucket.class.getSimpleName() + name + key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.limits;
|
||||
|
||||
|
||||
import net.spy.memcached.MemcachedClient;
|
||||
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration;
|
||||
|
||||
public class RateLimiters {
|
||||
|
||||
private final RateLimiter smsDestinationLimiter;
|
||||
private final RateLimiter voiceDestinationLimiter;
|
||||
private final RateLimiter verifyLimiter;
|
||||
|
||||
private final RateLimiter attachmentLimiter;
|
||||
private final RateLimiter contactsLimiter;
|
||||
private final RateLimiter preKeysLimiter;
|
||||
private final RateLimiter messagesLimiter;
|
||||
|
||||
public RateLimiters(RateLimitsConfiguration config, MemcachedClient memcachedClient) {
|
||||
this.smsDestinationLimiter = new RateLimiter(memcachedClient, "smsDestination",
|
||||
config.getSmsDestination().getBucketSize(),
|
||||
config.getSmsDestination().getLeakRatePerMinute());
|
||||
|
||||
this.voiceDestinationLimiter = new RateLimiter(memcachedClient, "voxDestination",
|
||||
config.getVoiceDestination().getBucketSize(),
|
||||
config.getVoiceDestination().getLeakRatePerMinute());
|
||||
|
||||
this.verifyLimiter = new RateLimiter(memcachedClient, "verify",
|
||||
config.getVerifyNumber().getBucketSize(),
|
||||
config.getVerifyNumber().getLeakRatePerMinute());
|
||||
|
||||
this.attachmentLimiter = new RateLimiter(memcachedClient, "attachmentCreate",
|
||||
config.getAttachments().getBucketSize(),
|
||||
config.getAttachments().getLeakRatePerMinute());
|
||||
|
||||
this.contactsLimiter = new RateLimiter(memcachedClient, "contactsQuery",
|
||||
config.getContactQueries().getBucketSize(),
|
||||
config.getContactQueries().getLeakRatePerMinute());
|
||||
|
||||
this.preKeysLimiter = new RateLimiter(memcachedClient, "prekeys",
|
||||
config.getPreKeys().getBucketSize(),
|
||||
config.getPreKeys().getLeakRatePerMinute());
|
||||
|
||||
this.messagesLimiter = new RateLimiter(memcachedClient, "messages",
|
||||
config.getMessages().getBucketSize(),
|
||||
config.getMessages().getLeakRatePerMinute());
|
||||
}
|
||||
|
||||
public RateLimiter getMessagesLimiter() {
|
||||
return messagesLimiter;
|
||||
}
|
||||
|
||||
public RateLimiter getPreKeysLimiter() {
|
||||
return preKeysLimiter;
|
||||
}
|
||||
|
||||
public RateLimiter getContactsLimiter() {
|
||||
return contactsLimiter;
|
||||
}
|
||||
|
||||
public RateLimiter getAttachmentLimiter() {
|
||||
return this.attachmentLimiter;
|
||||
}
|
||||
|
||||
public RateLimiter getSmsDestinationLimiter() {
|
||||
return smsDestinationLimiter;
|
||||
}
|
||||
|
||||
public RateLimiter getVoiceDestinationLimiter() {
|
||||
return voiceDestinationLimiter;
|
||||
}
|
||||
|
||||
public RateLimiter getVerifyLimiter() {
|
||||
return verifyLimiter;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.mappers;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
import java.io.IOException;
|
||||
|
||||
@Provider
|
||||
public class IOExceptionMapper implements ExceptionMapper<IOException> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(IOExceptionMapper.class);
|
||||
|
||||
@Override
|
||||
public Response toResponse(IOException e) {
|
||||
logger.warn("IOExceptionMapper", e);
|
||||
return Response.status(503).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.mappers;
|
||||
|
||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
@Provider
|
||||
public class RateLimitExceededExceptionMapper implements ExceptionMapper<RateLimitExceededException> {
|
||||
@Override
|
||||
public Response toResponse(RateLimitExceededException e) {
|
||||
return Response.status(413).build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.providers;
|
||||
|
||||
import com.yammer.metrics.core.HealthCheck;
|
||||
import com.yammer.metrics.core.HealthCheck.Result;
|
||||
import net.spy.memcached.MemcachedClient;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class MemcacheHealthCheck extends HealthCheck {
|
||||
|
||||
private final MemcachedClient client;
|
||||
|
||||
public MemcacheHealthCheck(MemcachedClient client) {
|
||||
super("memcached");
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result check() throws Exception {
|
||||
if (client == null) {
|
||||
return Result.unhealthy("not configured");
|
||||
}
|
||||
|
||||
int random = SecureRandom.getInstance("SHA1PRNG").nextInt();
|
||||
int value = SecureRandom.getInstance("SHA1PRNG").nextInt();
|
||||
|
||||
this.client.set("HEALTH" + random, 2000, String.valueOf(value));
|
||||
String result = (String)this.client.get("HEALTH" + random);
|
||||
|
||||
if (result == null || Integer.parseInt(result) != value) {
|
||||
return Result.unhealthy("Fetch failed");
|
||||
}
|
||||
|
||||
return Result.healthy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.providers;
|
||||
|
||||
import net.spy.memcached.AddrUtil;
|
||||
import net.spy.memcached.ConnectionFactoryBuilder;
|
||||
import net.spy.memcached.MemcachedClient;
|
||||
import net.spy.memcached.auth.AuthDescriptor;
|
||||
import net.spy.memcached.auth.PlainCallbackHandler;
|
||||
import org.whispersystems.textsecuregcm.configuration.MemcacheConfiguration;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MemcachedClientFactory {
|
||||
|
||||
private final MemcachedClient client;
|
||||
|
||||
public MemcachedClientFactory(MemcacheConfiguration config) throws IOException {
|
||||
ConnectionFactoryBuilder builder = new ConnectionFactoryBuilder();
|
||||
builder.setProtocol(ConnectionFactoryBuilder.Protocol.BINARY);
|
||||
|
||||
if (!Util.isEmpty(config.getUser())) {
|
||||
AuthDescriptor ad = new AuthDescriptor(new String[] { "PLAIN" },
|
||||
new PlainCallbackHandler(config.getUser(),
|
||||
config.getPassword()));
|
||||
|
||||
builder.setAuthDescriptor(ad);
|
||||
}
|
||||
|
||||
|
||||
this.client = new MemcachedClient(builder.build(),
|
||||
AddrUtil.getAddresses(config.getServers()));
|
||||
}
|
||||
|
||||
|
||||
public MemcachedClient getClient() {
|
||||
return client;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.providers;
|
||||
|
||||
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
import redis.clients.jedis.Protocol;
|
||||
|
||||
public class RedisClientFactory {
|
||||
|
||||
private final JedisPool jedisPool;
|
||||
|
||||
public RedisClientFactory(RedisConfiguration redisConfig) throws URISyntaxException {
|
||||
JedisPoolConfig poolConfig = new JedisPoolConfig();
|
||||
poolConfig.setTestOnBorrow(true);
|
||||
|
||||
URI redisURI = new URI(redisConfig.getUrl());
|
||||
String redisHost = redisURI.getHost();
|
||||
int redisPort = redisURI.getPort();
|
||||
String redisPassword = null;
|
||||
|
||||
if (!Util.isEmpty(redisURI.getUserInfo())) {
|
||||
redisPassword = redisURI.getUserInfo().split(":",2)[1];
|
||||
}
|
||||
|
||||
this.jedisPool = new JedisPool(poolConfig, redisHost, redisPort,
|
||||
Protocol.DEFAULT_TIMEOUT, redisPassword);
|
||||
}
|
||||
|
||||
public JedisPool getRedisClientPool() {
|
||||
return jedisPool;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.providers;
|
||||
|
||||
import com.yammer.metrics.core.HealthCheck;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
|
||||
public class RedisHealthCheck extends HealthCheck {
|
||||
|
||||
private final JedisPool clientPool;
|
||||
|
||||
public RedisHealthCheck(JedisPool clientPool) {
|
||||
super("redis");
|
||||
this.clientPool = clientPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result check() throws Exception {
|
||||
Jedis client = clientPool.getResource();
|
||||
|
||||
try {
|
||||
client.set("HEALTH", "test");
|
||||
|
||||
if (!"test".equals(client.get("HEALTH"))) {
|
||||
return Result.unhealthy("fetch failed");
|
||||
}
|
||||
|
||||
return Result.healthy();
|
||||
} finally {
|
||||
clientPool.returnResource(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.push;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.notnoop.apns.APNS;
|
||||
import com.notnoop.apns.ApnsService;
|
||||
import com.notnoop.exceptions.NetworkIOException;
|
||||
import com.yammer.metrics.Metrics;
|
||||
import com.yammer.metrics.core.Meter;
|
||||
import org.bouncycastle.openssl.PEMReader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class APNSender {
|
||||
|
||||
private final Meter success = Metrics.newMeter(APNSender.class, "sent", "success", TimeUnit.MINUTES);
|
||||
private final Meter failure = Metrics.newMeter(APNSender.class, "sent", "failure", TimeUnit.MINUTES);
|
||||
private final Logger logger = LoggerFactory.getLogger(APNSender.class);
|
||||
|
||||
private static final String MESSAGE_BODY = "m";
|
||||
|
||||
private final Optional<ApnsService> apnService;
|
||||
|
||||
public APNSender(String apnCertificate, String apnKey)
|
||||
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException
|
||||
{
|
||||
if (!Util.isEmpty(apnCertificate) && !Util.isEmpty(apnKey)) {
|
||||
byte[] keyStore = initializeKeyStore(apnCertificate, apnKey);
|
||||
this.apnService = Optional.of(APNS.newService()
|
||||
.withCert(new ByteArrayInputStream(keyStore), "insecure")
|
||||
.withSandboxDestination().build());
|
||||
} else {
|
||||
this.apnService = Optional.absent();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendMessage(String registrationId, EncryptedOutgoingMessage message)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
if (!apnService.isPresent()) {
|
||||
failure.mark();
|
||||
throw new IOException("APN access not configured!");
|
||||
}
|
||||
|
||||
String payload = APNS.newPayload()
|
||||
.alertBody("Message!")
|
||||
.customField(MESSAGE_BODY, message.serialize())
|
||||
.build();
|
||||
|
||||
logger.debug("APN Payload: " + payload);
|
||||
|
||||
apnService.get().push(registrationId, payload);
|
||||
success.mark();
|
||||
} catch (MalformedURLException mue) {
|
||||
throw new AssertionError(mue);
|
||||
} catch (NetworkIOException nioe) {
|
||||
logger.warn("Network Error", nioe);
|
||||
failure.mark();
|
||||
throw new IOException("Error sending APN");
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] initializeKeyStore(String pemCertificate, String pemKey)
|
||||
throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException
|
||||
{
|
||||
PEMReader reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(pemCertificate.getBytes())));
|
||||
X509Certificate certificate = (X509Certificate) reader.readObject();
|
||||
Certificate[] certificateChain = {certificate};
|
||||
|
||||
reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(pemKey.getBytes())));
|
||||
KeyPair keyPair = (KeyPair) reader.readObject();
|
||||
|
||||
KeyStore keyStore = KeyStore.getInstance("pkcs12");
|
||||
keyStore.load(null);
|
||||
keyStore.setEntry("apn",
|
||||
new KeyStore.PrivateKeyEntry(keyPair.getPrivate(), certificateChain),
|
||||
new KeyStore.PasswordProtection("insecure".toCharArray()));
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
keyStore.store(baos, "insecure".toCharArray());
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.push;
|
||||
|
||||
import com.google.android.gcm.server.Constants;
|
||||
import com.google.android.gcm.server.Message;
|
||||
import com.google.android.gcm.server.Result;
|
||||
import com.google.android.gcm.server.Sender;
|
||||
import com.yammer.metrics.Metrics;
|
||||
import com.yammer.metrics.core.Meter;
|
||||
import org.whispersystems.textsecuregcm.controllers.NoSuchUserException;
|
||||
import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class GCMSender {
|
||||
|
||||
private final Meter success = Metrics.newMeter(GCMSender.class, "sent", "success", TimeUnit.MINUTES);
|
||||
private final Meter failure = Metrics.newMeter(GCMSender.class, "sent", "failure", TimeUnit.MINUTES);
|
||||
|
||||
private final Sender sender;
|
||||
|
||||
public GCMSender(String apiKey) {
|
||||
this.sender = new Sender(apiKey);
|
||||
}
|
||||
|
||||
public String sendMessage(String gcmRegistrationId, EncryptedOutgoingMessage outgoingMessage)
|
||||
throws IOException, NoSuchUserException
|
||||
{
|
||||
Message gcmMessage = new Message.Builder().addData("type", "message")
|
||||
.addData("message", outgoingMessage.serialize())
|
||||
.build();
|
||||
|
||||
Result result = sender.send(gcmMessage, gcmRegistrationId, 5);
|
||||
|
||||
if (result.getMessageId() != null) {
|
||||
success.mark();
|
||||
return result.getCanonicalRegistrationId();
|
||||
} else {
|
||||
failure.mark();
|
||||
if (result.getErrorCodeName().equals(Constants.ERROR_NOT_REGISTERED)) {
|
||||
throw new NoSuchUserException("User no longer registered with GCM.");
|
||||
} else {
|
||||
throw new IOException("GCM Failed: " + result.getErrorCodeName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.push;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.GcmConfiguration;
|
||||
import org.whispersystems.textsecuregcm.controllers.NoSuchUserException;
|
||||
import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
public class PushSender {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PushSender.class);
|
||||
|
||||
private final AccountsManager accounts;
|
||||
private final DirectoryManager directory;
|
||||
|
||||
private final GCMSender gcmSender;
|
||||
private final APNSender apnSender;
|
||||
|
||||
public PushSender(GcmConfiguration gcmConfiguration,
|
||||
ApnConfiguration apnConfiguration,
|
||||
AccountsManager accounts,
|
||||
DirectoryManager directory)
|
||||
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException
|
||||
{
|
||||
this.accounts = accounts;
|
||||
this.directory = directory;
|
||||
|
||||
this.gcmSender = new GCMSender(gcmConfiguration.getApiKey());
|
||||
this.apnSender = new APNSender(apnConfiguration.getCertificate(), apnConfiguration.getKey());
|
||||
}
|
||||
|
||||
public void sendMessage(String destination, MessageProtos.OutgoingMessageSignal outgoingMessage)
|
||||
throws IOException, NoSuchUserException
|
||||
{
|
||||
Optional<Account> account = accounts.get(destination);
|
||||
|
||||
if (!account.isPresent()) {
|
||||
directory.remove(destination);
|
||||
throw new NoSuchUserException("No such local destination: " + destination);
|
||||
}
|
||||
|
||||
String signalingKey = account.get().getSignalingKey();
|
||||
EncryptedOutgoingMessage message = new EncryptedOutgoingMessage(outgoingMessage, signalingKey);
|
||||
|
||||
if (account.get().getGcmRegistrationId() != null) sendGcmMessage(account.get(), message);
|
||||
else if (account.get().getApnRegistrationId() != null) sendApnMessage(account.get(), message);
|
||||
else throw new NoSuchUserException("No push identifier!");
|
||||
}
|
||||
|
||||
private void sendGcmMessage(Account account, EncryptedOutgoingMessage outgoingMessage)
|
||||
throws IOException, NoSuchUserException
|
||||
{
|
||||
try {
|
||||
String canonicalId = gcmSender.sendMessage(account.getGcmRegistrationId(),
|
||||
outgoingMessage);
|
||||
|
||||
if (canonicalId != null) {
|
||||
account.setGcmRegistrationId(canonicalId);
|
||||
accounts.update(account);
|
||||
}
|
||||
|
||||
} catch (NoSuchUserException e) {
|
||||
logger.debug("No Such User", e);
|
||||
account.setGcmRegistrationId(null);
|
||||
accounts.update(account);
|
||||
throw new NoSuchUserException("User no longer exists in GCM.");
|
||||
}
|
||||
}
|
||||
|
||||
private void sendApnMessage(Account account, EncryptedOutgoingMessage outgoingMessage)
|
||||
throws IOException
|
||||
{
|
||||
apnSender.sendMessage(account.getApnRegistrationId(), outgoingMessage);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.sms;
|
||||
|
||||
import com.yammer.metrics.Metrics;
|
||||
import com.yammer.metrics.core.Meter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.configuration.NexmoConfiguration;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.whispersystems.textsecuregcm.sms.SenderFactory.VoxSender;
|
||||
import org.whispersystems.textsecuregcm.sms.SenderFactory.SmsSender;
|
||||
|
||||
public class NexmoSmsSender implements SmsSender, VoxSender {
|
||||
|
||||
private final Meter smsMeter = Metrics.newMeter(NexmoSmsSender.class, "sms", "delivered", TimeUnit.MINUTES);
|
||||
private final Meter voxMeter = Metrics.newMeter(NexmoSmsSender.class, "vox", "delivered", TimeUnit.MINUTES);
|
||||
private final Logger logger = LoggerFactory.getLogger(NexmoSmsSender.class);
|
||||
|
||||
private static final String NEXMO_SMS_URL =
|
||||
"https://rest.nexmo.com/sms/json?api_key=%s&api_secret=%s&from=%s&to=%s&text=%s";
|
||||
|
||||
private static final String NEXMO_VOX_URL =
|
||||
"https://rest.nexmo.com/tts/json?api_key=%s&api_secret=%s&to=%s&text=%s";
|
||||
|
||||
private final String apiKey;
|
||||
private final String apiSecret;
|
||||
private final String number;
|
||||
|
||||
public NexmoSmsSender(NexmoConfiguration config) {
|
||||
this.apiKey = config.getApiKey();
|
||||
this.apiSecret = config.getApiSecret();
|
||||
this.number = config.getNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverSmsVerification(String destination, String verificationCode) throws IOException {
|
||||
URL url = new URL(String.format(NEXMO_SMS_URL, apiKey, apiSecret, number, destination,
|
||||
URLEncoder.encode(SmsSender.VERIFICATION_TEXT + verificationCode, "UTF-8")));
|
||||
|
||||
URLConnection connection = url.openConnection();
|
||||
connection.setDoInput(true);
|
||||
connection.connect();
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
while (reader.readLine() != null) {}
|
||||
reader.close();
|
||||
smsMeter.mark();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverVoxVerification(String destination, String message) throws IOException {
|
||||
URL url = new URL(String.format(NEXMO_VOX_URL, apiKey, apiSecret, destination,
|
||||
URLEncoder.encode(VoxSender.VERIFICATION_TEXT + message, "UTF-8")));
|
||||
|
||||
URLConnection connection = url.openConnection();
|
||||
connection.setDoInput(true);
|
||||
connection.connect();
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
logger.debug(line);
|
||||
}
|
||||
reader.close();
|
||||
voxMeter.mark();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.sms;
|
||||
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import org.whispersystems.textsecuregcm.configuration.NexmoConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class SenderFactory {
|
||||
|
||||
private final TwilioSmsSender twilioSender;
|
||||
private final Optional<NexmoSmsSender> nexmoSender;
|
||||
|
||||
public SenderFactory(TwilioConfiguration twilioConfig, NexmoConfiguration nexmoConfig) {
|
||||
this.twilioSender = new TwilioSmsSender(twilioConfig);
|
||||
|
||||
if (nexmoConfig != null) {
|
||||
this.nexmoSender = Optional.of(new NexmoSmsSender(nexmoConfig));
|
||||
} else {
|
||||
this.nexmoSender = Optional.absent();
|
||||
}
|
||||
}
|
||||
|
||||
public SmsSender getSmsSender(String number) {
|
||||
if (nexmoSender.isPresent() && !isTwilioDestination(number)) {
|
||||
return nexmoSender.get();
|
||||
} else {
|
||||
return twilioSender;
|
||||
}
|
||||
}
|
||||
|
||||
public VoxSender getVoxSender(String number) {
|
||||
if (nexmoSender.isPresent()) {
|
||||
return nexmoSender.get();
|
||||
}
|
||||
|
||||
throw new AssertionError("FIX ME!");
|
||||
}
|
||||
|
||||
private boolean isTwilioDestination(String number) {
|
||||
return number.length() == 12 && number.startsWith("+1");
|
||||
}
|
||||
|
||||
public interface SmsSender {
|
||||
public static final String VERIFICATION_TEXT = "Your TextSecure verification code: ";
|
||||
public void deliverSmsVerification(String destination, String verificationCode) throws IOException;
|
||||
}
|
||||
|
||||
public interface VoxSender {
|
||||
public static final String VERIFICATION_TEXT = "Your TextSecure verification code is: ";
|
||||
public void deliverVoxVerification(String destination, String verificationCode) throws IOException;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.sms;
|
||||
|
||||
import com.sun.jersey.core.util.Base64;
|
||||
import com.yammer.metrics.Metrics;
|
||||
import com.yammer.metrics.core.Meter;
|
||||
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TwilioSmsSender implements SenderFactory.SmsSender {
|
||||
|
||||
private final Meter smsMeter = Metrics.newMeter(TwilioSmsSender.class, "sms", "delivered", TimeUnit.MINUTES);
|
||||
|
||||
private static final String TWILIO_URL = "https://api.twilio.com/2010-04-01/Accounts/%s/SMS/Messages";
|
||||
|
||||
private final String accountId;
|
||||
private final String accountToken;
|
||||
private final String number;
|
||||
|
||||
public TwilioSmsSender(TwilioConfiguration config) {
|
||||
this.accountId = config.getAccountId();
|
||||
this.accountToken = config.getAccountToken();
|
||||
this.number = config.getNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliverSmsVerification(String destination, String verificationCode) throws IOException {
|
||||
URL url = new URL(String.format(TWILIO_URL, accountId));
|
||||
URLConnection connection = url.openConnection();
|
||||
connection.setDoOutput(true);
|
||||
connection.setRequestProperty("Authorization", getTwilioAuthorizationHeader());
|
||||
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
Map<String, String> formData = new HashMap<>();
|
||||
formData.put("From", number);
|
||||
formData.put("To", destination);
|
||||
formData.put("Body", VERIFICATION_TEXT + verificationCode);
|
||||
|
||||
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
|
||||
writer.write(Util.encodeFormParams(formData));
|
||||
writer.flush();
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
while (reader.readLine() != null) {}
|
||||
writer.close();
|
||||
reader.close();
|
||||
|
||||
smsMeter.mark();
|
||||
}
|
||||
|
||||
private String getTwilioAuthorizationHeader() {
|
||||
String encoded = new String(Base64.encode(String.format("%s:%s", accountId, accountToken)));
|
||||
return "Basic " + encoded.replace("\n", "");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class Account implements Serializable {
|
||||
|
||||
public static final int MEMCACHE_VERION = 1;
|
||||
|
||||
private long id;
|
||||
private String number;
|
||||
private String hashedAuthenticationToken;
|
||||
private String salt;
|
||||
private String signalingKey;
|
||||
private String gcmRegistrationId;
|
||||
private String apnRegistrationId;
|
||||
private boolean supportsSms;
|
||||
|
||||
public Account() {}
|
||||
|
||||
public Account(long id, String number, String hashedAuthenticationToken, String salt,
|
||||
String signalingKey, String gcmRegistrationId, String apnRegistrationId,
|
||||
boolean supportsSms)
|
||||
{
|
||||
this.id = id;
|
||||
this.number = number;
|
||||
this.hashedAuthenticationToken = hashedAuthenticationToken;
|
||||
this.salt = salt;
|
||||
this.signalingKey = signalingKey;
|
||||
this.gcmRegistrationId = gcmRegistrationId;
|
||||
this.apnRegistrationId = apnRegistrationId;
|
||||
this.supportsSms = supportsSms;
|
||||
}
|
||||
|
||||
public String getApnRegistrationId() {
|
||||
return apnRegistrationId;
|
||||
}
|
||||
|
||||
public void setApnRegistrationId(String apnRegistrationId) {
|
||||
this.apnRegistrationId = apnRegistrationId;
|
||||
}
|
||||
|
||||
public String getGcmRegistrationId() {
|
||||
return gcmRegistrationId;
|
||||
}
|
||||
|
||||
public void setGcmRegistrationId(String gcmRegistrationId) {
|
||||
this.gcmRegistrationId = gcmRegistrationId;
|
||||
}
|
||||
|
||||
public void setNumber(String number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public String getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setAuthenticationCredentials(AuthenticationCredentials credentials) {
|
||||
this.hashedAuthenticationToken = credentials.getHashedAuthenticationToken();
|
||||
this.salt = credentials.getSalt();
|
||||
}
|
||||
|
||||
public AuthenticationCredentials getAuthenticationCredentials() {
|
||||
return new AuthenticationCredentials(hashedAuthenticationToken, salt);
|
||||
}
|
||||
|
||||
public String getSignalingKey() {
|
||||
return signalingKey;
|
||||
}
|
||||
|
||||
public void setSignalingKey(String signalingKey) {
|
||||
this.signalingKey = signalingKey;
|
||||
}
|
||||
|
||||
public boolean getSupportsSms() {
|
||||
return supportsSms;
|
||||
}
|
||||
|
||||
public void setSupportsSms(boolean supportsSms) {
|
||||
this.supportsSms = supportsSms;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import org.skife.jdbi.v2.SQLStatement;
|
||||
import org.skife.jdbi.v2.StatementContext;
|
||||
import org.skife.jdbi.v2.TransactionIsolationLevel;
|
||||
import org.skife.jdbi.v2.sqlobject.Bind;
|
||||
import org.skife.jdbi.v2.sqlobject.Binder;
|
||||
import org.skife.jdbi.v2.sqlobject.BinderFactory;
|
||||
import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
|
||||
import org.skife.jdbi.v2.sqlobject.GetGeneratedKeys;
|
||||
import org.skife.jdbi.v2.sqlobject.SqlQuery;
|
||||
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
|
||||
import org.skife.jdbi.v2.sqlobject.Transaction;
|
||||
import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
|
||||
import org.skife.jdbi.v2.tweak.ResultSetMapper;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class Accounts {
|
||||
|
||||
public static final String ID = "id";
|
||||
public static final String NUMBER = "number";
|
||||
public static final String AUTH_TOKEN = "auth_token";
|
||||
public static final String SALT = "salt";
|
||||
public static final String SIGNALING_KEY = "signaling_key";
|
||||
public static final String GCM_ID = "gcm_id";
|
||||
public static final String APN_ID = "apn_id";
|
||||
public static final String SUPPORTS_SMS = "supports_sms";
|
||||
|
||||
@SqlUpdate("INSERT INTO accounts (" + NUMBER + ", " + AUTH_TOKEN + ", " +
|
||||
SALT + ", " + SIGNALING_KEY + ", " + GCM_ID + ", " +
|
||||
APN_ID + ", " + SUPPORTS_SMS + ") " +
|
||||
"VALUES (:number, :auth_token, :salt, :signaling_key, :gcm_id, :apn_id, :supports_sms)")
|
||||
@GetGeneratedKeys
|
||||
abstract long createStep(@AccountBinder Account account);
|
||||
|
||||
@SqlUpdate("DELETE FROM accounts WHERE number = :number")
|
||||
abstract void removeStep(@Bind("number") String number);
|
||||
|
||||
@SqlUpdate("UPDATE accounts SET " + AUTH_TOKEN + " = :auth_token, " + SALT + " = :salt, " +
|
||||
SIGNALING_KEY + " = :signaling_key, " + GCM_ID + " = :gcm_id, " +
|
||||
APN_ID + " = :apn_id, " + SUPPORTS_SMS + " = :supports_sms " +
|
||||
"WHERE " + NUMBER + " = :number")
|
||||
abstract void update(@AccountBinder Account account);
|
||||
|
||||
@Mapper(AccountMapper.class)
|
||||
@SqlQuery("SELECT * FROM accounts WHERE " + NUMBER + " = :number")
|
||||
abstract Account get(@Bind("number") String number);
|
||||
|
||||
@SqlQuery("SELECT COUNT(*) from accounts")
|
||||
abstract long getCount();
|
||||
|
||||
@Mapper(AccountMapper.class)
|
||||
@SqlQuery("SELECT * FROM accounts OFFSET :offset LIMIT :limit")
|
||||
abstract List<Account> getAll(@Bind("offset") int offset, @Bind("limit") int length);
|
||||
|
||||
@Mapper(AccountMapper.class)
|
||||
@SqlQuery("SELECT * FROM accounts")
|
||||
abstract Iterator<Account> getAll();
|
||||
|
||||
@Transaction(TransactionIsolationLevel.REPEATABLE_READ)
|
||||
public long create(Account account) {
|
||||
removeStep(account.getNumber());
|
||||
return createStep(account);
|
||||
}
|
||||
|
||||
public static class AccountMapper implements ResultSetMapper<Account> {
|
||||
|
||||
@Override
|
||||
public Account map(int i, ResultSet resultSet, StatementContext statementContext)
|
||||
throws SQLException
|
||||
{
|
||||
return new Account(resultSet.getLong(ID), resultSet.getString(NUMBER),
|
||||
resultSet.getString(AUTH_TOKEN), resultSet.getString(SALT),
|
||||
resultSet.getString(SIGNALING_KEY), resultSet.getString(GCM_ID),
|
||||
resultSet.getString(APN_ID),
|
||||
resultSet.getInt(SUPPORTS_SMS) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAnnotation(AccountBinder.AccountBinderFactory.class)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
public @interface AccountBinder {
|
||||
public static class AccountBinderFactory implements BinderFactory {
|
||||
@Override
|
||||
public Binder build(Annotation annotation) {
|
||||
return new Binder<AccountBinder, Account>() {
|
||||
@Override
|
||||
public void bind(SQLStatement<?> sql,
|
||||
AccountBinder accountBinder,
|
||||
Account account)
|
||||
{
|
||||
sql.bind(ID, account.getId());
|
||||
sql.bind(NUMBER, account.getNumber());
|
||||
sql.bind(AUTH_TOKEN, account.getAuthenticationCredentials()
|
||||
.getHashedAuthenticationToken());
|
||||
sql.bind(SALT, account.getAuthenticationCredentials().getSalt());
|
||||
sql.bind(SIGNALING_KEY, account.getSignalingKey());
|
||||
sql.bind(GCM_ID, account.getGcmRegistrationId());
|
||||
sql.bind(APN_ID, account.getApnRegistrationId());
|
||||
sql.bind(SUPPORTS_SMS, account.getSupportsSms() ? 1 : 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import net.spy.memcached.MemcachedClient;
|
||||
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class AccountsManager {
|
||||
|
||||
private final Accounts accounts;
|
||||
private final MemcachedClient memcachedClient;
|
||||
private final DirectoryManager directory;
|
||||
|
||||
public AccountsManager(Accounts accounts,
|
||||
DirectoryManager directory,
|
||||
MemcachedClient memcachedClient)
|
||||
{
|
||||
this.accounts = accounts;
|
||||
this.directory = directory;
|
||||
this.memcachedClient = memcachedClient;
|
||||
}
|
||||
|
||||
public long getCount() {
|
||||
return accounts.getCount();
|
||||
}
|
||||
|
||||
public List<Account> getAll(int offset, int length) {
|
||||
return accounts.getAll(offset, length);
|
||||
}
|
||||
|
||||
public Iterator<Account> getAll() {
|
||||
return accounts.getAll();
|
||||
}
|
||||
|
||||
public void create(Account account) {
|
||||
long id = accounts.create(account);
|
||||
|
||||
account.setId(id);
|
||||
|
||||
if (memcachedClient != null) {
|
||||
memcachedClient.set(getKey(account.getNumber()), 0, account);
|
||||
}
|
||||
|
||||
updateDirectory(account);
|
||||
}
|
||||
|
||||
public void update(Account account) {
|
||||
if (memcachedClient != null) {
|
||||
memcachedClient.set(getKey(account.getNumber()), 0, account);
|
||||
}
|
||||
|
||||
accounts.update(account);
|
||||
updateDirectory(account);
|
||||
}
|
||||
|
||||
public Optional<Account> get(String number) {
|
||||
Account account = null;
|
||||
|
||||
if (memcachedClient != null) {
|
||||
account = (Account)memcachedClient.get(getKey(number));
|
||||
}
|
||||
|
||||
if (account == null) {
|
||||
account = accounts.get(number);
|
||||
|
||||
if (account != null && memcachedClient != null) {
|
||||
memcachedClient.set(getKey(number), 0, account);
|
||||
}
|
||||
}
|
||||
|
||||
if (account != null) return Optional.of(account);
|
||||
else return Optional.absent();
|
||||
}
|
||||
|
||||
private void updateDirectory(Account account) {
|
||||
if (account.getGcmRegistrationId() != null || account.getApnRegistrationId() != null) {
|
||||
byte[] token = Util.getContactToken(account.getNumber());
|
||||
ClientContact clientContact = new ClientContact(token, null, account.getSupportsSms());
|
||||
directory.add(clientContact);
|
||||
} else {
|
||||
directory.remove(account.getNumber());
|
||||
}
|
||||
}
|
||||
|
||||
private String getKey(String number) {
|
||||
return Account.class.getSimpleName() + Account.MEMCACHE_VERION + number;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
||||
import org.whispersystems.textsecuregcm.util.IterablePair;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.Pipeline;
|
||||
import redis.clients.jedis.Response;
|
||||
|
||||
public class DirectoryManager {
|
||||
|
||||
private static final byte[] DIRECTORY_KEY = {'d', 'i', 'r', 'e', 'c', 't', 'o', 'r', 'y'};
|
||||
|
||||
private final JedisPool redisPool;
|
||||
|
||||
public DirectoryManager(JedisPool redisPool) {
|
||||
this.redisPool = redisPool;
|
||||
}
|
||||
|
||||
public void remove(String number) {
|
||||
remove(Util.getContactToken(number));
|
||||
}
|
||||
|
||||
public void remove(BatchOperationHandle handle, String number) {
|
||||
remove(handle, Util.getContactToken(number));
|
||||
}
|
||||
|
||||
public void remove(byte[] token) {
|
||||
Jedis jedis = redisPool.getResource();
|
||||
jedis.hdel(DIRECTORY_KEY, token);
|
||||
redisPool.returnResource(jedis);
|
||||
}
|
||||
|
||||
public void remove(BatchOperationHandle handle, byte[] token) {
|
||||
Pipeline pipeline = handle.pipeline;
|
||||
pipeline.hdel(DIRECTORY_KEY, token);
|
||||
}
|
||||
|
||||
public void add(ClientContact contact) {
|
||||
TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isSupportsSms());
|
||||
Jedis jedis = redisPool.getResource();
|
||||
|
||||
jedis.hset(DIRECTORY_KEY, contact.getToken(), new Gson().toJson(tokenValue).getBytes());
|
||||
redisPool.returnResource(jedis);
|
||||
}
|
||||
|
||||
public void add(BatchOperationHandle handle, ClientContact contact) {
|
||||
Pipeline pipeline = handle.pipeline;
|
||||
TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isSupportsSms());
|
||||
|
||||
pipeline.hset(DIRECTORY_KEY, contact.getToken(), new Gson().toJson(tokenValue).getBytes());
|
||||
}
|
||||
|
||||
public Optional<ClientContact> get(byte[] token) {
|
||||
Jedis jedis = redisPool.getResource();
|
||||
|
||||
try {
|
||||
byte[] result = jedis.hget(DIRECTORY_KEY, token);
|
||||
|
||||
if (result == null) {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
TokenValue tokenValue = new Gson().fromJson(new String(result), TokenValue.class);
|
||||
return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.supportsSms));
|
||||
} finally {
|
||||
redisPool.returnResource(jedis);
|
||||
}
|
||||
}
|
||||
|
||||
public List<ClientContact> get(List<byte[]> tokens) {
|
||||
Jedis jedis = redisPool.getResource();
|
||||
|
||||
try {
|
||||
Pipeline pipeline = jedis.pipelined();
|
||||
List<Response<byte[]>> futures = new LinkedList<>();
|
||||
List<ClientContact> results = new LinkedList<>();
|
||||
|
||||
try {
|
||||
for (byte[] token : tokens) {
|
||||
futures.add(pipeline.hget(DIRECTORY_KEY, token));
|
||||
}
|
||||
} finally {
|
||||
pipeline.sync();
|
||||
}
|
||||
|
||||
IterablePair<byte[], Response<byte[]>> lists = new IterablePair<>(tokens, futures);
|
||||
|
||||
for (IterablePair.Pair<byte[], Response<byte[]>> pair : lists) {
|
||||
if (pair.second().get() != null) {
|
||||
TokenValue tokenValue = new Gson().fromJson(new String(pair.second().get()), TokenValue.class);
|
||||
ClientContact clientContact = new ClientContact(pair.first(), tokenValue.relay, tokenValue.supportsSms);
|
||||
|
||||
results.add(clientContact);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
} finally {
|
||||
redisPool.returnResource(jedis);
|
||||
}
|
||||
}
|
||||
|
||||
public BatchOperationHandle startBatchOperation() {
|
||||
Jedis jedis = redisPool.getResource();
|
||||
return new BatchOperationHandle(jedis, jedis.pipelined());
|
||||
}
|
||||
|
||||
public void stopBatchOperation(BatchOperationHandle handle) {
|
||||
Pipeline pipeline = handle.pipeline;
|
||||
Jedis jedis = handle.jedis;
|
||||
|
||||
pipeline.sync();
|
||||
redisPool.returnResource(jedis);
|
||||
}
|
||||
|
||||
public static class BatchOperationHandle {
|
||||
|
||||
public final Pipeline pipeline;
|
||||
public final Jedis jedis;
|
||||
|
||||
public BatchOperationHandle(Jedis jedis, Pipeline pipeline) {
|
||||
this.pipeline = pipeline;
|
||||
this.jedis = jedis;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TokenValue {
|
||||
@SerializedName("r")
|
||||
private String relay;
|
||||
|
||||
@SerializedName("s")
|
||||
private boolean supportsSms;
|
||||
|
||||
public TokenValue(String relay, boolean supportsSms) {
|
||||
this.relay = relay;
|
||||
this.supportsSms = supportsSms;
|
||||
}
|
||||
}
|
||||
}
|
||||
121
src/main/java/org/whispersystems/textsecuregcm/storage/Keys.java
Normal file
121
src/main/java/org/whispersystems/textsecuregcm/storage/Keys.java
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import org.skife.jdbi.v2.SQLStatement;
|
||||
import org.skife.jdbi.v2.StatementContext;
|
||||
import org.skife.jdbi.v2.TransactionIsolationLevel;
|
||||
import org.skife.jdbi.v2.sqlobject.Bind;
|
||||
import org.skife.jdbi.v2.sqlobject.Binder;
|
||||
import org.skife.jdbi.v2.sqlobject.BinderFactory;
|
||||
import org.skife.jdbi.v2.sqlobject.BindingAnnotation;
|
||||
import org.skife.jdbi.v2.sqlobject.SqlBatch;
|
||||
import org.skife.jdbi.v2.sqlobject.SqlQuery;
|
||||
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
|
||||
import org.skife.jdbi.v2.sqlobject.Transaction;
|
||||
import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
|
||||
import org.skife.jdbi.v2.tweak.ResultSetMapper;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class Keys {
|
||||
|
||||
@SqlUpdate("DELETE FROM keys WHERE number = :number")
|
||||
abstract void removeKeys(@Bind("number") String number);
|
||||
|
||||
@SqlUpdate("DELETE FROM keys WHERE id = :id")
|
||||
abstract void removeKey(@Bind("id") long id);
|
||||
|
||||
@SqlBatch("INSERT INTO keys (number, key_id, public_key, identity_key, last_resort) VALUES (:number, :key_id, :public_key, :identity_key, :last_resort)")
|
||||
abstract void append(@PreKeyBinder List<PreKey> preKeys);
|
||||
|
||||
@SqlUpdate("INSERT INTO keys (number, key_id, public_key, identity_key, last_resort) VALUES (:number, :key_id, :public_key, :identity_key, :last_resort)")
|
||||
abstract void append(@PreKeyBinder PreKey preKey);
|
||||
|
||||
@SqlQuery("SELECT * FROM keys WHERE number = :number ORDER BY id LIMIT 1 FOR UPDATE")
|
||||
@Mapper(PreKeyMapper.class)
|
||||
abstract PreKey retrieveFirst(@Bind("number") String number);
|
||||
|
||||
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
|
||||
public void store(String number, PreKey lastResortKey, List<PreKey> keys) {
|
||||
for (PreKey key : keys) {
|
||||
key.setNumber(number);
|
||||
}
|
||||
|
||||
lastResortKey.setNumber(number);
|
||||
|
||||
removeKeys(number);
|
||||
append(keys);
|
||||
append(lastResortKey);
|
||||
}
|
||||
|
||||
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
|
||||
public PreKey get(String number) {
|
||||
PreKey preKey = retrieveFirst(number);
|
||||
|
||||
if (preKey != null && !preKey.isLastResort()) {
|
||||
removeKey(preKey.getId());
|
||||
}
|
||||
|
||||
return preKey;
|
||||
}
|
||||
|
||||
@BindingAnnotation(PreKeyBinder.PreKeyBinderFactory.class)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
public @interface PreKeyBinder {
|
||||
public static class PreKeyBinderFactory implements BinderFactory {
|
||||
@Override
|
||||
public Binder build(Annotation annotation) {
|
||||
return new Binder<PreKeyBinder, PreKey>() {
|
||||
@Override
|
||||
public void bind(SQLStatement<?> sql, PreKeyBinder accountBinder, PreKey preKey)
|
||||
{
|
||||
sql.bind("id", preKey.getId());
|
||||
sql.bind("number", preKey.getNumber());
|
||||
sql.bind("key_id", preKey.getKeyId());
|
||||
sql.bind("public_key", preKey.getPublicKey());
|
||||
sql.bind("identity_key", preKey.getIdentityKey());
|
||||
sql.bind("last_resort", preKey.isLastResort() ? 1 : 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class PreKeyMapper implements ResultSetMapper<PreKey> {
|
||||
@Override
|
||||
public PreKey map(int i, ResultSet resultSet, StatementContext statementContext)
|
||||
throws SQLException
|
||||
{
|
||||
return new PreKey(resultSet.getLong("id"), resultSet.getString("number"),
|
||||
resultSet.getLong("key_id"), resultSet.getString("public_key"),
|
||||
resultSet.getString("identity_key"),
|
||||
resultSet.getInt("last_resort") == 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import org.skife.jdbi.v2.sqlobject.Bind;
|
||||
import org.skife.jdbi.v2.sqlobject.SqlQuery;
|
||||
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
|
||||
|
||||
public interface PendingAccounts {
|
||||
|
||||
@SqlUpdate("WITH upsert AS (UPDATE pending_accounts SET verification_code = :verification_code WHERE number = :number RETURNING *) " +
|
||||
"INSERT INTO pending_accounts (number, verification_code) SELECT :number, :verification_code WHERE NOT EXISTS (SELECT * FROM upsert)")
|
||||
void insert(@Bind("number") String number, @Bind("verification_code") String verificationCode);
|
||||
|
||||
@SqlQuery("SELECT verification_code FROM pending_accounts WHERE number = :number")
|
||||
String getCodeForNumber(@Bind("number") String number);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import net.spy.memcached.MemcachedClient;
|
||||
|
||||
public class PendingAccountsManager {
|
||||
|
||||
private static final String MEMCACHE_PREFIX = "pending_account";
|
||||
|
||||
private final PendingAccounts pendingAccounts;
|
||||
private final MemcachedClient memcachedClient;
|
||||
|
||||
public PendingAccountsManager(PendingAccounts pendingAccounts,
|
||||
MemcachedClient memcachedClient)
|
||||
{
|
||||
this.pendingAccounts = pendingAccounts;
|
||||
this.memcachedClient = memcachedClient;
|
||||
}
|
||||
|
||||
public void store(String number, String code) {
|
||||
if (memcachedClient != null) {
|
||||
memcachedClient.set(MEMCACHE_PREFIX + number, 0, code);
|
||||
}
|
||||
|
||||
pendingAccounts.insert(number, code);
|
||||
}
|
||||
|
||||
public Optional<String> getCodeForNumber(String number) {
|
||||
String code = null;
|
||||
|
||||
if (memcachedClient != null) {
|
||||
code = (String)memcachedClient.get(MEMCACHE_PREFIX + number);
|
||||
}
|
||||
|
||||
if (code == null) {
|
||||
code = pendingAccounts.getCodeForNumber(number);
|
||||
|
||||
if (code != null && memcachedClient != null) {
|
||||
memcachedClient.set(MEMCACHE_PREFIX + number, 0, code);
|
||||
}
|
||||
}
|
||||
|
||||
if (code != null) return Optional.of(code);
|
||||
else return Optional.absent();
|
||||
}
|
||||
}
|
||||
2096
src/main/java/org/whispersystems/textsecuregcm/util/Base64.java
Normal file
2096
src/main/java/org/whispersystems/textsecuregcm/util/Base64.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.util;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ByteArrayAdapter {
|
||||
|
||||
public static class Serializing extends JsonSerializer<byte[]> {
|
||||
@Override
|
||||
public void serialize(byte[] bytes, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
|
||||
throws IOException, JsonProcessingException
|
||||
{
|
||||
jsonGenerator.writeString(Base64.encodeBytesWithoutPadding(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
public static class Deserializing extends JsonDeserializer<byte[]> {
|
||||
@Override
|
||||
public byte[] deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
|
||||
throws IOException, JsonProcessingException
|
||||
{
|
||||
return Base64.decodeWithoutPadding(jsonParser.getValueAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.util;
|
||||
|
||||
public class Conversions {
|
||||
|
||||
public static byte intsToByteHighAndLow(int highValue, int lowValue) {
|
||||
return (byte)((highValue << 4 | lowValue) & 0xFF);
|
||||
}
|
||||
|
||||
public static int highBitsToInt(byte value) {
|
||||
return (value & 0xFF) >> 4;
|
||||
}
|
||||
|
||||
public static int lowBitsToInt(byte value) {
|
||||
return (value & 0xF);
|
||||
}
|
||||
|
||||
public static int highBitsToMedium(int value) {
|
||||
return (value >> 12);
|
||||
}
|
||||
|
||||
public static int lowBitsToMedium(int value) {
|
||||
return (value & 0xFFF);
|
||||
}
|
||||
|
||||
public static byte[] shortToByteArray(int value) {
|
||||
byte[] bytes = new byte[2];
|
||||
shortToByteArray(bytes, 0, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int shortToByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset+1] = (byte)value;
|
||||
bytes[offset] = (byte)(value >> 8);
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static int shortToLittleEndianByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset] = (byte)value;
|
||||
bytes[offset+1] = (byte)(value >> 8);
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static byte[] mediumToByteArray(int value) {
|
||||
byte[] bytes = new byte[3];
|
||||
mediumToByteArray(bytes, 0, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int mediumToByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset + 2] = (byte)value;
|
||||
bytes[offset + 1] = (byte)(value >> 8);
|
||||
bytes[offset] = (byte)(value >> 16);
|
||||
return 3;
|
||||
}
|
||||
|
||||
public static byte[] intToByteArray(int value) {
|
||||
byte[] bytes = new byte[4];
|
||||
intToByteArray(bytes, 0, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int intToByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset + 3] = (byte)value;
|
||||
bytes[offset + 2] = (byte)(value >> 8);
|
||||
bytes[offset + 1] = (byte)(value >> 16);
|
||||
bytes[offset] = (byte)(value >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static int intToLittleEndianByteArray(byte[] bytes, int offset, int value) {
|
||||
bytes[offset] = (byte)value;
|
||||
bytes[offset+1] = (byte)(value >> 8);
|
||||
bytes[offset+2] = (byte)(value >> 16);
|
||||
bytes[offset+3] = (byte)(value >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static byte[] longToByteArray(long l) {
|
||||
byte[] bytes = new byte[8];
|
||||
longToByteArray(bytes, 0, l);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static int longToByteArray(byte[] bytes, int offset, long value) {
|
||||
bytes[offset + 7] = (byte)value;
|
||||
bytes[offset + 6] = (byte)(value >> 8);
|
||||
bytes[offset + 5] = (byte)(value >> 16);
|
||||
bytes[offset + 4] = (byte)(value >> 24);
|
||||
bytes[offset + 3] = (byte)(value >> 32);
|
||||
bytes[offset + 2] = (byte)(value >> 40);
|
||||
bytes[offset + 1] = (byte)(value >> 48);
|
||||
bytes[offset] = (byte)(value >> 56);
|
||||
return 8;
|
||||
}
|
||||
|
||||
public static int longTo4ByteArray(byte[] bytes, int offset, long value) {
|
||||
bytes[offset + 3] = (byte)value;
|
||||
bytes[offset + 2] = (byte)(value >> 8);
|
||||
bytes[offset + 1] = (byte)(value >> 16);
|
||||
bytes[offset + 0] = (byte)(value >> 24);
|
||||
return 4;
|
||||
}
|
||||
|
||||
public static int byteArrayToShort(byte[] bytes) {
|
||||
return byteArrayToShort(bytes, 0);
|
||||
}
|
||||
|
||||
public static int byteArrayToShort(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset] & 0xff) << 8 | (bytes[offset + 1] & 0xff);
|
||||
}
|
||||
|
||||
// The SSL patented 3-byte Value.
|
||||
public static int byteArrayToMedium(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset] & 0xff) << 16 |
|
||||
(bytes[offset + 1] & 0xff) << 8 |
|
||||
(bytes[offset + 2] & 0xff);
|
||||
}
|
||||
|
||||
public static int byteArrayToInt(byte[] bytes) {
|
||||
return byteArrayToInt(bytes, 0);
|
||||
}
|
||||
|
||||
public static int byteArrayToInt(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset] & 0xff) << 24 |
|
||||
(bytes[offset + 1] & 0xff) << 16 |
|
||||
(bytes[offset + 2] & 0xff) << 8 |
|
||||
(bytes[offset + 3] & 0xff);
|
||||
}
|
||||
|
||||
public static int byteArrayToIntLittleEndian(byte[] bytes, int offset) {
|
||||
return
|
||||
(bytes[offset + 3] & 0xff) << 24 |
|
||||
(bytes[offset + 2] & 0xff) << 16 |
|
||||
(bytes[offset + 1] & 0xff) << 8 |
|
||||
(bytes[offset] & 0xff);
|
||||
}
|
||||
|
||||
public static long byteArrayToLong(byte[] bytes) {
|
||||
return byteArrayToLong(bytes, 0);
|
||||
}
|
||||
|
||||
public static long byteArray4ToLong(byte[] bytes, int offset) {
|
||||
return
|
||||
((bytes[offset + 0] & 0xffL) << 24) |
|
||||
((bytes[offset + 1] & 0xffL) << 16) |
|
||||
((bytes[offset + 2] & 0xffL) << 8) |
|
||||
((bytes[offset + 3] & 0xffL));
|
||||
}
|
||||
|
||||
public static long byteArrayToLong(byte[] bytes, int offset) {
|
||||
return
|
||||
((bytes[offset] & 0xffL) << 56) |
|
||||
((bytes[offset + 1] & 0xffL) << 48) |
|
||||
((bytes[offset + 2] & 0xffL) << 40) |
|
||||
((bytes[offset + 3] & 0xffL) << 32) |
|
||||
((bytes[offset + 4] & 0xffL) << 24) |
|
||||
((bytes[offset + 5] & 0xffL) << 16) |
|
||||
((bytes[offset + 6] & 0xffL) << 8) |
|
||||
((bytes[offset + 7] & 0xffL));
|
||||
}
|
||||
}
|
||||
109
src/main/java/org/whispersystems/textsecuregcm/util/Hex.java
Normal file
109
src/main/java/org/whispersystems/textsecuregcm/util/Hex.java
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.util;
|
||||
|
||||
/**
|
||||
* Utility for generating hex dumps a la hexl-mode in emacs.
|
||||
*/
|
||||
public class Hex {
|
||||
|
||||
private final static int HEX_DIGITS_START = 10;
|
||||
private final static int ASCII_TEXT_START = HEX_DIGITS_START + (16*2 + (16/2));
|
||||
|
||||
final static String EOL = System.getProperty("line.separator");
|
||||
|
||||
private final static char[] HEX_DIGITS = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
|
||||
};
|
||||
|
||||
public static String toString(byte[] bytes) {
|
||||
return toString(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
public static String toString(byte[] bytes, int offset, int length) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i = 0; i < length; i++) {
|
||||
appendHexChar(buf, bytes[offset + i]);
|
||||
buf.append(' ');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static String dump(byte[] bytes) {
|
||||
return dump(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
public static String dump(byte[] bytes, int offset, int length) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
int lines = ((length - 1) / 16) + 1;
|
||||
int lineOffset;
|
||||
int lineLength;
|
||||
|
||||
for (int i = 0; i < lines; i++) {
|
||||
lineOffset = (i * 16) + offset;
|
||||
lineLength = Math.min(16, (length - (i * 16)));
|
||||
appendDumpLine(buf, i, bytes, lineOffset, lineLength);
|
||||
buf.append(EOL);
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static void appendDumpLine(StringBuffer buf, int line,
|
||||
byte[] bytes, int lineOffset,
|
||||
int lineLength)
|
||||
{
|
||||
buf.append(HEX_DIGITS[(line >> 28) & 0xf]);
|
||||
buf.append(HEX_DIGITS[(line >> 24) & 0xf]);
|
||||
buf.append(HEX_DIGITS[(line >> 20) & 0xf]);
|
||||
buf.append(HEX_DIGITS[(line >> 16) & 0xf]);
|
||||
buf.append(HEX_DIGITS[(line >> 12) & 0xf]);
|
||||
buf.append(HEX_DIGITS[(line >> 8) & 0xf]);
|
||||
buf.append(HEX_DIGITS[(line >> 4) & 0xf]);
|
||||
buf.append(HEX_DIGITS[(line ) & 0xf]);
|
||||
buf.append(": ");
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int idx = i + lineOffset;
|
||||
if (i < lineLength) {
|
||||
int b = bytes[idx];
|
||||
appendHexChar(buf, b);
|
||||
} else {
|
||||
buf.append(" ");
|
||||
}
|
||||
if ((i % 2) == 1) {
|
||||
buf.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16 && i < lineLength; i++) {
|
||||
int idx = i + lineOffset;
|
||||
int b = bytes[idx];
|
||||
if (b >= 0x20 && b <= 0x7e) {
|
||||
buf.append((char)b);
|
||||
} else {
|
||||
buf.append('.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendHexChar(StringBuffer buf, int b) {
|
||||
buf.append(HEX_DIGITS[(b >> 4) & 0xf]);
|
||||
buf.append(HEX_DIGITS[b & 0xf]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.util;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class IterablePair <T1, T2> implements Iterable<IterablePair.Pair<T1,T2>> {
|
||||
private final List<T1> first;
|
||||
private final List<T2> second;
|
||||
|
||||
public IterablePair(List<T1> first, List<T2> second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Pair<T1, T2>> iterator(){
|
||||
return new ParallelIterator<>( first.iterator(), second.iterator() );
|
||||
}
|
||||
|
||||
public static class Pair<T1, T2> {
|
||||
private final T1 v1;
|
||||
private final T2 v2;
|
||||
|
||||
Pair(T1 v1, T2 v2) {
|
||||
this.v1 = v1;
|
||||
this.v2 = v2;
|
||||
}
|
||||
|
||||
public T1 first(){
|
||||
return v1;
|
||||
}
|
||||
|
||||
public T2 second(){
|
||||
return v2;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ParallelIterator <T1, T2> implements Iterator<Pair<T1, T2>> {
|
||||
|
||||
private final Iterator<T1> it1;
|
||||
private final Iterator<T2> it2;
|
||||
|
||||
public ParallelIterator(Iterator<T1> it1, Iterator<T2> it2) {
|
||||
this.it1 = it1; this.it2 = it2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() { return it1.hasNext() && it2.hasNext(); }
|
||||
|
||||
@Override
|
||||
public Pair<T1, T2> next() {
|
||||
return new Pair<>(it1.next(), it2.next());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(){
|
||||
it1.remove();
|
||||
it2.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.util;
|
||||
|
||||
import com.amazonaws.HttpMethod;
|
||||
import com.amazonaws.auth.AWSCredentials;
|
||||
import com.amazonaws.auth.BasicAWSCredentials;
|
||||
import com.amazonaws.services.s3.AmazonS3;
|
||||
import com.amazonaws.services.s3.AmazonS3Client;
|
||||
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
|
||||
import org.whispersystems.textsecuregcm.configuration.S3Configuration;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
|
||||
public class UrlSigner {
|
||||
|
||||
private static final long DURATION = 60 * 60 * 1000;
|
||||
|
||||
private final AWSCredentials credentials;
|
||||
private final String bucket;
|
||||
|
||||
public UrlSigner(S3Configuration config) {
|
||||
this.credentials = new BasicAWSCredentials(config.getAccessKey(), config.getAccessSecret());
|
||||
this.bucket = config.getAttachmentsBucket();
|
||||
}
|
||||
|
||||
public URL getPreSignedUrl(long attachmentId, HttpMethod method) {
|
||||
AmazonS3 client = new AmazonS3Client(credentials);
|
||||
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucket, String.valueOf(attachmentId), method);
|
||||
|
||||
request.setExpiration(new Date(System.currentTimeMillis() + DURATION));
|
||||
request.setContentType("application/octet-stream");
|
||||
|
||||
return client.generatePresignedUrl(request);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.util;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Map;
|
||||
|
||||
public class Util {
|
||||
|
||||
public static byte[] getContactToken(String number) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA1");
|
||||
byte[] result = digest.digest(number.getBytes());
|
||||
byte[] truncated = Util.truncate(result, 10);
|
||||
|
||||
return truncated;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getEncodedContactToken(String number) {
|
||||
return Base64.encodeBytesWithoutPadding(getContactToken(number));
|
||||
}
|
||||
|
||||
public static boolean isValidNumber(String number) {
|
||||
return number.matches("^\\+[0-9]{10,}");
|
||||
}
|
||||
|
||||
public static String encodeFormParams(Map<String, String> params) {
|
||||
try {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
|
||||
for (String key : params.keySet()) {
|
||||
buffer.append(String.format("%s=%s",
|
||||
URLEncoder.encode(key, "UTF-8"),
|
||||
URLEncoder.encode(params.get(key), "UTF-8")));
|
||||
buffer.append("&");
|
||||
}
|
||||
|
||||
buffer.deleteCharAt(buffer.length()-1);
|
||||
return buffer.toString();
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isEmpty(String param) {
|
||||
return param == null || param.length() == 0;
|
||||
}
|
||||
|
||||
public static byte[] combine(byte[] one, byte[] two, byte[] three, byte[] four) {
|
||||
byte[] combined = new byte[one.length + two.length + three.length + four.length];
|
||||
System.arraycopy(one, 0, combined, 0, one.length);
|
||||
System.arraycopy(two, 0, combined, one.length, two.length);
|
||||
System.arraycopy(three, 0, combined, one.length + two.length, three.length);
|
||||
System.arraycopy(four, 0, combined, one.length + two.length + three.length, four.length);
|
||||
|
||||
return combined;
|
||||
}
|
||||
|
||||
public static byte[] truncate(byte[] element, int length) {
|
||||
byte[] result = new byte[length];
|
||||
System.arraycopy(element, 0, result, 0, result.length);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.util;
|
||||
|
||||
public class VerificationCode {
|
||||
|
||||
private String verificationCode;
|
||||
private String verificationCodeDisplay;
|
||||
private String verificationCodeSpeech;
|
||||
|
||||
public VerificationCode(int verificationCode) {
|
||||
this.verificationCode = verificationCode + "";
|
||||
this.verificationCodeDisplay = this.verificationCode.substring(0, 3) + "-" +
|
||||
this.verificationCode.substring(3, 6);
|
||||
this.verificationCodeSpeech = delimit(verificationCode + "");
|
||||
}
|
||||
|
||||
public String getVerificationCode() {
|
||||
return verificationCode;
|
||||
}
|
||||
|
||||
public String getVerificationCodeDisplay() {
|
||||
return verificationCodeDisplay;
|
||||
}
|
||||
|
||||
public String getVerificationCodeSpeech() {
|
||||
return verificationCodeSpeech;
|
||||
}
|
||||
|
||||
private String delimit(String code) {
|
||||
String delimited = "";
|
||||
|
||||
for (int i=0;i<code.length();i++) {
|
||||
delimited += code.charAt(i);
|
||||
|
||||
if (i != code.length() - 1)
|
||||
delimited += ',';
|
||||
}
|
||||
|
||||
return delimited;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.workers;
|
||||
|
||||
import com.yammer.dropwizard.cli.ConfiguredCommand;
|
||||
import com.yammer.dropwizard.config.Bootstrap;
|
||||
import com.yammer.dropwizard.db.DatabaseConfiguration;
|
||||
import com.yammer.dropwizard.jdbi.ImmutableListContainerFactory;
|
||||
import com.yammer.dropwizard.jdbi.ImmutableSetContainerFactory;
|
||||
import com.yammer.dropwizard.jdbi.OptionalContainerFactory;
|
||||
import com.yammer.dropwizard.jdbi.args.OptionalArgumentFactory;
|
||||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.spy.memcached.MemcachedClient;
|
||||
import org.skife.jdbi.v2.DBI;
|
||||
import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||
import org.whispersystems.textsecuregcm.providers.MemcachedClientFactory;
|
||||
import org.whispersystems.textsecuregcm.providers.RedisClientFactory;
|
||||
import org.whispersystems.textsecuregcm.storage.Accounts;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryManager;
|
||||
|
||||
import redis.clients.jedis.JedisPool;
|
||||
|
||||
public class DirectoryCommand extends ConfiguredCommand<WhisperServerConfiguration> {
|
||||
|
||||
public DirectoryCommand() {
|
||||
super("directory", "Update directory from DB and peers.");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Bootstrap<WhisperServerConfiguration> bootstrap,
|
||||
Namespace namespace,
|
||||
WhisperServerConfiguration config)
|
||||
throws Exception
|
||||
{
|
||||
try {
|
||||
DatabaseConfiguration dbConfig = config.getDatabaseConfiguration();
|
||||
DBI dbi = new DBI(dbConfig.getUrl(), dbConfig.getUser(), dbConfig.getPassword());
|
||||
|
||||
dbi.registerArgumentFactory(new OptionalArgumentFactory(dbConfig.getDriverClass()));
|
||||
dbi.registerContainerFactory(new ImmutableListContainerFactory());
|
||||
dbi.registerContainerFactory(new ImmutableSetContainerFactory());
|
||||
dbi.registerContainerFactory(new OptionalContainerFactory());
|
||||
|
||||
Accounts accounts = dbi.onDemand(Accounts.class);
|
||||
MemcachedClient memcachedClient = new MemcachedClientFactory(config.getMemcacheConfiguration()).getClient();
|
||||
JedisPool redisClient = new RedisClientFactory(config.getRedisConfiguration()).getRedisClientPool();
|
||||
DirectoryManager directory = new DirectoryManager(redisClient);
|
||||
AccountsManager accountsManager = new AccountsManager(accounts, directory, memcachedClient);
|
||||
FederatedClientManager federatedClientManager = new FederatedClientManager(config.getFederationConfiguration());
|
||||
|
||||
DirectoryUpdater update = new DirectoryUpdater(accountsManager, federatedClientManager, directory);
|
||||
|
||||
update.updateFromLocalDatabase();
|
||||
update.updateFromPeers();
|
||||
} finally {
|
||||
Thread.sleep(3000);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.workers;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedClient;
|
||||
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryManager;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryManager.BatchOperationHandle;
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class DirectoryUpdater {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DirectoryUpdater.class);
|
||||
|
||||
private final AccountsManager accountsManager;
|
||||
private final FederatedClientManager federatedClientManager;
|
||||
private final DirectoryManager directory;
|
||||
|
||||
public DirectoryUpdater(AccountsManager accountsManager,
|
||||
FederatedClientManager federatedClientManager,
|
||||
DirectoryManager directory)
|
||||
{
|
||||
this.accountsManager = accountsManager;
|
||||
this.federatedClientManager = federatedClientManager;
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
public void updateFromLocalDatabase() {
|
||||
BatchOperationHandle batchOperation = directory.startBatchOperation();
|
||||
|
||||
try {
|
||||
Iterator<Account> accounts = accountsManager.getAll();
|
||||
|
||||
if (accounts == null)
|
||||
return;
|
||||
|
||||
while (accounts.hasNext()) {
|
||||
Account account = accounts.next();
|
||||
if (account.getApnRegistrationId() != null || account.getGcmRegistrationId() != null) {
|
||||
byte[] token = Util.getContactToken(account.getNumber());
|
||||
ClientContact clientContact = new ClientContact(token, null, account.getSupportsSms());
|
||||
|
||||
directory.add(batchOperation, clientContact);
|
||||
|
||||
logger.debug("Adding local token: " + Base64.encodeBytesWithoutPadding(token));
|
||||
} else {
|
||||
directory.remove(batchOperation, account.getNumber());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
directory.stopBatchOperation(batchOperation);
|
||||
}
|
||||
|
||||
logger.info("Local directory is updated.");
|
||||
}
|
||||
|
||||
public void updateFromPeers() {
|
||||
logger.info("Updating peer directories.");
|
||||
List<FederatedClient> clients = federatedClientManager.getClients();
|
||||
|
||||
for (FederatedClient client : clients) {
|
||||
logger.info("Updating directory from peer: " + client.getPeerName());
|
||||
BatchOperationHandle handle = directory.startBatchOperation();
|
||||
|
||||
try {
|
||||
int userCount = client.getUserCount();
|
||||
int retrieved = 0;
|
||||
|
||||
logger.info("Remote peer user count: " + userCount);
|
||||
|
||||
while (retrieved < userCount) {
|
||||
List<ClientContact> clientContacts = client.getUserTokens(retrieved);
|
||||
|
||||
if (clientContacts == null)
|
||||
break;
|
||||
|
||||
for (ClientContact clientContact : clientContacts) {
|
||||
clientContact.setRelay(client.getPeerName());
|
||||
|
||||
Optional<ClientContact> existing = directory.get(clientContact.getToken());
|
||||
|
||||
if (!clientContact.isInactive() && (!existing.isPresent() || existing.get().getRelay().equals(client.getPeerName()))) {
|
||||
directory.add(handle, clientContact);
|
||||
} else {
|
||||
if (existing != null && client.getPeerName().equals(existing.get().getRelay())) {
|
||||
directory.remove(clientContact.getToken());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retrieved += clientContacts.size();
|
||||
}
|
||||
|
||||
logger.info("Update from peer complete.");
|
||||
} finally {
|
||||
directory.stopBatchOperation(handle);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Update from peer directories complete.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user