Update to libsignal 0.45 and use libsignal's BackupLevel
This commit is contained in:
parent
c8efcf5105
commit
19944bfdb2
2
pom.xml
2
pom.xml
|
@ -278,7 +278,7 @@
|
|||
<dependency>
|
||||
<groupId>org.signal</groupId>
|
||||
<artifactId>libsignal-server</artifactId>
|
||||
<version>0.44.0</version>
|
||||
<version>0.45.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.signal.forks</groupId>
|
||||
|
|
|
@ -5,6 +5,6 @@
|
|||
|
||||
package org.whispersystems.textsecuregcm.auth;
|
||||
|
||||
import org.whispersystems.textsecuregcm.backup.BackupTier;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupLevel;
|
||||
|
||||
public record AuthenticatedBackupUser(byte[] backupId, BackupTier backupTier, String backupDir, String mediaDir) {}
|
||||
public record AuthenticatedBackupUser(byte[] backupId, BackupLevel backupLevel, String backupDir, String mediaDir) {}
|
||||
|
|
|
@ -11,7 +11,6 @@ import java.time.Clock;
|
|||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
@ -21,6 +20,7 @@ import org.signal.libsignal.zkgroup.InvalidInputException;
|
|||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialRequest;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialResponse;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupLevel;
|
||||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation;
|
||||
import org.signal.libsignal.zkgroup.receipts.ReceiptSerial;
|
||||
import org.signal.libsignal.zkgroup.receipts.ServerZkReceiptOperations;
|
||||
|
@ -89,7 +89,7 @@ public class BackupAuthManager {
|
|||
*/
|
||||
public CompletableFuture<Void> commitBackupId(final Account account,
|
||||
final BackupAuthCredentialRequest backupAuthCredentialRequest) throws RateLimitExceededException {
|
||||
if (configuredReceiptLevel(account).isEmpty()) {
|
||||
if (configuredBackupLevel(account).isEmpty()) {
|
||||
throw Status.PERMISSION_DENIED.withDescription("Backups not allowed on account").asRuntimeException();
|
||||
}
|
||||
|
||||
|
@ -141,7 +141,7 @@ public class BackupAuthManager {
|
|||
}
|
||||
|
||||
// If this account isn't allowed some level of backup access via configuration, don't continue
|
||||
final long configuredReceiptLevel = configuredReceiptLevel(account).orElseThrow(() ->
|
||||
final BackupLevel configuredBackupLevel = configuredBackupLevel(account).orElseThrow(() ->
|
||||
Status.PERMISSION_DENIED.withDescription("Backups not allowed on account").asRuntimeException());
|
||||
|
||||
final Instant startOfDay = clock.instant().truncatedTo(ChronoUnit.DAYS);
|
||||
|
@ -169,9 +169,9 @@ public class BackupAuthManager {
|
|||
.map(redemptionTime -> {
|
||||
// Check if the account has a voucher that's good for a certain receiptLevel at redemption time, otherwise
|
||||
// use the default receipt level
|
||||
final long receiptLevel = storedReceiptLevel(account, redemptionTime).orElse(configuredReceiptLevel);
|
||||
final BackupLevel backupLevel = storedBackupLevel(account, redemptionTime).orElse(configuredBackupLevel);
|
||||
return new Credential(
|
||||
credentialReq.issueCredential(redemptionTime, receiptLevel, serverSecretParams),
|
||||
credentialReq.issueCredential(redemptionTime, backupLevel, serverSecretParams),
|
||||
redemptionTime);
|
||||
})
|
||||
.toList());
|
||||
|
@ -208,10 +208,11 @@ public class BackupAuthManager {
|
|||
|
||||
final long receiptLevel = receiptCredentialPresentation.getReceiptLevel();
|
||||
|
||||
BackupTier.fromReceiptLevel(receiptLevel).filter(BackupTier.MEDIA::equals)
|
||||
.orElseThrow(() -> Status.INVALID_ARGUMENT
|
||||
.withDescription("server does not recognize the requested receipt level")
|
||||
.asRuntimeException());
|
||||
if (BackupLevelUtil.fromReceiptLevel(receiptLevel) != BackupLevel.MEDIA) {
|
||||
throw Status.INVALID_ARGUMENT
|
||||
.withDescription("server does not recognize the requested receipt level")
|
||||
.asRuntimeException();
|
||||
}
|
||||
|
||||
return redeemedReceiptsManager
|
||||
.put(receiptSerial, receiptExpiration.getEpochSecond(), receiptLevel, account.getUuid())
|
||||
|
@ -262,10 +263,11 @@ public class BackupAuthManager {
|
|||
* @param redemptionTime The time to check against the expiration time
|
||||
* @return The receipt level on the backup voucher, or empty if the account does not have one or it is expired
|
||||
*/
|
||||
private Optional<Long> storedReceiptLevel(final Account account, final Instant redemptionTime) {
|
||||
private Optional<BackupLevel> storedBackupLevel(final Account account, final Instant redemptionTime) {
|
||||
return Optional.ofNullable(account.getBackupVoucher())
|
||||
.filter(backupVoucher -> !redemptionTime.isAfter(backupVoucher.expiration()))
|
||||
.map(Account.BackupVoucher::receiptLevel);
|
||||
.map(Account.BackupVoucher::receiptLevel)
|
||||
.map(BackupLevelUtil::fromReceiptLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -275,12 +277,12 @@ public class BackupAuthManager {
|
|||
* @return If present, the default receipt level that should be used for the account if the account does not have a
|
||||
* BackupVoucher. Empty if the account should never have backup access
|
||||
*/
|
||||
private Optional<Long> configuredReceiptLevel(final Account account) {
|
||||
private Optional<BackupLevel> configuredBackupLevel(final Account account) {
|
||||
if (inExperiment(BACKUP_MEDIA_EXPERIMENT_NAME, account)) {
|
||||
return Optional.of(BackupTier.MEDIA.getReceiptLevel());
|
||||
return Optional.of(BackupLevel.MEDIA);
|
||||
}
|
||||
if (inExperiment(BACKUP_EXPERIMENT_NAME, account)) {
|
||||
return Optional.of(BackupTier.MESSAGES.getReceiptLevel());
|
||||
return Optional.of(BackupLevel.MESSAGES);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.backup;
|
||||
|
||||
import org.signal.libsignal.zkgroup.backups.BackupLevel;
|
||||
|
||||
public class BackupLevelUtil {
|
||||
public static BackupLevel fromReceiptLevel(long receiptLevel) {
|
||||
try {
|
||||
return BackupLevel.fromValue(Math.toIntExact(receiptLevel));
|
||||
} catch (ArithmeticException e) {
|
||||
throw new IllegalArgumentException("Invalid receipt level: " + receiptLevel);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ import org.signal.libsignal.protocol.ecc.ECPublicKey;
|
|||
import org.signal.libsignal.zkgroup.GenericServerSecretParams;
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialPresentation;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupLevel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.attachments.AttachmentGenerator;
|
||||
|
@ -126,14 +127,8 @@ public class BackupManager {
|
|||
// Note: this is a special case where we can't validate the presentation signature against the stored public key
|
||||
// because we are currently setting it. We check against the provided public key, but we must also verify that
|
||||
// there isn't an existing, different stored public key for the backup-id (verified with a condition expression)
|
||||
final BackupTier backupTier = verifyPresentation(presentation).verifySignature(signature, publicKey);
|
||||
if (backupTier.compareTo(BackupTier.MESSAGES) < 0) {
|
||||
Metrics.counter(ZK_AUTHZ_FAILURE_COUNTER_NAME).increment();
|
||||
throw Status.PERMISSION_DENIED
|
||||
.withDescription("credential does not support setting public key")
|
||||
.asRuntimeException();
|
||||
}
|
||||
return backupsDb.setPublicKey(presentation.getBackupId(), backupTier, publicKey)
|
||||
final BackupLevel backupLevel = verifyPresentation(presentation).verifySignature(signature, publicKey);
|
||||
return backupsDb.setPublicKey(presentation.getBackupId(), backupLevel, publicKey)
|
||||
.exceptionally(ExceptionUtils.exceptionallyHandler(PublicKeyConflictException.class, ex -> {
|
||||
Metrics.counter(ZK_AUTHN_COUNTER_NAME,
|
||||
SUCCESS_TAG_NAME, String.valueOf(false),
|
||||
|
@ -156,7 +151,7 @@ public class BackupManager {
|
|||
*/
|
||||
public CompletableFuture<BackupUploadDescriptor> createMessageBackupUploadDescriptor(
|
||||
final AuthenticatedBackupUser backupUser) {
|
||||
checkBackupTier(backupUser, BackupTier.MESSAGES);
|
||||
checkBackupLevel(backupUser, BackupLevel.MESSAGES);
|
||||
|
||||
// this could race with concurrent updates, but the only effect would be last-writer-wins on the timestamp
|
||||
return backupsDb
|
||||
|
@ -166,7 +161,7 @@ public class BackupManager {
|
|||
|
||||
public BackupUploadDescriptor createTemporaryAttachmentUploadDescriptor(final AuthenticatedBackupUser backupUser)
|
||||
throws RateLimitExceededException {
|
||||
checkBackupTier(backupUser, BackupTier.MEDIA);
|
||||
checkBackupLevel(backupUser, BackupLevel.MEDIA);
|
||||
|
||||
RateLimiter.adaptLegacyException(() -> rateLimiters
|
||||
.forDescriptor(RateLimiters.For.BACKUP_ATTACHMENT)
|
||||
|
@ -185,7 +180,7 @@ public class BackupManager {
|
|||
* @param backupUser an already ZK authenticated backup user
|
||||
*/
|
||||
public CompletableFuture<Void> ttlRefresh(final AuthenticatedBackupUser backupUser) {
|
||||
checkBackupTier(backupUser, BackupTier.MESSAGES);
|
||||
checkBackupLevel(backupUser, BackupLevel.MESSAGES);
|
||||
// update message backup TTL
|
||||
return backupsDb.ttlRefresh(backupUser);
|
||||
}
|
||||
|
@ -200,7 +195,7 @@ public class BackupManager {
|
|||
* @return Information about the existing backup
|
||||
*/
|
||||
public CompletableFuture<BackupInfo> backupInfo(final AuthenticatedBackupUser backupUser) {
|
||||
checkBackupTier(backupUser, BackupTier.MESSAGES);
|
||||
checkBackupLevel(backupUser, BackupLevel.MESSAGES);
|
||||
return backupsDb.describeBackup(backupUser)
|
||||
.thenApply(backupDescription -> new BackupInfo(
|
||||
backupDescription.cdn(),
|
||||
|
@ -218,7 +213,7 @@ public class BackupManager {
|
|||
* @return true if mediaLength bytes can be stored
|
||||
*/
|
||||
public CompletableFuture<Boolean> canStoreMedia(final AuthenticatedBackupUser backupUser, final long mediaLength) {
|
||||
checkBackupTier(backupUser, BackupTier.MEDIA);
|
||||
checkBackupLevel(backupUser, BackupLevel.MEDIA);
|
||||
return backupsDb.getMediaUsage(backupUser)
|
||||
.thenComposeAsync(info -> {
|
||||
final boolean canStore = MAX_TOTAL_BACKUP_MEDIA_BYTES - info.usageInfo().bytesUsed() >= mediaLength;
|
||||
|
@ -269,7 +264,7 @@ public class BackupManager {
|
|||
final int sourceLength,
|
||||
final MediaEncryptionParameters encryptionParameters,
|
||||
final byte[] destinationMediaId) {
|
||||
checkBackupTier(backupUser, BackupTier.MEDIA);
|
||||
checkBackupLevel(backupUser, BackupLevel.MEDIA);
|
||||
if (sourceLength > MAX_MEDIA_OBJECT_SIZE) {
|
||||
throw Status.INVALID_ARGUMENT
|
||||
.withDescription("Invalid sourceObject size")
|
||||
|
@ -331,7 +326,7 @@ public class BackupManager {
|
|||
* @return A map of headers to include with CDN requests
|
||||
*/
|
||||
public Map<String, String> generateReadAuth(final AuthenticatedBackupUser backupUser, final int cdnNumber) {
|
||||
checkBackupTier(backupUser, BackupTier.MESSAGES);
|
||||
checkBackupLevel(backupUser, BackupLevel.MESSAGES);
|
||||
if (cdnNumber != 3) {
|
||||
throw Status.INVALID_ARGUMENT.withDescription("unknown cdn").asRuntimeException();
|
||||
}
|
||||
|
@ -359,7 +354,7 @@ public class BackupManager {
|
|||
final AuthenticatedBackupUser backupUser,
|
||||
final Optional<String> cursor,
|
||||
final int limit) {
|
||||
checkBackupTier(backupUser, BackupTier.MESSAGES);
|
||||
checkBackupLevel(backupUser, BackupLevel.MESSAGES);
|
||||
return remoteStorageManager.list(cdnMediaDirectory(backupUser), cursor, limit)
|
||||
.thenApply(result ->
|
||||
new ListMediaResult(
|
||||
|
@ -377,7 +372,7 @@ public class BackupManager {
|
|||
}
|
||||
|
||||
public CompletableFuture<Void> deleteEntireBackup(final AuthenticatedBackupUser backupUser) {
|
||||
checkBackupTier(backupUser, BackupTier.MESSAGES);
|
||||
checkBackupLevel(backupUser, BackupLevel.MESSAGES);
|
||||
return backupsDb
|
||||
// Try to swap out the backupDir for the user
|
||||
.scheduleBackupDeletion(backupUser)
|
||||
|
@ -395,7 +390,7 @@ public class BackupManager {
|
|||
|
||||
public CompletableFuture<Void> delete(final AuthenticatedBackupUser backupUser,
|
||||
final List<StorageDescriptor> storageDescriptors) {
|
||||
checkBackupTier(backupUser, BackupTier.MESSAGES);
|
||||
checkBackupLevel(backupUser, BackupLevel.MESSAGES);
|
||||
|
||||
if (storageDescriptors.stream().anyMatch(sd -> sd.cdn() != remoteStorageManager.cdnNumber())) {
|
||||
throw Status.INVALID_ARGUMENT
|
||||
|
@ -556,7 +551,7 @@ public class BackupManager {
|
|||
|
||||
interface PresentationSignatureVerifier {
|
||||
|
||||
BackupTier verifySignature(byte[] signature, ECPublicKey publicKey);
|
||||
BackupLevel verifySignature(byte[] signature, ECPublicKey publicKey);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -588,27 +583,19 @@ public class BackupManager {
|
|||
.withDescription("backup auth credential presentation signature verification failed")
|
||||
.asRuntimeException();
|
||||
}
|
||||
return BackupTier
|
||||
.fromReceiptLevel(presentation.getReceiptLevel())
|
||||
.orElseThrow(() -> {
|
||||
Metrics.counter(ZK_AUTHN_COUNTER_NAME,
|
||||
SUCCESS_TAG_NAME, String.valueOf(false),
|
||||
FAILURE_REASON_TAG_NAME, "invalid_receipt_level")
|
||||
.increment();
|
||||
return Status.PERMISSION_DENIED.withDescription("invalid receipt level").asRuntimeException();
|
||||
});
|
||||
return presentation.getBackupLevel();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the authenticated backup user is authorized to use the provided backupTier
|
||||
* Check that the authenticated backup user is authorized to use the provided backupLevel
|
||||
*
|
||||
* @param backupUser The backup user to check
|
||||
* @param backupTier The authorization level to verify the backupUser has access to
|
||||
* @throws {@link Status#PERMISSION_DENIED} error if the backup user is not authorized to access {@code backupTier}
|
||||
* @param backupLevel The authorization level to verify the backupUser has access to
|
||||
* @throws {@link Status#PERMISSION_DENIED} error if the backup user is not authorized to access {@code backupLevel}
|
||||
*/
|
||||
private static void checkBackupTier(final AuthenticatedBackupUser backupUser, final BackupTier backupTier) {
|
||||
if (backupUser.backupTier().compareTo(backupTier) < 0) {
|
||||
private static void checkBackupLevel(final AuthenticatedBackupUser backupUser, final BackupLevel backupLevel) {
|
||||
if (backupUser.backupLevel().compareTo(backupLevel) < 0) {
|
||||
Metrics.counter(ZK_AUTHZ_FAILURE_COUNTER_NAME).increment();
|
||||
throw Status.PERMISSION_DENIED
|
||||
.withDescription("credential does not support the requested operation")
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.backup;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Maps receipt levels to BackupTiers. Existing receipt levels should never be remapped to a different tier.
|
||||
* <p>
|
||||
* Today, receipt levels 1:1 correspond to tiers, but in the future multiple receipt levels may be accepted for access
|
||||
* to a single tier.
|
||||
*/
|
||||
public enum BackupTier {
|
||||
NONE(0),
|
||||
MESSAGES(200),
|
||||
MEDIA(201);
|
||||
|
||||
private static Map<Long, BackupTier> LOOKUP = Arrays.stream(BackupTier.values())
|
||||
.collect(Collectors.toMap(BackupTier::getReceiptLevel, Function.identity()));
|
||||
private long receiptLevel;
|
||||
|
||||
BackupTier(long receiptLevel) {
|
||||
this.receiptLevel = receiptLevel;
|
||||
}
|
||||
|
||||
long getReceiptLevel() {
|
||||
return receiptLevel;
|
||||
}
|
||||
|
||||
public static Optional<BackupTier> fromReceiptLevel(long receiptLevel) {
|
||||
return Optional.ofNullable(LOOKUP.get(receiptLevel));
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ import java.util.concurrent.CompletableFuture;
|
|||
import java.util.function.Predicate;
|
||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupLevel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedBackupUser;
|
||||
|
@ -45,10 +46,10 @@ import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
|
|||
* Tracks backup metadata in a persistent store.
|
||||
* <p>
|
||||
* It's assumed that the caller has already validated that the backupUser being operated on has valid credentials and
|
||||
* possesses the appropriate {@link BackupTier} to perform the current operation.
|
||||
* possesses the appropriate {@link BackupLevel} to perform the current operation.
|
||||
* <p>
|
||||
* Backup records track two timestamps indicating the last time that a user interacted with their backup. One for the
|
||||
* last refresh that contained a credential including media tier, and the other for any access. After a period of
|
||||
* last refresh that contained a credential including media level, and the other for any access. After a period of
|
||||
* inactivity stale backups can be purged (either just the media, or the entire backup). Callers can discover what
|
||||
* backups are stale and whether only the media or the entire backup is stale via {@link #getExpiredBackups}.
|
||||
* <p>
|
||||
|
@ -86,7 +87,7 @@ public class BackupsDb {
|
|||
// garbage collection of archive objects.
|
||||
public static final String ATTR_LAST_REFRESH = "R";
|
||||
// N: Time in seconds since epoch of the last backup media refresh. This timestamp can only be updated if the client
|
||||
// has BackupTier.MEDIA, and must be periodically updated to avoid garbage collection of media objects.
|
||||
// has BackupLevel.MEDIA, and must be periodically updated to avoid garbage collection of media objects.
|
||||
public static final String ATTR_LAST_MEDIA_REFRESH = "MR";
|
||||
// B: A 32 byte public key that should be used to sign the presentation used to authenticate requests against the
|
||||
// backup-id
|
||||
|
@ -120,18 +121,18 @@ public class BackupsDb {
|
|||
/**
|
||||
* Set the public key associated with a backupId.
|
||||
*
|
||||
* @param authenticatedBackupId The backup-id bytes that should be associated with the provided public key
|
||||
* @param authenticatedBackupTier The backup tier
|
||||
* @param publicKey The public key to associate with the backup id
|
||||
* @param authenticatedBackupId The backup-id bytes that should be associated with the provided public key
|
||||
* @param authenticatedBackupLevel The backup level
|
||||
* @param publicKey The public key to associate with the backup id
|
||||
* @return A stage that completes when the public key has been set. If the backup-id already has a set public key that
|
||||
* does not match, the stage will be completed exceptionally with a {@link PublicKeyConflictException}
|
||||
*/
|
||||
CompletableFuture<Void> setPublicKey(
|
||||
final byte[] authenticatedBackupId,
|
||||
final BackupTier authenticatedBackupTier,
|
||||
final BackupLevel authenticatedBackupLevel,
|
||||
final ECPublicKey publicKey) {
|
||||
final byte[] hashedBackupId = hashedBackupId(authenticatedBackupId);
|
||||
return dynamoClient.updateItem(new UpdateBuilder(backupTableName, authenticatedBackupTier, hashedBackupId)
|
||||
return dynamoClient.updateItem(new UpdateBuilder(backupTableName, authenticatedBackupLevel, hashedBackupId)
|
||||
.addSetExpression("#publicKey = :publicKey",
|
||||
Map.entry("#publicKey", ATTR_PUBLIC_KEY),
|
||||
Map.entry(":publicKey", AttributeValues.b(publicKey.serialize())))
|
||||
|
@ -284,7 +285,7 @@ public class BackupsDb {
|
|||
final byte[] hashedBackupId = hashedBackupId(backupUser);
|
||||
|
||||
// Clear usage metadata, swap names of things we intend to delete, and record our intent to delete in attr_expired_prefix
|
||||
return dynamoClient.updateItem(new UpdateBuilder(backupTableName, BackupTier.MEDIA, hashedBackupId)
|
||||
return dynamoClient.updateItem(new UpdateBuilder(backupTableName, BackupLevel.MEDIA, hashedBackupId)
|
||||
.clearMediaUsage(clock)
|
||||
.expireDirectoryNames(secureRandom, ExpiredBackup.ExpirationType.ALL)
|
||||
.setRefreshTimes(Instant.ofEpochSecond(0))
|
||||
|
@ -299,7 +300,7 @@ public class BackupsDb {
|
|||
// is toggling backups on and off. In this case, it should be pretty cheap to directly delete the backup.
|
||||
// Instead of changing the backupDir, just make sure the row has expired/ timestamps and tell the caller we
|
||||
// couldn't schedule the deletion.
|
||||
dynamoClient.updateItem(new UpdateBuilder(backupTableName, BackupTier.MEDIA, hashedBackupId)
|
||||
dynamoClient.updateItem(new UpdateBuilder(backupTableName, BackupLevel.MEDIA, hashedBackupId)
|
||||
.setRefreshTimes(Instant.ofEpochSecond(0))
|
||||
.updateItemBuilder()
|
||||
.build())
|
||||
|
@ -398,7 +399,7 @@ public class BackupsDb {
|
|||
}
|
||||
|
||||
// Clear usage metadata, swap names of things we intend to delete, and record our intent to delete in attr_expired_prefix
|
||||
return dynamoClient.updateItem(new UpdateBuilder(backupTableName, BackupTier.MEDIA, expiredBackup.hashedBackupId())
|
||||
return dynamoClient.updateItem(new UpdateBuilder(backupTableName, BackupLevel.MEDIA, expiredBackup.hashedBackupId())
|
||||
.clearMediaUsage(clock)
|
||||
.expireDirectoryNames(secureRandom, expiredBackup.expirationType())
|
||||
.addRemoveExpression(Map.entry("#mediaRefresh", ATTR_LAST_MEDIA_REFRESH))
|
||||
|
@ -432,7 +433,7 @@ public class BackupsDb {
|
|||
.build())
|
||||
.thenRun(Util.NOOP);
|
||||
} else {
|
||||
return dynamoClient.updateItem(new UpdateBuilder(backupTableName, BackupTier.MEDIA, hashedBackupId)
|
||||
return dynamoClient.updateItem(new UpdateBuilder(backupTableName, BackupLevel.MEDIA, hashedBackupId)
|
||||
.addRemoveExpression(Map.entry("#expiredPrefixes", ATTR_EXPIRED_PREFIX))
|
||||
.updateItemBuilder()
|
||||
.build())
|
||||
|
@ -539,17 +540,17 @@ public class BackupsDb {
|
|||
private final Map<String, String> attrNames = new HashMap<>();
|
||||
|
||||
private final String tableName;
|
||||
private final BackupTier backupTier;
|
||||
private final BackupLevel backupLevel;
|
||||
private final byte[] hashedBackupId;
|
||||
private String conditionExpression = null;
|
||||
|
||||
static UpdateBuilder forUser(String tableName, AuthenticatedBackupUser backupUser) {
|
||||
return new UpdateBuilder(tableName, backupUser.backupTier(), hashedBackupId(backupUser));
|
||||
return new UpdateBuilder(tableName, backupUser.backupLevel(), hashedBackupId(backupUser));
|
||||
}
|
||||
|
||||
UpdateBuilder(String tableName, BackupTier backupTier, byte[] hashedBackupId) {
|
||||
UpdateBuilder(String tableName, BackupLevel backupLevel, byte[] hashedBackupId) {
|
||||
this.tableName = tableName;
|
||||
this.backupTier = backupTier;
|
||||
this.backupLevel = backupLevel;
|
||||
this.hashedBackupId = hashedBackupId;
|
||||
}
|
||||
|
||||
|
@ -679,7 +680,7 @@ public class BackupsDb {
|
|||
* Set the lastRefresh time as part of the update
|
||||
* <p>
|
||||
* This always updates lastRefreshTime, and updates lastMediaRefreshTime if the backup user has the appropriate
|
||||
* tier.
|
||||
* level.
|
||||
*/
|
||||
UpdateBuilder setRefreshTimes(final Clock clock) {
|
||||
return this.setRefreshTimes(clock.instant());
|
||||
|
@ -690,8 +691,8 @@ public class BackupsDb {
|
|||
Map.entry("#lastRefreshTime", ATTR_LAST_REFRESH),
|
||||
Map.entry(":lastRefreshTime", AttributeValues.n(refreshTime.getEpochSecond())));
|
||||
|
||||
if (backupTier.compareTo(BackupTier.MEDIA) >= 0) {
|
||||
// update the media time if we have the appropriate tier
|
||||
if (backupLevel.compareTo(BackupLevel.MEDIA) >= 0) {
|
||||
// update the media time if we have the appropriate level
|
||||
addSetExpression("#lastMediaRefreshTime = :lastMediaRefreshTime",
|
||||
Map.entry("#lastMediaRefreshTime", ATTR_LAST_MEDIA_REFRESH),
|
||||
Map.entry(":lastMediaRefreshTime", AttributeValues.n(refreshTime.getEpochSecond())));
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
package org.whispersystems.textsecuregcm.backup;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a backup that requires some or all of its content to be deleted
|
||||
|
|
|
@ -10,17 +10,18 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
|||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.collect.Sets;
|
||||
import io.dropwizard.validation.ValidationMethod;
|
||||
import org.whispersystems.textsecuregcm.backup.BackupLevelUtil;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import org.whispersystems.textsecuregcm.backup.BackupTier;
|
||||
|
||||
public class SubscriptionConfiguration {
|
||||
|
||||
|
@ -78,11 +79,11 @@ public class SubscriptionConfiguration {
|
|||
// We have a tier for all configured backup levels
|
||||
final boolean backupLevelsMatch = backupLevels.keySet()
|
||||
.stream()
|
||||
.allMatch(level -> BackupTier.fromReceiptLevel(level).orElse(BackupTier.NONE) != BackupTier.NONE);
|
||||
.allMatch(SubscriptionConfiguration::isValidBackupLevel);
|
||||
|
||||
// None of the donation levels correspond to backup levels
|
||||
final boolean donationLevelsDontMatch = donationLevels.keySet().stream()
|
||||
.allMatch(level -> BackupTier.fromReceiptLevel(level).orElse(BackupTier.NONE) == BackupTier.NONE);
|
||||
.allMatch(Predicate.not(SubscriptionConfiguration::isValidBackupLevel));
|
||||
|
||||
// The configured donation and backup levels don't intersect
|
||||
final boolean levelsDontIntersect = Sets.intersection(backupLevels.keySet(), donationLevels.keySet()).isEmpty();
|
||||
|
@ -105,4 +106,13 @@ public class SubscriptionConfiguration {
|
|||
Set<String> currencies = any.get().prices().keySet();
|
||||
return subscriptionLevels.values().stream().allMatch(level -> currencies.equals(level.prices().keySet()));
|
||||
}
|
||||
|
||||
private static boolean isValidBackupLevel(final long receiptLevel) {
|
||||
try {
|
||||
BackupLevelUtil.fromReceiptLevel(receiptLevel);
|
||||
return true;
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.junit.jupiter.params.ParameterizedTest;
|
|||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.NullSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
|
@ -44,6 +45,7 @@ import org.signal.libsignal.zkgroup.ServerSecretParams;
|
|||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialRequest;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialRequestContext;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupLevel;
|
||||
import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations;
|
||||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredential;
|
||||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation;
|
||||
|
@ -61,6 +63,7 @@ import org.whispersystems.textsecuregcm.tests.util.ExperimentHelper;
|
|||
import org.whispersystems.textsecuregcm.util.CompletableFutureTestUtil;
|
||||
import org.whispersystems.textsecuregcm.util.TestClock;
|
||||
import org.whispersystems.textsecuregcm.util.TestRandomUtil;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class BackupAuthManagerTest {
|
||||
|
||||
|
@ -79,9 +82,9 @@ public class BackupAuthManagerTest {
|
|||
reset(redeemedReceiptsManager);
|
||||
}
|
||||
|
||||
BackupAuthManager create(BackupTier backupTier, boolean rateLimit) {
|
||||
BackupAuthManager create(@Nullable BackupLevel backupLevel, boolean rateLimit) {
|
||||
return new BackupAuthManager(
|
||||
ExperimentHelper.withEnrollment(experimentName(backupTier), aci),
|
||||
ExperimentHelper.withEnrollment(experimentName(backupLevel), aci),
|
||||
rateLimit ? denyRateLimiter(aci) : allowRateLimiter(),
|
||||
accountsManager,
|
||||
new ServerZkReceiptOperations(receiptParams),
|
||||
|
@ -92,15 +95,16 @@ public class BackupAuthManagerTest {
|
|||
|
||||
@ParameterizedTest
|
||||
@EnumSource
|
||||
void commitRequiresBackupTier(final BackupTier backupTier) {
|
||||
final BackupAuthManager authManager = create(backupTier, false);
|
||||
@NullSource
|
||||
void commitRequiresBackupLevel(final BackupLevel backupLevel) {
|
||||
final BackupAuthManager authManager = create(backupLevel, false);
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getUuid()).thenReturn(aci);
|
||||
when(accountsManager.updateAsync(any(), any())).thenReturn(CompletableFuture.completedFuture(account));
|
||||
|
||||
final ThrowableAssert.ThrowingCallable commit = () ->
|
||||
authManager.commitBackupId(account, backupAuthTestUtil.getRequest(backupKey, aci)).join();
|
||||
if (backupTier == BackupTier.NONE) {
|
||||
if (backupLevel == null) {
|
||||
Assertions.assertThatExceptionOfType(StatusRuntimeException.class)
|
||||
.isThrownBy(commit)
|
||||
.extracting(ex -> ex.getStatus().getCode())
|
||||
|
@ -113,8 +117,9 @@ public class BackupAuthManagerTest {
|
|||
|
||||
@ParameterizedTest
|
||||
@EnumSource
|
||||
void credentialsRequiresBackupTier(final BackupTier backupTier) {
|
||||
final BackupAuthManager authManager = create(backupTier, false);
|
||||
@NullSource
|
||||
void credentialsRequiresBackupLevel(final BackupLevel backupLevel) {
|
||||
final BackupAuthManager authManager = create(backupLevel, false);
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getUuid()).thenReturn(aci);
|
||||
|
@ -125,7 +130,7 @@ public class BackupAuthManagerTest {
|
|||
clock.instant().truncatedTo(ChronoUnit.DAYS),
|
||||
clock.instant().plus(Duration.ofDays(1)).truncatedTo(ChronoUnit.DAYS)).join())
|
||||
.hasSize(2);
|
||||
if (backupTier == BackupTier.NONE) {
|
||||
if (backupLevel == null) {
|
||||
Assertions.assertThatExceptionOfType(StatusRuntimeException.class)
|
||||
.isThrownBy(getCreds)
|
||||
.extracting(ex -> ex.getStatus().getCode())
|
||||
|
@ -136,9 +141,9 @@ public class BackupAuthManagerTest {
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(mode = EnumSource.Mode.EXCLUDE, names = {"NONE"})
|
||||
void getReceiptCredentials(final BackupTier backupTier) throws VerificationFailedException {
|
||||
final BackupAuthManager authManager = create(backupTier, false);
|
||||
@EnumSource
|
||||
void getReceiptCredentials(final BackupLevel backupLevel) throws VerificationFailedException {
|
||||
final BackupAuthManager authManager = create(backupLevel, false);
|
||||
|
||||
final BackupAuthCredentialRequestContext requestContext = BackupAuthCredentialRequestContext.create(backupKey, aci);
|
||||
|
||||
|
@ -153,10 +158,11 @@ public class BackupAuthManagerTest {
|
|||
assertThat(creds).hasSize(8);
|
||||
Instant redemptionTime = start;
|
||||
for (BackupAuthManager.Credential cred : creds) {
|
||||
requestContext.receiveResponse(cred.credential(), backupAuthTestUtil.params.getPublicParams(),
|
||||
backupTier.getReceiptLevel());
|
||||
assertThat(cred.redemptionTime().getEpochSecond())
|
||||
.isEqualTo(redemptionTime.getEpochSecond());
|
||||
assertThat(requestContext
|
||||
.receiveResponse(cred.credential(), redemptionTime, backupAuthTestUtil.params.getPublicParams())
|
||||
.getBackupLevel())
|
||||
.isEqualTo(backupLevel);
|
||||
assertThat(cred.redemptionTime().getEpochSecond()).isEqualTo(redemptionTime.getEpochSecond());
|
||||
redemptionTime = redemptionTime.plus(Duration.ofDays(1));
|
||||
}
|
||||
}
|
||||
|
@ -185,7 +191,7 @@ public class BackupAuthManagerTest {
|
|||
@MethodSource
|
||||
void invalidCredentialTimeWindows(final Instant requestRedemptionStart, final Instant requestRedemptionEnd,
|
||||
final Instant now) {
|
||||
final BackupAuthManager authManager = create(BackupTier.MESSAGES, false);
|
||||
final BackupAuthManager authManager = create(BackupLevel.MESSAGES, false);
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getUuid()).thenReturn(aci);
|
||||
|
@ -206,21 +212,24 @@ public class BackupAuthManagerTest {
|
|||
final Instant day4 = Instant.EPOCH.plus(Duration.ofDays(4));
|
||||
final Instant dayMax = day0.plus(BackupAuthManager.MAX_REDEMPTION_DURATION);
|
||||
|
||||
final BackupAuthManager authManager = create(BackupTier.MESSAGES, false);
|
||||
final BackupAuthManager authManager = create(BackupLevel.MESSAGES, false);
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getUuid()).thenReturn(aci);
|
||||
when(account.getBackupCredentialRequest()).thenReturn(backupAuthTestUtil.getRequest(backupKey, aci).serialize());
|
||||
when(account.getBackupVoucher()).thenReturn(new Account.BackupVoucher(BackupTier.MEDIA.getReceiptLevel(), day4));
|
||||
when(account.getBackupVoucher()).thenReturn(new Account.BackupVoucher(201, day4));
|
||||
|
||||
final List<BackupAuthManager.Credential> creds = authManager.getBackupAuthCredentials(account, day0, dayMax).join();
|
||||
Instant redemptionTime = day0;
|
||||
final BackupAuthCredentialRequestContext requestContext = BackupAuthCredentialRequestContext.create(backupKey, aci);
|
||||
for (int i = 0; i < creds.size(); i++) {
|
||||
// Before the expiration, credentials should have a media receipt, otherwise messages only
|
||||
final long level = i < 5 ? BackupTier.MEDIA.getReceiptLevel() : BackupTier.MESSAGES.getReceiptLevel();
|
||||
final BackupLevel level = i < 5 ? BackupLevel.MEDIA : BackupLevel.MESSAGES;
|
||||
final BackupAuthManager.Credential cred = creds.get(i);
|
||||
requestContext.receiveResponse(cred.credential(), backupAuthTestUtil.params.getPublicParams(), level);
|
||||
assertThat(requestContext
|
||||
.receiveResponse(cred.credential(), redemptionTime, backupAuthTestUtil.params.getPublicParams())
|
||||
.getBackupLevel())
|
||||
.isEqualTo(level);
|
||||
assertThat(cred.redemptionTime().getEpochSecond()).isEqualTo(redemptionTime.getEpochSecond());
|
||||
redemptionTime = redemptionTime.plus(Duration.ofDays(1));
|
||||
}
|
||||
|
@ -232,7 +241,7 @@ public class BackupAuthManagerTest {
|
|||
final Instant day2 = Instant.EPOCH.plus(Duration.ofDays(2));
|
||||
final Instant day3 = Instant.EPOCH.plus(Duration.ofDays(3));
|
||||
|
||||
final BackupAuthManager authManager = create(BackupTier.MESSAGES, false);
|
||||
final BackupAuthManager authManager = create(BackupLevel.MESSAGES, false);
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getUuid()).thenReturn(aci);
|
||||
when(account.getBackupVoucher()).thenReturn(new Account.BackupVoucher(3, day1));
|
||||
|
@ -247,8 +256,8 @@ public class BackupAuthManagerTest {
|
|||
assertThat(authManager.getBackupAuthCredentials(account, day2, day2.plus(Duration.ofDays(7))).join())
|
||||
.hasSize(8);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final ArgumentCaptor<Consumer<Account>> accountUpdater = ArgumentCaptor.forClass(Consumer.class);
|
||||
@SuppressWarnings("unchecked") final ArgumentCaptor<Consumer<Account>> accountUpdater = ArgumentCaptor.forClass(
|
||||
Consumer.class);
|
||||
verify(accountsManager, times(1)).updateAsync(any(), accountUpdater.capture());
|
||||
|
||||
// If the account is not expired when we go to update it, we shouldn't wipe it out
|
||||
|
@ -268,7 +277,7 @@ public class BackupAuthManagerTest {
|
|||
@Test
|
||||
void redeemReceipt() throws InvalidInputException, VerificationFailedException {
|
||||
final Instant expirationTime = Instant.EPOCH.plus(Duration.ofDays(1));
|
||||
final BackupAuthManager authManager = create(BackupTier.MESSAGES, false);
|
||||
final BackupAuthManager authManager = create(BackupLevel.MESSAGES, false);
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getUuid()).thenReturn(aci);
|
||||
|
||||
|
@ -285,8 +294,7 @@ public class BackupAuthManagerTest {
|
|||
final Instant newExpirationTime = Instant.EPOCH.plus(Duration.ofDays(1));
|
||||
final Instant existingExpirationTime = Instant.EPOCH.plus(Duration.ofDays(1)).plus(Duration.ofSeconds(1));
|
||||
|
||||
|
||||
final BackupAuthManager authManager = create(BackupTier.MESSAGES, false);
|
||||
final BackupAuthManager authManager = create(BackupLevel.MESSAGES, false);
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getUuid()).thenReturn(aci);
|
||||
|
||||
|
@ -311,7 +319,7 @@ public class BackupAuthManagerTest {
|
|||
void redeemExpiredReceipt() {
|
||||
final Instant expirationTime = Instant.EPOCH.plus(Duration.ofDays(1));
|
||||
clock.pin(expirationTime.plus(Duration.ofSeconds(1)));
|
||||
final BackupAuthManager authManager = create(BackupTier.MESSAGES, false);
|
||||
final BackupAuthManager authManager = create(BackupLevel.MESSAGES, false);
|
||||
Assertions.assertThatExceptionOfType(StatusRuntimeException.class)
|
||||
.isThrownBy(() -> authManager.redeemReceipt(mock(Account.class), receiptPresentation(3, expirationTime)).join())
|
||||
.extracting(ex -> ex.getStatus().getCode())
|
||||
|
@ -325,7 +333,7 @@ public class BackupAuthManagerTest {
|
|||
void redeemInvalidLevel(long level) {
|
||||
final Instant expirationTime = Instant.EPOCH.plus(Duration.ofDays(1));
|
||||
clock.pin(expirationTime.plus(Duration.ofSeconds(1)));
|
||||
final BackupAuthManager authManager = create(BackupTier.MESSAGES, false);
|
||||
final BackupAuthManager authManager = create(BackupLevel.MESSAGES, false);
|
||||
Assertions.assertThatExceptionOfType(StatusRuntimeException.class)
|
||||
.isThrownBy(() ->
|
||||
authManager.redeemReceipt(mock(Account.class), receiptPresentation(level, expirationTime)).join())
|
||||
|
@ -337,7 +345,7 @@ public class BackupAuthManagerTest {
|
|||
|
||||
@Test
|
||||
void redeemInvalidPresentation() throws InvalidInputException, VerificationFailedException {
|
||||
final BackupAuthManager authManager = create(BackupTier.MESSAGES, false);
|
||||
final BackupAuthManager authManager = create(BackupLevel.MESSAGES, false);
|
||||
final ReceiptCredentialPresentation invalid = receiptPresentation(ServerSecretParams.generate(), 3L, Instant.EPOCH);
|
||||
Assertions.assertThatExceptionOfType(StatusRuntimeException.class)
|
||||
.isThrownBy(() -> authManager.redeemReceipt(mock(Account.class), invalid).join())
|
||||
|
@ -350,7 +358,7 @@ public class BackupAuthManagerTest {
|
|||
@Test
|
||||
void receiptAlreadyRedeemed() throws InvalidInputException, VerificationFailedException {
|
||||
final Instant expirationTime = Instant.EPOCH.plus(Duration.ofDays(1));
|
||||
final BackupAuthManager authManager = create(BackupTier.MESSAGES, false);
|
||||
final BackupAuthManager authManager = create(BackupLevel.MESSAGES, false);
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getUuid()).thenReturn(aci);
|
||||
|
||||
|
@ -390,7 +398,7 @@ public class BackupAuthManagerTest {
|
|||
@Test
|
||||
void testRateLimits() {
|
||||
final AccountsManager accountsManager = mock(AccountsManager.class);
|
||||
final BackupAuthManager authManager = create(BackupTier.MESSAGES, true);
|
||||
final BackupAuthManager authManager = create(BackupLevel.MESSAGES, true);
|
||||
|
||||
final BackupAuthCredentialRequest credentialRequest = backupAuthTestUtil.getRequest(backupKey, aci);
|
||||
|
||||
|
@ -407,11 +415,11 @@ public class BackupAuthManagerTest {
|
|||
assertDoesNotThrow(() -> authManager.commitBackupId(account, credentialRequest).join());
|
||||
}
|
||||
|
||||
private static String experimentName(BackupTier backupTier) {
|
||||
return switch (backupTier) {
|
||||
private static String experimentName(@Nullable BackupLevel backupLevel) {
|
||||
return switch (backupLevel) {
|
||||
case MESSAGES -> BackupAuthManager.BACKUP_EXPERIMENT_NAME;
|
||||
case MEDIA -> BackupAuthManager.BACKUP_MEDIA_EXPERIMENT_NAME;
|
||||
case NONE -> "fake_experiment";
|
||||
case null -> "fake_experiment";
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.signal.libsignal.zkgroup.VerificationFailedException;
|
|||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialPresentation;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialRequest;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialRequestContext;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupLevel;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.tests.util.ExperimentHelper;
|
||||
|
||||
|
@ -35,32 +36,32 @@ public class BackupAuthTestUtil {
|
|||
}
|
||||
|
||||
public BackupAuthCredentialPresentation getPresentation(
|
||||
final BackupTier backupTier, final byte[] backupKey, final UUID aci)
|
||||
final BackupLevel backupLevel, final byte[] backupKey, final UUID aci)
|
||||
throws VerificationFailedException {
|
||||
return getPresentation(params, backupTier, backupKey, aci);
|
||||
return getPresentation(params, backupLevel, backupKey, aci);
|
||||
}
|
||||
|
||||
public BackupAuthCredentialPresentation getPresentation(
|
||||
GenericServerSecretParams params, final BackupTier backupTier, final byte[] backupKey, final UUID aci)
|
||||
GenericServerSecretParams params, final BackupLevel backupLevel, final byte[] backupKey, final UUID aci)
|
||||
throws VerificationFailedException {
|
||||
final Instant redemptionTime = clock.instant().truncatedTo(ChronoUnit.DAYS);
|
||||
final BackupAuthCredentialRequestContext ctx = BackupAuthCredentialRequestContext.create(backupKey, aci);
|
||||
return ctx.receiveResponse(
|
||||
ctx.getRequest()
|
||||
.issueCredential(clock.instant().truncatedTo(ChronoUnit.DAYS), backupTier.getReceiptLevel(), params),
|
||||
params.getPublicParams(),
|
||||
backupTier.getReceiptLevel())
|
||||
.issueCredential(clock.instant().truncatedTo(ChronoUnit.DAYS), backupLevel, params),
|
||||
redemptionTime,
|
||||
params.getPublicParams())
|
||||
.present(params.getPublicParams());
|
||||
}
|
||||
|
||||
public List<BackupAuthManager.Credential> getCredentials(
|
||||
final BackupTier backupTier,
|
||||
final BackupLevel backupLevel,
|
||||
final BackupAuthCredentialRequest request,
|
||||
final Instant redemptionStart,
|
||||
final Instant redemptionEnd) {
|
||||
final UUID aci = UUID.randomUUID();
|
||||
|
||||
final String experimentName = switch (backupTier) {
|
||||
case NONE -> "notUsed";
|
||||
final String experimentName = switch (backupLevel) {
|
||||
case MESSAGES -> BackupAuthManager.BACKUP_EXPERIMENT_NAME;
|
||||
case MEDIA -> BackupAuthManager.BACKUP_MEDIA_EXPERIMENT_NAME;
|
||||
};
|
||||
|
|
|
@ -30,7 +30,6 @@ import java.nio.ByteBuffer;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
|
@ -61,6 +60,7 @@ import org.signal.libsignal.protocol.ecc.ECKeyPair;
|
|||
import org.signal.libsignal.zkgroup.GenericServerSecretParams;
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialPresentation;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupLevel;
|
||||
import org.whispersystems.textsecuregcm.attachments.TusAttachmentGenerator;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedBackupUser;
|
||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
|
@ -90,7 +90,6 @@ public class BackupManagerTest {
|
|||
private final RemoteStorageManager remoteStorageManager = mock(RemoteStorageManager.class);
|
||||
private final byte[] backupKey = TestRandomUtil.nextBytes(32);
|
||||
private final UUID aci = UUID.randomUUID();
|
||||
private final SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
private BackupManager backupManager;
|
||||
private BackupsDb backupsDb;
|
||||
|
@ -119,13 +118,13 @@ public class BackupManagerTest {
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(mode = EnumSource.Mode.EXCLUDE, names = {"NONE"})
|
||||
public void createBackup(final BackupTier backupTier) {
|
||||
@EnumSource
|
||||
public void createBackup(final BackupLevel backupLevel) {
|
||||
|
||||
final Instant now = Instant.ofEpochSecond(Duration.ofDays(1).getSeconds());
|
||||
testClock.pin(now);
|
||||
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), backupTier);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), backupLevel);
|
||||
|
||||
backupManager.createMessageBackupUploadDescriptor(backupUser).join();
|
||||
verify(tusCredentialGenerator, times(1))
|
||||
|
@ -137,12 +136,12 @@ public class BackupManagerTest {
|
|||
assertThat(info.mediaUsedSpace()).isEqualTo(Optional.empty());
|
||||
|
||||
// Check that the initial expiration times are the initial write times
|
||||
checkExpectedExpirations(now, backupTier == BackupTier.MEDIA ? now : null, backupUser);
|
||||
checkExpectedExpirations(now, backupLevel == BackupLevel.MEDIA ? now : null, backupUser);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createTemporaryMediaAttachmentRateLimited() throws RateLimitExceededException {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
doThrow(new RateLimitExceededException(null, true))
|
||||
.when(mediaUploadLimiter)
|
||||
.validate(eq(BackupManager.rateLimitKey(backupUser)));
|
||||
|
@ -153,8 +152,8 @@ public class BackupManagerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void createTemporaryMediaAttachmentWrongTier() throws RateLimitExceededException {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MESSAGES);
|
||||
public void createTemporaryMediaAttachmentWrongTier() {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MESSAGES);
|
||||
assertThatExceptionOfType(StatusRuntimeException.class)
|
||||
.isThrownBy(() -> backupManager.createTemporaryAttachmentUploadDescriptor(backupUser))
|
||||
.extracting(StatusRuntimeException::getStatus)
|
||||
|
@ -163,9 +162,9 @@ public class BackupManagerTest {
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(mode = EnumSource.Mode.EXCLUDE, names = {"NONE"})
|
||||
public void ttlRefresh(final BackupTier backupTier) {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), backupTier);
|
||||
@EnumSource
|
||||
public void ttlRefresh(final BackupLevel backupLevel) {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), backupLevel);
|
||||
|
||||
final Instant tstart = Instant.ofEpochSecond(1).plus(Duration.ofDays(1));
|
||||
final Instant tnext = tstart.plus(Duration.ofSeconds(1));
|
||||
|
@ -180,17 +179,17 @@ public class BackupManagerTest {
|
|||
|
||||
checkExpectedExpirations(
|
||||
tnext,
|
||||
backupTier == BackupTier.MEDIA ? tnext : null,
|
||||
backupLevel == BackupLevel.MEDIA ? tnext : null,
|
||||
backupUser);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(mode = EnumSource.Mode.EXCLUDE, names = {"NONE"})
|
||||
public void createBackupRefreshesTtl(final BackupTier backupTier) {
|
||||
@EnumSource
|
||||
public void createBackupRefreshesTtl(final BackupLevel backupLevel) {
|
||||
final Instant tstart = Instant.ofEpochSecond(1).plus(Duration.ofDays(1));
|
||||
final Instant tnext = tstart.plus(Duration.ofSeconds(1));
|
||||
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), backupTier);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), backupLevel);
|
||||
|
||||
// create backup at t=tstart
|
||||
testClock.pin(tstart);
|
||||
|
@ -202,7 +201,7 @@ public class BackupManagerTest {
|
|||
|
||||
checkExpectedExpirations(
|
||||
tnext,
|
||||
backupTier == BackupTier.MEDIA ? tnext : null,
|
||||
backupLevel == BackupLevel.MEDIA ? tnext : null,
|
||||
backupUser);
|
||||
}
|
||||
|
||||
|
@ -210,7 +209,7 @@ public class BackupManagerTest {
|
|||
public void invalidPresentationNoPublicKey() throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation invalidPresentation = backupAuthTestUtil.getPresentation(
|
||||
GenericServerSecretParams.generate(),
|
||||
BackupTier.MESSAGES, backupKey, aci);
|
||||
BackupLevel.MESSAGES, backupKey, aci);
|
||||
|
||||
final ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
|
||||
|
@ -228,10 +227,10 @@ public class BackupManagerTest {
|
|||
@Test
|
||||
public void invalidPresentationCorrectSignature() throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MESSAGES, backupKey, aci);
|
||||
BackupLevel.MESSAGES, backupKey, aci);
|
||||
final BackupAuthCredentialPresentation invalidPresentation = backupAuthTestUtil.getPresentation(
|
||||
GenericServerSecretParams.generate(),
|
||||
BackupTier.MESSAGES, backupKey, aci);
|
||||
BackupLevel.MESSAGES, backupKey, aci);
|
||||
|
||||
final ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
backupManager.setPublicKey(
|
||||
|
@ -251,7 +250,7 @@ public class BackupManagerTest {
|
|||
@Test
|
||||
public void unknownPublicKey() throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MESSAGES, backupKey, aci);
|
||||
BackupLevel.MESSAGES, backupKey, aci);
|
||||
|
||||
final ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
final byte[] signature = keyPair.getPrivateKey().calculateSignature(presentation.serialize());
|
||||
|
@ -267,7 +266,7 @@ public class BackupManagerTest {
|
|||
@Test
|
||||
public void mismatchedPublicKey() throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MESSAGES, backupKey, aci);
|
||||
BackupLevel.MESSAGES, backupKey, aci);
|
||||
|
||||
final ECKeyPair keyPair1 = Curve.generateKeyPair();
|
||||
final ECKeyPair keyPair2 = Curve.generateKeyPair();
|
||||
|
@ -290,7 +289,7 @@ public class BackupManagerTest {
|
|||
@Test
|
||||
public void signatureValidation() throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MESSAGES, backupKey, aci);
|
||||
BackupLevel.MESSAGES, backupKey, aci);
|
||||
|
||||
final ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
final byte[] signature = keyPair.getPrivateKey().calculateSignature(presentation.serialize());
|
||||
|
@ -317,7 +316,7 @@ public class BackupManagerTest {
|
|||
// correct signature
|
||||
final AuthenticatedBackupUser user = backupManager.authenticateBackupUser(presentation, signature).join();
|
||||
assertThat(user.backupId()).isEqualTo(presentation.getBackupId());
|
||||
assertThat(user.backupTier()).isEqualTo(BackupTier.MESSAGES);
|
||||
assertThat(user.backupLevel()).isEqualTo(BackupLevel.MESSAGES);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -325,7 +324,7 @@ public class BackupManagerTest {
|
|||
|
||||
// credential for 1 day after epoch
|
||||
testClock.pin(Instant.ofEpochSecond(1).plus(Duration.ofDays(1)));
|
||||
final BackupAuthCredentialPresentation oldCredential = backupAuthTestUtil.getPresentation(BackupTier.MESSAGES,
|
||||
final BackupAuthCredentialPresentation oldCredential = backupAuthTestUtil.getPresentation(BackupLevel.MESSAGES,
|
||||
backupKey, aci);
|
||||
final ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
final byte[] signature = keyPair.getPrivateKey().calculateSignature(oldCredential.serialize());
|
||||
|
@ -350,7 +349,7 @@ public class BackupManagerTest {
|
|||
|
||||
@Test
|
||||
public void copySuccess() {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
when(tusCredentialGenerator.generateUpload(any()))
|
||||
.thenReturn(new BackupUploadDescriptor(3, "def", Collections.emptyMap(), ""));
|
||||
when(remoteStorageManager.copy(eq(URI.create("cdn3.example.org/attachments/abc")), eq(100), any(), any()))
|
||||
|
@ -376,7 +375,7 @@ public class BackupManagerTest {
|
|||
|
||||
@Test
|
||||
public void copyFailure() {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
when(tusCredentialGenerator.generateUpload(any()))
|
||||
.thenReturn(new BackupUploadDescriptor(3, "def", Collections.emptyMap(), ""));
|
||||
when(remoteStorageManager.copy(eq(URI.create("cdn3.example.org/attachments/abc")), eq(100), any(), any()))
|
||||
|
@ -397,7 +396,7 @@ public class BackupManagerTest {
|
|||
|
||||
@Test
|
||||
public void unknownSourceCdn() {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
CompletableFutureTestUtil.assertFailsWithCause(SourceObjectNotFoundException.class,
|
||||
backupManager.copyToBackup(
|
||||
backupUser,
|
||||
|
@ -408,7 +407,7 @@ public class BackupManagerTest {
|
|||
|
||||
@Test
|
||||
public void quotaEnforcementNoRecalculation() {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
verifyNoInteractions(remoteStorageManager);
|
||||
|
||||
// set the backupsDb to be out of quota at t=0
|
||||
|
@ -423,7 +422,7 @@ public class BackupManagerTest {
|
|||
|
||||
@Test
|
||||
public void quotaEnforcementRecalculation() {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
final String backupMediaPrefix = "%s/%s/".formatted(backupUser.backupDir(), backupUser.mediaDir());
|
||||
|
||||
// on recalculation, say there's actually 10 bytes left
|
||||
|
@ -460,7 +459,7 @@ public class BackupManagerTest {
|
|||
final long spaceLeft,
|
||||
final long mediaToAddSize,
|
||||
boolean shouldAccept) {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
final String backupMediaPrefix = "%s/%s/".formatted(backupUser.backupDir(), backupUser.mediaDir());
|
||||
|
||||
// set the backupsDb to be out of quota at t=0
|
||||
|
@ -485,7 +484,7 @@ public class BackupManagerTest {
|
|||
@ValueSource(strings = {"", "cursor"})
|
||||
public void list(final String cursorVal) {
|
||||
final Optional<String> cursor = Optional.of(cursorVal).filter(StringUtils::isNotBlank);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
final String backupMediaPrefix = "%s/%s/".formatted(backupUser.backupDir(), backupUser.mediaDir());
|
||||
|
||||
when(remoteStorageManager.cdnNumber()).thenReturn(13);
|
||||
|
@ -498,24 +497,24 @@ public class BackupManagerTest {
|
|||
final BackupManager.ListMediaResult result = backupManager.list(backupUser, cursor, 17)
|
||||
.toCompletableFuture().join();
|
||||
assertThat(result.media()).hasSize(1);
|
||||
assertThat(result.media().get(0).cdn()).isEqualTo(13);
|
||||
assertThat(result.media().get(0).key()).isEqualTo(
|
||||
assertThat(result.media().getFirst().cdn()).isEqualTo(13);
|
||||
assertThat(result.media().getFirst().key()).isEqualTo(
|
||||
Base64.getDecoder().decode("aaa".getBytes(StandardCharsets.UTF_8)));
|
||||
assertThat(result.media().get(0).length()).isEqualTo(123);
|
||||
assertThat(result.media().getFirst().length()).isEqualTo(123);
|
||||
assertThat(result.cursor().get()).isEqualTo("newCursor");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteEntireBackup() {
|
||||
final AuthenticatedBackupUser original = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser original = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
|
||||
testClock.pin(Instant.ofEpochSecond(10));
|
||||
|
||||
// Deleting should swap the backupDir for the user
|
||||
backupManager.deleteEntireBackup(original).join();
|
||||
verifyNoInteractions(remoteStorageManager);
|
||||
final AuthenticatedBackupUser after = retrieveBackupUser(original.backupId(), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser after = retrieveBackupUser(original.backupId(), BackupLevel.MEDIA);
|
||||
assertThat(original.backupDir()).isNotEqualTo(after.backupDir());
|
||||
assertThat(original.mediaDir()).isNotEqualTo(after.mediaDir());
|
||||
|
||||
|
@ -541,7 +540,7 @@ public class BackupManagerTest {
|
|||
|
||||
@Test
|
||||
public void delete() {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
final byte[] mediaId = TestRandomUtil.nextBytes(16);
|
||||
final String backupMediaKey = "%s/%s/%s".formatted(
|
||||
backupUser.backupDir(),
|
||||
|
@ -562,7 +561,7 @@ public class BackupManagerTest {
|
|||
|
||||
@Test
|
||||
public void deleteUnknownCdn() {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
when(remoteStorageManager.cdnNumber()).thenReturn(5);
|
||||
assertThatThrownBy(() ->
|
||||
backupManager.delete(backupUser, List.of(new BackupManager.StorageDescriptor(4, TestRandomUtil.nextBytes(15)))))
|
||||
|
@ -572,7 +571,7 @@ public class BackupManagerTest {
|
|||
|
||||
@Test
|
||||
public void deletePartialFailure() {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
|
||||
final List<BackupManager.StorageDescriptor> descriptors = new ArrayList<>();
|
||||
long initialBytes = 0;
|
||||
|
@ -605,7 +604,7 @@ public class BackupManagerTest {
|
|||
|
||||
@Test
|
||||
public void alreadyDeleted() {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
final byte[] mediaId = TestRandomUtil.nextBytes(16);
|
||||
final String backupMediaKey = "%s/%s/%s".formatted(
|
||||
backupUser.backupDir(),
|
||||
|
@ -627,7 +626,7 @@ public class BackupManagerTest {
|
|||
@Test
|
||||
public void listExpiredBackups() {
|
||||
final List<AuthenticatedBackupUser> backupUsers = IntStream.range(0, 10)
|
||||
.mapToObj(i -> backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA))
|
||||
.mapToObj(i -> backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA))
|
||||
.toList();
|
||||
for (int i = 0; i < backupUsers.size(); i++) {
|
||||
testClock.pin(Instant.ofEpochSecond(i));
|
||||
|
@ -665,11 +664,11 @@ public class BackupManagerTest {
|
|||
|
||||
// refreshed media timestamp at t=5
|
||||
testClock.pin(Instant.ofEpochSecond(5));
|
||||
backupManager.createMessageBackupUploadDescriptor(backupUser(backupId, BackupTier.MEDIA)).join();
|
||||
backupManager.createMessageBackupUploadDescriptor(backupUser(backupId, BackupLevel.MEDIA)).join();
|
||||
|
||||
// refreshed messages timestamp at t=6
|
||||
testClock.pin(Instant.ofEpochSecond(6));
|
||||
backupManager.createMessageBackupUploadDescriptor(backupUser(backupId, BackupTier.MESSAGES)).join();
|
||||
backupManager.createMessageBackupUploadDescriptor(backupUser(backupId, BackupLevel.MESSAGES)).join();
|
||||
|
||||
Function<Instant, List<ExpiredBackup>> getExpired = time -> backupManager
|
||||
.getExpiredBackups(1, Schedulers.immediate(), time)
|
||||
|
@ -689,7 +688,7 @@ public class BackupManagerTest {
|
|||
@ParameterizedTest
|
||||
@EnumSource(mode = EnumSource.Mode.INCLUDE, names = {"MEDIA", "ALL"})
|
||||
public void expireBackup(ExpiredBackup.ExpirationType expirationType) {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
backupManager.createMessageBackupUploadDescriptor(backupUser).join();
|
||||
|
||||
final String expectedPrefixToDelete = switch (expirationType) {
|
||||
|
@ -731,7 +730,7 @@ public class BackupManagerTest {
|
|||
|
||||
@Test
|
||||
public void deleteBackupPaginated() {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
backupManager.createMessageBackupUploadDescriptor(backupUser).join();
|
||||
|
||||
final ExpiredBackup expiredBackup = expiredBackup(ExpiredBackup.ExpirationType.MEDIA, backupUser);
|
||||
|
@ -814,23 +813,23 @@ public class BackupManagerTest {
|
|||
/**
|
||||
* Create BackupUser with the provided backupId and tier
|
||||
*/
|
||||
private AuthenticatedBackupUser backupUser(final byte[] backupId, final BackupTier backupTier) {
|
||||
private AuthenticatedBackupUser backupUser(final byte[] backupId, final BackupLevel backupLevel) {
|
||||
// Won't actually validate the public key, but need to have a public key to perform BackupsDB operations
|
||||
byte[] privateKey = new byte[32];
|
||||
ByteBuffer.wrap(privateKey).put(backupId);
|
||||
try {
|
||||
backupsDb.setPublicKey(backupId, backupTier, Curve.decodePrivatePoint(privateKey).publicKey()).join();
|
||||
backupsDb.setPublicKey(backupId, backupLevel, Curve.decodePrivatePoint(privateKey).publicKey()).join();
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return retrieveBackupUser(backupId, backupTier);
|
||||
return retrieveBackupUser(backupId, backupLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an existing BackupUser from the database
|
||||
*/
|
||||
private AuthenticatedBackupUser retrieveBackupUser(final byte[] backupId, final BackupTier backupTier) {
|
||||
private AuthenticatedBackupUser retrieveBackupUser(final byte[] backupId, final BackupLevel backupLevel) {
|
||||
final BackupsDb.AuthenticationData authData = backupsDb.retrieveAuthenticationData(backupId).join().get();
|
||||
return new AuthenticatedBackupUser(backupId, backupTier, authData.backupDir(), authData.mediaDir());
|
||||
return new AuthenticatedBackupUser(backupId, backupLevel, authData.backupDir(), authData.mediaDir());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.junit.jupiter.params.ParameterizedTest;
|
|||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.signal.libsignal.protocol.ecc.Curve;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupLevel;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedBackupUser;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtension;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema;
|
||||
|
@ -48,7 +49,7 @@ public class BackupsDbTest {
|
|||
|
||||
@Test
|
||||
public void trackMediaStats() {
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
// add at least one message backup so we can describe it
|
||||
backupsDb.addMessageBackup(backupUser).join();
|
||||
int total = 0;
|
||||
|
@ -71,7 +72,7 @@ public class BackupsDbTest {
|
|||
@ValueSource(booleans = {false, true})
|
||||
public void setUsage(boolean mediaAlreadyExists) {
|
||||
testClock.pin(Instant.ofEpochSecond(5));
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupTier.MEDIA);
|
||||
final AuthenticatedBackupUser backupUser = backupUser(TestRandomUtil.nextBytes(16), BackupLevel.MEDIA);
|
||||
if (mediaAlreadyExists) {
|
||||
this.backupsDb.trackMedia(backupUser, 1, 10).join();
|
||||
}
|
||||
|
@ -87,12 +88,12 @@ public class BackupsDbTest {
|
|||
final byte[] backupId = TestRandomUtil.nextBytes(16);
|
||||
// Refresh media/messages at t=0
|
||||
testClock.pin(Instant.ofEpochSecond(0L));
|
||||
backupsDb.setPublicKey(backupId, BackupTier.MEDIA, Curve.generateKeyPair().getPublicKey()).join();
|
||||
this.backupsDb.ttlRefresh(backupUser(backupId, BackupTier.MEDIA)).join();
|
||||
backupsDb.setPublicKey(backupId, BackupLevel.MEDIA, Curve.generateKeyPair().getPublicKey()).join();
|
||||
this.backupsDb.ttlRefresh(backupUser(backupId, BackupLevel.MEDIA)).join();
|
||||
|
||||
// refresh only messages at t=2
|
||||
testClock.pin(Instant.ofEpochSecond(2L));
|
||||
this.backupsDb.ttlRefresh(backupUser(backupId, BackupTier.MESSAGES)).join();
|
||||
this.backupsDb.ttlRefresh(backupUser(backupId, BackupLevel.MESSAGES)).join();
|
||||
|
||||
final Function<Instant, List<ExpiredBackup>> expiredBackups = purgeTime -> backupsDb
|
||||
.getExpiredBackups(1, Schedulers.immediate(), purgeTime)
|
||||
|
@ -104,8 +105,8 @@ public class BackupsDbTest {
|
|||
.matches(eb -> eb.expirationType() == ExpiredBackup.ExpirationType.MEDIA);
|
||||
|
||||
// Expire the media
|
||||
backupsDb.startExpiration(expired.get(0)).join();
|
||||
backupsDb.finishExpiration(expired.get(0)).join();
|
||||
backupsDb.startExpiration(expired.getFirst()).join();
|
||||
backupsDb.finishExpiration(expired.getFirst()).join();
|
||||
|
||||
// should be nothing to expire at t=1
|
||||
assertThat(expiredBackups.apply(Instant.ofEpochSecond(1))).isEmpty();
|
||||
|
@ -116,8 +117,8 @@ public class BackupsDbTest {
|
|||
.matches(eb -> eb.expirationType() == ExpiredBackup.ExpirationType.ALL);
|
||||
|
||||
// Expire the messages
|
||||
backupsDb.startExpiration(expired.get(0)).join();
|
||||
backupsDb.finishExpiration(expired.get(0)).join();
|
||||
backupsDb.startExpiration(expired.getFirst()).join();
|
||||
backupsDb.finishExpiration(expired.getFirst()).join();
|
||||
|
||||
// should be nothing to expire at t=3
|
||||
assertThat(expiredBackups.apply(Instant.ofEpochSecond(3))).isEmpty();
|
||||
|
@ -129,13 +130,13 @@ public class BackupsDbTest {
|
|||
final byte[] backupId = TestRandomUtil.nextBytes(16);
|
||||
// Refresh media/messages at t=0
|
||||
testClock.pin(Instant.ofEpochSecond(0L));
|
||||
backupsDb.setPublicKey(backupId, BackupTier.MEDIA, Curve.generateKeyPair().getPublicKey()).join();
|
||||
this.backupsDb.ttlRefresh(backupUser(backupId, BackupTier.MEDIA)).join();
|
||||
backupsDb.setPublicKey(backupId, BackupLevel.MEDIA, Curve.generateKeyPair().getPublicKey()).join();
|
||||
this.backupsDb.ttlRefresh(backupUser(backupId, BackupLevel.MEDIA)).join();
|
||||
|
||||
if (expirationType == ExpiredBackup.ExpirationType.MEDIA) {
|
||||
// refresh only messages at t=2 so that we only expire media at t=1
|
||||
testClock.pin(Instant.ofEpochSecond(2L));
|
||||
this.backupsDb.ttlRefresh(backupUser(backupId, BackupTier.MESSAGES)).join();
|
||||
this.backupsDb.ttlRefresh(backupUser(backupId, BackupLevel.MESSAGES)).join();
|
||||
}
|
||||
|
||||
final Function<Instant, Optional<ExpiredBackup>> expiredBackups = purgeTime -> {
|
||||
|
@ -160,17 +161,17 @@ public class BackupsDbTest {
|
|||
if (expirationType == ExpiredBackup.ExpirationType.MEDIA) {
|
||||
// Media expiration should swap the media name and keep the backup name, marking the old media name for expiration
|
||||
assertThat(expired.prefixToDelete())
|
||||
.isEqualTo(originalBackupDir + "/" + originalMediaDir)
|
||||
.withFailMessage("Should expire media directory, expired %s", expired.prefixToDelete());
|
||||
assertThat(info.backupDir()).isEqualTo(originalBackupDir).withFailMessage("should keep backupDir");
|
||||
assertThat(info.mediaDir()).isNotEqualTo(originalMediaDir).withFailMessage("should change mediaDir");
|
||||
.withFailMessage("Should expire media directory, expired %s", expired.prefixToDelete())
|
||||
.isEqualTo(originalBackupDir + "/" + originalMediaDir);
|
||||
assertThat(info.backupDir()).withFailMessage("should keep backupDir").isEqualTo(originalBackupDir);
|
||||
assertThat(info.mediaDir()).withFailMessage("should change mediaDir").isNotEqualTo(originalMediaDir);
|
||||
} else {
|
||||
// Full expiration should swap the media name and the backup name, marking the old backup name for expiration
|
||||
assertThat(expired.prefixToDelete())
|
||||
.isEqualTo(originalBackupDir)
|
||||
.withFailMessage("Should expire whole backupDir, expired %s", expired.prefixToDelete());
|
||||
assertThat(info.backupDir()).isNotEqualTo(originalBackupDir).withFailMessage("should change backupDir");
|
||||
assertThat(info.mediaDir()).isNotEqualTo(originalMediaDir).withFailMessage("should change mediaDir");
|
||||
.withFailMessage("Should expire whole backupDir, expired %s", expired.prefixToDelete())
|
||||
.isEqualTo(originalBackupDir);
|
||||
assertThat(info.backupDir()).withFailMessage("should change backupDir").isNotEqualTo(originalBackupDir);
|
||||
assertThat(info.mediaDir()).withFailMessage("should change mediaDir").isNotEqualTo(originalMediaDir);
|
||||
}
|
||||
final String expiredPrefix = expired.prefixToDelete();
|
||||
|
||||
|
@ -189,7 +190,7 @@ public class BackupsDbTest {
|
|||
// should be nothing to expire at t=1
|
||||
assertThat(opt).isEmpty();
|
||||
// The backup should still exist
|
||||
backupsDb.describeBackup(backupUser(backupId, BackupTier.MEDIA)).join();
|
||||
backupsDb.describeBackup(backupUser(backupId, BackupLevel.MEDIA)).join();
|
||||
} else {
|
||||
// Cleaned up the failed attempt, now should tell us to clean the whole backup
|
||||
assertThat(opt.get()).matches(eb -> eb.expirationType() == ExpiredBackup.ExpirationType.ALL,
|
||||
|
@ -199,20 +200,14 @@ public class BackupsDbTest {
|
|||
|
||||
// The backup entry should be gone
|
||||
assertThat(CompletableFutureTestUtil.assertFailsWithCause(StatusRuntimeException.class,
|
||||
backupsDb.describeBackup(backupUser(backupId, BackupTier.MEDIA)))
|
||||
.getStatus().getCode())
|
||||
backupsDb.describeBackup(backupUser(backupId, BackupLevel.MEDIA)))
|
||||
.getStatus().getCode())
|
||||
.isEqualTo(Status.Code.NOT_FOUND);
|
||||
assertThat(expiredBackups.apply(Instant.ofEpochSecond(10))).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private AuthenticatedBackupUser backupUser(final byte[] backupId, final BackupTier backupTier) {
|
||||
return new AuthenticatedBackupUser(backupId, backupTier, "myBackupDir", "myMediaDir");
|
||||
}
|
||||
|
||||
private AuthenticatedBackupUser backupUserFromDb(final byte[] backupId, final BackupTier backupTier) {
|
||||
final BackupsDb.AuthenticationData authenticationData = backupsDb.retrieveAuthenticationData(backupId).join().get();
|
||||
return new AuthenticatedBackupUser(backupId, backupTier,
|
||||
authenticationData.backupDir(), authenticationData.mediaDir());
|
||||
private AuthenticatedBackupUser backupUser(final byte[] backupId, final BackupLevel backupLevel) {
|
||||
return new AuthenticatedBackupUser(backupId, backupLevel, "myBackupDir", "myMediaDir");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,18 +51,18 @@ import org.whispersystems.textsecuregcm.util.TestRandomUtil;
|
|||
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||
public class Cdn3RemoteStorageManagerTest {
|
||||
|
||||
private static byte[] HMAC_KEY = TestRandomUtil.nextBytes(32);
|
||||
private static byte[] AES_KEY = TestRandomUtil.nextBytes(32);
|
||||
private static byte[] IV = TestRandomUtil.nextBytes(16);
|
||||
private static final byte[] HMAC_KEY = TestRandomUtil.nextBytes(32);
|
||||
private static final byte[] AES_KEY = TestRandomUtil.nextBytes(32);
|
||||
private static final byte[] IV = TestRandomUtil.nextBytes(16);
|
||||
|
||||
@RegisterExtension
|
||||
private final WireMockExtension wireMock = WireMockExtension.newInstance()
|
||||
private static final WireMockExtension wireMock = WireMockExtension.newInstance()
|
||||
.options(wireMockConfig().dynamicPort())
|
||||
.build();
|
||||
|
||||
private static String SMALL_CDN2 = "a small object from cdn2";
|
||||
private static String SMALL_CDN3 = "a small object from cdn3";
|
||||
private static String LARGE = "a".repeat(1024 * 1024 * 5);
|
||||
private static final String SMALL_CDN2 = "a small object from cdn2";
|
||||
private static final String SMALL_CDN3 = "a small object from cdn3";
|
||||
private static final String LARGE = "a".repeat(1024 * 1024 * 5);
|
||||
|
||||
private RemoteStorageManager remoteStorageManager;
|
||||
|
||||
|
@ -127,7 +127,7 @@ public class Cdn3RemoteStorageManagerTest {
|
|||
new BackupUploadDescriptor(3, "test", Collections.emptyMap(), wireMock.url("/cdn3/dest")))
|
||||
.toCompletableFuture().join();
|
||||
|
||||
final byte[] destBody = wireMock.findAll(postRequestedFor(urlEqualTo("/cdn3/dest"))).get(0).getBody();
|
||||
final byte[] destBody = wireMock.findAll(postRequestedFor(urlEqualTo("/cdn3/dest"))).getFirst().getBody();
|
||||
assertThat(new String(decrypt(destBody), StandardCharsets.UTF_8))
|
||||
.isEqualTo(expectedSource);
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ public class Cdn3RemoteStorageManagerTest {
|
|||
new BackupUploadDescriptor(3, "test", Collections.emptyMap(), wireMock.url("/cdn3/dest")))
|
||||
.toCompletableFuture().join();
|
||||
|
||||
final byte[] destBody = wireMock.findAll(postRequestedFor(urlEqualTo("/cdn3/dest"))).get(0).getBody();
|
||||
final byte[] destBody = wireMock.findAll(postRequestedFor(urlEqualTo("/cdn3/dest"))).getFirst().getBody();
|
||||
assertThat(destBody.length)
|
||||
.isEqualTo(new BackupMediaEncrypter(params).outputSize(LARGE.length()))
|
||||
.isEqualTo(params.outputSize(LARGE.length()));
|
||||
|
|
|
@ -53,6 +53,7 @@ import org.signal.libsignal.zkgroup.InvalidInputException;
|
|||
import org.signal.libsignal.zkgroup.ServerSecretParams;
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialPresentation;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupLevel;
|
||||
import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations;
|
||||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredential;
|
||||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation;
|
||||
|
@ -65,7 +66,6 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedBackupUser;
|
|||
import org.whispersystems.textsecuregcm.backup.BackupAuthManager;
|
||||
import org.whispersystems.textsecuregcm.backup.BackupAuthTestUtil;
|
||||
import org.whispersystems.textsecuregcm.backup.BackupManager;
|
||||
import org.whispersystems.textsecuregcm.backup.BackupTier;
|
||||
import org.whispersystems.textsecuregcm.backup.InvalidLengthException;
|
||||
import org.whispersystems.textsecuregcm.backup.SourceObjectNotFoundException;
|
||||
import org.whispersystems.textsecuregcm.backup.BackupUploadDescriptor;
|
||||
|
@ -133,7 +133,7 @@ public class ArchiveControllerTest {
|
|||
public void anonymousAuthOnly(final String method, final String path, final String body)
|
||||
throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MEDIA, backupKey, aci);
|
||||
BackupLevel.MEDIA, backupKey, aci);
|
||||
final Invocation.Builder request = resources.getJerseyTest()
|
||||
.target(path)
|
||||
.request()
|
||||
|
@ -192,7 +192,7 @@ public class ArchiveControllerTest {
|
|||
when(backupManager.setPublicKey(any(), any(), any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MEDIA, backupKey, aci);
|
||||
BackupLevel.MEDIA, backupKey, aci);
|
||||
final Response response = resources.getJerseyTest()
|
||||
.target("v1/archives/keys")
|
||||
.request()
|
||||
|
@ -209,7 +209,7 @@ public class ArchiveControllerTest {
|
|||
when(backupManager.setPublicKey(any(), any(), any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MEDIA, backupKey, aci);
|
||||
BackupLevel.MEDIA, backupKey, aci);
|
||||
final Response response = resources.getJerseyTest()
|
||||
.target("v1/archives/keys")
|
||||
.request()
|
||||
|
@ -224,7 +224,7 @@ public class ArchiveControllerTest {
|
|||
when(backupManager.setPublicKey(any(), any(), any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MEDIA, backupKey, aci);
|
||||
BackupLevel.MEDIA, backupKey, aci);
|
||||
final Response response = resources.getJerseyTest()
|
||||
.target("v1/archives/keys")
|
||||
.request()
|
||||
|
@ -283,7 +283,7 @@ public class ArchiveControllerTest {
|
|||
final Instant start = Instant.now().truncatedTo(ChronoUnit.DAYS);
|
||||
final Instant end = start.plus(Duration.ofDays(1));
|
||||
final List<BackupAuthManager.Credential> expectedResponse = backupAuthTestUtil.getCredentials(
|
||||
BackupTier.MEDIA, backupAuthTestUtil.getRequest(backupKey, aci), start, end);
|
||||
BackupLevel.MEDIA, backupAuthTestUtil.getRequest(backupKey, aci), start, end);
|
||||
when(backupAuthManager.getBackupAuthCredentials(any(), eq(start), eq(end))).thenReturn(
|
||||
CompletableFuture.completedFuture(expectedResponse));
|
||||
final ArchiveController.BackupAuthCredentialsResponse creds = resources.getJerseyTest()
|
||||
|
@ -293,10 +293,10 @@ public class ArchiveControllerTest {
|
|||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get(ArchiveController.BackupAuthCredentialsResponse.class);
|
||||
assertThat(creds.credentials().get(0).redemptionTime()).isEqualTo(start.getEpochSecond());
|
||||
assertThat(creds.credentials().getFirst().redemptionTime()).isEqualTo(start.getEpochSecond());
|
||||
}
|
||||
|
||||
enum BadCredentialsType {MISSING_START, MISSING_END, MISSING_BOTH}
|
||||
public enum BadCredentialsType {MISSING_START, MISSING_END, MISSING_BOTH}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource
|
||||
|
@ -323,9 +323,9 @@ public class ArchiveControllerTest {
|
|||
@Test
|
||||
public void getBackupInfo() throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MEDIA, backupKey, aci);
|
||||
BackupLevel.MEDIA, backupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupLevel.MEDIA)));
|
||||
when(backupManager.backupInfo(any())).thenReturn(CompletableFuture.completedFuture(new BackupManager.BackupInfo(
|
||||
1, "myBackupDir", "myMediaDir", "filename", Optional.empty())));
|
||||
final ArchiveController.BackupInfoResponse response = resources.getJerseyTest()
|
||||
|
@ -343,9 +343,9 @@ public class ArchiveControllerTest {
|
|||
@Test
|
||||
public void putMediaBatchSuccess() throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MEDIA, backupKey, aci);
|
||||
BackupLevel.MEDIA, backupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupLevel.MEDIA)));
|
||||
when(backupManager.canStoreMedia(any(), anyLong())).thenReturn(CompletableFuture.completedFuture(true));
|
||||
when(backupManager.copyToBackup(any(), anyInt(), any(), anyInt(), any(), any()))
|
||||
.thenAnswer(invocation -> {
|
||||
|
@ -393,9 +393,9 @@ public class ArchiveControllerTest {
|
|||
public void putMediaBatchPartialFailure() throws VerificationFailedException {
|
||||
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MEDIA, backupKey, aci);
|
||||
BackupLevel.MEDIA, backupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupLevel.MEDIA)));
|
||||
|
||||
final byte[][] mediaIds = IntStream.range(0, 3).mapToObj(i -> TestRandomUtil.nextBytes(15)).toArray(byte[][]::new);
|
||||
when(backupManager.canStoreMedia(any(), anyLong())).thenReturn(CompletableFuture.completedFuture(true));
|
||||
|
@ -448,9 +448,9 @@ public class ArchiveControllerTest {
|
|||
@Test
|
||||
public void putMediaBatchOutOfSpace() throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MEDIA, backupKey, aci);
|
||||
BackupLevel.MEDIA, backupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupLevel.MEDIA)));
|
||||
|
||||
when(backupManager.canStoreMedia(any(), eq(1L + 2L + 3L)))
|
||||
.thenReturn(CompletableFuture.completedFuture(false));
|
||||
|
@ -478,9 +478,9 @@ public class ArchiveControllerTest {
|
|||
@CartesianTest.Values(booleans = {true, false}) final boolean cursorReturned)
|
||||
throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(
|
||||
BackupTier.MEDIA, backupKey, aci);
|
||||
BackupLevel.MEDIA, backupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupLevel.MEDIA)));
|
||||
|
||||
final byte[] mediaId = TestRandomUtil.nextBytes(15);
|
||||
final Optional<String> expectedCursor = cursorProvided ? Optional.of("myCursor") : Optional.empty();
|
||||
|
@ -505,17 +505,17 @@ public class ArchiveControllerTest {
|
|||
.get(ArchiveController.ListResponse.class);
|
||||
|
||||
assertThat(response.storedMediaObjects()).hasSize(1);
|
||||
assertThat(response.storedMediaObjects().get(0).objectLength()).isEqualTo(100);
|
||||
assertThat(response.storedMediaObjects().get(0).mediaId()).isEqualTo(mediaId);
|
||||
assertThat(response.storedMediaObjects().getFirst().objectLength()).isEqualTo(100);
|
||||
assertThat(response.storedMediaObjects().getFirst().mediaId()).isEqualTo(mediaId);
|
||||
assertThat(response.cursor()).isEqualTo(returnedCursor.orElse(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delete() throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(BackupTier.MEDIA,
|
||||
final BackupAuthCredentialPresentation presentation = backupAuthTestUtil.getPresentation(BackupLevel.MEDIA,
|
||||
backupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupLevel.MEDIA)));
|
||||
|
||||
final ArchiveController.DeleteMedia deleteRequest = new ArchiveController.DeleteMedia(
|
||||
IntStream
|
||||
|
@ -537,9 +537,9 @@ public class ArchiveControllerTest {
|
|||
@Test
|
||||
public void mediaUploadForm() throws RateLimitExceededException, VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation =
|
||||
backupAuthTestUtil.getPresentation(BackupTier.MEDIA, backupKey, aci);
|
||||
backupAuthTestUtil.getPresentation(BackupLevel.MEDIA, backupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupLevel.MEDIA)));
|
||||
when(backupManager.createTemporaryAttachmentUploadDescriptor(any()))
|
||||
.thenReturn(new BackupUploadDescriptor(3, "abc", Map.of("k", "v"), "example.org"));
|
||||
final ArchiveController.UploadDescriptorResponse desc = resources.getJerseyTest()
|
||||
|
@ -568,9 +568,9 @@ public class ArchiveControllerTest {
|
|||
@Test
|
||||
public void readAuth() throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation =
|
||||
backupAuthTestUtil.getPresentation(BackupTier.MEDIA, backupKey, aci);
|
||||
backupAuthTestUtil.getPresentation(BackupLevel.MEDIA, backupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupLevel.MEDIA)));
|
||||
when(backupManager.generateReadAuth(any(), eq(3))).thenReturn(Map.of("key", "value"));
|
||||
final ArchiveController.ReadAuthResponse response = resources.getJerseyTest()
|
||||
.target("v1/archives/auth/read")
|
||||
|
@ -585,7 +585,7 @@ public class ArchiveControllerTest {
|
|||
@Test
|
||||
public void readAuthInvalidParam() throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation =
|
||||
backupAuthTestUtil.getPresentation(BackupTier.MEDIA, backupKey, aci);
|
||||
backupAuthTestUtil.getPresentation(BackupLevel.MEDIA, backupKey, aci);
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("v1/archives/auth/read")
|
||||
.request()
|
||||
|
@ -607,9 +607,9 @@ public class ArchiveControllerTest {
|
|||
@Test
|
||||
public void deleteEntireBackup() throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation =
|
||||
backupAuthTestUtil.getPresentation(BackupTier.MEDIA, backupKey, aci);
|
||||
backupAuthTestUtil.getPresentation(BackupLevel.MEDIA, backupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupTier.MEDIA)));
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupLevel.MEDIA)));
|
||||
when(backupManager.deleteEntireBackup(any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("v1/archives/")
|
||||
|
@ -620,7 +620,7 @@ public class ArchiveControllerTest {
|
|||
assertThat(response.getStatus()).isEqualTo(204);
|
||||
}
|
||||
|
||||
private static AuthenticatedBackupUser backupUser(byte[] backupId, BackupTier backupTier) {
|
||||
return new AuthenticatedBackupUser(backupId, backupTier, "myBackupDir", "myMediaDir");
|
||||
private static AuthenticatedBackupUser backupUser(byte[] backupId, BackupLevel backupLevel) {
|
||||
return new AuthenticatedBackupUser(backupId, backupLevel, "myBackupDir", "myMediaDir");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue