Return report spam token from spam check instead of separate call

This commit is contained in:
Ameya Lokare 2024-09-23 15:34:48 -07:00
parent 237d0fd4e2
commit e9b3e15556
6 changed files with 37 additions and 71 deletions

View File

@ -208,7 +208,6 @@ import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2
import org.whispersystems.textsecuregcm.spam.ChallengeConstraintChecker; import org.whispersystems.textsecuregcm.spam.ChallengeConstraintChecker;
import org.whispersystems.textsecuregcm.spam.RegistrationFraudChecker; import org.whispersystems.textsecuregcm.spam.RegistrationFraudChecker;
import org.whispersystems.textsecuregcm.spam.RegistrationRecoveryChecker; import org.whispersystems.textsecuregcm.spam.RegistrationRecoveryChecker;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider;
import org.whispersystems.textsecuregcm.spam.SpamChecker; import org.whispersystems.textsecuregcm.spam.SpamChecker;
import org.whispersystems.textsecuregcm.spam.SpamFilter; import org.whispersystems.textsecuregcm.spam.SpamFilter;
import org.whispersystems.textsecuregcm.storage.AccountLockManager; import org.whispersystems.textsecuregcm.storage.AccountLockManager;
@ -1049,12 +1048,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
if (spamFilter.isEmpty()) { if (spamFilter.isEmpty()) {
log.warn("No spam filters installed"); log.warn("No spam filters installed");
} }
final ReportSpamTokenProvider reportSpamTokenProvider = spamFilter
.map(SpamFilter::getReportSpamTokenProvider)
.orElseGet(() -> {
log.warn("No spam-reporting token providers found; using default (no-op) provider as a default");
return ReportSpamTokenProvider.noop();
});
final SpamChecker spamChecker = spamFilter final SpamChecker spamChecker = spamFilter
.map(SpamFilter::getSpamChecker) .map(SpamFilter::getSpamChecker)
.orElseGet(() -> { .orElseGet(() -> {
@ -1123,7 +1116,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
new KeyTransparencyController(keyTransparencyServiceClient), new KeyTransparencyController(keyTransparencyServiceClient),
new MessageController(rateLimiters, messageByteLimitCardinalityEstimator, messageSender, receiptSender, new MessageController(rateLimiters, messageByteLimitCardinalityEstimator, messageSender, receiptSender,
accountsManager, messagesManager, pushNotificationManager, pushNotificationScheduler, reportMessageManager, accountsManager, messagesManager, pushNotificationManager, pushNotificationScheduler, reportMessageManager,
multiRecipientMessageExecutor, messageDeliveryScheduler, reportSpamTokenProvider, clientReleaseManager, multiRecipientMessageExecutor, messageDeliveryScheduler, clientReleaseManager,
dynamicConfigurationManager, zkSecretParams, spamChecker, messageMetrics, messageDeliveryLoopMonitor, dynamicConfigurationManager, zkSecretParams, spamChecker, messageMetrics, messageDeliveryLoopMonitor,
Clock.systemUTC()), Clock.systemUTC()),
new PaymentsController(currencyManager, paymentsCredentialsGenerator), new PaymentsController(currencyManager, paymentsCredentialsGenerator),

View File

@ -116,7 +116,6 @@ import org.whispersystems.textsecuregcm.push.MessageSender;
import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.PushNotificationScheduler; import org.whispersystems.textsecuregcm.push.PushNotificationScheduler;
import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider;
import org.whispersystems.textsecuregcm.spam.SpamChecker; import org.whispersystems.textsecuregcm.spam.SpamChecker;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
@ -163,7 +162,6 @@ public class MessageController {
private final ReportMessageManager reportMessageManager; private final ReportMessageManager reportMessageManager;
private final ExecutorService multiRecipientMessageExecutor; private final ExecutorService multiRecipientMessageExecutor;
private final Scheduler messageDeliveryScheduler; private final Scheduler messageDeliveryScheduler;
private final ReportSpamTokenProvider reportSpamTokenProvider;
private final ClientReleaseManager clientReleaseManager; private final ClientReleaseManager clientReleaseManager;
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager; private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
private final ServerSecretParams serverSecretParams; private final ServerSecretParams serverSecretParams;
@ -226,7 +224,6 @@ public class MessageController {
ReportMessageManager reportMessageManager, ReportMessageManager reportMessageManager,
@Nonnull ExecutorService multiRecipientMessageExecutor, @Nonnull ExecutorService multiRecipientMessageExecutor,
Scheduler messageDeliveryScheduler, Scheduler messageDeliveryScheduler,
@Nonnull ReportSpamTokenProvider reportSpamTokenProvider,
final ClientReleaseManager clientReleaseManager, final ClientReleaseManager clientReleaseManager,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager, final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
final ServerSecretParams serverSecretParams, final ServerSecretParams serverSecretParams,
@ -245,7 +242,6 @@ public class MessageController {
this.reportMessageManager = reportMessageManager; this.reportMessageManager = reportMessageManager;
this.multiRecipientMessageExecutor = Objects.requireNonNull(multiRecipientMessageExecutor); this.multiRecipientMessageExecutor = Objects.requireNonNull(multiRecipientMessageExecutor);
this.messageDeliveryScheduler = messageDeliveryScheduler; this.messageDeliveryScheduler = messageDeliveryScheduler;
this.reportSpamTokenProvider = reportSpamTokenProvider;
this.clientReleaseManager = clientReleaseManager; this.clientReleaseManager = clientReleaseManager;
this.dynamicConfigurationManager = dynamicConfigurationManager; this.dynamicConfigurationManager = dynamicConfigurationManager;
this.serverSecretParams = serverSecretParams; this.serverSecretParams = serverSecretParams;
@ -304,7 +300,7 @@ public class MessageController {
final Sample sample = Timer.start(); final Sample sample = Timer.start();
try { try {
if (source.isEmpty() && accessKey.isEmpty() && groupSendToken == null && !isStory) { if (source.isEmpty() && accessKey.isEmpty() && groupSendToken == null && !isStory) {
throw new WebApplicationException(Response.Status.UNAUTHORIZED); throw new WebApplicationException(Status.UNAUTHORIZED);
} }
if (groupSendToken != null) { if (groupSendToken != null) {
@ -333,17 +329,14 @@ public class MessageController {
destination = source.map(AuthenticatedDevice::getAccount); destination = source.map(AuthenticatedDevice::getAccount);
} }
final Optional<Response> spamCheck = spamChecker.checkForSpam( final SpamChecker.SpamCheckResult spamCheck = spamChecker.checkForSpam(
context, source.map(AuthenticatedDevice::getAccount), destination); context, source, destination);
if (spamCheck.isPresent()) { final Optional<byte[]> reportSpamToken;
return spamCheck.get(); switch (spamCheck) {
case final SpamChecker.Spam spam: return spam.response();
case final SpamChecker.NotSpam notSpam: reportSpamToken = notSpam.token();
} }
final Optional<byte[]> spamReportToken = switch (senderType) {
case SENDER_TYPE_IDENTIFIED -> reportSpamTokenProvider.makeReportSpamToken(context, source.get(), destination);
default -> Optional.empty();
};
int totalContentLength = 0; int totalContentLength = 0;
for (final IncomingMessage message : messages.messages()) { for (final IncomingMessage message : messages.messages()) {
@ -453,7 +446,7 @@ public class MessageController {
messages.urgent(), messages.urgent(),
incomingMessage, incomingMessage,
userAgent, userAgent,
spamReportToken); reportSpamToken);
}); });
} }
@ -555,9 +548,9 @@ public class MessageController {
@Context ContainerRequestContext context) throws RateLimitExceededException { @Context ContainerRequestContext context) throws RateLimitExceededException {
final Optional<Response> spamCheck = spamChecker.checkForSpam(context, Optional.empty(), Optional.empty()); final SpamChecker.SpamCheckResult spamCheck = spamChecker.checkForSpam(context, Optional.empty(), Optional.empty());
if (spamCheck.isPresent()) { if (spamCheck instanceof final SpamChecker.Spam spam) {
return spamCheck.get(); return spam.response();
} }
if (groupSendToken == null && accessKeys == null && !isStory) { if (groupSendToken == null && accessKeys == null && !isStory) {

View File

@ -1,32 +0,0 @@
package org.whispersystems.textsecuregcm.spam;
import org.whispersystems.textsecuregcm.auth.AccountAndAuthenticatedDeviceHolder;
import org.whispersystems.textsecuregcm.storage.Account;
import javax.ws.rs.container.ContainerRequestContext;
import java.util.Optional;
/**
* Generates ReportSpamTokens to be used for spam reports.
*/
public interface ReportSpamTokenProvider {
/**
* Generate a new ReportSpamToken
*
* @param context the message request context
* @param sender the account that sent the unsealed sender message
* @param maybeDestination the intended recepient of the message if available
* @return either a generated token or nothing
*/
Optional<byte[]> makeReportSpamToken(ContainerRequestContext context, final AccountAndAuthenticatedDeviceHolder sender,
final Optional<Account> maybeDestination);
/**
* Provider which generates nothing
*
* @return the provider
*/
static ReportSpamTokenProvider noop() {
return (ignoredContext, ignoredSender, ignoredDest) -> Optional.empty();
}
}

View File

@ -4,6 +4,7 @@
*/ */
package org.whispersystems.textsecuregcm.spam; package org.whispersystems.textsecuregcm.spam;
import org.whispersystems.textsecuregcm.auth.AccountAndAuthenticatedDeviceHolder;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
@ -11,6 +12,25 @@ import java.util.Optional;
public interface SpamChecker { public interface SpamChecker {
/**
* A result from the spam checker that is one of:
* <ul>
* <li>
* Message is determined to be spam, and a response is returned
* </li>
* <li>
* Message is not spam, and an optional spam token is returned
* </li>
* </ul>
*/
sealed interface SpamCheckResult {}
record Spam(Response response) implements SpamCheckResult {}
record NotSpam(Optional<byte[]> token) implements SpamCheckResult {
public static final NotSpam EMPTY_TOKEN = new NotSpam(Optional.empty());
}
/** /**
* Determine if a message may be spam * Determine if a message may be spam
* *
@ -18,14 +38,14 @@ public interface SpamChecker {
* @param maybeSource The sender of the message, could be empty if this as message sent with sealed sender * @param maybeSource The sender of the message, could be empty if this as message sent with sealed sender
* @param maybeDestination The destination of the message, could be empty if the destination does not exist or could * @param maybeDestination The destination of the message, could be empty if the destination does not exist or could
* not be retrieved * not be retrieved
* @return A response to return if the request is determined to be spam, otherwise empty if the message should be sent * @return A {@link SpamCheckResult}
*/ */
Optional<Response> checkForSpam( SpamCheckResult checkForSpam(
final ContainerRequestContext requestContext, final ContainerRequestContext requestContext,
final Optional<Account> maybeSource, final Optional<? extends AccountAndAuthenticatedDeviceHolder> maybeSource,
final Optional<Account> maybeDestination); final Optional<Account> maybeDestination);
static SpamChecker noop() { static SpamChecker noop() {
return (ignoredContext, ignoredSource, ignoredDestination) -> Optional.empty(); return (ignoredContext, ignoredSource, ignoredDestination) -> NotSpam.EMPTY_TOKEN;
} }
} }

View File

@ -33,13 +33,6 @@ public interface SpamFilter extends Managed {
*/ */
void configure(String environmentName, Validator validator) throws IOException, ConfigurationValidationException; void configure(String environmentName, Validator validator) throws IOException, ConfigurationValidationException;
/**
* Builds a spam report token provider. This will generate tokens used by the spam reporting system.
*
* @return the configured spam report token provider.
*/
ReportSpamTokenProvider getReportSpamTokenProvider();
/** /**
* Return a reported message listener controlled by the spam filter. Listeners will be registered with the * Return a reported message listener controlled by the spam filter. Listeners will be registered with the
* {@link org.whispersystems.textsecuregcm.storage.ReportMessageManager}. * {@link org.whispersystems.textsecuregcm.storage.ReportMessageManager}.

View File

@ -114,7 +114,6 @@ import org.whispersystems.textsecuregcm.push.MessageSender;
import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.PushNotificationScheduler; import org.whispersystems.textsecuregcm.push.PushNotificationScheduler;
import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider;
import org.whispersystems.textsecuregcm.spam.SpamChecker; import org.whispersystems.textsecuregcm.spam.SpamChecker;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
@ -207,7 +206,7 @@ class MessageControllerTest {
.addResource( .addResource(
new MessageController(rateLimiters, cardinalityEstimator, messageSender, receiptSender, accountsManager, new MessageController(rateLimiters, cardinalityEstimator, messageSender, receiptSender, accountsManager,
messagesManager, pushNotificationManager, pushNotificationScheduler, reportMessageManager, multiRecipientMessageExecutor, messagesManager, pushNotificationManager, pushNotificationScheduler, reportMessageManager, multiRecipientMessageExecutor,
messageDeliveryScheduler, ReportSpamTokenProvider.noop(), mock(ClientReleaseManager.class), dynamicConfigurationManager, messageDeliveryScheduler, mock(ClientReleaseManager.class), dynamicConfigurationManager,
serverSecretParams, SpamChecker.noop(), new MessageMetrics(), mock(MessageDeliveryLoopMonitor.class), serverSecretParams, SpamChecker.noop(), new MessageMetrics(), mock(MessageDeliveryLoopMonitor.class),
clock)) clock))
.build(); .build();