Add support for properly dealing with incorrect passwords (#1672)

This commit is contained in:
Harsh Shandilya 2022-01-21 00:27:04 +05:30 committed by GitHub
parent 541bf62038
commit 78a90aacb3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 72 additions and 24 deletions

View file

@ -42,7 +42,7 @@ constructor(
) { ) {
val keys = pgpKeyManager.getAllKeys().unwrap() val keys = pgpKeyManager.getAllKeys().unwrap()
// Iterates through the keys until the first successful decryption, then returns. // Iterates through the keys until the first successful decryption, then returns.
keys.first { key -> keys.firstOrNull { key ->
runCatching { pgpCryptoHandler.decrypt(key, password, message, out) }.isOk() runCatching { pgpCryptoHandler.decrypt(key, password, message, out) }.isOk()
} }
} }

View file

@ -10,6 +10,7 @@ import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.github.michaelbull.result.runCatching
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.msfjarvis.aps.R import dev.msfjarvis.aps.R
import dev.msfjarvis.aps.data.crypto.CryptoRepository import dev.msfjarvis.aps.data.crypto.CryptoRepository
@ -17,6 +18,7 @@ import dev.msfjarvis.aps.data.passfile.PasswordEntry
import dev.msfjarvis.aps.data.password.FieldItem import dev.msfjarvis.aps.data.password.FieldItem
import dev.msfjarvis.aps.databinding.DecryptLayoutBinding import dev.msfjarvis.aps.databinding.DecryptLayoutBinding
import dev.msfjarvis.aps.ui.adapters.FieldItemAdapter import dev.msfjarvis.aps.ui.adapters.FieldItemAdapter
import dev.msfjarvis.aps.util.extensions.isErr
import dev.msfjarvis.aps.util.extensions.unsafeLazy import dev.msfjarvis.aps.util.extensions.unsafeLazy
import dev.msfjarvis.aps.util.extensions.viewBinding import dev.msfjarvis.aps.util.extensions.viewBinding
import dev.msfjarvis.aps.util.settings.PreferenceKeys import dev.msfjarvis.aps.util.settings.PreferenceKeys
@ -42,6 +44,7 @@ class DecryptActivityV2 : BasePgpActivity() {
private val relativeParentPath by unsafeLazy { getParentPath(fullPath, repoPath) } private val relativeParentPath by unsafeLazy { getParentPath(fullPath, repoPath) }
private var passwordEntry: PasswordEntry? = null private var passwordEntry: PasswordEntry? = null
private var retries = 0
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -56,7 +59,7 @@ class DecryptActivityV2 : BasePgpActivity() {
true true
} }
} }
decrypt() decrypt(isError = false)
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
@ -126,36 +129,51 @@ class DecryptActivityV2 : BasePgpActivity() {
) )
} }
private fun decrypt() { private fun decrypt(isError: Boolean) {
if (retries < MAX_RETRIES) {
retries += 1
} else {
finish()
}
val dialog = PasswordDialog() val dialog = PasswordDialog()
if (isError) {
dialog.setError()
}
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
dialog.password.collectLatest { value -> dialog.password.collectLatest { value ->
if (value != null) { if (value != null) {
decrypt(value) if (runCatching { decrypt(value) }.isErr()) {
decrypt(isError = true)
}
} }
} }
} }
dialog.show(supportFragmentManager, "PASSWORD_DIALOG") dialog.show(supportFragmentManager, "PASSWORD_DIALOG")
} }
private fun decrypt(password: String) { private suspend fun decrypt(password: String) {
lifecycleScope.launch { val message = withContext(Dispatchers.IO) { File(fullPath).readBytes().inputStream() }
val message = withContext(Dispatchers.IO) { File(fullPath).readBytes().inputStream() } val result =
val result = withContext(Dispatchers.IO) {
withContext(Dispatchers.IO) { val outputStream = ByteArrayOutputStream()
val outputStream = ByteArrayOutputStream() repository.decrypt(
repository.decrypt( password,
password, message,
message, outputStream,
outputStream, )
) outputStream
outputStream }
} require(result.size() != 0) { "Incorrect password" }
startAutoDismissTimer() startAutoDismissTimer()
val entry = passwordEntryFactory.create(lifecycleScope, result.toByteArray())
passwordEntry = entry
createPasswordUi(entry)
}
private suspend fun createPasswordUi(entry: PasswordEntry) =
withContext(Dispatchers.Main) {
val showPassword = settings.getBoolean(PreferenceKeys.SHOW_PASSWORD, true) val showPassword = settings.getBoolean(PreferenceKeys.SHOW_PASSWORD, true)
val entry = passwordEntryFactory.create(lifecycleScope, result.toByteArray())
passwordEntry = entry
invalidateOptionsMenu() invalidateOptionsMenu()
val items = arrayListOf<FieldItem>() val items = arrayListOf<FieldItem>()
@ -187,5 +205,8 @@ class DecryptActivityV2 : BasePgpActivity() {
} }
} }
} }
private companion object {
private const val MAX_RETRIES = 3
} }
} }

View file

@ -8,6 +8,9 @@ package dev.msfjarvis.aps.ui.crypto
import android.app.Dialog import android.app.Dialog
import android.content.DialogInterface import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.view.KeyEvent
import android.view.WindowManager
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dev.msfjarvis.aps.R import dev.msfjarvis.aps.R
@ -21,6 +24,7 @@ import kotlinx.coroutines.flow.asStateFlow
class PasswordDialog : DialogFragment() { class PasswordDialog : DialogFragment() {
private val binding by unsafeLazy { DialogPasswordEntryBinding.inflate(layoutInflater) } private val binding by unsafeLazy { DialogPasswordEntryBinding.inflate(layoutInflater) }
private var isError: Boolean = false
private val _password = MutableStateFlow<String?>(null) private val _password = MutableStateFlow<String?>(null)
val password = _password.asStateFlow() val password = _password.asStateFlow()
@ -28,15 +32,37 @@ class PasswordDialog : DialogFragment() {
val builder = MaterialAlertDialogBuilder(requireContext()) val builder = MaterialAlertDialogBuilder(requireContext())
builder.setView(binding.root) builder.setView(binding.root)
builder.setTitle(R.string.password) builder.setTitle(R.string.password)
builder.setPositiveButton(android.R.string.ok) { _, _ -> builder.setPositiveButton(android.R.string.ok) { _, _ -> tryEmitPassword() }
do {} while (!_password.tryEmit(binding.passwordEditText.text.toString())) val dialog = builder.create()
dismiss() dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
dialog.setOnShowListener {
if (isError) {
binding.passwordField.error = getString(R.string.git_operation_wrong_password)
}
binding.passwordEditText.doOnTextChanged { _, _, _, _ -> binding.passwordField.error = null }
binding.passwordEditText.setOnKeyListener { _, keyCode, _ ->
if (keyCode == KeyEvent.KEYCODE_ENTER) {
tryEmitPassword()
return@setOnKeyListener true
}
false
}
} }
return builder.create() return dialog
}
fun setError() {
isError = true
} }
override fun onCancel(dialog: DialogInterface) { override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog) super.onCancel(dialog)
finish() finish()
} }
@Suppress("ControlFlowWithEmptyBody")
private fun tryEmitPassword() {
do {} while (!_password.tryEmit(binding.passwordEditText.text.toString()))
dismissAllowingStateLoss()
}
} }

View file

@ -24,6 +24,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="textPassword" /> android:inputType="textPassword" />
<requestFocus />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</LinearLayout> </LinearLayout>