mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-28 05:35:44 +00:00
Add some initial backupV2 network infrastructure.
This commit is contained in:
committed by
Cody Henthorne
parent
e17b07bb12
commit
6230a7553d
@@ -21,12 +21,16 @@ import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupWriter
|
||||
import org.thoughtcrime.securesms.backup.v2.stream.PlainTextBackupReader
|
||||
import org.thoughtcrime.securesms.backup.v2.stream.PlainTextBackupWriter
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.whispersystems.signalservice.api.NetworkResult
|
||||
import org.whispersystems.signalservice.api.archive.ArchiveServiceCredential
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
object BackupRepository {
|
||||
|
||||
@@ -149,6 +153,54 @@ object BackupRepository {
|
||||
Log.d(TAG, "import() ${eventTimer.stop().summary}")
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple test method that just hits various network endpoints. Only useful for the playground.
|
||||
*/
|
||||
fun testNetworkInteractions() {
|
||||
val api = ApplicationDependencies.getSignalServiceAccountManager().archiveApi
|
||||
val backupKey = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey()
|
||||
|
||||
// Just running some sample requests
|
||||
api
|
||||
.triggerBackupIdReservation(backupKey)
|
||||
.then { getAuthCredential() }
|
||||
.then { credential ->
|
||||
api.setPublicKey(backupKey, credential)
|
||||
.also { Log.i(TAG, "PublicKeyResult: $it") }
|
||||
.map { credential }
|
||||
}
|
||||
.then { credential ->
|
||||
api.getBackupInfo(backupKey, credential)
|
||||
.also { Log.i(TAG, "BackupInfoResult: $it") }
|
||||
.map { credential }
|
||||
}
|
||||
.then { credential ->
|
||||
api.getMessageBackupUploadForm(backupKey, credential)
|
||||
.also { Log.i(TAG, "UploadFormResult: $it") }
|
||||
}.also { Log.i(TAG, "OverallResponse: $it") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an auth credential, preferring a cached value if available.
|
||||
*/
|
||||
private fun getAuthCredential(): NetworkResult<ArchiveServiceCredential> {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
val credential = SignalStore.backup().credentialsByDay.getForCurrentTime(currentTime.milliseconds)
|
||||
|
||||
if (credential != null) {
|
||||
return NetworkResult.Success(credential)
|
||||
}
|
||||
|
||||
Log.w(TAG, "No credentials found for today, need to fetch new ones! This shouldn't happen under normal circumstances. We should ensure the routine fetch is running properly.")
|
||||
|
||||
return ApplicationDependencies.getSignalServiceAccountManager().archiveApi.getServiceCredentials(currentTime).map { result ->
|
||||
SignalStore.backup().addCredentials(result.credentials.toList())
|
||||
SignalStore.backup().clearCredentialsOlderThan(currentTime)
|
||||
SignalStore.backup().credentialsByDay.getForCurrentTime(currentTime.milliseconds)!!
|
||||
}
|
||||
}
|
||||
|
||||
data class SelfData(
|
||||
val aci: ACI,
|
||||
val pni: PNI,
|
||||
|
||||
@@ -97,7 +97,8 @@ class InternalBackupPlaygroundFragment : ComposeFragment() {
|
||||
}
|
||||
|
||||
exportFileLauncher.launch(intent)
|
||||
}
|
||||
},
|
||||
onTestNetworkClicked = { viewModel.testNetworkInteractions() }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -113,7 +114,8 @@ fun Screen(
|
||||
onImportMemoryClicked: () -> Unit = {},
|
||||
onImportFileClicked: () -> Unit = {},
|
||||
onPlaintextClicked: () -> Unit = {},
|
||||
onSaveToDiskClicked: () -> Unit = {}
|
||||
onSaveToDiskClicked: () -> Unit = {},
|
||||
onTestNetworkClicked: () -> Unit = {}
|
||||
) {
|
||||
Surface {
|
||||
Column(
|
||||
@@ -142,6 +144,12 @@ fun Screen(
|
||||
) {
|
||||
Text("Export")
|
||||
}
|
||||
Buttons.LargePrimary(
|
||||
onClick = onTestNetworkClicked,
|
||||
enabled = state.backupState == BackupState.EXPORT_DONE
|
||||
) {
|
||||
Text("Test network")
|
||||
}
|
||||
Buttons.LargeTonal(
|
||||
onClick = onImportMemoryClicked,
|
||||
enabled = state.backupState == BackupState.EXPORT_DONE
|
||||
|
||||
@@ -80,6 +80,13 @@ class InternalBackupPlaygroundViewModel : ViewModel() {
|
||||
_state.value = _state.value.copy(plaintext = !_state.value.plaintext)
|
||||
}
|
||||
|
||||
fun testNetworkInteractions() {
|
||||
disposables += Single
|
||||
.fromCallable { BackupRepository.testNetworkInteractions() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
disposables.clear()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.thoughtcrime.securesms.keyvalue
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.whispersystems.signalservice.api.archive.ArchiveServiceCredential
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||
import java.io.IOException
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
internal class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
||||
companion object {
|
||||
val TAG = Log.tag(BackupValues::class.java)
|
||||
val KEY_CREDENTIALS = "backup.credentials"
|
||||
}
|
||||
|
||||
override fun onFirstEverAppLaunch() = Unit
|
||||
override fun getKeysToIncludeInBackup(): List<String> = emptyList()
|
||||
|
||||
/**
|
||||
* Retrieves the stored credentials, mapped by the day they're valid. The day is represented as
|
||||
* the unix time (in seconds) of the start of the day. Wrapped in a [ArchiveServiceCredentials]
|
||||
* type to make it easier to use. See [ArchiveServiceCredentials.getForCurrentTime].
|
||||
*/
|
||||
val credentialsByDay: ArchiveServiceCredentials
|
||||
get() {
|
||||
val serialized = store.getString(KEY_CREDENTIALS, null) ?: return ArchiveServiceCredentials()
|
||||
|
||||
return try {
|
||||
val map = JsonUtil.fromJson(serialized, SerializedCredentials::class.java).credentialsByDay
|
||||
ArchiveServiceCredentials(map)
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Invalid JSON! Clearing.", e)
|
||||
putString(KEY_CREDENTIALS, null)
|
||||
ArchiveServiceCredentials()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given credentials to the existing list of stored credentials.
|
||||
*/
|
||||
fun addCredentials(credentials: List<ArchiveServiceCredential>) {
|
||||
val current: MutableMap<Long, ArchiveServiceCredential> = credentialsByDay.toMutableMap()
|
||||
current.putAll(credentials.associateBy { it.redemptionTime })
|
||||
putString(KEY_CREDENTIALS, JsonUtil.toJson(SerializedCredentials(current)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims out any credentials that are for days older than the given timestamp.
|
||||
*/
|
||||
fun clearCredentialsOlderThan(startOfDayInSeconds: Long) {
|
||||
val current: MutableMap<Long, ArchiveServiceCredential> = credentialsByDay.toMutableMap()
|
||||
val updated = current.filterKeys { it < startOfDayInSeconds }
|
||||
putString(KEY_CREDENTIALS, JsonUtil.toJson(SerializedCredentials(updated)))
|
||||
}
|
||||
|
||||
class SerializedCredentials(
|
||||
@JsonProperty
|
||||
val credentialsByDay: Map<Long, ArchiveServiceCredential>
|
||||
)
|
||||
|
||||
/**
|
||||
* A [Map] wrapper that makes it easier to get the credential for the current time.
|
||||
*/
|
||||
class ArchiveServiceCredentials(map: Map<Long, ArchiveServiceCredential>) : Map<Long, ArchiveServiceCredential> by map {
|
||||
constructor() : this(mapOf())
|
||||
|
||||
/**
|
||||
* Retrieves a credential that is valid for the current time, otherwise null.
|
||||
*/
|
||||
fun getForCurrentTime(currentTime: Duration): ArchiveServiceCredential? {
|
||||
val startOfDayInSeconds: Long = currentTime.inWholeDays.days.inWholeSeconds
|
||||
return this[startOfDayInSeconds]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ public final class SignalStore {
|
||||
private final ReleaseChannelValues releaseChannelValues;
|
||||
private final StoryValues storyValues;
|
||||
private final ApkUpdateValues apkUpdate;
|
||||
private final BackupValues backupValues;
|
||||
|
||||
private final PlainTextSharedPrefsDataStore plainTextValues;
|
||||
|
||||
@@ -89,6 +90,7 @@ public final class SignalStore {
|
||||
this.releaseChannelValues = new ReleaseChannelValues(store);
|
||||
this.storyValues = new StoryValues(store);
|
||||
this.apkUpdate = new ApkUpdateValues(store);
|
||||
this.backupValues = new BackupValues(store);
|
||||
this.plainTextValues = new PlainTextSharedPrefsDataStore(ApplicationDependencies.getApplication());
|
||||
}
|
||||
|
||||
@@ -270,6 +272,10 @@ public final class SignalStore {
|
||||
return getInstance().apkUpdate;
|
||||
}
|
||||
|
||||
public static @NonNull BackupValues backup() {
|
||||
return getInstance().backupValues;
|
||||
}
|
||||
|
||||
public static @NonNull GroupsV2AuthorizationSignalStoreCache groupsV2AciAuthorizationCache() {
|
||||
return GroupsV2AuthorizationSignalStoreCache.createAciCache(getStore());
|
||||
}
|
||||
|
||||
@@ -158,6 +158,12 @@ open class SignalServiceNetworkAccess(context: Context) {
|
||||
throw AssertionError(e)
|
||||
}
|
||||
|
||||
private val backupServerPublicParams: ByteArray = try {
|
||||
Base64.decode(BuildConfig.BACKUP_SERVER_PUBLIC_PARAMS)
|
||||
} catch (e: IOException) {
|
||||
throw AssertionError(e)
|
||||
}
|
||||
|
||||
private val baseGHostConfigs: List<HostConfig> = listOf(
|
||||
HostConfig("https://www.google.com", G_HOST, GMAIL_CONNECTION_SPEC),
|
||||
HostConfig("https://android.clients.google.com", G_HOST, PLAY_CONNECTION_SPEC),
|
||||
@@ -182,7 +188,8 @@ open class SignalServiceNetworkAccess(context: Context) {
|
||||
dns = Optional.of(DNS),
|
||||
signalProxy = Optional.empty(),
|
||||
zkGroupServerPublicParams = zkGroupServerPublicParams,
|
||||
genericServerPublicParams = genericServerPublicParams
|
||||
genericServerPublicParams = genericServerPublicParams,
|
||||
backupServerPublicParams = backupServerPublicParams
|
||||
)
|
||||
|
||||
private val censorshipConfiguration: Map<Int, SignalServiceConfiguration> = mapOf(
|
||||
@@ -234,7 +241,8 @@ open class SignalServiceNetworkAccess(context: Context) {
|
||||
dns = Optional.of(DNS),
|
||||
signalProxy = if (SignalStore.proxy().isProxyEnabled) Optional.ofNullable(SignalStore.proxy().proxy) else Optional.empty(),
|
||||
zkGroupServerPublicParams = zkGroupServerPublicParams,
|
||||
genericServerPublicParams = genericServerPublicParams
|
||||
genericServerPublicParams = genericServerPublicParams,
|
||||
backupServerPublicParams = backupServerPublicParams
|
||||
)
|
||||
|
||||
open fun getConfiguration(): SignalServiceConfiguration {
|
||||
@@ -303,7 +311,8 @@ open class SignalServiceNetworkAccess(context: Context) {
|
||||
dns = Optional.of(DNS),
|
||||
signalProxy = Optional.empty(),
|
||||
zkGroupServerPublicParams = zkGroupServerPublicParams,
|
||||
genericServerPublicParams = genericServerPublicParams
|
||||
genericServerPublicParams = genericServerPublicParams,
|
||||
backupServerPublicParams = backupServerPublicParams
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user