Skip to content

Commit 2137fc5

Browse files
committed
Split BackupInfo RPC into media/message variants
1 parent f792285 commit 2137fc5

3 files changed

Lines changed: 103 additions & 26 deletions

File tree

service/src/main/java/org/whispersystems/textsecuregcm/grpc/BackupsAnonymousGrpcService.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
import org.signal.chat.backup.DeleteMediaRequest;
1717
import org.signal.chat.backup.DeleteMediaResponse;
1818
import org.signal.chat.backup.GetBackupInfoRequest;
19-
import org.signal.chat.backup.GetBackupInfoResponse;
2019
import org.signal.chat.backup.GetCdnCredentialsRequest;
2120
import org.signal.chat.backup.GetCdnCredentialsResponse;
21+
import org.signal.chat.backup.GetMediaBackupInfoResponse;
22+
import org.signal.chat.backup.GetMessageBackupInfoResponse;
2223
import org.signal.chat.backup.GetSvrBCredentialsRequest;
2324
import org.signal.chat.backup.GetSvrBCredentialsResponse;
2425
import org.signal.chat.backup.GetUploadFormRequest;
@@ -37,6 +38,7 @@
3738
import org.signal.libsignal.protocol.ecc.ECPublicKey;
3839
import org.signal.libsignal.zkgroup.InvalidInputException;
3940
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialPresentation;
41+
import org.signal.libsignal.zkgroup.backups.BackupCredentialType;
4042
import org.whispersystems.textsecuregcm.auth.AuthenticatedBackupUser;
4143
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
4244
import org.whispersystems.textsecuregcm.backup.BackupFailedZkAuthenticationException;
@@ -99,19 +101,40 @@ public GetSvrBCredentialsResponse getSvrBCredentials(final GetSvrBCredentialsReq
99101
}
100102

