mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-20 08:08:02 +01:00
Add a command to clear IAP issued receipt count
Co-authored-by: Katherine <katherine@signal.org>
This commit is contained in:
@@ -273,6 +273,7 @@ import org.whispersystems.textsecuregcm.workers.BackupMetricsCommand;
|
||||
import org.whispersystems.textsecuregcm.workers.BackupUsageRecalculationCommand;
|
||||
import org.whispersystems.textsecuregcm.workers.CertificateCommand;
|
||||
import org.whispersystems.textsecuregcm.workers.CheckDynamicConfigurationCommand;
|
||||
import org.whispersystems.textsecuregcm.workers.ClearIssuedReceiptRedemptionsCommand;
|
||||
import org.whispersystems.textsecuregcm.workers.DeleteUserCommand;
|
||||
import org.whispersystems.textsecuregcm.workers.IdleDeviceNotificationSchedulerFactory;
|
||||
import org.whispersystems.textsecuregcm.workers.MessagePersisterServiceCommand;
|
||||
@@ -342,6 +343,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
bootstrap.addCommand(new BackupUsageRecalculationCommand());
|
||||
bootstrap.addCommand(new RemoveExpiredLinkedDevicesCommand());
|
||||
bootstrap.addCommand(new NotifyIdleDevicesCommand());
|
||||
bootstrap.addCommand(new ClearIssuedReceiptRedemptionsCommand());
|
||||
|
||||
bootstrap.addCommand(new ProcessScheduledJobsServiceCommand("process-idle-device-notification-jobs",
|
||||
"Processes scheduled jobs to send notifications to idle devices",
|
||||
|
||||
@@ -34,6 +34,7 @@ import software.amazon.awssdk.core.SdkBytes;
|
||||
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
|
||||
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException;
|
||||
import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ReturnValue;
|
||||
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
|
||||
|
||||
@@ -109,6 +110,24 @@ public class IssuedReceiptsManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the recorded issuances for a particular item
|
||||
*
|
||||
* @param processorItemId The itemId within the processor to clear
|
||||
* @param processor The processor
|
||||
* @return a future that yields true if the item was deleted, false if the item already did not exist
|
||||
*/
|
||||
public CompletableFuture<Boolean> clearIssuance(String processorItemId, PaymentProvider processor) {
|
||||
final AttributeValue key = dynamoDbKey(processor, processorItemId);
|
||||
final DeleteItemRequest deleteItemRequest = DeleteItemRequest.builder()
|
||||
.tableName(table)
|
||||
.key(Map.of(KEY_PROCESSOR_ITEM_ID, key))
|
||||
.returnValues(ReturnValue.ALL_OLD)
|
||||
.build();
|
||||
return dynamoDbAsyncClient.deleteItem(deleteItemRequest)
|
||||
.thenApply(item -> item.hasAttributes() && !item.attributes().isEmpty());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static AttributeValue dynamoDbKey(final PaymentProvider processor, String processorItemId) {
|
||||
if (processor == PaymentProvider.STRIPE) {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.workers;
|
||||
|
||||
import io.dropwizard.core.Application;
|
||||
import io.dropwizard.core.setup.Environment;
|
||||
import java.time.Clock;
|
||||
import java.util.Optional;
|
||||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
import net.sourceforge.argparse4j.inf.Subparser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.storage.IssuedReceiptsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.SubscriberCredentials;
|
||||
import org.whispersystems.textsecuregcm.storage.SubscriptionManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Subscriptions;
|
||||
import org.whispersystems.textsecuregcm.subscriptions.PaymentProvider;
|
||||
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionPaymentProcessor;
|
||||
|
||||
public class ClearIssuedReceiptRedemptionsCommand extends AbstractCommandWithDependencies {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ClearIssuedReceiptRedemptionsCommand.class);
|
||||
|
||||
public ClearIssuedReceiptRedemptionsCommand() {
|
||||
super(new Application<>() {
|
||||
@Override
|
||||
public void run(WhisperServerConfiguration configuration, Environment environment) {
|
||||
|
||||
}
|
||||
}, "clear-issued-receipt-redemptions", "Clear issued receipt redemptions");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(Subparser subparser) {
|
||||
super.configure(subparser);
|
||||
subparser.addArgument("-s", "--subscriber-id")
|
||||
.dest("subscriberId")
|
||||
.type(String.class)
|
||||
.required(true)
|
||||
.help("The subscriber-id whose receipt redemptions should be clear");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(Environment environment, Namespace namespace, WhisperServerConfiguration configuration,
|
||||
CommandDependencies deps) throws Exception {
|
||||
try {
|
||||
final String subscriberId = namespace.getString("subscriberId");
|
||||
|
||||
final SubscriberCredentials creds = SubscriberCredentials
|
||||
.process(Optional.empty(), subscriberId, Clock.systemUTC());
|
||||
|
||||
final IssuedReceiptsManager issuedReceiptsManager = deps.issuedReceiptsManager();
|
||||
final SubscriptionManager subscriptionManager = deps.subscriptionManager();
|
||||
|
||||
final Subscriptions.Record subscriber = subscriptionManager.getSubscriber(creds);
|
||||
final PaymentProvider processorType = subscriber.getProcessorCustomer()
|
||||
.orElseThrow(() -> new IllegalArgumentException("susbcriber did not have a subscription"))
|
||||
.processor();
|
||||
final SubscriptionPaymentProcessor processor = switch (processorType) {
|
||||
case APPLE_APP_STORE -> deps.appleAppStoreManager();
|
||||
case GOOGLE_PLAY_BILLING -> deps.googlePlayBillingManager();
|
||||
default ->
|
||||
throw new IllegalStateException("Cannot clear issued receipts for a non-IAP processor: " + processorType);
|
||||
};
|
||||
final SubscriptionPaymentProcessor.ReceiptItem receiptItem = processor.getReceiptItem(subscriber.subscriptionId);
|
||||
final boolean deleted = issuedReceiptsManager.clearIssuance(receiptItem.itemId(), processorType).join();
|
||||
logger.info("Deleted issuances for receiptItem: {}, subscriberId: {}, hadExistingIssuances: {}",
|
||||
receiptItem.itemId(), subscriberId, deleted);
|
||||
} catch (Exception ex) {
|
||||
logger.warn("Removal Exception", ex);
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,16 +11,23 @@ import com.codahale.metrics.MetricRegistry;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import io.dropwizard.core.setup.Environment;
|
||||
import io.lettuce.core.resource.ClientResources;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.time.Clock;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import org.signal.libsignal.zkgroup.GenericServerSecretParams;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.ServerSecretParams;
|
||||
import org.signal.libsignal.zkgroup.receipts.ServerZkReceiptOperations;
|
||||
import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.WhisperServerService;
|
||||
import org.whispersystems.textsecuregcm.attachments.TusAttachmentGenerator;
|
||||
@@ -72,6 +79,10 @@ import org.whispersystems.textsecuregcm.storage.ReportMessageDynamoDb;
|
||||
import org.whispersystems.textsecuregcm.storage.ReportMessageManager;
|
||||
import org.whispersystems.textsecuregcm.storage.SingleUseECPreKeyStore;
|
||||
import org.whispersystems.textsecuregcm.storage.SingleUseKEMPreKeyStore;
|
||||
import org.whispersystems.textsecuregcm.storage.SubscriptionManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Subscriptions;
|
||||
import org.whispersystems.textsecuregcm.subscriptions.AppleAppStoreManager;
|
||||
import org.whispersystems.textsecuregcm.subscriptions.GooglePlayBillingManager;
|
||||
import org.whispersystems.textsecuregcm.util.ManagedAwsCrt;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
@@ -101,6 +112,9 @@ record CommandDependencies(
|
||||
ClientResources.Builder redisClusterClientResourcesBuilder,
|
||||
BackupManager backupManager,
|
||||
IssuedReceiptsManager issuedReceiptsManager,
|
||||
GooglePlayBillingManager googlePlayBillingManager,
|
||||
AppleAppStoreManager appleAppStoreManager,
|
||||
SubscriptionManager subscriptionManager,
|
||||
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
|
||||
DynamoDbAsyncClient dynamoDbAsyncClient,
|
||||
PhoneNumberIdentifiers phoneNumberIdentifiers,
|
||||
@@ -110,7 +124,7 @@ record CommandDependencies(
|
||||
final String name,
|
||||
final Environment environment,
|
||||
final WhisperServerConfiguration configuration)
|
||||
throws IOException, CertificateException, NoSuchAlgorithmException, InvalidKeyException {
|
||||
throws IOException, GeneralSecurityException, InvalidInputException {
|
||||
Clock clock = Clock.systemUTC();
|
||||
|
||||
environment.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
@@ -310,6 +324,30 @@ record CommandDependencies(
|
||||
configuration.getDynamoDbTables().getIssuedReceipts().getGenerator(),
|
||||
configuration.getDynamoDbTables().getIssuedReceipts().getmaxIssuedReceiptsPerPaymentId());
|
||||
|
||||
final ServerSecretParams zkSecretParams = new ServerSecretParams(configuration.getZkConfig().serverSecret().value());
|
||||
final ServerZkReceiptOperations zkReceiptOperations = new ServerZkReceiptOperations(zkSecretParams);
|
||||
GooglePlayBillingManager googlePlayBillingManager = new GooglePlayBillingManager(
|
||||
new ByteArrayInputStream(configuration.getGooglePlayBilling().credentialsJson().value().getBytes(StandardCharsets.UTF_8)),
|
||||
configuration.getGooglePlayBilling().packageName(),
|
||||
configuration.getGooglePlayBilling().applicationName(),
|
||||
configuration.getGooglePlayBilling().productIdToLevel());
|
||||
AppleAppStoreManager appleAppStoreManager = new AppleAppStoreManager(
|
||||
configuration.getAppleAppStore().env(),
|
||||
configuration.getAppleAppStore().bundleId(),
|
||||
configuration.getAppleAppStore().appAppleId(),
|
||||
configuration.getAppleAppStore().issuerId(),
|
||||
configuration.getAppleAppStore().keyId(),
|
||||
configuration.getAppleAppStore().encodedKey().value(),
|
||||
configuration.getAppleAppStore().subscriptionGroupId(),
|
||||
configuration.getAppleAppStore().productIdToLevel(),
|
||||
configuration.getAppleAppStore().appleRootCerts(),
|
||||
configuration.getAppleAppStore().retryConfigurationName());
|
||||
final SubscriptionManager subscriptionManager = new SubscriptionManager(
|
||||
new Subscriptions(configuration.getDynamoDbTables().getSubscriptions().getTableName(), dynamoDbAsyncClient),
|
||||
List.of(googlePlayBillingManager, appleAppStoreManager),
|
||||
zkReceiptOperations,
|
||||
issuedReceiptsManager);
|
||||
|
||||
APNSender apnSender = new APNSender(apnSenderExecutor, configuration.getApnConfiguration());
|
||||
FcmSender fcmSender = new FcmSender(fcmSenderExecutor, configuration.getFcmConfiguration().credentials().value());
|
||||
PushNotificationScheduler pushNotificationScheduler = new PushNotificationScheduler(pushSchedulerCluster,
|
||||
@@ -346,6 +384,9 @@ record CommandDependencies(
|
||||
redisClientResourcesBuilder,
|
||||
backupManager,
|
||||
issuedReceiptsManager,
|
||||
googlePlayBillingManager,
|
||||
appleAppStoreManager,
|
||||
subscriptionManager,
|
||||
dynamicConfigurationManager,
|
||||
dynamoDbAsyncClient,
|
||||
phoneNumberIdentifiers,
|
||||
|
||||
Reference in New Issue
Block a user