Retire `ReportSpamTokenHandler` interface in favor of `ReportedMessageListener`

This commit is contained in:
Jon Chambers 2023-01-30 11:48:32 -05:00 committed by Jon Chambers
parent 4a2768b81d
commit 38a0737afb
5 changed files with 18 additions and 101 deletions

View File

@ -70,11 +70,6 @@ import org.signal.libsignal.zkgroup.receipts.ServerZkReceiptOperations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.dispatch.DispatchManager;
import org.whispersystems.textsecuregcm.spam.SpamFilter;
import org.whispersystems.textsecuregcm.spam.FilterSpam;
import org.whispersystems.textsecuregcm.spam.RateLimitChallengeListener;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenHandler;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider;
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.CertificateGenerator;
@ -168,6 +163,10 @@ import org.whispersystems.textsecuregcm.s3.PolicySigner;
import org.whispersystems.textsecuregcm.s3.PostPolicyGenerator;
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
import org.whispersystems.textsecuregcm.spam.FilterSpam;
import org.whispersystems.textsecuregcm.spam.RateLimitChallengeListener;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider;
import org.whispersystems.textsecuregcm.spam.SpamFilter;
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
import org.whispersystems.textsecuregcm.storage.AccountCleaner;
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawler;
@ -679,7 +678,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
boolean registeredSpamFilter = false;
ReportSpamTokenProvider reportSpamTokenProvider = null;
ReportSpamTokenHandler reportSpamTokenHandler = null;
for (final SpamFilter filter : ServiceLoader.load(SpamFilter.class)) {
if (filter.getClass().isAnnotationPresent(FilterSpam.class)) {
@ -693,12 +691,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
log.info("Multiple spam report token providers found. Using the first.");
}
ReportSpamTokenHandler thisHandler = filter.getReportSpamTokenHandler();
if (reportSpamTokenHandler == null) {
reportSpamTokenHandler = thisHandler;
} else if (thisProvider != null) {
log.info("Multiple spam report token handlers found. Using the first.");
}
filter.getReportedMessageListeners().forEach(reportMessageManager::addListener);
environment.lifecycle().manage(filter);
environment.jersey().register(filter);
@ -728,10 +721,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
reportSpamTokenProvider = ReportSpamTokenProvider.noop();
}
if (reportSpamTokenHandler == null) {
reportSpamTokenHandler = ReportSpamTokenHandler.noop();
}
final List<Object> commonControllers = Lists.newArrayList(
new ArtController(rateLimiters, artCredentialsGenerator),
new AttachmentControllerV2(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getRegion(), config.getAwsAttachmentsConfiguration().getBucket()),
@ -744,7 +733,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
new DonationController(clock, zkReceiptOperations, redeemedReceiptsManager, accountsManager, config.getBadges(),
ReceiptCredentialPresentation::new),
new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, deletedAccountsManager, messagesManager, pushNotificationManager, reportMessageManager, multiRecipientMessageExecutor,
reportSpamTokenProvider, reportSpamTokenHandler),
reportSpamTokenProvider),
new PaymentsController(currencyManager, paymentsCredentialsGenerator),
new ProfileController(clock, rateLimiters, accountsManager, profilesManager, dynamicConfigurationManager,
profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner,

View File

@ -62,9 +62,6 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.spam.FilterSpam;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenHandler;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider;
import org.whispersystems.textsecuregcm.auth.Anonymous;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.CombinedUnidentifiedSenderAccessKeys;
@ -92,6 +89,8 @@ import org.whispersystems.textsecuregcm.push.MessageSender;
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.spam.FilterSpam;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.DeletedAccountsManager;
@ -123,7 +122,6 @@ public class MessageController {
private final ReportMessageManager reportMessageManager;
private final ExecutorService multiRecipientMessageExecutor;
private final ReportSpamTokenProvider reportSpamTokenProvider;
private final ReportSpamTokenHandler reportSpamTokenHandler;
private static final String REJECT_OVERSIZE_MESSAGE_COUNTER = name(MessageController.class, "rejectOversizeMessage");
private static final String SENT_MESSAGE_COUNTER_NAME = name(MessageController.class, "sentMessages");
@ -131,7 +129,6 @@ public class MessageController {
private static final String OUTGOING_MESSAGE_LIST_SIZE_BYTES_DISTRIBUTION_NAME = name(MessageController.class, "outgoingMessageListSizeBytes");
private static final String RATE_LIMITED_MESSAGE_COUNTER_NAME = name(MessageController.class, "rateLimitedMessage");
private static final String REJECT_INVALID_ENVELOPE_TYPE = name(MessageController.class, "rejectInvalidEnvelopeType");
private static final String REPORT_SPAM_TOKENS_RECEIVED_COUNTER_NAME = name(MessageController.class, "reportSpamTokensReceived");
private static final String EPHEMERAL_TAG_NAME = "ephemeral";
private static final String SENDER_TYPE_TAG_NAME = "senderType";
@ -156,8 +153,7 @@ public class MessageController {
PushNotificationManager pushNotificationManager,
ReportMessageManager reportMessageManager,
@Nonnull ExecutorService multiRecipientMessageExecutor,
@Nonnull ReportSpamTokenProvider reportSpamTokenProvider,
@Nonnull ReportSpamTokenHandler reportSpamTokenHandler) {
@Nonnull ReportSpamTokenProvider reportSpamTokenProvider) {
this.rateLimiters = rateLimiters;
this.messageSender = messageSender;
this.receiptSender = receiptSender;
@ -168,8 +164,6 @@ public class MessageController {
this.reportMessageManager = reportMessageManager;
this.multiRecipientMessageExecutor = Objects.requireNonNull(multiRecipientMessageExecutor);
this.reportSpamTokenProvider = reportSpamTokenProvider;
this.reportSpamTokenHandler = reportSpamTokenHandler;
}
@Timed
@ -646,10 +640,6 @@ public class MessageController {
final Optional<byte[]> maybeSpamReportToken =
spamReport != null ? Optional.of(spamReport.token()) : Optional.empty();
// fire-and-forget: we don't want to block the response on this action.
CompletableFuture<Boolean> ignored =
reportSpamTokenHandler.handle(sourceNumber, sourceAci, sourcePni, messageGuid, spamReporterUuid, maybeSpamReportToken.orElse(null));
reportMessageManager.report(sourceNumber, sourceAci, sourcePni, messageGuid, spamReporterUuid, maybeSpamReportToken);
return Response.status(Status.ACCEPTED)

View File

@ -1,47 +0,0 @@
package org.whispersystems.textsecuregcm.spam;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
* Handles ReportSpamTokens during spam reports.
*/
public interface ReportSpamTokenHandler {
/**
* Handle spam reports using the given ReportSpamToken and other provided parameters.
*
* @param reportSpamToken binary data representing a spam report token.
* @return true if the token could be handled (and was), false otherwise.
*/
CompletableFuture<Boolean> handle(
Optional<String> sourceNumber,
Optional<UUID> sourceAci,
Optional<UUID> sourcePni,
UUID messageGuid,
UUID spamReporterUuid,
byte[] reportSpamToken);
/**
* Handler which does nothing.
*
* @return the handler
*/
static ReportSpamTokenHandler noop() {
return new ReportSpamTokenHandler() {
@Override
public CompletableFuture<Boolean> handle(
final Optional<String> sourceNumber,
final Optional<UUID> sourceAci,
final Optional<UUID> sourcePni,
final UUID messageGuid,
final UUID spamReporterUuid,
final byte[] reportSpamToken) {
return CompletableFuture.completedFuture(false);
}
};
}
}

View File

@ -6,8 +6,10 @@
package org.whispersystems.textsecuregcm.spam;
import io.dropwizard.lifecycle.Managed;
import org.whispersystems.textsecuregcm.storage.ReportedMessageListener;
import javax.ws.rs.container.ContainerRequestFilter;
import java.io.IOException;
import java.util.List;
/**
* A spam filter is a {@link ContainerRequestFilter} that filters requests to message-sending endpoints to
@ -39,10 +41,10 @@ public interface SpamFilter extends ContainerRequestFilter, Managed {
ReportSpamTokenProvider getReportSpamTokenProvider();
/**
* Builds a spam report token handler. This will handle tokens received by the spam reporting system.
* Return any and all reported message listeners controlled by the spam filter. Listeners will be registered with the
* {@link org.whispersystems.textsecuregcm.storage.ReportMessageManager}.
*
* @return the configured spam report token handler
* @return a list of reported message listeners controlled by the spam filter
*/
ReportSpamTokenHandler getReportSpamTokenHandler();
List<ReportedMessageListener> getReportedMessageListeners();
}

View File

@ -8,7 +8,6 @@ package org.whispersystems.textsecuregcm.controllers;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
@ -19,7 +18,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@ -68,9 +66,6 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenHandler;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.OptionalAccess;
@ -94,6 +89,7 @@ import org.whispersystems.textsecuregcm.providers.MultiRecipientMessageProvider;
import org.whispersystems.textsecuregcm.push.MessageSender;
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.DeletedAccountsManager;
@ -144,7 +140,6 @@ class MessageControllerTest {
private static final PushNotificationManager pushNotificationManager = mock(PushNotificationManager.class);
private static final ReportMessageManager reportMessageManager = mock(ReportMessageManager.class);
private static final ExecutorService multiRecipientMessageExecutor = mock(ExecutorService.class);
private static final ReportSpamTokenHandler REPORT_SPAM_TOKEN_HANDLER = mock(ReportSpamTokenHandler.class);
private static final ResourceExtension resources = ResourceExtension.builder()
.addProperty(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE)
@ -157,7 +152,7 @@ class MessageControllerTest {
.addResource(
new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, deletedAccountsManager,
messagesManager, pushNotificationManager, reportMessageManager, multiRecipientMessageExecutor,
ReportSpamTokenProvider.noop(), REPORT_SPAM_TOKEN_HANDLER))
ReportSpamTokenProvider.noop()))
.build();
@BeforeEach
@ -184,8 +179,6 @@ class MessageControllerTest {
when(accountsManager.getByAccountIdentifier(INTERNATIONAL_UUID)).thenReturn(Optional.of(internationalAccount));
when(rateLimiters.getMessagesLimiter()).thenReturn(rateLimiter);
when(REPORT_SPAM_TOKEN_HANDLER.handle(any(), any(), any(), any(), any(), any())).thenReturn(CompletableFuture.completedFuture(false));
}
private static Device generateTestDevice(final long id, final int registrationId, final int pniRegistrationId, final SignedPreKey signedPreKey, final long createdAt, final long lastSeen) {
@ -214,8 +207,7 @@ class MessageControllerTest {
rateLimiter,
pushNotificationManager,
reportMessageManager,
multiRecipientMessageExecutor,
REPORT_SPAM_TOKEN_HANDLER
multiRecipientMessageExecutor
);
}
@ -720,9 +712,6 @@ class MessageControllerTest {
when(accountsManager.getByAccountIdentifier(senderAci)).thenReturn(Optional.of(account));
when(deletedAccountsManager.findDeletedAccountE164(senderAci)).thenReturn(Optional.of(senderNumber));
when(accountsManager.getPhoneNumberIdentifier(senderNumber)).thenReturn(senderPni);
when(REPORT_SPAM_TOKEN_HANDLER.handle(any(), any(), any(), any(), any(), any())).thenReturn(CompletableFuture.completedFuture(true));
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
Entity<SpamReport> entity = Entity.entity(new SpamReport(new byte[3]), "application/json");
Response response =
@ -733,8 +722,6 @@ class MessageControllerTest {
.post(entity);
assertThat(response.getStatus(), is(equalTo(202)));
verify(REPORT_SPAM_TOKEN_HANDLER).handle(any(), any(), any(), any(), any(), captor.capture());
assertArrayEquals(new byte[3], captor.getValue());
verify(reportMessageManager).report(eq(Optional.of(senderNumber)),
eq(Optional.of(senderAci)),
eq(Optional.of(senderPni)),
@ -745,8 +732,6 @@ class MessageControllerTest {
verify(accountsManager, never()).getPhoneNumberIdentifier(anyString());
when(accountsManager.getByAccountIdentifier(senderAci)).thenReturn(Optional.empty());
clearInvocations(REPORT_SPAM_TOKEN_HANDLER);
messageGuid = UUID.randomUUID();
entity = Entity.entity(new SpamReport(new byte[5]), "application/json");
@ -758,8 +743,6 @@ class MessageControllerTest {
.post(entity);
assertThat(response.getStatus(), is(equalTo(202)));
verify(REPORT_SPAM_TOKEN_HANDLER).handle(any(), any(), any(), any(), any(), captor.capture());
assertArrayEquals(new byte[5], captor.getValue());
verify(reportMessageManager).report(eq(Optional.of(senderNumber)),
eq(Optional.of(senderAci)),
eq(Optional.of(senderPni)),