From 651c3fc426082ce7e13648184518f285da69bcce Mon Sep 17 00:00:00 2001 From: Dominik Dzienia Date: Fri, 4 Sep 2020 03:55:02 +0200 Subject: [PATCH] Add additional prompt for old master password Prompt for old master password (used to encrypt exported preferences) if current master password is different and cannot decrypt file. --- .../general/maintenance/ImportExportPrefs.kt | 75 ++++++++++++++----- app/src/main/res/values/strings.xml | 3 + .../utils/protection/PasswordCheck.kt | 51 +++++++++++++ core/src/main/res/layout/passwordprompt.xml | 12 +++ core/src/main/res/values/colors.xml | 1 + 5 files changed, 122 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt index d90de7a086..74abd439f8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt @@ -130,6 +130,15 @@ class ImportExportPrefs @Inject constructor( }) } + private fun askForEncryptionPass(activity: Activity, @StringRes canceledMsg: Int, @StringRes passwordName: Int, @StringRes passwordExplanation: Int?, + @StringRes passwordWarning: Int?, then: ((password: String) -> Unit)) { + passwordCheck.queryAnyPassword(activity, passwordName, R.string.key_master_password, passwordExplanation, passwordWarning, { password -> + then(password) + }, { + ToastUtils.warnToast(activity, resourceHelper.gs(canceledMsg)) + }) + } + private fun askForMasterPassIfNeeded(activity: Activity, @StringRes canceledMsg: Int, then: ((password: String) -> Unit)) { if (prefsEncryptionIsDisabled()) { then("") @@ -182,6 +191,28 @@ class ImportExportPrefs @Inject constructor( } } + private fun promptForDecryptionPasswordIfNeeded(activity: Activity, prefs: Prefs, importOk: Boolean, + format: PrefsFormat, importFile: PrefsFile, then: ((prefs: Prefs, importOk: Boolean) -> Unit)) { + + // current master password was not the one used for decryption, so we prompt for old password... + if (!importOk && (prefs.metadata[PrefsMetadataKey.ENCRYPTION]?.status == PrefsStatus.ERROR)) { + askForEncryptionPass(activity, R.string.preferences_import_canceled, R.string.old_master_password, + R.string.different_password_used, R.string.master_password_will_be_replaced) { password -> + + // ...and use it to load & decrypt file again + val prefsReloaded = format.loadPreferences(importFile.file, password) + prefsReloaded.metadata = prefFileList.checkMetadata(prefsReloaded.metadata) + + // import is OK when we do not have errors (warnings are allowed) + val importOkCheckedAgain = checkIfImportIsOk(prefsReloaded) + + then(prefsReloaded, importOkCheckedAgain); + } + } else { + then(prefs, importOk); + } + } + private fun exportSharedPreferences(activity: Activity) { prefFileList.ensureExportDirExists() @@ -258,32 +289,36 @@ class ImportExportPrefs @Inject constructor( try { - val prefs = format.loadPreferences(importFile.file, password) - prefs.metadata = prefFileList.checkMetadata(prefs.metadata) + val prefsAttempted = format.loadPreferences(importFile.file, password) + prefsAttempted.metadata = prefFileList.checkMetadata(prefsAttempted.metadata) // import is OK when we do not have errors (warnings are allowed) - val importOk = checkIfImportIsOk(prefs) + val importOkAttempted = checkIfImportIsOk(prefsAttempted) - // if at end we allow to import preferences - val importPossible = (importOk || buildHelper.isEngineeringMode()) && (prefs.values.size > 0) + promptForDecryptionPasswordIfNeeded(activity, prefsAttempted, importOkAttempted, format, importFile) { prefs, importOk -> - PrefImportSummaryDialog.showSummary(activity, importOk, importPossible, prefs, { - if (importPossible) { - sp.clear() - for ((key, value) in prefs.values) { - if (value == "true" || value == "false") { - sp.putBoolean(key, value.toBoolean()) - } else { - sp.putString(key, value) + // if at end we allow to import preferences + val importPossible = (importOk || buildHelper.isEngineeringMode()) && (prefs.values.size > 0) + + PrefImportSummaryDialog.showSummary(activity, importOk, importPossible, prefs, { + if (importPossible) { + sp.clear() + for ((key, value) in prefs.values) { + if (value == "true" || value == "false") { + sp.putBoolean(key, value.toBoolean()) + } else { + sp.putString(key, value) + } } - } - restartAppAfterImport(activity) - } else { - // for impossible imports it should not be called - ToastUtils.errorToast(activity, resourceHelper.gs(R.string.preferences_import_impossible)) - } - }) + restartAppAfterImport(activity) + } else { + // for impossible imports it should not be called + ToastUtils.errorToast(activity, resourceHelper.gs(R.string.preferences_import_impossible)) + } + }) + + } } catch (e: PrefFileNotFoundError) { ToastUtils.errorToast(activity, resourceHelper.gs(R.string.filenotfound) + " " + importFile) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 17343701cf..e54109587d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -208,6 +208,9 @@ Import canceled! Preferences were NOT imported! Cannot import preferences! Please go back to main screen and try again. + Old Master Password + This file was exported and encrypted with different master password. Provide old master password to decrypt file. + As a result of successful import current master password WILL BE REPLACED with that old master password! Select file to import diff --git a/core/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt b/core/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt index 04399eb95f..efc3f6dfb1 100644 --- a/core/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt +++ b/core/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt @@ -6,6 +6,7 @@ import android.os.Build import android.view.LayoutInflater import android.view.View import android.widget.EditText +import android.widget.TextView import androidx.annotation.StringRes import info.nightscout.androidaps.core.R import info.nightscout.androidaps.utils.CryptoUtil @@ -24,6 +25,9 @@ class PasswordCheck @Inject constructor( private val cryptoUtil: CryptoUtil ) { + /** + Asks for "managed" kind of password, checking if it is valid. + */ @SuppressLint("InflateParams") fun queryPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ((String) -> Unit)?, cancel: (() -> Unit)? = null, fail: (() -> Unit)? = null) { val password = sp.getString(preference, "") @@ -115,4 +119,51 @@ class PasswordCheck @Inject constructor( alertDialogBuilder.create().show() } + + /** + Prompt free-form password, with additional help and warning messages. + Preference ID (preference) is used only to generate ID for password managers, + since this query does NOT check validity of password. + */ + @SuppressLint("InflateParams") + fun queryAnyPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, @StringRes passwordExplanation: Int?, + @StringRes passwordWarning: Int?, ok: ((String) -> Unit)?, cancel: (() -> Unit)? = null) { + + val promptsView = LayoutInflater.from(context).inflate(R.layout.passwordprompt, null) + val alertDialogBuilder = AlertDialogHelper.Builder(context) + alertDialogBuilder.setView(promptsView) + passwordExplanation?.let { alertDialogBuilder.setMessage(it) } + + passwordWarning?.let { + val extraWarning: TextView = promptsView.findViewById(R.id.password_prompt_extra_message) as TextView; + extraWarning.text = context.getString(it); + extraWarning.visibility = View.VISIBLE; + } + + val userInput = promptsView.findViewById(R.id.password_prompt_pass) as EditText + val userInput2 = promptsView.findViewById(R.id.password_prompt_pass_confirm) as EditText + + userInput2.visibility = View.GONE + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val autoFillHintPasswordKind = context.getString(preference) + userInput.setAutofillHints(View.AUTOFILL_HINT_PASSWORD, "aaps_${autoFillHintPasswordKind}") + userInput.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_YES + } + + alertDialogBuilder + .setCancelable(false) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, context.getString(labelId), R.drawable.ic_header_key)) + .setPositiveButton(context.getString(R.string.ok)) { _, _ -> + val enteredPassword = userInput.text.toString() + ok?.invoke(enteredPassword) + } + .setNegativeButton(context.getString(R.string.cancel) + ) { dialog, _ -> + cancel?.invoke() + dialog.cancel() + } + + alertDialogBuilder.create().show() + } } diff --git a/core/src/main/res/layout/passwordprompt.xml b/core/src/main/res/layout/passwordprompt.xml index e363256be9..90b14b14e4 100644 --- a/core/src/main/res/layout/passwordprompt.xml +++ b/core/src/main/res/layout/passwordprompt.xml @@ -5,6 +5,18 @@ android:orientation="vertical" android:padding="10dp"> + + #FFFFFF #FFFB8C00 #FF000000 + #FFFB8C00 #FFFF5555 #FF000000 #FFFF5555