mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-27 14:40:22 +00:00
Convert chat-based backup calls to WebSocket.
This commit is contained in:
committed by
Greyson Parrelli
parent
305b380fef
commit
cf78c76bbb
@@ -382,7 +382,7 @@ object AppDependencies {
|
||||
fun provideScheduledMessageManager(): ScheduledMessageManager
|
||||
fun provideLibsignalNetwork(config: SignalServiceConfiguration): Network
|
||||
fun provideBillingApi(): BillingApi
|
||||
fun provideArchiveApi(pushServiceSocket: PushServiceSocket): ArchiveApi
|
||||
fun provideArchiveApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket, unauthWebSocket: SignalWebSocket.UnauthenticatedWebSocket, pushServiceSocket: PushServiceSocket): ArchiveApi
|
||||
fun provideKeysApi(pushServiceSocket: PushServiceSocket): KeysApi
|
||||
fun provideAttachmentApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket, pushServiceSocket: PushServiceSocket): AttachmentApi
|
||||
fun provideLinkDeviceApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): LinkDeviceApi
|
||||
|
||||
@@ -465,8 +465,8 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull ArchiveApi provideArchiveApi(@NonNull PushServiceSocket pushServiceSocket) {
|
||||
return new ArchiveApi(pushServiceSocket);
|
||||
public @NonNull ArchiveApi provideArchiveApi(@NonNull SignalWebSocket.AuthenticatedWebSocket authWebSocket, @NonNull SignalWebSocket.UnauthenticatedWebSocket unauthWebSocket, @NonNull PushServiceSocket pushServiceSocket) {
|
||||
return new ArchiveApi(authWebSocket, unauthWebSocket, pushServiceSocket);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -136,7 +136,7 @@ class NetworkDependenciesModule(
|
||||
}
|
||||
|
||||
val archiveApi: ArchiveApi by lazy {
|
||||
provider.provideArchiveApi(pushServiceSocket)
|
||||
provider.provideArchiveApi(authWebSocket, unauthWebSocket, pushServiceSocket)
|
||||
}
|
||||
|
||||
val keysApi: KeysApi by lazy {
|
||||
|
||||
@@ -875,6 +875,8 @@ class RegistrationViewModel : ViewModel() {
|
||||
SignalStore.registration.localRegistrationMetadata = metadata
|
||||
RegistrationRepository.registerAccountLocally(context, metadata)
|
||||
|
||||
AppDependencies.authWebSocket.connect()
|
||||
|
||||
if (!remoteResult.storageCapable && SignalStore.registration.restoreDecisionState.isDecisionPending) {
|
||||
Log.v(TAG, "Not storage capable and still pending restore decision, likely an account with no data to restore, skipping post register restore")
|
||||
SignalStore.registration.restoreDecisionState = RestoreDecisionState.NewAccount
|
||||
|
||||
@@ -216,7 +216,7 @@ class MockApplicationDependencyProvider : AppDependencies.Provider {
|
||||
return mockk(relaxed = true)
|
||||
}
|
||||
|
||||
override fun provideArchiveApi(pushServiceSocket: PushServiceSocket): ArchiveApi {
|
||||
override fun provideArchiveApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket, unauthWebSocket: SignalWebSocket.UnauthenticatedWebSocket, pushServiceSocket: PushServiceSocket): ArchiveApi {
|
||||
return mockk(relaxed = true)
|
||||
}
|
||||
|
||||
|
||||
@@ -120,6 +120,19 @@ sealed class NetworkResult<T>(
|
||||
ApplicationError(e)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a local operation, [block], that may throw an exception that should be wrapped in an [ApplicationError]
|
||||
* and abort downstream network requests that directly depend on the output of the local operation. Should
|
||||
* be used almost exclusively prior to a [then].
|
||||
*/
|
||||
fun <T : Any> fromLocal(block: () -> T): NetworkResult<T> {
|
||||
return try {
|
||||
Success(block())
|
||||
} catch (e: Throwable) {
|
||||
ApplicationError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs [operation] to perform a network call. If [shouldRetry] returns false for the result, then returns it. Otherwise will call [operation] repeatedly
|
||||
* until [shouldRetry] returns false or is called [maxAttempts] number of times.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.whispersystems.signalservice.api.archive
|
||||
|
||||
import org.signal.core.util.isNotNullOrBlank
|
||||
import org.signal.libsignal.protocol.ecc.ECPrivateKey
|
||||
import org.signal.libsignal.protocol.ecc.ECPublicKey
|
||||
import org.signal.libsignal.zkgroup.GenericServerPublicParams
|
||||
@@ -18,26 +19,31 @@ import org.whispersystems.signalservice.api.backup.MediaRootBackupKey
|
||||
import org.whispersystems.signalservice.api.backup.MessageBackupKey
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.websocket.SignalWebSocket
|
||||
import org.whispersystems.signalservice.internal.delete
|
||||
import org.whispersystems.signalservice.internal.get
|
||||
import org.whispersystems.signalservice.internal.post
|
||||
import org.whispersystems.signalservice.internal.push.AttachmentUploadForm
|
||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket
|
||||
import org.whispersystems.signalservice.internal.put
|
||||
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage
|
||||
import java.io.InputStream
|
||||
import java.time.Instant
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
* Class to interact with various archive-related endpoints.
|
||||
* Why is it called archive instead of backup? Because SVR took the "backup" endpoint namespace first :)
|
||||
*/
|
||||
class ArchiveApi(private val pushServiceSocket: PushServiceSocket) {
|
||||
class ArchiveApi(
|
||||
private val authWebSocket: SignalWebSocket.AuthenticatedWebSocket,
|
||||
private val unauthWebSocket: SignalWebSocket.UnauthenticatedWebSocket,
|
||||
private val pushServiceSocket: PushServiceSocket
|
||||
) {
|
||||
|
||||
private val backupServerPublicParams: GenericServerPublicParams = GenericServerPublicParams(pushServiceSocket.configuration.backupServerPublicParams)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun create(pushServiceSocket: PushServiceSocket): ArchiveApi {
|
||||
return ArchiveApi(pushServiceSocket)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a set of credentials one can use to authorize other requests.
|
||||
*
|
||||
@@ -56,9 +62,11 @@ class ArchiveApi(private val pushServiceSocket: PushServiceSocket) {
|
||||
* - 429: Rate-limited
|
||||
*/
|
||||
fun getServiceCredentials(currentTime: Long): NetworkResult<ArchiveServiceCredentialsResponse> {
|
||||
return NetworkResult.fromFetch {
|
||||
pushServiceSocket.getArchiveCredentials(currentTime)
|
||||
}
|
||||
val roundedToNearestDay = currentTime.milliseconds.inWholeDays.days
|
||||
val endTime = roundedToNearestDay + 7.days
|
||||
|
||||
val request = WebSocketRequestMessage.get("/v1/archives/auth?redemptionStartSeconds=${roundedToNearestDay.inWholeSeconds}&redemptionEndSeconds=${endTime.inWholeSeconds}")
|
||||
return NetworkResult.fromWebSocketRequest(authWebSocket, request, ArchiveServiceCredentialsResponse::class)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,12 +81,12 @@ class ArchiveApi(private val pushServiceSocket: PushServiceSocket) {
|
||||
* - 429: Rate-limited
|
||||
*/
|
||||
fun getCdnReadCredentials(cdnNumber: Int, aci: ACI, archiveServiceAccess: ArchiveServiceAccess<*>): NetworkResult<GetArchiveCdnCredentialsResponse> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(aci, archiveServiceAccess)
|
||||
val presentationData = CredentialPresentationData.from(archiveServiceAccess.backupKey, aci, zkCredential, backupServerPublicParams)
|
||||
|
||||
pushServiceSocket.getArchiveCdnReadCredentials(cdnNumber, presentationData.toArchiveCredentialPresentation())
|
||||
}
|
||||
return getCredentialPresentation(aci, archiveServiceAccess)
|
||||
.map { it.toArchiveCredentialPresentation().toHeaders() }
|
||||
.then { headers ->
|
||||
val request = WebSocketRequestMessage.get("/v1/archives/auth/read?cdn=$cdnNumber", headers)
|
||||
NetworkResult.fromWebSocketRequest(unauthWebSocket, request, GetArchiveCdnCredentialsResponse::class)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,11 +100,15 @@ class ArchiveApi(private val pushServiceSocket: PushServiceSocket) {
|
||||
* - 429: Rate-limited
|
||||
*/
|
||||
fun triggerBackupIdReservation(messageBackupKey: MessageBackupKey, mediaRootBackupKey: MediaRootBackupKey, aci: ACI): NetworkResult<Unit> {
|
||||
return NetworkResult.fromFetch {
|
||||
val messageBackupRequestContext = BackupAuthCredentialRequestContext.create(messageBackupKey.value, aci.rawUuid)
|
||||
val mediaBackupRequestContext = BackupAuthCredentialRequestContext.create(mediaRootBackupKey.value, aci.rawUuid)
|
||||
pushServiceSocket.setArchiveBackupId(messageBackupRequestContext.request, mediaBackupRequestContext.request)
|
||||
}
|
||||
val messageBackupRequestContext = BackupAuthCredentialRequestContext.create(messageBackupKey.value, aci.rawUuid)
|
||||
val mediaBackupRequestContext = BackupAuthCredentialRequestContext.create(mediaRootBackupKey.value, aci.rawUuid)
|
||||
|
||||
val request = WebSocketRequestMessage.put(
|
||||
"/v1/archives/backupid",
|
||||
ArchiveSetBackupIdRequest(messageBackupRequestContext.request, mediaBackupRequestContext.request)
|
||||
)
|
||||
|
||||
return NetworkResult.fromWebSocketRequest(authWebSocket, request)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,11 +125,14 @@ class ArchiveApi(private val pushServiceSocket: PushServiceSocket) {
|
||||
* - 429: Rate-limited
|
||||
*/
|
||||
fun setPublicKey(aci: ACI, archiveServiceAccess: ArchiveServiceAccess<*>): NetworkResult<Unit> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(aci, archiveServiceAccess)
|
||||
val presentationData = CredentialPresentationData.from(archiveServiceAccess.backupKey, aci, zkCredential, backupServerPublicParams)
|
||||
pushServiceSocket.setArchivePublicKey(presentationData.publicKey, presentationData.toArchiveCredentialPresentation())
|
||||
}
|
||||
return getCredentialPresentation(aci, archiveServiceAccess)
|
||||
.then { presentation ->
|
||||
val headers = presentation.toArchiveCredentialPresentation().toHeaders()
|
||||
val publicKey = presentation.publicKey
|
||||
|
||||
val request = WebSocketRequestMessage.put("/v1/archives/keys", ArchiveSetPublicKeyRequest(publicKey), headers)
|
||||
NetworkResult.fromWebSocketRequest(unauthWebSocket, request)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,11 +145,12 @@ class ArchiveApi(private val pushServiceSocket: PushServiceSocket) {
|
||||
* - 429: Rate-limited
|
||||
*/
|
||||
fun getMessageBackupUploadForm(aci: ACI, archiveServiceAccess: ArchiveServiceAccess<MessageBackupKey>): NetworkResult<AttachmentUploadForm> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(aci, archiveServiceAccess)
|
||||
val presentationData = CredentialPresentationData.from(archiveServiceAccess.backupKey, aci, zkCredential, backupServerPublicParams)
|
||||
pushServiceSocket.getArchiveMessageBackupUploadForm(presentationData.toArchiveCredentialPresentation())
|
||||
}
|
||||
return getCredentialPresentation(aci, archiveServiceAccess)
|
||||
.map { it.toArchiveCredentialPresentation().toHeaders() }
|
||||
.then { headers ->
|
||||
val request = WebSocketRequestMessage.get("/v1/archives/upload/form", headers)
|
||||
NetworkResult.fromWebSocketRequest(unauthWebSocket, request, AttachmentUploadForm::class)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,11 +160,12 @@ class ArchiveApi(private val pushServiceSocket: PushServiceSocket) {
|
||||
* Will return a [NetworkResult.StatusCodeError] with status code 404 if you haven't uploaded a backup yet.
|
||||
*/
|
||||
fun getBackupInfo(aci: ACI, archiveServiceAccess: ArchiveServiceAccess<*>): NetworkResult<ArchiveGetBackupInfoResponse> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(aci, archiveServiceAccess)
|
||||
val presentationData = CredentialPresentationData.from(archiveServiceAccess.backupKey, aci, zkCredential, backupServerPublicParams)
|
||||
pushServiceSocket.getArchiveBackupInfo(presentationData.toArchiveCredentialPresentation())
|
||||
}
|
||||
return getCredentialPresentation(aci, archiveServiceAccess)
|
||||
.map { it.toArchiveCredentialPresentation().toHeaders() }
|
||||
.then { headers ->
|
||||
val request = WebSocketRequestMessage.get("/v1/archives", headers)
|
||||
NetworkResult.fromWebSocketRequest(unauthWebSocket, request, ArchiveGetBackupInfoResponse::class)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,11 +182,12 @@ class ArchiveApi(private val pushServiceSocket: PushServiceSocket) {
|
||||
* - 429: Rate limited.
|
||||
*/
|
||||
fun refreshBackup(aci: ACI, archiveServiceAccess: ArchiveServiceAccess<MessageBackupKey>): NetworkResult<Unit> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(aci, archiveServiceAccess)
|
||||
val presentationData = CredentialPresentationData.from(archiveServiceAccess.backupKey, aci, zkCredential, backupServerPublicParams)
|
||||
pushServiceSocket.refreshBackup(presentationData.toArchiveCredentialPresentation())
|
||||
}
|
||||
return getCredentialPresentation(aci, archiveServiceAccess)
|
||||
.map { it.toArchiveCredentialPresentation().toHeaders() }
|
||||
.then { headers ->
|
||||
val request = WebSocketRequestMessage.post(path = "/v1/archives", body = null, headers = headers)
|
||||
NetworkResult.fromWebSocketRequest(unauthWebSocket, request)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,22 +204,12 @@ class ArchiveApi(private val pushServiceSocket: PushServiceSocket) {
|
||||
*
|
||||
*/
|
||||
fun deleteBackup(aci: ACI, archiveServiceAccess: ArchiveServiceAccess<MessageBackupKey>): NetworkResult<Unit> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(aci, archiveServiceAccess)
|
||||
val presentationData = CredentialPresentationData.from(archiveServiceAccess.backupKey, aci, zkCredential, backupServerPublicParams)
|
||||
pushServiceSocket.deleteBackup(presentationData.toArchiveCredentialPresentation())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the media objects in the backup
|
||||
*/
|
||||
fun listMediaObjects(aci: ACI, archiveServiceAccess: ArchiveServiceAccess<MediaRootBackupKey>, limit: Int, cursor: String? = null): NetworkResult<ArchiveGetMediaItemsResponse> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(aci, archiveServiceAccess)
|
||||
val presentationData = CredentialPresentationData.from(archiveServiceAccess.backupKey, aci, zkCredential, backupServerPublicParams)
|
||||
pushServiceSocket.getArchiveMediaItemsPage(presentationData.toArchiveCredentialPresentation(), limit, cursor)
|
||||
}
|
||||
return getCredentialPresentation(aci, archiveServiceAccess)
|
||||
.map { it.toArchiveCredentialPresentation().toHeaders() }
|
||||
.then { headers ->
|
||||
val request = WebSocketRequestMessage.delete("/v1/archives", headers)
|
||||
NetworkResult.fromWebSocketRequest(unauthWebSocket, request)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,11 +246,12 @@ class ArchiveApi(private val pushServiceSocket: PushServiceSocket) {
|
||||
* - 429: Rate-limited
|
||||
*/
|
||||
fun getMediaUploadForm(aci: ACI, archiveServiceAccess: ArchiveServiceAccess<MediaRootBackupKey>): NetworkResult<AttachmentUploadForm> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(aci, archiveServiceAccess)
|
||||
val presentationData = CredentialPresentationData.from(archiveServiceAccess.backupKey, aci, zkCredential, backupServerPublicParams)
|
||||
pushServiceSocket.getArchiveMediaUploadForm(presentationData.toArchiveCredentialPresentation())
|
||||
}
|
||||
return getCredentialPresentation(aci, archiveServiceAccess)
|
||||
.map { it.toArchiveCredentialPresentation().toHeaders() }
|
||||
.then { headers ->
|
||||
val request = WebSocketRequestMessage.get("/v1/archives/media/upload/form", headers)
|
||||
NetworkResult.fromWebSocketRequest(unauthWebSocket, request, AttachmentUploadForm::class)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,12 +279,12 @@ class ArchiveApi(private val pushServiceSocket: PushServiceSocket) {
|
||||
* @param cursor A token that can be read from your previous response, telling the server where to start the next page.
|
||||
*/
|
||||
fun getArchiveMediaItemsPage(aci: ACI, archiveServiceAccess: ArchiveServiceAccess<MediaRootBackupKey>, limit: Int, cursor: String?): NetworkResult<ArchiveGetMediaItemsResponse> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(aci, archiveServiceAccess)
|
||||
val presentationData = CredentialPresentationData.from(archiveServiceAccess.backupKey, aci, zkCredential, backupServerPublicParams)
|
||||
|
||||
pushServiceSocket.getArchiveMediaItemsPage(presentationData.toArchiveCredentialPresentation(), limit, cursor)
|
||||
}
|
||||
return getCredentialPresentation(aci, archiveServiceAccess)
|
||||
.map { it.toArchiveCredentialPresentation().toHeaders() }
|
||||
.then { headers ->
|
||||
val request = WebSocketRequestMessage.get("/v1/archives/media?limit=$limit${if (cursor.isNotNullOrBlank()) "&cursor=$cursor" else ""}", headers)
|
||||
NetworkResult.fromWebSocketRequest(unauthWebSocket, request, ArchiveGetMediaItemsResponse::class)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,12 +303,12 @@ class ArchiveApi(private val pushServiceSocket: PushServiceSocket) {
|
||||
archiveServiceAccess: ArchiveServiceAccess<MediaRootBackupKey>,
|
||||
item: ArchiveMediaRequest
|
||||
): NetworkResult<ArchiveMediaResponse> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(aci, archiveServiceAccess)
|
||||
val presentationData = CredentialPresentationData.from(archiveServiceAccess.backupKey, aci, zkCredential, backupServerPublicParams)
|
||||
|
||||
pushServiceSocket.archiveAttachmentMedia(presentationData.toArchiveCredentialPresentation(), item)
|
||||
}
|
||||
return getCredentialPresentation(aci, archiveServiceAccess)
|
||||
.map { it.toArchiveCredentialPresentation().toHeaders() }
|
||||
.then { headers ->
|
||||
val request = WebSocketRequestMessage.put("/v1/archives/media", item, headers)
|
||||
NetworkResult.fromWebSocketRequest(unauthWebSocket, request, ArchiveMediaResponse::class)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -310,14 +319,12 @@ class ArchiveApi(private val pushServiceSocket: PushServiceSocket) {
|
||||
archiveServiceAccess: ArchiveServiceAccess<MediaRootBackupKey>,
|
||||
items: List<ArchiveMediaRequest>
|
||||
): NetworkResult<BatchArchiveMediaResponse> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(aci, archiveServiceAccess)
|
||||
val presentationData = CredentialPresentationData.from(archiveServiceAccess.backupKey, aci, zkCredential, backupServerPublicParams)
|
||||
|
||||
val request = BatchArchiveMediaRequest(items = items)
|
||||
|
||||
pushServiceSocket.archiveAttachmentMedia(presentationData.toArchiveCredentialPresentation(), request)
|
||||
}
|
||||
return getCredentialPresentation(aci, archiveServiceAccess)
|
||||
.map { it.toArchiveCredentialPresentation().toHeaders() }
|
||||
.then { headers ->
|
||||
val request = WebSocketRequestMessage.put("/v1/archives/media/batch", BatchArchiveMediaRequest(items = items), headers)
|
||||
NetworkResult.fromWebSocketRequest(unauthWebSocket, request, BatchArchiveMediaResponse::class)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,12 +342,18 @@ class ArchiveApi(private val pushServiceSocket: PushServiceSocket) {
|
||||
archiveServiceAccess: ArchiveServiceAccess<MediaRootBackupKey>,
|
||||
mediaToDelete: List<DeleteArchivedMediaRequest.ArchivedMediaObject>
|
||||
): NetworkResult<Unit> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(aci, archiveServiceAccess)
|
||||
val presentationData = CredentialPresentationData.from(archiveServiceAccess.backupKey, aci, zkCredential, backupServerPublicParams)
|
||||
val request = DeleteArchivedMediaRequest(mediaToDelete = mediaToDelete)
|
||||
return getCredentialPresentation(aci, archiveServiceAccess)
|
||||
.map { it.toArchiveCredentialPresentation().toHeaders() }
|
||||
.then { headers ->
|
||||
val request = WebSocketRequestMessage.post("/v1/archives/media/delete", DeleteArchivedMediaRequest(mediaToDelete = mediaToDelete), headers)
|
||||
NetworkResult.fromWebSocketRequest(unauthWebSocket, request)
|
||||
}
|
||||
}
|
||||
|
||||
pushServiceSocket.deleteArchivedMedia(presentationData.toArchiveCredentialPresentation(), request)
|
||||
private fun getCredentialPresentation(aci: ACI, archiveServiceAccess: ArchiveServiceAccess<*>): NetworkResult<CredentialPresentationData> {
|
||||
return NetworkResult.fromLocal {
|
||||
val zkCredential = getZkCredential(aci, archiveServiceAccess)
|
||||
CredentialPresentationData.from(archiveServiceAccess.backupKey, aci, zkCredential, backupServerPublicParams)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,24 @@ import java.security.SecureRandom
|
||||
/**
|
||||
* Create a basic GET web socket request
|
||||
*/
|
||||
fun WebSocketRequestMessage.Companion.get(path: String): WebSocketRequestMessage {
|
||||
fun WebSocketRequestMessage.Companion.get(path: String, headers: Map<String, String> = emptyMap()): WebSocketRequestMessage {
|
||||
return WebSocketRequestMessage(
|
||||
verb = "GET",
|
||||
path = path,
|
||||
headers = headers.toHeaderList(),
|
||||
id = SecureRandom().nextLong()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a basic POST web socket request
|
||||
*/
|
||||
fun WebSocketRequestMessage.Companion.post(path: String, body: Any?, headers: Map<String, String> = emptyMap()): WebSocketRequestMessage {
|
||||
return WebSocketRequestMessage(
|
||||
verb = "POST",
|
||||
path = path,
|
||||
body = body?.let { JsonUtil.toJsonByteString(body) },
|
||||
headers = (if (body != null) listOf("content-type:application/json") else emptyList()) + headers.toHeaderList(),
|
||||
id = SecureRandom().nextLong()
|
||||
)
|
||||
}
|
||||
@@ -23,10 +37,11 @@ fun WebSocketRequestMessage.Companion.get(path: String): WebSocketRequestMessage
|
||||
/**
|
||||
* Create a basic DELETE web socket request
|
||||
*/
|
||||
fun WebSocketRequestMessage.Companion.delete(path: String): WebSocketRequestMessage {
|
||||
fun WebSocketRequestMessage.Companion.delete(path: String, headers: Map<String, String> = emptyMap()): WebSocketRequestMessage {
|
||||
return WebSocketRequestMessage(
|
||||
verb = "DELETE",
|
||||
path = path,
|
||||
headers = headers.toHeaderList(),
|
||||
id = SecureRandom().nextLong()
|
||||
)
|
||||
}
|
||||
@@ -34,12 +49,16 @@ fun WebSocketRequestMessage.Companion.delete(path: String): WebSocketRequestMess
|
||||
/**
|
||||
* Create a basic PUT web socket request, where body is JSON-ified.
|
||||
*/
|
||||
fun WebSocketRequestMessage.Companion.put(path: String, body: Any): WebSocketRequestMessage {
|
||||
fun WebSocketRequestMessage.Companion.put(path: String, body: Any, headers: Map<String, String> = emptyMap()): WebSocketRequestMessage {
|
||||
return WebSocketRequestMessage(
|
||||
verb = "PUT",
|
||||
path = path,
|
||||
headers = listOf("content-type:application/json"),
|
||||
headers = listOf("content-type:application/json") + headers.toHeaderList(),
|
||||
body = JsonUtil.toJsonByteString(body),
|
||||
id = SecureRandom().nextLong()
|
||||
)
|
||||
}
|
||||
|
||||
private fun Map<String, String>.toHeaderList(): List<String> {
|
||||
return map { (key, value) -> "$key:$value" }
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.signal.libsignal.protocol.logging.Log;
|
||||
import org.signal.libsignal.protocol.state.PreKeyBundle;
|
||||
import org.signal.libsignal.protocol.util.Pair;
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||
import org.signal.libsignal.zkgroup.backups.BackupAuthCredentialRequest;
|
||||
import org.signal.libsignal.zkgroup.calllinks.CreateCallLinkCredentialRequest;
|
||||
import org.signal.libsignal.zkgroup.calllinks.CreateCallLinkCredentialResponse;
|
||||
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
|
||||
@@ -47,17 +46,12 @@ import org.whispersystems.signalservice.api.account.AccountAttributes;
|
||||
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.ArchiveGetBackupInfoResponse;
|
||||
import org.whispersystems.signalservice.api.archive.ArchiveGetMediaItemsResponse;
|
||||
import org.whispersystems.signalservice.api.archive.ArchiveMediaRequest;
|
||||
import org.whispersystems.signalservice.api.archive.ArchiveMediaResponse;
|
||||
import org.whispersystems.signalservice.api.archive.ArchiveServiceCredentialsResponse;
|
||||
import org.whispersystems.signalservice.api.archive.ArchiveSetBackupIdRequest;
|
||||
import org.whispersystems.signalservice.api.archive.ArchiveSetPublicKeyRequest;
|
||||
import org.whispersystems.signalservice.api.archive.BatchArchiveMediaRequest;
|
||||
import org.whispersystems.signalservice.api.archive.BatchArchiveMediaResponse;
|
||||
import org.whispersystems.signalservice.api.archive.DeleteArchivedMediaRequest;
|
||||
import org.whispersystems.signalservice.api.archive.GetArchiveCdnCredentialsResponse;
|
||||
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess;
|
||||
import org.whispersystems.signalservice.api.groupsv2.CredentialResponse;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
|
||||
@@ -230,6 +224,7 @@ public class PushServiceSocket {
|
||||
private static final String ATTACHMENT_V4_PATH = "/v4/attachments/form/upload";
|
||||
|
||||
private static final String PAYMENTS_AUTH_PATH = "/v1/payments/auth";
|
||||
private static final String PAYMENTS_CONVERSIONS = "/v1/payments/conversions";
|
||||
|
||||
private static final String PROFILE_PATH = "/v1/profile/%s";
|
||||
private static final String PROFILE_BATCH_CHECK_PATH = "/v1/profile/identity_check/batch";
|
||||
@@ -239,7 +234,6 @@ public class PushServiceSocket {
|
||||
|
||||
private static final String ATTACHMENT_KEY_DOWNLOAD_PATH = "attachments/%s";
|
||||
private static final String ATTACHMENT_ID_DOWNLOAD_PATH = "attachments/%d";
|
||||
private static final String ATTACHMENT_UPLOAD_PATH = "attachments/";
|
||||
private static final String AVATAR_UPLOAD_PATH = "";
|
||||
|
||||
private static final String STICKER_MANIFEST_PATH = "stickers/%s/manifest.proto";
|
||||
@@ -254,9 +248,6 @@ public class PushServiceSocket {
|
||||
private static final String GROUPSV2_TOKEN = "/v2/groups/token";
|
||||
private static final String GROUPSV2_JOINED_AT = "/v2/groups/joined_at_version";
|
||||
|
||||
private static final String PAYMENTS_CONVERSIONS = "/v1/payments/conversions";
|
||||
|
||||
|
||||
private static final String SUBMIT_RATE_LIMIT_CHALLENGE = "/v1/challenge";
|
||||
private static final String REQUEST_RATE_LIMIT_PUSH_CHALLENGE = "/v1/challenge/push";
|
||||
|
||||
@@ -293,22 +284,10 @@ public class PushServiceSocket {
|
||||
private static final String BACKUP_AUTH_CHECK_V2 = "/v2/backup/auth/check";
|
||||
private static final String BACKUP_AUTH_CHECK_V3 = "/v3/backup/auth/check";
|
||||
|
||||
private static final String ARCHIVE_CREDENTIALS = "/v1/archives/auth?redemptionStartSeconds=%d&redemptionEndSeconds=%d";
|
||||
private static final String ARCHIVE_READ_CREDENTIALS = "/v1/archives/auth/read?cdn=%d";
|
||||
private static final String ARCHIVE_BACKUP_ID = "/v1/archives/backupid";
|
||||
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_UPLOAD_FORM = "/v1/archives/media/upload/form";
|
||||
private static final String ARCHIVE_MEDIA = "/v1/archives/media";
|
||||
private static final String ARCHIVE_MEDIA_LIST = "/v1/archives/media?limit=%d";
|
||||
private static final String ARCHIVE_MEDIA_BATCH = "/v1/archives/media/batch";
|
||||
private static final String ARCHIVE_MEDIA_DELETE = "/v1/archives/media/delete";
|
||||
private static final String ARCHIVE_MEDIA_DOWNLOAD_PATH = "backups/%s/%s";
|
||||
|
||||
private static final String SET_SHARE_SET_PATH = "/v3/backup/share-set";
|
||||
|
||||
private static final String CALL_LINK_CREATION_AUTH = "/v1/call-link/create-auth";
|
||||
|
||||
private static final String SERVER_DELIVERED_TIMESTAMP_HEADER = "X-Signal-Timestamp";
|
||||
|
||||
private static final Map<String, String> NO_HEADERS = Collections.emptyMap();
|
||||
@@ -483,139 +462,6 @@ public class PushServiceSocket {
|
||||
return JsonUtil.fromJsonResponse(body, Svr3Credentials.class);
|
||||
}
|
||||
|
||||
public ArchiveServiceCredentialsResponse getArchiveCredentials(long currentTime) throws IOException {
|
||||
long secondsRoundedToNearestDay = TimeUnit.DAYS.toSeconds(TimeUnit.MILLISECONDS.toDays(currentTime));
|
||||
long endTimeInSeconds = secondsRoundedToNearestDay + TimeUnit.DAYS.toSeconds(7);
|
||||
|
||||
String response = makeServiceRequest(String.format(Locale.US, ARCHIVE_CREDENTIALS, secondsRoundedToNearestDay, endTimeInSeconds), "GET", null, NO_HEADERS, UNOPINIONATED_HANDLER, SealedSenderAccess.NONE);
|
||||
|
||||
return JsonUtil.fromJson(response, ArchiveServiceCredentialsResponse.class);
|
||||
}
|
||||
|
||||
public void setArchiveBackupId(BackupAuthCredentialRequest messageRequest, BackupAuthCredentialRequest mediaRequest) throws IOException {
|
||||
String body = JsonUtil.toJson(new ArchiveSetBackupIdRequest(messageRequest, mediaRequest));
|
||||
makeServiceRequest(ARCHIVE_BACKUP_ID, "PUT", body, NO_HEADERS, UNOPINIONATED_HANDLER, SealedSenderAccess.NONE);
|
||||
}
|
||||
|
||||
public void setArchivePublicKey(ECPublicKey publicKey, ArchiveCredentialPresentation credentialPresentation) throws IOException {
|
||||
Map<String, String> headers = credentialPresentation.toHeaders();
|
||||
|
||||
String body = JsonUtil.toJson(new ArchiveSetPublicKeyRequest(publicKey));
|
||||
makeServiceRequestWithoutAuthentication(ARCHIVE_PUBLIC_KEY, "PUT", body, headers, NO_HANDLER);
|
||||
}
|
||||
|
||||
public ArchiveGetBackupInfoResponse getArchiveBackupInfo(ArchiveCredentialPresentation credentialPresentation) throws IOException {
|
||||
Map<String, String> headers = credentialPresentation.toHeaders();
|
||||
|
||||
String response = makeServiceRequestWithoutAuthentication(ARCHIVE_INFO, "GET", null, headers, NO_HANDLER);
|
||||
return JsonUtil.fromJson(response, ArchiveGetBackupInfoResponse.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST credential presentation to the server to keep backup alive.
|
||||
*/
|
||||
public void refreshBackup(ArchiveCredentialPresentation credentialPresentation) throws IOException {
|
||||
Map<String, String> headers = credentialPresentation.toHeaders();
|
||||
|
||||
makeServiceRequestWithoutAuthentication(ARCHIVE_INFO, "POST", null, headers, UNOPINIONATED_HANDLER);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE credential presentation to the server to delete backup.
|
||||
*/
|
||||
public void deleteBackup(ArchiveCredentialPresentation credentialPresentation) throws IOException {
|
||||
Map<String, String> headers = credentialPresentation.toHeaders();
|
||||
|
||||
makeServiceRequestWithoutAuthentication(ARCHIVE_INFO, "DELETE", null, headers, UNOPINIONATED_HANDLER);
|
||||
}
|
||||
|
||||
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 = credentialPresentation.toHeaders();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy and re-encrypt media from the attachments cdn into the backup cdn.
|
||||
*/
|
||||
public ArchiveMediaResponse archiveAttachmentMedia(@Nonnull ArchiveCredentialPresentation credentialPresentation, @Nonnull ArchiveMediaRequest request) throws IOException {
|
||||
Map<String, String> headers = credentialPresentation.toHeaders();
|
||||
|
||||
String response = makeServiceRequestWithoutAuthentication(ARCHIVE_MEDIA, "PUT", JsonUtil.toJson(request), headers, UNOPINIONATED_HANDLER);
|
||||
|
||||
return JsonUtil.fromJson(response, ArchiveMediaResponse.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy and re-encrypt media from the attachments cdn into the backup cdn.
|
||||
*/
|
||||
public BatchArchiveMediaResponse archiveAttachmentMedia(@Nonnull ArchiveCredentialPresentation credentialPresentation, @Nonnull BatchArchiveMediaRequest request) throws IOException {
|
||||
Map<String, String> headers = credentialPresentation.toHeaders();
|
||||
|
||||
String response = makeServiceRequestWithoutAuthentication(ARCHIVE_MEDIA_BATCH, "PUT", JsonUtil.toJson(request), headers, UNOPINIONATED_HANDLER);
|
||||
|
||||
return JsonUtil.fromJson(response, BatchArchiveMediaResponse.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete media from the backup cdn.
|
||||
*/
|
||||
public void deleteArchivedMedia(@Nonnull ArchiveCredentialPresentation credentialPresentation, @Nonnull DeleteArchivedMediaRequest request) throws IOException {
|
||||
Map<String, String> headers = credentialPresentation.toHeaders();
|
||||
|
||||
makeServiceRequestWithoutAuthentication(ARCHIVE_MEDIA_DELETE, "POST", JsonUtil.toJson(request), headers, NO_HANDLER);
|
||||
}
|
||||
|
||||
public AttachmentUploadForm getArchiveMessageBackupUploadForm(ArchiveCredentialPresentation credentialPresentation) throws IOException {
|
||||
Map<String, String> headers = credentialPresentation.toHeaders();
|
||||
|
||||
String response = makeServiceRequestWithoutAuthentication(ARCHIVE_MESSAGE_UPLOAD_FORM, "GET", null, headers, NO_HANDLER);
|
||||
return JsonUtil.fromJson(response, AttachmentUploadForm.class);
|
||||
}
|
||||
|
||||
public AttachmentUploadForm getArchiveMediaUploadForm(@NotNull ArchiveCredentialPresentation credentialPresentation) throws IOException {
|
||||
Map<String, String> headers = credentialPresentation.toHeaders();
|
||||
|
||||
String response = makeServiceRequestWithoutAuthentication(ARCHIVE_MEDIA_UPLOAD_FORM, "GET", null, headers, UNOPINIONATED_HANDLER);
|
||||
return JsonUtil.fromJson(response, AttachmentUploadForm.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy and re-encrypt media from the attachments cdn into the backup cdn.
|
||||
*/
|
||||
public GetArchiveCdnCredentialsResponse getArchiveCdnReadCredentials(int cdnNumber, @Nonnull ArchiveCredentialPresentation credentialPresentation) throws IOException {
|
||||
Map<String, String> headers = credentialPresentation.toHeaders();
|
||||
|
||||
String response = makeServiceRequestWithoutAuthentication(String.format(Locale.US, ARCHIVE_READ_CREDENTIALS, cdnNumber), "GET", null, headers, NO_HANDLER);
|
||||
|
||||
return JsonUtil.fromJson(response, GetArchiveCdnCredentialsResponse.class);
|
||||
}
|
||||
|
||||
public void setRestoreMethodChosen(@Nonnull String token, @Nonnull RestoreMethodBody request) throws IOException {
|
||||
String body = JsonUtil.toJson(request);
|
||||
makeServiceRequest(String.format(Locale.US, SET_RESTORE_METHOD_PATH, urlEncode(token)), "PUT", body, NO_HEADERS, UNOPINIONATED_HANDLER, SealedSenderAccess.NONE);
|
||||
@@ -1308,10 +1154,6 @@ public class PushServiceSocket {
|
||||
return token;
|
||||
}
|
||||
|
||||
private String getCredentials(String authPath) throws IOException {
|
||||
return getAuthCredentials(authPath).asBasic();
|
||||
}
|
||||
|
||||
public AuthCredentials getPaymentsAuthorization() throws IOException {
|
||||
return getAuthCredentials(PAYMENTS_AUTH_PATH);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user