Add support for fetching archive media metadata.

This commit is contained in:
Greyson Parrelli
2024-01-16 16:16:27 -05:00
parent cf59249d3d
commit 2194fbd535
6 changed files with 118 additions and 6 deletions

View File

@@ -13,6 +13,7 @@ import org.signal.libsignal.zkgroup.backups.BackupAuthCredential
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialRequestContext
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialResponse
import org.whispersystems.signalservice.api.NetworkResult
import org.whispersystems.signalservice.api.archive.ArchiveGetMediaItemsResponse.StoredMediaObject
import org.whispersystems.signalservice.api.backup.BackupKey
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.internal.push.PushServiceSocket
@@ -120,6 +121,43 @@ class ArchiveApi(
}
}
/**
* Retrieves all media items in the user's archive. Note that this could be a very large number of items, making this only suitable for debugging.
* Use [getArchiveMediaItemsPage] in production.
*/
fun debugGetUploadedMediaItemMetadata(backupKey: BackupKey, serviceCredential: ArchiveServiceCredential): NetworkResult<List<ArchiveGetMediaItemsResponse.StoredMediaObject>> {
return NetworkResult.fromFetch {
val zkCredential = getZkCredential(backupKey, serviceCredential)
val presentationData = CredentialPresentationData.from(backupKey, zkCredential, backupServerPublicParams)
val credentialPresentation = presentationData.toArchiveCredentialPresentation()
val mediaObjects: MutableList<StoredMediaObject> = ArrayList()
var cursor: String? = null
do {
val response: ArchiveGetMediaItemsResponse = pushServiceSocket.getArchiveMediaItemsPage(credentialPresentation, 512, cursor)
mediaObjects += response.storedMediaObjects
cursor = response.cursor
} while (cursor != null)
mediaObjects
}
}
/**
* Retrieves a page of media items in the user's archive.
* @param limit The maximum number of items to return.
* @param cursor A token that can be read from your previous response, telling the server where to start the next page.
*/
fun getArchiveMediaItemsPage(backupKey: BackupKey, serviceCredential: ArchiveServiceCredential, limit: Int, cursor: String): NetworkResult<ArchiveGetMediaItemsResponse> {
return NetworkResult.fromFetch {
val zkCredential = getZkCredential(backupKey, serviceCredential)
val presentationData = CredentialPresentationData.from(backupKey, zkCredential, backupServerPublicParams)
pushServiceSocket.getArchiveMediaItemsPage(presentationData.toArchiveCredentialPresentation(), 512, cursor)
}
}
private fun getZkCredential(backupKey: BackupKey, serviceCredential: ArchiveServiceCredential): BackupAuthCredential {
val backupAuthResponse = BackupAuthCredentialResponse(serviceCredential.credential)
val backupRequestContext = BackupAuthCredentialRequestContext.create(backupKey.value, aci.rawUuid)
@@ -127,7 +165,7 @@ class ArchiveApi(
return backupRequestContext.receiveResponse(
backupAuthResponse,
backupServerPublicParams,
10
20
)
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.api.archive
import com.fasterxml.jackson.annotation.JsonProperty
/**
* Response body for getting the media items stored in the user's archive.
*/
class ArchiveGetMediaItemsResponse(
@JsonProperty val storedMediaObjects: List<StoredMediaObject>,
@JsonProperty val cursor: String?
) {
class StoredMediaObject(
@JsonProperty val cdn: Int,
@JsonProperty val mediaId: String,
@JsonProperty val objectLength: Long
)
}

View File

@@ -44,6 +44,7 @@ import org.whispersystems.signalservice.api.account.PniKeyDistributionRequest;
import org.whispersystems.signalservice.api.account.PreKeyCollection;
import org.whispersystems.signalservice.api.account.PreKeyUpload;
import org.whispersystems.signalservice.api.archive.ArchiveCredentialPresentation;
import org.whispersystems.signalservice.api.archive.ArchiveGetMediaItemsResponse;
import org.whispersystems.signalservice.api.archive.ArchiveServiceCredentialsResponse;
import org.whispersystems.signalservice.api.archive.ArchiveGetBackupInfoResponse;
import org.whispersystems.signalservice.api.archive.ArchiveMessageBackupUploadFormResponse;
@@ -161,6 +162,7 @@ import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -311,6 +313,7 @@ public class PushServiceSocket {
private static final String ARCHIVE_PUBLIC_KEY = "/v1/archives/keys";
private static final String ARCHIVE_INFO = "/v1/archives";
private static final String ARCHIVE_MESSAGE_UPLOAD_FORM = "/v1/archives/upload/form";
private static final String ARCHIVE_MEDIA_LIST = "/v1/archives/media?limit=%d";
private static final String CALL_LINK_CREATION_AUTH = "/v1/call-link/create-auth";
private static final String SERVER_DELIVERED_TIMESTAMP_HEADER = "X-Signal-Timestamp";
@@ -512,6 +515,39 @@ public class PushServiceSocket {
return JsonUtil.fromJson(response, ArchiveGetBackupInfoResponse.class);
}
public List<ArchiveGetMediaItemsResponse.StoredMediaObject> debugGetAllArchiveMediaItems(ArchiveCredentialPresentation credentialPresentation) throws IOException {
List<ArchiveGetMediaItemsResponse.StoredMediaObject> mediaObjects = new ArrayList<>();
String cursor = null;
do {
ArchiveGetMediaItemsResponse response = getArchiveMediaItemsPage(credentialPresentation, 512, cursor);
mediaObjects.addAll(response.getStoredMediaObjects());
cursor = response.getCursor();
} while (cursor != null);
return mediaObjects;
}
/**
* Retrieves a page of media items in the user's archive.
* @param cursor A token that can be read from your previous response, telling the server where to start the next page.
*/
public ArchiveGetMediaItemsResponse getArchiveMediaItemsPage(ArchiveCredentialPresentation credentialPresentation, int limit, String cursor) throws IOException {
Map<String, String> headers = new HashMap<>();
headers.put("X-Signal-ZK-Auth", Base64.encodeWithPadding(credentialPresentation.getPresentation()));
headers.put("X-Signal-ZK-Auth-Signature", Base64.encodeWithPadding(credentialPresentation.getSignedPresentation()));
String url = String.format(Locale.US, ARCHIVE_MEDIA_LIST, limit);
if (cursor != null) {
url += "&cursor=" + cursor;
}
String response = makeServiceRequestWithoutAuthentication(url, "GET", null, headers, NO_HANDLER);
return JsonUtil.fromJson(response, ArchiveGetMediaItemsResponse.class);
}
public ArchiveMessageBackupUploadFormResponse getArchiveMessageBackupUploadForm(ArchiveCredentialPresentation credentialPresentation) throws IOException {
Map<String, String> headers = new HashMap<>();
headers.put("X-Signal-ZK-Auth", Base64.encodeWithPadding(credentialPresentation.getPresentation()));