mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Merge pull request #1662 from vector-im/feature/manage_4s_setting
4S settings screen
This commit is contained in:
commit
a08a1d4f74
@ -9,6 +9,8 @@ Improvements 🙌:
|
||||
- Creating and listening to EventInsertEntity. (#1634)
|
||||
- Handling (almost) properly the groups fetching (#1634)
|
||||
- Improve fullscreen media display (#327)
|
||||
- Setup server recovery banner (#1648)
|
||||
- Set up SSSS from security settings (#1567)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Regression | Share action menu do not work (#1647)
|
||||
|
@ -17,9 +17,14 @@
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.query.QueryStringValue
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
||||
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
|
||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||
import im.vector.matrix.android.api.session.identity.ThreePid
|
||||
@ -36,9 +41,11 @@ import im.vector.matrix.android.api.util.toOptional
|
||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.Function3
|
||||
|
||||
class RxSession(private val session: Session) {
|
||||
|
||||
@ -165,6 +172,38 @@ class RxSession(private val session: Session) {
|
||||
session.widgetService().getRoomWidgets(roomId, widgetId, widgetTypes, excludedTypes)
|
||||
}
|
||||
}
|
||||
|
||||
fun liveSecretSynchronisationInfo(): Observable<SecretsSynchronisationInfo> {
|
||||
return Observable.combineLatest<List<UserAccountData>, Optional<MXCrossSigningInfo>, Optional<PrivateKeysInfo>, SecretsSynchronisationInfo>(
|
||||
liveAccountData(setOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME)),
|
||||
liveCrossSigningInfo(session.myUserId),
|
||||
liveCrossSigningPrivateKeys(),
|
||||
Function3 { _, crossSigningInfo, pInfo ->
|
||||
// first check if 4S is already setup
|
||||
val is4SSetup = session.sharedSecretStorageService.isRecoverySetup()
|
||||
val isCrossSigningEnabled = crossSigningInfo.getOrNull() != null
|
||||
val isCrossSigningTrusted = crossSigningInfo.getOrNull()?.isTrusted() == true
|
||||
val allPrivateKeysKnown = pInfo.getOrNull()?.allKnown().orFalse()
|
||||
|
||||
val keysBackupService = session.cryptoService().keysBackupService()
|
||||
val currentBackupVersion = keysBackupService.currentBackupVersion
|
||||
val megolmBackupAvailable = currentBackupVersion != null
|
||||
val savedBackupKey = keysBackupService.getKeyBackupRecoveryKeyInfo()
|
||||
|
||||
val megolmKeyKnown = savedBackupKey?.version == currentBackupVersion
|
||||
SecretsSynchronisationInfo(
|
||||
isBackupSetup = is4SSetup,
|
||||
isCrossSigningEnabled = isCrossSigningEnabled,
|
||||
isCrossSigningTrusted = isCrossSigningTrusted,
|
||||
allPrivateKeysKnown = allPrivateKeysKnown,
|
||||
megolmBackupAvailable = megolmBackupAvailable,
|
||||
megolmSecretKnown = megolmKeyKnown,
|
||||
isMegolmKeyIn4S = session.sharedSecretStorageService.isMegolmKeyInBackup()
|
||||
)
|
||||
}
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
}
|
||||
|
||||
fun Session.rx(): RxSession {
|
||||
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.rx
|
||||
|
||||
data class SecretsSynchronisationInfo(
|
||||
val isBackupSetup: Boolean,
|
||||
val isCrossSigningEnabled: Boolean,
|
||||
val isCrossSigningTrusted: Boolean,
|
||||
val allPrivateKeysKnown: Boolean,
|
||||
val megolmBackupAvailable: Boolean,
|
||||
val megolmSecretKnown: Boolean,
|
||||
val isMegolmKeyIn4S: Boolean
|
||||
)
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.securestorage
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
||||
@ -124,6 +125,13 @@ interface SharedSecretStorageService {
|
||||
) is IntegrityResult.Success
|
||||
}
|
||||
|
||||
fun isMegolmKeyInBackup(): Boolean {
|
||||
return checkShouldBeAbleToAccessSecrets(
|
||||
secretNames = listOf(KEYBACKUP_SECRET_SSSS_NAME),
|
||||
keyId = null
|
||||
) is IntegrityResult.Success
|
||||
}
|
||||
|
||||
fun checkShouldBeAbleToAccessSecrets(secretNames: List<String>, keyId: String?): IntegrityResult
|
||||
|
||||
fun requestSecret(name: String, myOtherDeviceId: String)
|
||||
|
@ -71,8 +71,8 @@ internal class OutgoingGossipingRequestManager @Inject constructor(
|
||||
delay(1500)
|
||||
cryptoStore.getOrAddOutgoingSecretShareRequest(secretName, recipients)?.let {
|
||||
// TODO check if there is already one that is being sent?
|
||||
if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) {
|
||||
Timber.v("## CRYPTO - GOSSIP sendSecretShareRequest() : we already request for that session: $it")
|
||||
if (it.state == OutgoingGossipingRequestState.SENDING /**|| it.state == OutgoingGossipingRequestState.SENT*/) {
|
||||
Timber.v("## CRYPTO - GOSSIP sendSecretShareRequest() : we are already sending for that session: $it")
|
||||
return@launch
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto.crosssigning
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
@ -509,9 +510,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
||||
|
||||
override fun allPrivateKeysKnown(): Boolean {
|
||||
return checkSelfTrust().isVerified()
|
||||
&& cryptoStore.getCrossSigningPrivateKeys()?.selfSigned != null
|
||||
&& cryptoStore.getCrossSigningPrivateKeys()?.user != null
|
||||
&& cryptoStore.getCrossSigningPrivateKeys()?.master != null
|
||||
&& cryptoStore.getCrossSigningPrivateKeys()?.allKnown().orFalse()
|
||||
}
|
||||
|
||||
override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) {
|
||||
|
@ -20,4 +20,6 @@ data class PrivateKeysInfo(
|
||||
val master: String? = null,
|
||||
val selfSigned: String? = null,
|
||||
val user: String? = null
|
||||
)
|
||||
) {
|
||||
fun allKnown() = master != null && selfSigned != null && user != null
|
||||
}
|
||||
|
@ -162,9 +162,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
||||
return this
|
||||
}
|
||||
|
||||
protected fun Disposable.disposeOnDestroy(): Disposable {
|
||||
protected fun Disposable.disposeOnDestroy() {
|
||||
uiDisposables.add(this)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -234,9 +234,8 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
||||
|
||||
private val uiDisposables = CompositeDisposable()
|
||||
|
||||
protected fun Disposable.disposeOnDestroyView(): Disposable {
|
||||
protected fun Disposable.disposeOnDestroyView() {
|
||||
uiDisposables.add(this)
|
||||
return this
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
|
@ -185,7 +185,6 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment()
|
||||
AlertDialog.Builder(it)
|
||||
.setTitle(R.string.dialog_title_error)
|
||||
.setMessage(errorFormatter.toHumanReadable(throwable))
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -45,7 +45,8 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
|
||||
@Parcelize
|
||||
data class Args(
|
||||
val initCrossSigningOnly: Boolean
|
||||
val initCrossSigningOnly: Boolean,
|
||||
val forceReset4S: Boolean
|
||||
) : Parcelable
|
||||
|
||||
override val showExpanded = true
|
||||
@ -180,10 +181,15 @@ class BootstrapBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
|
||||
const val EXTRA_ARGS = "EXTRA_ARGS"
|
||||
|
||||
fun show(fragmentManager: FragmentManager, initCrossSigningOnly: Boolean) {
|
||||
fun show(fragmentManager: FragmentManager, initCrossSigningOnly: Boolean, forceReset4S: Boolean) {
|
||||
BootstrapBottomSheet().apply {
|
||||
isCancelable = false
|
||||
arguments = Bundle().apply { this.putParcelable(EXTRA_ARGS, Args(initCrossSigningOnly)) }
|
||||
arguments = Bundle().apply {
|
||||
this.putParcelable(EXTRA_ARGS, Args(
|
||||
initCrossSigningOnly,
|
||||
forceReset4S
|
||||
))
|
||||
}
|
||||
}.show(fragmentManager, "BootstrapBottomSheet")
|
||||
}
|
||||
}
|
||||
|
@ -258,6 +258,30 @@ class BootstrapCrossSigningTask @Inject constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Existing megolm backup found")
|
||||
// ensure we store existing backup secret if we have it!
|
||||
val knownSecret = session.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()
|
||||
if (knownSecret != null && knownSecret.version == serverVersion.version) {
|
||||
// check it matches
|
||||
val isValid = awaitCallback<Boolean> {
|
||||
session.cryptoService().keysBackupService().isValidRecoveryKeyForCurrentVersion(knownSecret.recoveryKey, it)
|
||||
}
|
||||
if (isValid) {
|
||||
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known")
|
||||
awaitCallback<Unit> {
|
||||
extractCurveKeyFromRecoveryKey(knownSecret.recoveryKey)?.toBase64NoPadding()?.let { secret ->
|
||||
ssssService.storeSecret(
|
||||
KEYBACKUP_SECRET_SSSS_NAME,
|
||||
secret,
|
||||
listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)), it
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key is unknown by this session")
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e("## BootstrapCrossSigningTask: Failed to init keybackup")
|
||||
|
@ -58,6 +58,13 @@ class BootstrapSetupRecoveryKeyFragment @Inject constructor() : VectorBaseFragme
|
||||
bootstrapSetupSecureUseSecurityPassphrase.isVisible = false
|
||||
bootstrapSetupSecureUseSecurityPassphraseSeparator.isVisible = false
|
||||
} else {
|
||||
if (state.step.reset) {
|
||||
bootstrapSetupSecureText.text = getString(R.string.reset_secure_backup_title)
|
||||
bootstrapSetupWarningTextView.isVisible = true
|
||||
} else {
|
||||
bootstrapSetupSecureText.text = getString(R.string.bottom_sheet_setup_secure_backup_subtitle)
|
||||
bootstrapSetupWarningTextView.isVisible = false
|
||||
}
|
||||
// Choose between create a passphrase or use a recovery key
|
||||
bootstrapSetupSecureSubmit.isVisible = false
|
||||
bootstrapSetupSecureUseSecurityKey.isVisible = true
|
||||
|
@ -69,7 +69,11 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||
|
||||
init {
|
||||
|
||||
if (args.initCrossSigningOnly) {
|
||||
if (args.forceReset4S) {
|
||||
setState {
|
||||
copy(step = BootstrapStep.FirstForm(keyBackUpExist = false, reset = true))
|
||||
}
|
||||
} else if (args.initCrossSigningOnly) {
|
||||
// Go straight to account password
|
||||
setState {
|
||||
copy(step = BootstrapStep.AccountPassword(false))
|
||||
@ -554,7 +558,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||
override fun create(viewModelContext: ViewModelContext, state: BootstrapViewState): BootstrapSharedViewModel? {
|
||||
val fragment: BootstrapBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
|
||||
val args: BootstrapBottomSheet.Args = fragment.arguments?.getParcelable(BootstrapBottomSheet.EXTRA_ARGS)
|
||||
?: BootstrapBottomSheet.Args(initCrossSigningOnly = true)
|
||||
?: BootstrapBottomSheet.Args(initCrossSigningOnly = true, forceReset4S = false)
|
||||
return fragment.bootstrapViewModelFactory.create(state, args)
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ sealed class BootstrapStep {
|
||||
object CheckingMigration : BootstrapStep()
|
||||
|
||||
// Use will be asked to choose between passphrase or recovery key, or to start process if a key backup exists
|
||||
data class FirstForm(val keyBackUpExist: Boolean) : BootstrapStep()
|
||||
data class FirstForm(val keyBackUpExist: Boolean, val reset: Boolean = false) : BootstrapStep()
|
||||
|
||||
data class SetupPassphrase(val isPasswordVisible: Boolean) : BootstrapStep()
|
||||
data class ConfirmPassphrase(val isPasswordVisible: Boolean) : BootstrapStep()
|
||||
|
@ -146,7 +146,7 @@ class VerificationRequestController @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
if (state.isMe && state.currentDeviceCanCrossSign) {
|
||||
if (state.isMe && state.currentDeviceCanCrossSign && !state.selfVerificationMode) {
|
||||
dividerItem {
|
||||
id("sep_notMe")
|
||||
}
|
||||
|
@ -193,14 +193,15 @@ class HomeDetailFragment @Inject constructor(
|
||||
}
|
||||
|
||||
private fun setupKeysBackupBanner() {
|
||||
serverBackupStatusViewModel.subscribe(this) {
|
||||
when (val banState = it.bannerState.invoke()) {
|
||||
is BannerState.Setup -> homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false)
|
||||
BannerState.BackingUp -> homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false)
|
||||
null,
|
||||
BannerState.Hidden -> homeKeysBackupBanner.render(KeysBackupBanner.State.Hidden, false)
|
||||
}
|
||||
}.disposeOnDestroyView()
|
||||
serverBackupStatusViewModel
|
||||
.subscribe(this) {
|
||||
when (val banState = it.bannerState.invoke()) {
|
||||
is BannerState.Setup -> homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false)
|
||||
BannerState.BackingUp -> homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false)
|
||||
null,
|
||||
BannerState.Hidden -> homeKeysBackupBanner.render(KeysBackupBanner.State.Hidden, false)
|
||||
}
|
||||
}
|
||||
homeKeysBackupBanner.delegate = this
|
||||
}
|
||||
|
||||
|
@ -145,7 +145,7 @@ class DefaultNavigator @Inject constructor(
|
||||
|
||||
override fun upgradeSessionSecurity(context: Context, initCrossSigningOnly: Boolean) {
|
||||
if (context is VectorBaseActivity) {
|
||||
BootstrapBottomSheet.show(context.supportFragmentManager, initCrossSigningOnly)
|
||||
BootstrapBottomSheet.show(context.supportFragmentManager, initCrossSigningOnly, false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,7 +221,7 @@ class DefaultNavigator @Inject constructor(
|
||||
// if cross signing is enabled we should propose full 4S
|
||||
sessionHolder.getSafeActiveSession()?.let { session ->
|
||||
if (session.cryptoService().crossSigningService().canCrossSign() && context is VectorBaseActivity) {
|
||||
BootstrapBottomSheet.show(context.supportFragmentManager, false)
|
||||
BootstrapBottomSheet.show(context.supportFragmentManager, initCrossSigningOnly = false, forceReset4S = false)
|
||||
} else {
|
||||
context.startActivity(KeysBackupSetupActivity.intent(context, showManualExport))
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||
const val SETTINGS_ALLOW_INTEGRATIONS_KEY = "SETTINGS_ALLOW_INTEGRATIONS_KEY"
|
||||
const val SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY = "SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY"
|
||||
const val SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY = "SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY"
|
||||
// const val SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY = "SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY"
|
||||
|
||||
// user
|
||||
const val SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY = "SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY"
|
||||
|
@ -23,6 +23,7 @@ import android.content.Intent
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
@ -33,6 +34,8 @@ import im.vector.matrix.android.internal.crypto.crosssigning.isVerified
|
||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||
import im.vector.matrix.rx.SecretsSynchronisationInfo
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.core.dialogs.ExportKeysDialog
|
||||
@ -42,11 +45,16 @@ import im.vector.riotx.core.intent.analyseIntent
|
||||
import im.vector.riotx.core.intent.getFilenameFromUri
|
||||
import im.vector.riotx.core.platform.SimpleTextWatcher
|
||||
import im.vector.riotx.core.preference.VectorPreference
|
||||
import im.vector.riotx.core.preference.VectorPreferenceCategory
|
||||
import im.vector.riotx.core.utils.openFileSelection
|
||||
import im.vector.riotx.core.utils.toast
|
||||
import im.vector.riotx.features.crypto.keys.KeysExporter
|
||||
import im.vector.riotx.features.crypto.keys.KeysImporter
|
||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||
import im.vector.riotx.features.crypto.recover.BootstrapBottomSheet
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import javax.inject.Inject
|
||||
|
||||
class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||
@ -56,6 +64,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||
|
||||
override var titleRes = R.string.settings_security_and_privacy
|
||||
override val preferenceXmlRes = R.xml.vector_settings_security_privacy
|
||||
private var disposables = mutableListOf<Disposable>()
|
||||
|
||||
// cryptography
|
||||
private val mCryptographyCategory by lazy {
|
||||
@ -92,6 +101,97 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||
// My device name may have been updated
|
||||
refreshMyDevice()
|
||||
refreshXSigningStatus()
|
||||
session.rx().liveSecretSynchronisationInfo()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
refresh4SSection(it)
|
||||
refreshXSigningStatus()
|
||||
}.also {
|
||||
disposables.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
private val secureBackupCategory by lazy {
|
||||
findPreference<VectorPreferenceCategory>("SETTINGS_CRYPTOGRAPHY_MANAGE_4S_CATEGORY_KEY")!!
|
||||
}
|
||||
private val secureBackupPreference by lazy {
|
||||
findPreference<VectorPreference>("SETTINGS_SECURE_BACKUP_RECOVERY_PREFERENCE_KEY")!!
|
||||
}
|
||||
// private val secureBackupResetPreference by lazy {
|
||||
// findPreference<VectorPreference>(VectorPreferences.SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY)
|
||||
// }
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
disposables.forEach {
|
||||
it.dispose()
|
||||
}
|
||||
disposables.clear()
|
||||
}
|
||||
|
||||
private fun refresh4SSection(info: SecretsSynchronisationInfo) {
|
||||
// it's a lot of if / else if / else
|
||||
// But it's not yet clear how to manage all cases
|
||||
if (!info.isCrossSigningEnabled) {
|
||||
// There is not cross signing, so we can remove the section
|
||||
secureBackupCategory.isVisible = false
|
||||
} else {
|
||||
if (!info.isBackupSetup) {
|
||||
if (info.isCrossSigningEnabled && info.allPrivateKeysKnown) {
|
||||
// You can setup recovery!
|
||||
secureBackupCategory.isVisible = true
|
||||
secureBackupPreference.title = getString(R.string.settings_secure_backup_setup)
|
||||
secureBackupPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
BootstrapBottomSheet.show(parentFragmentManager, initCrossSigningOnly = false, forceReset4S = false)
|
||||
true
|
||||
}
|
||||
} else {
|
||||
// just hide all, you can't setup from here
|
||||
// you should synchronize to get gossips
|
||||
secureBackupCategory.isVisible = false
|
||||
}
|
||||
} else {
|
||||
// so here we know that 4S is setup
|
||||
if (info.isCrossSigningTrusted && info.allPrivateKeysKnown) {
|
||||
// Looks like we have all cross signing secrets and session is trusted
|
||||
// Let's see if there is a megolm backup
|
||||
if (!info.megolmBackupAvailable || info.megolmSecretKnown) {
|
||||
// Only option here is to create a new backup if you want?
|
||||
// aka reset
|
||||
secureBackupCategory.isVisible = true
|
||||
secureBackupPreference.title = getString(R.string.settings_secure_backup_reset)
|
||||
secureBackupPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
BootstrapBottomSheet.show(parentFragmentManager, initCrossSigningOnly = false, forceReset4S = true)
|
||||
true
|
||||
}
|
||||
} else if (!info.megolmSecretKnown) {
|
||||
// megolm backup is available but we don't have key
|
||||
// you could try to synchronize to get missing megolm key ?
|
||||
secureBackupCategory.isVisible = true
|
||||
secureBackupPreference.title = getString(R.string.settings_secure_backup_enter_to_setup)
|
||||
secureBackupPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
vectorActivity.let {
|
||||
it.navigator.requestSelfSessionVerification(it)
|
||||
}
|
||||
true
|
||||
}
|
||||
} else {
|
||||
secureBackupCategory.isVisible = false
|
||||
}
|
||||
} else {
|
||||
// there is a backup, but this session is not trusted, or is missing some secrets
|
||||
// you should enter passphrase to get them or verify against another session
|
||||
secureBackupCategory.isVisible = true
|
||||
secureBackupPreference.title = getString(R.string.settings_secure_backup_enter_to_setup)
|
||||
secureBackupPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
vectorActivity.let {
|
||||
it.navigator.requestSelfSessionVerification(it)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun bindPref() {
|
||||
@ -115,26 +215,37 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
|
||||
}
|
||||
|
||||
refreshXSigningStatus()
|
||||
|
||||
secureBackupPreference.icon = activity?.let {
|
||||
ThemeUtils.tintDrawable(it,
|
||||
ContextCompat.getDrawable(it, R.drawable.ic_secure_backup)!!, R.attr.vctr_settings_icon_tint_color)
|
||||
}
|
||||
}
|
||||
|
||||
// Todo this should be refactored and use same state as 4S section
|
||||
private fun refreshXSigningStatus() {
|
||||
val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
||||
val xSigningIsEnableInAccount = crossSigningKeys != null
|
||||
val xSigningKeysAreTrusted = session.cryptoService().crossSigningService().checkUserTrust(session.myUserId).isVerified()
|
||||
val xSigningKeyCanSign = session.cryptoService().crossSigningService().canCrossSign()
|
||||
|
||||
if (xSigningKeyCanSign) {
|
||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
|
||||
} else if (xSigningKeysAreTrusted) {
|
||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
|
||||
} else if (xSigningIsEnableInAccount) {
|
||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted)
|
||||
} else {
|
||||
mCrossSigningStatePreference.setIcon(android.R.color.transparent)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled)
|
||||
when {
|
||||
xSigningKeyCanSign -> {
|
||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_trusted)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_complete)
|
||||
}
|
||||
xSigningKeysAreTrusted -> {
|
||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_custom)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_trusted)
|
||||
}
|
||||
xSigningIsEnableInAccount -> {
|
||||
mCrossSigningStatePreference.setIcon(R.drawable.ic_shield_black)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_not_trusted)
|
||||
}
|
||||
else -> {
|
||||
mCrossSigningStatePreference.setIcon(android.R.color.transparent)
|
||||
mCrossSigningStatePreference.summary = getString(R.string.encryption_information_dg_xsigning_disabled)
|
||||
}
|
||||
}
|
||||
|
||||
mCrossSigningStatePreference.isVisible = true
|
||||
|
@ -26,6 +26,7 @@ import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.extensions.orFalse
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
||||
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
@ -114,9 +115,7 @@ class ServerBackupStatusViewModel @AssistedInject constructor(@Assisted initialS
|
||||
// So recovery is not setup
|
||||
// Check if cross signing is enabled and local secrets known
|
||||
if (crossSigningInfo.getOrNull()?.isTrusted() == true
|
||||
&& pInfo.getOrNull()?.master != null
|
||||
&& pInfo.getOrNull()?.selfSigned != null
|
||||
&& pInfo.getOrNull()?.user != null
|
||||
&& pInfo.getOrNull()?.allKnown().orFalse()
|
||||
) {
|
||||
// So 4S is not setup and we have local secrets,
|
||||
return@Function4 BannerState.Setup(numberOfKeys = getNumberOfKeysToBackup())
|
||||
|
@ -121,7 +121,7 @@ class SignOutBottomSheetDialogFragment : VectorBaseBottomSheetDialogFragment(),
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
|
||||
setupRecoveryButton.action = {
|
||||
BootstrapBottomSheet.show(parentFragmentManager, false)
|
||||
BootstrapBottomSheet.show(parentFragmentManager, initCrossSigningOnly = false, forceReset4S = false)
|
||||
}
|
||||
|
||||
exitAnywayButton.action = {
|
||||
|
@ -43,6 +43,7 @@
|
||||
app:actionDescription="@string/bottom_sheet_setup_secure_backup_security_key_subtitle"
|
||||
app:actionTitle="@string/bottom_sheet_setup_secure_backup_security_key_title"
|
||||
app:leftIcon="@drawable/ic_security_key_24dp"
|
||||
app:tint="?attr/riotx_text_primary"
|
||||
app:rightIcon="@drawable/ic_arrow_right"
|
||||
tools:visibility="visible" />
|
||||
|
||||
@ -63,6 +64,7 @@
|
||||
app:actionDescription="@string/bottom_sheet_setup_secure_backup_security_phrase_subtitle"
|
||||
app:actionTitle="@string/bottom_sheet_setup_secure_backup_security_phrase_title"
|
||||
app:leftIcon="@drawable/ic_security_phrase_24dp"
|
||||
app:tint="?attr/riotx_text_primary"
|
||||
app:rightIcon="@drawable/ic_arrow_right"
|
||||
tools:visibility="visible" />
|
||||
|
||||
@ -71,4 +73,18 @@
|
||||
android:layout_height="1dp"
|
||||
android:background="?attr/vctr_list_divider_color" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bootstrapSetupWarningTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/reset_secure_backup_warning"
|
||||
android:textColor="@color/riotx_destructive_accent"
|
||||
android:drawableStart="@drawable/ic_warning_small"
|
||||
android:drawablePadding="4dp"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -843,6 +843,15 @@
|
||||
<string name="settings_send_message_with_enter">Send message with enter</string>
|
||||
<string name="settings_send_message_with_enter_summary">Enter button of the soft keyboard will send message instead of adding a line break</string>
|
||||
|
||||
<string name="settings_secure_backup_section_title">Secure Backup</string>
|
||||
<string name="settings_secure_backup_manage">Manage</string>
|
||||
<string name="settings_secure_backup_setup">Set up Secure Backup</string>
|
||||
<string name="settings_secure_backup_reset">Reset Secure Backup</string>
|
||||
<string name="settings_secure_backup_enter_to_setup">Set up on this device</string>
|
||||
<string name="settings_secure_backup_section_info">Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.</string>
|
||||
<string name="reset_secure_backup_title">Generate a new Security Key or set a new Security Phrase for your existing backup.</string>
|
||||
<string name="reset_secure_backup_warning">This will replace your current Key or Phrase.</string>
|
||||
|
||||
<string name="settings_deactivate_account_section">Deactivate account</string>
|
||||
<string name="settings_deactivate_my_account">Deactivate my account</string>
|
||||
<string name="settings_discovery_category">Discovery</string>
|
||||
|
@ -48,6 +48,23 @@
|
||||
|
||||
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||
|
||||
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
||||
android:key="SETTINGS_CRYPTOGRAPHY_MANAGE_4S_CATEGORY_KEY"
|
||||
android:title="@string/settings_secure_backup_section_title">
|
||||
|
||||
<im.vector.riotx.core.preference.VectorPreference
|
||||
android:key="SETTINGS_SECURE_BACKUP_RECOVERY_PREFERENCE_KEY"
|
||||
android:persistent="false"
|
||||
android:icon="@drawable/ic_secure_backup"
|
||||
android:title="@string/settings_secure_backup_setup" />
|
||||
|
||||
<im.vector.riotx.core.preference.VectorPreference
|
||||
android:focusable="false"
|
||||
android:persistent="false"
|
||||
android:summary="@string/settings_secure_backup_section_info" />
|
||||
</im.vector.riotx.core.preference.VectorPreferenceCategory>
|
||||
|
||||
|
||||
<im.vector.riotx.core.preference.VectorPreferenceCategory
|
||||
android:key="SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY"
|
||||
android:title="@string/settings_cryptography_manage_keys">
|
||||
|
Loading…
Reference in New Issue
Block a user