Make stored messages REST accessible.

Add REST endpoints for retrieving and acknowledging pending
messges, beyond the WebSocket.

// FREEBIE
This commit is contained in:
Moxie Marlinspike
2015-04-15 16:19:07 -07:00
parent 53e7ffa311
commit db6ee8f687
14 changed files with 412 additions and 150 deletions

View File

@@ -26,6 +26,8 @@ import org.whispersystems.textsecuregcm.entities.IncomingMessageList;
import org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal;
import org.whispersystems.textsecuregcm.entities.MessageResponse;
import org.whispersystems.textsecuregcm.entities.MismatchedDevices;
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity;
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntityList;
import org.whispersystems.textsecuregcm.entities.SendMessageResponse;
import org.whispersystems.textsecuregcm.entities.StaleDevices;
import org.whispersystems.textsecuregcm.federation.FederatedClient;
@@ -34,15 +36,19 @@ import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
import org.whispersystems.textsecuregcm.push.PushSender;
import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.push.TransientPushFailureException;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.MessagesManager;
import org.whispersystems.textsecuregcm.util.Base64;
import org.whispersystems.textsecuregcm.util.Util;
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
@@ -66,17 +72,23 @@ public class MessageController {
private final RateLimiters rateLimiters;
private final PushSender pushSender;
private final ReceiptSender receiptSender;
private final FederatedClientManager federatedClientManager;
private final AccountsManager accountsManager;
private final MessagesManager messagesManager;
public MessageController(RateLimiters rateLimiters,
PushSender pushSender,
ReceiptSender receiptSender,
AccountsManager accountsManager,
MessagesManager messagesManager,
FederatedClientManager federatedClientManager)
{
this.rateLimiters = rateLimiters;
this.pushSender = pushSender;
this.receiptSender = receiptSender;
this.accountsManager = accountsManager;
this.messagesManager = messagesManager;
this.federatedClientManager = federatedClientManager;
}
@@ -137,6 +149,36 @@ public class MessageController {
}
}
@Timed
@GET
@Produces(MediaType.APPLICATION_JSON)
public OutgoingMessageEntityList getPendingMessages(@Auth Account account) {
return new OutgoingMessageEntityList(messagesManager.getMessagesForDevice(account.getNumber(),
account.getAuthenticatedDevice()
.get().getId()));
}
@Timed
@DELETE
@Path("/{message_id}")
public void removePendingMessage(@Auth Account account, @PathParam("message_id") long id)
throws IOException
{
try {
Optional<OutgoingMessageEntity> message = messagesManager.delete(account.getNumber(), id);
if (message.isPresent() && message.get().getType() != OutgoingMessageSignal.Type.RECEIPT_VALUE) {
receiptSender.sendReceipt(account,
message.get().getSource(),
message.get().getTimestamp(),
Optional.fromNullable(message.get().getRelay()));
}
} catch (NoSuchUserException | NotPushRegisteredException | TransientPushFailureException e) {
logger.warn("Sending delivery receipt", e);
}
}
private void sendLocalMessage(Account source,
String destinationName,
IncomingMessageList messages,

View File

@@ -2,14 +2,10 @@ package org.whispersystems.textsecuregcm.controllers;
import com.codahale.metrics.annotation.Timed;
import com.google.common.base.Optional;
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
import org.whispersystems.textsecuregcm.push.PushSender;
import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.push.TransientPushFailureException;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
@@ -18,25 +14,16 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.Set;
import io.dropwizard.auth.Auth;
import static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal;
@Path("/v1/receipt")
public class ReceiptController {
private final AccountsManager accountManager;
private final PushSender pushSender;
private final FederatedClientManager federatedClientManager;
private final ReceiptSender receiptSender;
public ReceiptController(AccountsManager accountManager,
FederatedClientManager federatedClientManager,
PushSender pushSender)
{
this.accountManager = accountManager;
this.federatedClientManager = federatedClientManager;
this.pushSender = pushSender;
public ReceiptController(ReceiptSender receiptSender) {
this.receiptSender = receiptSender;
}
@Timed
@@ -49,11 +36,7 @@ public class ReceiptController {
throws IOException
{
try {
if (relay.isPresent() && !relay.get().isEmpty()) {
sendRelayedReceipt(source, destination, messageId, relay.get());
} else {
sendDirectReceipt(source, destination, messageId);
}
receiptSender.sendReceipt(source, destination, messageId, relay);
} catch (NoSuchUserException | NotPushRegisteredException e) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
} catch (TransientPushFailureException e) {
@@ -61,51 +44,4 @@ public class ReceiptController {
}
}
private void sendRelayedReceipt(Account source, String destination, long messageId, String relay)
throws NoSuchUserException, IOException
{
try {
federatedClientManager.getClient(relay)
.sendDeliveryReceipt(source.getNumber(),
source.getAuthenticatedDevice().get().getId(),
destination, messageId);
} catch (NoSuchPeerException e) {
throw new NoSuchUserException(e);
}
}
private void sendDirectReceipt(Account source, String destination, long messageId)
throws NotPushRegisteredException, TransientPushFailureException, NoSuchUserException
{
Account destinationAccount = getDestinationAccount(destination);
Set<Device> destinationDevices = destinationAccount.getDevices();
OutgoingMessageSignal.Builder message =
OutgoingMessageSignal.newBuilder()
.setSource(source.getNumber())
.setSourceDevice((int) source.getAuthenticatedDevice().get().getId())
.setTimestamp(messageId)
.setType(OutgoingMessageSignal.Type.RECEIPT_VALUE);
if (source.getRelay().isPresent()) {
message.setRelay(source.getRelay().get());
}
for (Device destinationDevice : destinationDevices) {
pushSender.sendMessage(destinationAccount, destinationDevice, message.build());
}
}
private Account getDestinationAccount(String destination)
throws NoSuchUserException
{
Optional<Account> account = accountManager.get(destination);
if (!account.isPresent()) {
throw new NoSuchUserException(destination);
}
return account.get();
}
}