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.
This commit is contained in:
Dominik Dzienia 2020-09-04 03:55:02 +02:00
parent 3457f8f511
commit 651c3fc426
5 changed files with 122 additions and 20 deletions

View file

@ -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)

View file

@ -208,6 +208,9 @@
<string name="preferences_import_canceled">Import canceled! Preferences were NOT imported!</string>
<string name="preferences_import_impossible">Cannot import preferences!</string>
<string name="goto_main_try_again">Please go back to main screen and try again.</string>
<string name="old_master_password">Old Master Password</string>
<string name="different_password_used">This file was exported and encrypted with different master password. Provide old master password to decrypt file.</string>
<string name="master_password_will_be_replaced">As a result of successful import current master password WILL BE REPLACED with that old master password!</string>
<string name="preferences_import_list_title">Select file to import</string>

View file

@ -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<View>(R.id.password_prompt_extra_message) as TextView;
extraWarning.text = context.getString(it);
extraWarning.visibility = View.VISIBLE;
}
val userInput = promptsView.findViewById<View>(R.id.password_prompt_pass) as EditText
val userInput2 = promptsView.findViewById<View>(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()
}
}

View file

@ -5,6 +5,18 @@
android:orientation="vertical"
android:padding="10dp">
<TextView
android:id="@+id/password_prompt_extra_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@color/warningAccentText"
android:visibility="gone"
/>
<EditText
android:id="@+id/password_prompt_pass"
android:layout_width="match_parent"

View file

@ -21,6 +21,7 @@
<color name="dialog_title_icon_tint">#FFFFFF</color>
<color name="warningAlertBackground">#FFFB8C00</color>
<color name="warningAlertHeaderText">#FF000000</color>
<color name="warningAccentText">#FFFB8C00</color>
<color name="errorAlertBackground">#FFFF5555</color>
<color name="errorAlertHeaderText">#FF000000</color>
<color name="examinedProfile">#FFFF5555</color>