Add common interface over SVR implementations.

This commit is contained in:
Greyson Parrelli
2023-06-14 11:27:44 -07:00
committed by Cody Henthorne
parent 51222738df
commit 38f2b39ac4
10 changed files with 531 additions and 59 deletions

View File

@@ -136,6 +136,14 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
}
)
clickPref(
title = DSLSettingsText.from("SVR Playground"),
summary = DSLSettingsText.from("Quickly test various SVR options and error conditions."),
onClick = {
findNavController().safeNavigate(InternalSettingsFragmentDirections.actionInternalSettingsFragmentToInternalSvrPlaygroundFragment())
}
)
switchPref(
title = DSLSettingsText.from("'Internal Details' button"),
summary = DSLSettingsText.from("Show a button in conversation settings that lets you see more information about a user."),

View File

@@ -0,0 +1,146 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.svr
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Surface
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.key.Key.Companion.Tab
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.fragment.app.viewModels
import kotlinx.collections.immutable.persistentListOf
import org.signal.core.ui.Rows
import org.signal.core.ui.theme.SignalTheme
import org.thoughtcrime.securesms.compose.ComposeFragment
class InternalSvrPlaygroundFragment : ComposeFragment() {
private val viewModel: InternalSvrPlaygroundViewModel by viewModels()
@Composable
override fun FragmentContent() {
val state: InternalSvrPlaygroundState by viewModel.state
SvrPlaygroundScreen(
state = state,
onTabSelected = viewModel::onTabSelected,
onCreateClicked = viewModel::onCreateClicked,
onRestoreClicked = viewModel::onRestoreClicked,
onDeleteClicked = viewModel::onDeleteClicked,
onPinChanged = viewModel::onPinChanged
)
}
}
@Composable
fun SvrPlaygroundScreen(
state: InternalSvrPlaygroundState,
modifier: Modifier = Modifier,
onTabSelected: (SvrImplementation) -> Unit = {},
onCreateClicked: () -> Unit = {},
onRestoreClicked: () -> Unit = {},
onDeleteClicked: () -> Unit = {},
onPinChanged: (String) -> Unit = {}
) {
Column(modifier = modifier.fillMaxWidth()) {
TabRow(selectedTabIndex = state.options.indexOf(state.selected)) {
state.options.forEach { option ->
Tab(
text = { Text(option.title) },
selected = option == state.selected,
onClick = { onTabSelected(option) }
)
}
}
Rows.TextRow(
text = "Create backup data",
onClick = onCreateClicked
)
Rows.TextRow(
text = "Restore backup data",
onClick = onRestoreClicked
)
Rows.TextRow(
text = "Delete backup data",
onClick = onDeleteClicked
)
Row(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier
.padding(8.dp)
.align(Alignment.CenterVertically)
) {
Text(text = "PIN: ")
}
Column {
TextField(
value = state.userPin,
onValueChange = onPinChanged,
modifier = Modifier.fillMaxWidth()
)
}
}
if (state.loading) {
Row(modifier = Modifier.fillMaxWidth().padding(48.dp)) {
CircularProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(),
color = Color.Blue
)
}
} else if (state.lastResult != null) {
Rows.TextRow(text = state.lastResult)
}
}
}
@Preview
@Composable
fun SvrPlaygroundScreenLightTheme() {
SignalTheme(isDarkMode = false) {
Surface {
SvrPlaygroundScreen(
state = InternalSvrPlaygroundState(
options = persistentListOf(SvrImplementation.SVR1, SvrImplementation.SVR2)
)
)
}
}
}
@Preview
@Composable
fun SvrPlaygroundScreenDarkTheme() {
SignalTheme(isDarkMode = true) {
Surface {
SvrPlaygroundScreen(
state = InternalSvrPlaygroundState(
options = persistentListOf(SvrImplementation.SVR1, SvrImplementation.SVR2)
)
)
}
}
}

View File

@@ -0,0 +1,17 @@
package org.thoughtcrime.securesms.components.settings.app.internal.svr
import kotlinx.collections.immutable.ImmutableList
data class InternalSvrPlaygroundState(
val options: ImmutableList<SvrImplementation>,
val selected: SvrImplementation = options[0],
val loading: Boolean = false,
val userPin: String = "",
val lastResult: String? = null
)
enum class SvrImplementation(
val title: String
) {
SVR1("KBS"), SVR2("SVR2")
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.svr
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.collections.immutable.persistentListOf
import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.whispersystems.signalservice.api.svr.SecureValueRecovery
import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV1
class InternalSvrPlaygroundViewModel : ViewModel() {
private val _state: MutableState<InternalSvrPlaygroundState> = mutableStateOf(
InternalSvrPlaygroundState(
options = persistentListOf(SvrImplementation.SVR1, SvrImplementation.SVR2)
)
)
val state: State<InternalSvrPlaygroundState> = _state
private val disposables: CompositeDisposable = CompositeDisposable()
fun onTabSelected(svr: SvrImplementation) {
_state.value = _state.value.copy(
selected = svr,
lastResult = null
)
}
fun onPinChanged(pin: String) {
_state.value = _state.value.copy(
userPin = pin
)
}
fun onCreateClicked() {
_state.value = _state.value.copy(
loading = true
)
disposables += _state.value.selected.toImplementation()
.setPin(_state.value.userPin, SignalStore.kbsValues().getOrCreateMasterKey())
.execute()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { response ->
_state.value = _state.value.copy(
loading = false,
lastResult = "${response.javaClass.simpleName}\n\n$response"
)
}
}
fun onRestoreClicked() {
_state.value = _state.value.copy(
loading = true
)
disposables += _state.value.selected.toImplementation()
.restoreDataPostRegistration(_state.value.userPin)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { response ->
_state.value = _state.value.copy(
loading = false,
lastResult = "${response.javaClass.simpleName}\n\n$response"
)
}
}
fun onDeleteClicked() {
_state.value = _state.value.copy(
loading = true
)
disposables += _state.value.selected.toImplementation()
.deleteData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { response ->
_state.value = _state.value.copy(
loading = false,
lastResult = "${response.javaClass.simpleName}\n\n$response"
)
}
}
override fun onCleared() {
disposables.clear()
}
private fun SvrImplementation.toImplementation(): SecureValueRecovery {
return when (this) {
SvrImplementation.SVR1 -> SecureValueRecoveryV1(ApplicationDependencies.getKeyBackupService(BuildConfig.KBS_ENCLAVE))
SvrImplementation.SVR2 -> ApplicationDependencies.getSignalServiceAccountManager().getSecureValueRecoveryV2(BuildConfig.SVR2_MRENCLAVE)
}
}
}