mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-19 19:28:03 +01:00
Relax issuing constraints in IssuedReceiptManager
This commit is contained in:
committed by
ravi-signal
parent
1970741049
commit
18c9b177f3
@@ -647,7 +647,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
config.getDynamoDbTables().getIssuedReceipts().getTableName(),
|
||||
config.getDynamoDbTables().getIssuedReceipts().getExpiration(),
|
||||
dynamoDbAsyncClient,
|
||||
config.getDynamoDbTables().getIssuedReceipts().getGenerator());
|
||||
config.getDynamoDbTables().getIssuedReceipts().getGenerator(),
|
||||
config.getDynamoDbTables().getIssuedReceipts().getmaxIssuedReceiptsPerPaymentId());
|
||||
OneTimeDonationsManager oneTimeDonationsManager = new OneTimeDonationsManager(
|
||||
config.getDynamoDbTables().getOnetimeDonations().getTableName(), config.getDynamoDbTables().getOnetimeDonations().getExpiration(), dynamoDbAsyncClient);
|
||||
RedeemedReceiptsManager redeemedReceiptsManager = new RedeemedReceiptsManager(clock,
|
||||
|
||||
@@ -7,22 +7,37 @@ package org.whispersystems.textsecuregcm.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import org.whispersystems.textsecuregcm.subscriptions.PaymentProvider;
|
||||
import org.whispersystems.textsecuregcm.util.EnumMapUtil;
|
||||
import java.time.Duration;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class IssuedReceiptsTableConfiguration extends DynamoDbTables.TableWithExpiration {
|
||||
|
||||
private final byte[] generator;
|
||||
|
||||
/**
|
||||
* The maximum number of issued receipts the issued receipt manager should issue for a particular itemId
|
||||
*/
|
||||
private final EnumMap<PaymentProvider, Integer> maxIssuedReceiptsPerPaymentId;
|
||||
|
||||
public IssuedReceiptsTableConfiguration(
|
||||
@JsonProperty("tableName") final String tableName,
|
||||
@JsonProperty("expiration") final Duration expiration,
|
||||
@JsonProperty("generator") final byte[] generator) {
|
||||
@JsonProperty("generator") final byte[] generator,
|
||||
@JsonProperty("maxIssuedReceiptsPerPaymentId") final Map<PaymentProvider, Integer> maxIssuedReceiptsPerPaymentId) {
|
||||
super(tableName, expiration);
|
||||
this.generator = generator;
|
||||
this.maxIssuedReceiptsPerPaymentId = EnumMapUtil.toCompleteEnumMap(PaymentProvider.class, maxIssuedReceiptsPerPaymentId);
|
||||
}
|
||||
|
||||
@NotEmpty
|
||||
public byte[] getGenerator() {
|
||||
return generator;
|
||||
}
|
||||
|
||||
public EnumMap<PaymentProvider, Integer> getmaxIssuedReceiptsPerPaymentId() {
|
||||
return maxIssuedReceiptsPerPaymentId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import static org.whispersystems.textsecuregcm.util.AttributeValues.b;
|
||||
import static org.whispersystems.textsecuregcm.util.AttributeValues.n;
|
||||
import static org.whispersystems.textsecuregcm.util.AttributeValues.s;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Throwables;
|
||||
import jakarta.ws.rs.ClientErrorException;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
@@ -17,6 +18,7 @@ import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -46,16 +48,19 @@ public class IssuedReceiptsManager {
|
||||
private final Duration expiration;
|
||||
private final DynamoDbAsyncClient dynamoDbAsyncClient;
|
||||
private final byte[] receiptTagGenerator;
|
||||
private final EnumMap<PaymentProvider, Integer> maxIssuedReceiptsPerPaymentId;
|
||||
|
||||
public IssuedReceiptsManager(
|
||||
@Nonnull String table,
|
||||
@Nonnull Duration expiration,
|
||||
@Nonnull DynamoDbAsyncClient dynamoDbAsyncClient,
|
||||
@Nonnull byte[] receiptTagGenerator) {
|
||||
@Nonnull byte[] receiptTagGenerator,
|
||||
@Nonnull EnumMap<PaymentProvider, Integer> maxIssuedReceiptsPerPaymentId) {
|
||||
this.table = Objects.requireNonNull(table);
|
||||
this.expiration = Objects.requireNonNull(expiration);
|
||||
this.dynamoDbAsyncClient = Objects.requireNonNull(dynamoDbAsyncClient);
|
||||
this.receiptTagGenerator = Objects.requireNonNull(receiptTagGenerator);
|
||||
this.maxIssuedReceiptsPerPaymentId = Objects.requireNonNull(maxIssuedReceiptsPerPaymentId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,19 +79,12 @@ public class IssuedReceiptsManager {
|
||||
ReceiptCredentialRequest request,
|
||||
Instant now) {
|
||||
|
||||
final AttributeValue key;
|
||||
if (processor == PaymentProvider.STRIPE) {
|
||||
// As the first processor, Stripe’s IDs were not prefixed. Its item IDs have documented prefixes (`il_`, `pi_`)
|
||||
// that will not collide with `SubscriptionProcessor` names
|
||||
key = s(processorItemId);
|
||||
} else {
|
||||
key = s(processor.name() + "_" + processorItemId);
|
||||
}
|
||||
final AttributeValue key = dynamoDbKey(processor, processorItemId);
|
||||
final byte[] tag = generateIssuedReceiptTag(request);
|
||||
UpdateItemRequest updateItemRequest = UpdateItemRequest.builder()
|
||||
.tableName(table)
|
||||
.key(Map.of(KEY_PROCESSOR_ITEM_ID, key))
|
||||
.conditionExpression("attribute_not_exists(#key) OR #tag = :tag")
|
||||
.conditionExpression("attribute_not_exists(#key) OR contains(#tags, :tag) OR size(#tags) < :maxTags")
|
||||
.returnValues(ReturnValue.NONE)
|
||||
.updateExpression("SET "
|
||||
+ "#tag = if_not_exists(#tag, :tag), "
|
||||
@@ -100,7 +98,8 @@ public class IssuedReceiptsManager {
|
||||
.expressionAttributeValues(Map.of(
|
||||
":tag", b(tag),
|
||||
":singletonTag", AttributeValue.fromBs(List.of(SdkBytes.fromByteArray(tag))),
|
||||
":exp", n(now.plus(expiration).getEpochSecond())))
|
||||
":exp", n(now.plus(expiration).getEpochSecond()),
|
||||
":maxTags", n(maxIssuedReceiptsPerPaymentId.get(processor))))
|
||||
.build();
|
||||
return dynamoDbAsyncClient.updateItem(updateItemRequest).handle((updateItemResponse, throwable) -> {
|
||||
if (throwable != null) {
|
||||
@@ -115,7 +114,20 @@ public class IssuedReceiptsManager {
|
||||
});
|
||||
}
|
||||
|
||||
private byte[] generateIssuedReceiptTag(ReceiptCredentialRequest request) {
|
||||
@VisibleForTesting
|
||||
static AttributeValue dynamoDbKey(final PaymentProvider processor, String processorItemId) {
|
||||
if (processor == PaymentProvider.STRIPE) {
|
||||
// As the first processor, Stripe’s IDs were not prefixed. Its item IDs have documented prefixes (`il_`, `pi_`)
|
||||
// that will not collide with `SubscriptionProcessor` names
|
||||
return s(processorItemId);
|
||||
} else {
|
||||
return s(processor.name() + "_" + processorItemId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@VisibleForTesting
|
||||
byte[] generateIssuedReceiptTag(ReceiptCredentialRequest request) {
|
||||
return generateHmac("issuedReceiptTag", mac -> mac.update(request.serialize()));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -21,4 +22,13 @@ public class EnumMapUtil {
|
||||
},
|
||||
() -> new EnumMap<>(enumClass)));
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>, V> EnumMap<E, V> toCompleteEnumMap(final Class<E> enumClass, final Map<E, V> map) {
|
||||
for (E e : enumClass.getEnumConstants()) {
|
||||
if (!map.containsKey(e)) {
|
||||
throw new IllegalArgumentException("Missing enum key: " + e);
|
||||
}
|
||||
}
|
||||
return new EnumMap<>(map);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +268,8 @@ record CommandDependencies(
|
||||
configuration.getDynamoDbTables().getIssuedReceipts().getTableName(),
|
||||
configuration.getDynamoDbTables().getIssuedReceipts().getExpiration(),
|
||||
dynamoDbAsyncClient,
|
||||
configuration.getDynamoDbTables().getIssuedReceipts().getGenerator());
|
||||
configuration.getDynamoDbTables().getIssuedReceipts().getGenerator(),
|
||||
configuration.getDynamoDbTables().getIssuedReceipts().getmaxIssuedReceiptsPerPaymentId());
|
||||
|
||||
APNSender apnSender = new APNSender(apnSenderExecutor, configuration.getApnConfiguration());
|
||||
FcmSender fcmSender = new FcmSender(fcmSenderExecutor, configuration.getFcmConfiguration().credentials().value());
|
||||
|
||||
Reference in New Issue
Block a user