101103
@Override
102-
public GetBackupInfoResponse getBackupInfo(final GetBackupInfoRequest request) throws BackupPermissionException {
104+
public GetMessageBackupInfoResponse getMessageBackupInfo(final GetBackupInfoRequest request) throws BackupPermissionException {
103105
try {
104106
final AuthenticatedBackupUser backupUser = authenticateBackupUser(request.getSignedPresentation());
107+
if (backupUser.credentialType() != BackupCredentialType.MESSAGES) {
108+
throw GrpcExceptions.badAuthentication("credential type for message backup info must be 'messages'");
109+
}
105110
final BackupManager.BackupInfo info = backupManager.backupInfo(backupUser);
106-
return GetBackupInfoResponse.newBuilder().setBackupInfo(GetBackupInfoResponse.BackupInfo.newBuilder()
111+
return GetMessageBackupInfoResponse.newBuilder().setBackupInfo(GetMessageBackupInfoResponse.MessageBackupInfo.newBuilder()
107112
.setBackupName(info.messageBackupKey())
108113
.setCdn(info.cdn())
109114
.setBackupDir(info.backupSubdir())
110-
.setMediaDir(info.mediaSubdir())
111-
.setUsedSpace(info.mediaUsedSpace().orElse(0L)))
115+
.build()).build();
116+
} catch (BackupFailedZkAuthenticationException e) {
117+
return GetMessageBackupInfoResponse.newBuilder()
118+
.setFailedAuthentication(FailedZkAuthentication.newBuilder().setDescription(e.getMessage()).build())
112119
.build();
120+
}
121+
}
122+
123+
@Override
124+
public GetMediaBackupInfoResponse getMediaBackupInfo(final GetBackupInfoRequest request) throws BackupPermissionException {
125+
try {
126+
final AuthenticatedBackupUser backupUser = authenticateBackupUser(request.getSignedPresentation());
127+
if (backupUser.credentialType() != BackupCredentialType.MEDIA) {
128+
throw GrpcExceptions.badAuthentication("credential type for media backup info must be 'media'");
129+
}
130+
final BackupManager.BackupInfo info = backupManager.backupInfo(backupUser);
131+
return GetMediaBackupInfoResponse.newBuilder().setBackupInfo(GetMediaBackupInfoResponse.MediaBackupInfo.newBuilder()
132+
.setBackupDir(info.backupSubdir())
133+
.setMediaDir(info.mediaSubdir())
134+
.setUsedSpace(info.mediaUsedSpace().orElse(0L))
135+
.build()).build();
113136
} catch (BackupFailedZkAuthenticationException e) {
114-
return GetBackupInfoResponse.newBuilder()
137+
return GetMediaBackupInfoResponse.newBuilder()
115138
.setFailedAuthentication(FailedZkAuthentication.newBuilder().setDescription(e.getMessage()).build())
116139
.build();
117140
}

service/src/main/proto/org/signal/chat/backups.proto

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,11 @@ service BackupsAnonymous {
152152
// Retrieve credentials used to interact with the SecureValueRecoveryB service
153153
rpc GetSvrBCredentials(GetSvrBCredentialsRequest) returns (GetSvrBCredentialsResponse) {}
154154

155-
// Retrieve information about the currently stored backup
156-
rpc GetBackupInfo(GetBackupInfoRequest) returns (GetBackupInfoResponse) {}
155+
// Retrieve information about the currently stored message backup
156+
rpc GetMessageBackupInfo(GetBackupInfoRequest) returns (GetMessageBackupInfoResponse) {}
157+
158+
// Retrieve information about the currently stored media backup
159+
rpc GetMediaBackupInfo(GetBackupInfoRequest) returns (GetMediaBackupInfoResponse) {}
157160

158161
// Permanently set the public key of an ED25519 key-pair for the backup-id.
159162
// All requests (including this one!) must sign their BackupAuthCredential
@@ -277,32 +280,47 @@ message GetSvrBCredentialsResponse {
277280
message GetBackupInfoRequest {
278281
SignedPresentation signed_presentation = 1;
279282
}
280-
message GetBackupInfoResponse {
281-
message BackupInfo {
283+
message GetMessageBackupInfoResponse {
284+
message MessageBackupInfo {
282285
// The base directory of your backup data on the cdn. The message backup can
283-
// be found in the returned cdn at /backup_dir/backup_name and stored media can
284-
// be found at /backup_dir/media_dir/media_id
286+
// be found in the returned cdn at /backup_dir/backup_name
287+
string backup_dir = 1;
288+
289+
// The CDN type where the message backup is stored. Media may be stored
290+
// elsewhere.
291+
uint32 cdn = 2;
292+
293+
// The location of the message backup on the cdn. If a backup was previously
294+
// uploaded and unexpired, it can be found at /backup_dir/backup_name.
295+
string backup_name = 3;
296+
}
297+
298+
oneof outcome {
299+
MessageBackupInfo backup_info = 1;
300+
301+
// The provided backup auth credential presentation could not be
302+
// authenticated. Either, the presentation could not be verified, or
303+
// the public key signature was invalid, or there is no backup associated
304+
// with the backup-id in the presentation.
305+
errors.FailedZkAuthentication failed_authentication = 2 [(tag.reason) = "failed_authentication"];
306+
}
307+
}
308+
message GetMediaBackupInfoResponse {
309+
message MediaBackupInfo {
310+
// The base directory of your backup data on the cdn.
285311
string backup_dir = 1;
286312

287313
// The prefix path component for media objects on a cdn. Stored media for a
288314
// media_id can be found at /backup_dir/media_dir/media_id, where the media_id
289315
// is encoded in unpadded url-safe base64.
290316
string media_dir = 2;
291317

292-
// The CDN type where the message backup is stored. Media may be stored
293-
// elsewhere. If absent, no message backup currently exists.
294-
optional uint32 cdn = 3;
295-
296-
// The name of the most recent message backup on the cdn. The backup is at
297-
// /backup_dir/backup_name. If absent, no message backup currently exists.
298-
optional string backup_name = 4;
299-
300318
// The amount of space used to store media
301-
uint64 used_space = 5;
319+
uint64 used_space = 3;
302320
}
303321

304322
oneof outcome {
305-
BackupInfo backup_info = 1;
323+
MediaBackupInfo backup_info = 1;
306324

307325
// The provided backup auth credential presentation could not be
308326
// authenticated. Either, the presentation could not be verified, or

service/src/test/java/org/whispersystems/textsecuregcm/grpc/BackupsAnonymousGrpcServiceTest.java

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.junit.jupiter.api.Test;
3232
import org.junit.jupiter.params.ParameterizedTest;
3333
import org.junit.jupiter.params.provider.Arguments;
34+
import org.junit.jupiter.params.provider.EnumSource;
3435
import org.junit.jupiter.params.provider.MethodSource;
3536
import org.junitpioneer.jupiter.cartesian.CartesianTest;
3637
import org.mockito.Mock;
@@ -41,9 +42,10 @@
4142
import org.signal.chat.backup.DeleteMediaItem;
4243
import org.signal.chat.backup.DeleteMediaRequest;
4344
import org.signal.chat.backup.GetBackupInfoRequest;
44-
import org.signal.chat.backup.GetBackupInfoResponse;
4545
import org.signal.chat.backup.GetCdnCredentialsRequest;
4646
import org.signal.chat.backup.GetCdnCredentialsResponse;
47+
import org.signal.chat.backup.GetMediaBackupInfoResponse;
48+
import org.signal.chat.backup.GetMessageBackupInfoResponse;
4749
import org.signal.chat.backup.GetUploadFormRequest;
4850
import org.signal.chat.backup.GetUploadFormResponse;
4951
import org.signal.chat.backup.ListMediaRequest;
@@ -212,17 +214,51 @@ void putMediaBatchPartialFailure() {
212214
}
213215

214216
@Test
215-
void getBackupInfo() throws BackupException {
217+
void getMessageBackupInfo() throws BackupException {
216218
when(backupManager.backupInfo(any()))
217219
.thenReturn(new BackupManager.BackupInfo(1, "myBackupDir", "myMediaDir", "filename", Optional.empty()));
218220

219-
final GetBackupInfoResponse response = unauthenticatedServiceStub().getBackupInfo(GetBackupInfoRequest.newBuilder()
221+
final GetMessageBackupInfoResponse response = unauthenticatedServiceStub().getMessageBackupInfo(GetBackupInfoRequest.newBuilder()
220222
.setSignedPresentation(signedPresentation(presentation))
221223
.build());
222224
assertThat(response.getBackupInfo().getBackupDir()).isEqualTo("myBackupDir");
223225
assertThat(response.getBackupInfo().getBackupName()).isEqualTo("filename");
224226
assertThat(response.getBackupInfo().getCdn()).isEqualTo(1);
225-
assertThat(response.getBackupInfo().getUsedSpace()).isEqualTo(0L);
227+
}
228+
229+
@Test
230+
void getMediaBackupInfo() throws BackupException {
231+
when(backupManager.authenticateBackupUser(any(), any(), any()))
232+
.thenReturn(backupUser(presentation.getBackupId(), BackupCredentialType.MEDIA, BackupLevel.PAID));
233+
when(backupManager.backupInfo(any()))
234+
.thenReturn(new BackupManager.BackupInfo(1, "myBackupDir", "myMediaDir", "filename", Optional.of(123L)));
235+
236+
final GetMediaBackupInfoResponse response = unauthenticatedServiceStub().getMediaBackupInfo(GetBackupInfoRequest.newBuilder()
237+
.setSignedPresentation(signedPresentation(presentation))
238+
.build());
239+
assertThat(response.getBackupInfo().getBackupDir()).isEqualTo("myBackupDir");
240+
assertThat(response.getBackupInfo().getMediaDir()).isEqualTo("myMediaDir");
241+
assertThat(response.getBackupInfo().getUsedSpace()).isEqualTo(123);
242+
}
243+
244+
@ParameterizedTest
245+
@EnumSource(BackupCredentialType.class)
246+
void getBackupInfoWrongCredentialType(BackupCredentialType credentialType)
247+
throws BackupFailedZkAuthenticationException {
248+
when(backupManager.authenticateBackupUser(any(), any(), any()))
249+
.thenReturn(backupUser(presentation.getBackupId(), credentialType, BackupLevel.PAID));
250+
assertThatExceptionOfType(StatusRuntimeException.class).isThrownBy(() -> {
251+
switch (credentialType) {
252+
case MEDIA -> unauthenticatedServiceStub().getMessageBackupInfo(GetBackupInfoRequest.newBuilder()
253+
.setSignedPresentation(signedPresentation(presentation))
254+
.build());
255+
case MESSAGES -> unauthenticatedServiceStub().getMediaBackupInfo(GetBackupInfoRequest.newBuilder()
256+
.setSignedPresentation(signedPresentation(presentation))
257+
.build());
258+
}
259+
})
260+
.matches(ex -> ex.getStatus().getCode() == Status.Code.INVALID_ARGUMENT)
261+
.matches(ex -> GrpcTestUtils.extractErrorInfo(ex).getReason().equals("BAD_AUTHENTICATION"));
226262
}
227263

228264

0 commit comments

Comments
 (0)