Improve internal backup import UI tool.
This commit is contained in:
parent
fa32f399b2
commit
f537fa6436
3 changed files with 55 additions and 23 deletions
|
@ -296,7 +296,7 @@ class ArchiveImportExportTests {
|
|||
length = importData.size.toLong(),
|
||||
inputStreamFactory = { ByteArrayInputStream(importData) },
|
||||
selfData = BackupRepository.SelfData(SELF_ACI, SELF_PNI, SELF_E164, ProfileKey(SELF_PROFILE_KEY)),
|
||||
plaintext = true
|
||||
backupKey = null
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@ import org.signal.core.ui.Rows
|
|||
import org.signal.core.ui.SignalPreview
|
||||
import org.signal.core.ui.Snackbars
|
||||
import org.signal.core.ui.TextFields.TextField
|
||||
import org.signal.core.util.Hex
|
||||
import org.signal.core.util.getLength
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||
|
@ -81,6 +82,7 @@ import org.thoughtcrime.securesms.components.settings.app.internal.backup.Intern
|
|||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.jobs.LocalBackupJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
|
||||
class InternalBackupPlaygroundFragment : ComposeFragment() {
|
||||
|
||||
|
@ -200,9 +202,9 @@ class InternalBackupPlaygroundFragment : ComposeFragment() {
|
|||
onImportEncryptedBackupFromDiskDismissed = {
|
||||
viewModel.onDialogDismissed()
|
||||
},
|
||||
onImportEncryptedBackupFromDiskConfirmed = { aci, aep ->
|
||||
onImportEncryptedBackupFromDiskConfirmed = { aci, backupKey ->
|
||||
viewModel.onDialogDismissed()
|
||||
val valid = viewModel.onImportConfirmed(aci, aep)
|
||||
val valid = viewModel.onImportConfirmed(aci, backupKey)
|
||||
if (valid) {
|
||||
val intent = Intent().apply {
|
||||
action = Intent.ACTION_GET_CONTENT
|
||||
|
@ -315,8 +317,9 @@ fun Screen(
|
|||
onSavePlaintextBackupToDiskClicked: () -> Unit = {},
|
||||
onImportEncryptedBackupFromDiskClicked: () -> Unit = {},
|
||||
onImportEncryptedBackupFromDiskDismissed: () -> Unit = {},
|
||||
onImportEncryptedBackupFromDiskConfirmed: (aesKey: String, macKey: String) -> Unit = { _, _ -> }
|
||||
onImportEncryptedBackupFromDiskConfirmed: (aci: String, backupKey: String) -> Unit = { _, _ -> }
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scrollState = rememberScrollState()
|
||||
val options = remember {
|
||||
mapOf(
|
||||
|
@ -419,6 +422,24 @@ fun Screen(
|
|||
onClick = onExportNewStyleLocalBackupClicked
|
||||
)
|
||||
|
||||
Rows.TextRow(
|
||||
text = "Copy Account Entropy Pool (AEP)",
|
||||
label = "Copies the Account Entropy Pool (AEP) to the clipboard, which is labeled as the \"Backup Key\" in the designs.",
|
||||
onClick = {
|
||||
Util.copyToClipboard(context, SignalStore.account.accountEntropyPool.value)
|
||||
Toast.makeText(context, "Copied!", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
)
|
||||
|
||||
Rows.TextRow(
|
||||
text = "Copy Cryptographic BackupKey",
|
||||
label = "Copies the cryptographic BackupKey to the clipboard as a hex string. Important: this is the key that is derived from the AEP, and therefore *not* the same as the key labeled \"Backup Key\" in the designs. That's actually the AEP, listed above.",
|
||||
onClick = {
|
||||
Util.copyToClipboard(context, Hex.toStringCondensed(SignalStore.account.accountEntropyPool.deriveMessageBackupKey().value))
|
||||
Toast.makeText(context, "Copied!", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
)
|
||||
|
||||
Dividers.Default()
|
||||
|
||||
Text(
|
||||
|
@ -479,10 +500,10 @@ fun Screen(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun ImportCredentialsDialog(onSubmit: (String, String) -> Unit = { _, _ -> }, onDismissed: () -> Unit = {}) {
|
||||
private fun ImportCredentialsDialog(onSubmit: (aci: String, backupKey: String) -> Unit = { _, _ -> }, onDismissed: () -> Unit = {}) {
|
||||
val dialogScrollState = rememberScrollState()
|
||||
var aesKey by remember { mutableStateOf("") }
|
||||
var macKey by remember { mutableStateOf("") }
|
||||
var aci by remember { mutableStateOf("") }
|
||||
var backupKey by remember { mutableStateOf("") }
|
||||
val inputOptions = KeyboardOptions(
|
||||
capitalization = KeyboardCapitalization.None,
|
||||
autoCorrectEnabled = false,
|
||||
|
@ -499,27 +520,27 @@ private fun ImportCredentialsDialog(onSubmit: (String, String) -> Unit = { _, _
|
|||
}
|
||||
Row(modifier = Modifier.padding(vertical = 10.dp)) {
|
||||
TextField(
|
||||
value = aesKey,
|
||||
value = aci,
|
||||
keyboardOptions = inputOptions,
|
||||
label = { Text("ACI") },
|
||||
supportingText = { Text("(leave blank for the current user)") },
|
||||
onValueChange = { aesKey = it }
|
||||
onValueChange = { aci = it }
|
||||
)
|
||||
}
|
||||
Row(modifier = Modifier.padding(vertical = 10.dp)) {
|
||||
TextField(
|
||||
value = macKey,
|
||||
value = backupKey,
|
||||
keyboardOptions = inputOptions.copy(imeAction = ImeAction.Done),
|
||||
label = { Text("\"Backup Key\" (AEP)") },
|
||||
label = { Text("Cryptographic BackupKey (*not* AEP!)") },
|
||||
supportingText = { Text("(leave blank for the current user)") },
|
||||
onValueChange = { macKey = it }
|
||||
onValueChange = { backupKey = it }
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onSubmit(aesKey, macKey)
|
||||
onSubmit(aci, backupKey)
|
||||
}) {
|
||||
Text(text = "Wipe and restore")
|
||||
}
|
||||
|
|
|
@ -18,10 +18,10 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
|
|||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.Hex
|
||||
import org.signal.core.util.bytes
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.copyTo
|
||||
import org.signal.core.util.isNotNullOrBlank
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.readNBytesOrThrow
|
||||
import org.signal.core.util.roundedString
|
||||
|
@ -56,9 +56,9 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
|
|||
import org.thoughtcrime.securesms.mms.IncomingMessage
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.whispersystems.signalservice.api.AccountEntropyPool
|
||||
import org.whispersystems.signalservice.api.NetworkResult
|
||||
import org.whispersystems.signalservice.api.backup.MediaName
|
||||
import org.whispersystems.signalservice.api.backup.MessageBackupKey
|
||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
|
@ -178,7 +178,7 @@ class InternalBackupPlaygroundViewModel : ViewModel() {
|
|||
val self = Recipient.self()
|
||||
val aci = customCredentials?.aci ?: self.aci.get()
|
||||
val selfData = BackupRepository.SelfData(aci, self.pni.get(), self.e164.get(), ProfileKey(self.profileKey))
|
||||
val backupKey = customCredentials?.aep?.deriveMessageBackupKey() ?: SignalStore.backup.messageBackupKey
|
||||
val backupKey = customCredentials?.messageBackupKey ?: SignalStore.backup.messageBackupKey
|
||||
|
||||
disposables += Single.fromCallable { BackupRepository.import(length, inputStreamFactory, selfData, backupKey) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
@ -302,22 +302,33 @@ class InternalBackupPlaygroundViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
/** True if data is valid, else false */
|
||||
fun onImportConfirmed(aci: String, aep: String): Boolean {
|
||||
fun onImportConfirmed(aci: String, backupKey: String): Boolean {
|
||||
val parsedAci: ACI? = ACI.parseOrNull(aci)
|
||||
|
||||
if (aci.isNotBlank() && parsedAci == null) {
|
||||
_state.value = _state.value.copy(statusMessage = "Invalid ACI! Cannot import.")
|
||||
return false
|
||||
}
|
||||
|
||||
val parsedAep = AccountEntropyPool.parseOrNull(aep)
|
||||
if (aep.isNotBlank() && parsedAep == null) {
|
||||
val parsedBackupKey: MessageBackupKey? = try {
|
||||
val bytes = Hex.fromStringOrThrow(backupKey)
|
||||
MessageBackupKey(bytes)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to parse key!", e)
|
||||
null
|
||||
}
|
||||
|
||||
if (backupKey.isNotBlank() && parsedBackupKey == null) {
|
||||
_state.value = _state.value.copy(statusMessage = "Invalid AEP! Cannot import.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (parsedAci != null && parsedAep != null) {
|
||||
_state.value = state.value.copy(customBackupCredentials = ImportCredentials(aep = parsedAep, aci = parsedAci))
|
||||
}
|
||||
_state.value = state.value.copy(
|
||||
customBackupCredentials = ImportCredentials(
|
||||
messageBackupKey = parsedBackupKey ?: SignalStore.backup.messageBackupKey,
|
||||
aci = parsedAci ?: SignalStore.account.aci!!
|
||||
)
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -602,7 +613,7 @@ class InternalBackupPlaygroundViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
data class ImportCredentials(
|
||||
val aep: AccountEntropyPool,
|
||||
val messageBackupKey: MessageBackupKey,
|
||||
val aci: ACI
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue