mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 09:10:35 +01:00
Move /v1/svrb/auth to /v1/archives/auth/svrb
This commit is contained in:
@@ -20,6 +20,7 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
|
||||
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
@@ -36,6 +37,7 @@ import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -43,11 +45,8 @@ import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import javax.annotation.Nullable;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
@@ -71,9 +70,13 @@ import org.signal.libsignal.zkgroup.backups.BackupCredentialType;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupLevel;
|
||||
import org.whispersystems.textsecuregcm.attachments.TusAttachmentGenerator;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedBackupUser;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
|
||||
import org.whispersystems.textsecuregcm.configuration.SecureValueRecoveryConfiguration;
|
||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecoveryClient;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtension;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema;
|
||||
import org.whispersystems.textsecuregcm.util.AttributeValues;
|
||||
@@ -107,6 +110,18 @@ public class BackupManagerTest {
|
||||
private final byte[] backupKey = TestRandomUtil.nextBytes(32);
|
||||
private final UUID aci = UUID.randomUUID();
|
||||
|
||||
|
||||
private static final SecureValueRecoveryConfiguration CFG = new SecureValueRecoveryConfiguration(
|
||||
"",
|
||||
randomSecretBytes(32),
|
||||
randomSecretBytes(32),
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
private final ExternalServiceCredentialsGenerator svrbCredentialGenerator =
|
||||
SecureValueRecoveryBCredentialsGeneratorFactory.svrbCredentialsGenerator(CFG, testClock);
|
||||
private final SecureValueRecoveryClient svrbClient = mock(SecureValueRecoveryClient.class);
|
||||
|
||||
private BackupManager backupManager;
|
||||
private BackupsDb backupsDb;
|
||||
|
||||
@@ -131,6 +146,8 @@ public class BackupManagerTest {
|
||||
tusAttachmentGenerator,
|
||||
tusCredentialGenerator,
|
||||
remoteStorageManager,
|
||||
svrbCredentialGenerator,
|
||||
svrbClient,
|
||||
testClock);
|
||||
}
|
||||
|
||||
@@ -697,9 +714,13 @@ public class BackupManagerTest {
|
||||
|
||||
testClock.pin(Instant.ofEpochSecond(10));
|
||||
|
||||
when(svrbClient.removeData(anyString())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
// Deleting should swap the backupDir for the user
|
||||
backupManager.deleteEntireBackup(original).join();
|
||||
verifyNoInteractions(remoteStorageManager);
|
||||
verify(svrbClient).removeData(HexFormat.of().formatHex(BackupsDb.hashedBackupId(original.backupId())));
|
||||
|
||||
final AuthenticatedBackupUser after = retrieveBackupUser(original.backupId(), BackupCredentialType.MESSAGES, BackupLevel.PAID);
|
||||
assertThat(original.backupDir()).isNotEqualTo(after.backupDir());
|
||||
assertThat(original.mediaDir()).isNotEqualTo(after.mediaDir());
|
||||
@@ -959,11 +980,15 @@ public class BackupManagerTest {
|
||||
new RemoteStorageManager.ListResult.Entry("ghi", 1)), Optional.empty())));
|
||||
when(remoteStorageManager.delete(anyString())).thenReturn(CompletableFuture.completedFuture(1L));
|
||||
|
||||
when(svrbClient.removeData(anyString())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
backupManager.expireBackup(expiredBackup(expirationType, backupUser)).join();
|
||||
verify(remoteStorageManager, times(1)).list(anyString(), any(), anyLong());
|
||||
verify(remoteStorageManager, times(1)).delete(expectedPrefixToDelete + "abc");
|
||||
verify(remoteStorageManager, times(1)).delete(expectedPrefixToDelete + "def");
|
||||
verify(remoteStorageManager, times(1)).delete(expectedPrefixToDelete + "ghi");
|
||||
verify(svrbClient, times(expirationType == ExpiredBackup.ExpirationType.ALL ? 1 : 0))
|
||||
.removeData(HexFormat.of().formatHex(BackupsDb.hashedBackupId(backupUser.backupId())));
|
||||
verifyNoMoreInteractions(remoteStorageManager);
|
||||
|
||||
final BackupsDb.TimestampedUsageInfo usage = backupsDb.getMediaUsage(backupUser).join();
|
||||
@@ -1020,6 +1045,32 @@ public class BackupManagerTest {
|
||||
verifyNoMoreInteractions(remoteStorageManager);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(BackupLevel.class)
|
||||
void svrbAuthValid(BackupLevel backupLevel) {
|
||||
testClock.pin(Instant.ofEpochSecond(123));
|
||||
final AuthenticatedBackupUser backupUser =
|
||||
backupUser(TestRandomUtil.nextBytes(16), BackupCredentialType.MESSAGES, backupLevel);
|
||||
final ExternalServiceCredentials creds = backupManager.generateSvrbAuth(backupUser);
|
||||
|
||||
assertThat(HexFormat.of().parseHex(creds.username())).hasSize(16);
|
||||
final String[] split = creds.password().split(":", 2);
|
||||
assertThat(Long.parseLong(split[0])).isEqualTo(123);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(BackupLevel.class)
|
||||
void svrbAuthInvalid(BackupLevel backupLevel) {
|
||||
// Can't use MEDIA for svrb auth
|
||||
final AuthenticatedBackupUser backupUser =
|
||||
backupUser(TestRandomUtil.nextBytes(16), BackupCredentialType.MEDIA, backupLevel);
|
||||
assertThatExceptionOfType(StatusRuntimeException.class)
|
||||
.isThrownBy(() -> backupManager.generateSvrbAuth(backupUser))
|
||||
.extracting(StatusRuntimeException::getStatus)
|
||||
.extracting(Status::getCode)
|
||||
.isEqualTo(Status.Code.UNAUTHENTICATED);
|
||||
}
|
||||
|
||||
private CopyResult copyError(final AuthenticatedBackupUser backupUser, Throwable copyException) {
|
||||
when(tusCredentialGenerator.generateUpload(any()))
|
||||
.thenReturn(new BackupUploadDescriptor(3, "def", Collections.emptyMap(), ""));
|
||||
|
||||
@@ -63,6 +63,7 @@ import org.signal.libsignal.zkgroup.receipts.ReceiptSerial;
|
||||
import org.signal.libsignal.zkgroup.receipts.ServerZkReceiptOperations;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedBackupUser;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
|
||||
import org.whispersystems.textsecuregcm.backup.BackupAuthManager;
|
||||
import org.whispersystems.textsecuregcm.backup.BackupAuthTestUtil;
|
||||
import org.whispersystems.textsecuregcm.backup.BackupManager;
|
||||
@@ -116,6 +117,7 @@ public class ArchiveControllerTest {
|
||||
@ParameterizedTest
|
||||
@CsvSource(textBlock = """
|
||||
GET, v1/archives/auth/read,
|
||||
GET, v1/archives/auth/svrb,
|
||||
GET, v1/archives/,
|
||||
GET, v1/archives/upload/form,
|
||||
GET, v1/archives/media/upload/form,
|
||||
@@ -663,6 +665,24 @@ public class ArchiveControllerTest {
|
||||
assertThat(response.headers()).containsExactlyEntriesOf(Map.of("key", "value"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void svrbAuth() throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation =
|
||||
backupAuthTestUtil.getPresentation(BackupLevel.PAID, messagesBackupKey, aci);
|
||||
when(backupManager.authenticateBackupUser(any(), any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(backupUser(presentation.getBackupId(), BackupCredentialType.MESSAGES, BackupLevel.PAID)));
|
||||
final ExternalServiceCredentials credentials = new ExternalServiceCredentials("username", "password");
|
||||
when(backupManager.generateSvrbAuth(any())).thenReturn(credentials);
|
||||
final ExternalServiceCredentials response = resources.getJerseyTest()
|
||||
.target("v1/archives/auth/svrb")
|
||||
.request()
|
||||
.header("X-Signal-ZK-Auth", Base64.getEncoder().encodeToString(presentation.serialize()))
|
||||
.header("X-Signal-ZK-Auth-Signature", "aaa")
|
||||
.get(ExternalServiceCredentials.class);
|
||||
assertThat(response).isEqualTo(credentials);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readAuthInvalidParam() throws VerificationFailedException {
|
||||
final BackupAuthCredentialPresentation presentation =
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.whispersystems.textsecuregcm.util.MockUtils.randomSecretBytes;
|
||||
|
||||
import io.dropwizard.auth.AuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||
import io.dropwizard.testing.junit5.ResourceExtension;
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
|
||||
import org.whispersystems.textsecuregcm.configuration.SecureValueRecoveryConfiguration;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.MutableClock;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
import java.time.Instant;
|
||||
import java.util.HexFormat;
|
||||
|
||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||
public class SecureValueRecoveryBControllerTest {
|
||||
|
||||
private static final SecureValueRecoveryConfiguration CFG = new SecureValueRecoveryConfiguration(
|
||||
"",
|
||||
randomSecretBytes(32),
|
||||
randomSecretBytes(32),
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
private static final MutableClock CLOCK = new MutableClock();
|
||||
|
||||
private static final ExternalServiceCredentialsGenerator CREDENTIAL_GENERATOR =
|
||||
SecureValueRecoveryBController.credentialsGenerator(CFG, CLOCK);
|
||||
|
||||
private static final SecureValueRecoveryBController CONTROLLER =
|
||||
new SecureValueRecoveryBController(CREDENTIAL_GENERATOR);
|
||||
|
||||
private static final ResourceExtension RESOURCES = ResourceExtension.builder()
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
.addProvider(new AuthValueFactoryProvider.Binder<>(AuthenticatedDevice.class))
|
||||
.setMapper(SystemMapper.jsonMapper())
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(CONTROLLER)
|
||||
.build();
|
||||
|
||||
@Test
|
||||
public void testGetCredentials() {
|
||||
CLOCK.setTimeInstant(Instant.ofEpochSecond(123));
|
||||
final ExternalServiceCredentials creds = RESOURCES.getJerseyTest()
|
||||
.target("/v1/svrb/auth")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get(ExternalServiceCredentials.class);
|
||||
|
||||
assertThat(HexFormat.of().parseHex(creds.username())).hasSize(16);
|
||||
System.out.println(creds.password());
|
||||
final String[] split = creds.password().split(":", 2);
|
||||
assertThat(Long.parseLong(split[0])).isEqualTo(123);
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,7 @@ class SecureValueRecoveryClientTest {
|
||||
final String username = RandomStringUtils.secure().nextAlphabetic(16);
|
||||
final String password = RandomStringUtils.secure().nextAlphanumeric(32);
|
||||
|
||||
when(credentialsGenerator.generateForUuid(accountUuid)).thenReturn(
|
||||
when(credentialsGenerator.generateFor(accountUuid.toString())).thenReturn(
|
||||
new ExternalServiceCredentials(username, password));
|
||||
|
||||
wireMock.stubFor(delete(urlEqualTo(SecureValueRecoveryClient.DELETE_PATH))
|
||||
|
||||
@@ -137,7 +137,7 @@ public class AccountCreationDeletionIntegrationTest {
|
||||
when(secureStorageClient.deleteStoredData(any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
final SecureValueRecoveryClient svr2Client = mock(SecureValueRecoveryClient.class);
|
||||
when(svr2Client.removeData(any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
when(svr2Client.removeData(any(UUID.class))).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
final PhoneNumberIdentifiers phoneNumberIdentifiers =
|
||||
new PhoneNumberIdentifiers(DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(),
|
||||
@@ -169,7 +169,6 @@ public class AccountCreationDeletionIntegrationTest {
|
||||
profilesManager,
|
||||
secureStorageClient,
|
||||
svr2Client,
|
||||
svr2Client,
|
||||
disconnectionRequestManager,
|
||||
registrationRecoveryPasswordsManager,
|
||||
clientPublicKeysManager,
|
||||
|
||||
@@ -128,7 +128,7 @@ class AccountsManagerChangeNumberIntegrationTest {
|
||||
when(secureStorageClient.deleteStoredData(any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
final SecureValueRecoveryClient svr2Client = mock(SecureValueRecoveryClient.class);
|
||||
when(svr2Client.removeData(any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
when(svr2Client.removeData(any(UUID.class))).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
disconnectionRequestManager = mock(DisconnectionRequestManager.class);
|
||||
|
||||
@@ -158,7 +158,6 @@ class AccountsManagerChangeNumberIntegrationTest {
|
||||
profilesManager,
|
||||
secureStorageClient,
|
||||
svr2Client,
|
||||
svr2Client,
|
||||
disconnectionRequestManager,
|
||||
registrationRecoveryPasswordsManager,
|
||||
clientPublicKeysManager,
|
||||
|
||||
@@ -135,7 +135,6 @@ class AccountsManagerConcurrentModificationIntegrationTest {
|
||||
mock(ProfilesManager.class),
|
||||
mock(SecureStorageClient.class),
|
||||
mock(SecureValueRecoveryClient.class),
|
||||
mock(SecureValueRecoveryClient.class),
|
||||
mock(DisconnectionRequestManager.class),
|
||||
mock(RegistrationRecoveryPasswordsManager.class),
|
||||
mock(ClientPublicKeysManager.class),
|
||||
|
||||
@@ -73,7 +73,6 @@ public class AccountsManagerDeviceTransferIntegrationTest {
|
||||
mock(ProfilesManager.class),
|
||||
mock(SecureStorageClient.class),
|
||||
mock(SecureValueRecoveryClient.class),
|
||||
mock(SecureValueRecoveryClient.class),
|
||||
mock(DisconnectionRequestManager.class),
|
||||
mock(RegistrationRecoveryPasswordsManager.class),
|
||||
mock(ClientPublicKeysManager.class),
|
||||
|
||||
@@ -134,7 +134,6 @@ class AccountsManagerTest {
|
||||
private RedisAdvancedClusterAsyncCommands<String, String> asyncClusterCommands;
|
||||
private AccountsManager accountsManager;
|
||||
private SecureValueRecoveryClient svr2Client;
|
||||
private SecureValueRecoveryClient svrbClient;
|
||||
private DynamicConfiguration dynamicConfiguration;
|
||||
|
||||
private static final Answer<?> ACCOUNT_UPDATE_ANSWER = (answer) -> {
|
||||
@@ -193,13 +192,10 @@ class AccountsManagerTest {
|
||||
}).when(accounts).changeNumber(any(), anyString(), any(), any(), any());
|
||||
|
||||
final SecureStorageClient storageClient = mock(SecureStorageClient.class);
|
||||
when(storageClient.deleteStoredData(any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
when(storageClient.deleteStoredData(any(UUID.class))).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
svr2Client = mock(SecureValueRecoveryClient.class);
|
||||
when(svr2Client.removeData(any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
svrbClient = mock(SecureValueRecoveryClient.class);
|
||||
when(svrbClient.removeData(any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
when(svr2Client.removeData(any(UUID.class))).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
final PhoneNumberIdentifiers phoneNumberIdentifiers = mock(PhoneNumberIdentifiers.class);
|
||||
phoneNumberIdentifiersByE164 = new HashMap<>();
|
||||
@@ -259,7 +255,6 @@ class AccountsManagerTest {
|
||||
profilesManager,
|
||||
storageClient,
|
||||
svr2Client,
|
||||
svrbClient,
|
||||
disconnectionRequestManager,
|
||||
registrationRecoveryPasswordsManager,
|
||||
clientPublicKeysManager,
|
||||
|
||||
@@ -162,7 +162,6 @@ class AccountsManagerUsernameIntegrationTest {
|
||||
profileManager,
|
||||
mock(SecureStorageClient.class),
|
||||
mock(SecureValueRecoveryClient.class),
|
||||
mock(SecureValueRecoveryClient.class),
|
||||
disconnectionRequestManager,
|
||||
mock(RegistrationRecoveryPasswordsManager.class),
|
||||
mock(ClientPublicKeysManager.class),
|
||||
|
||||
@@ -136,7 +136,7 @@ public class AddRemoveDeviceIntegrationTest {
|
||||
when(secureStorageClient.deleteStoredData(any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
final SecureValueRecoveryClient svr2Client = mock(SecureValueRecoveryClient.class);
|
||||
when(svr2Client.removeData(any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
when(svr2Client.removeData(any(UUID.class))).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
final PhoneNumberIdentifiers phoneNumberIdentifiers =
|
||||
new PhoneNumberIdentifiers(DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(),
|
||||
@@ -170,7 +170,6 @@ public class AddRemoveDeviceIntegrationTest {
|
||||
profilesManager,
|
||||
secureStorageClient,
|
||||
svr2Client,
|
||||
svr2Client,
|
||||
mock(DisconnectionRequestManager.class),
|
||||
mock(RegistrationRecoveryPasswordsManager.class),
|
||||
clientPublicKeysManager,
|
||||
|
||||
Reference in New Issue
Block a user