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()
// 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()
}
}

View file

@ -10,6 +10,7 @@ import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.lifecycle.lifecycleScope
import com.github.michaelbull.result.runCatching
import dagger.hilt.android.AndroidEntryPoint
import dev.msfjarvis.aps.R
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.databinding.DecryptLayoutBinding
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.viewBinding
import dev.msfjarvis.aps.util.settings.PreferenceKeys
@ -42,6 +44,7 @@ class DecryptActivityV2 : BasePgpActivity() {
private val relativeParentPath by unsafeLazy { getParentPath(fullPath, repoPath) }
private var passwordEntry: PasswordEntry? = null
private var retries = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -56,7 +59,7 @@ class DecryptActivityV2 : BasePgpActivity() {
true
}
}
decrypt()
decrypt(isError = false)
}
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()
if (isError) {
dialog.setError()
}
lifecycleScope.launch(Dispatchers.Main) {
dialog.password.collectLatest { value ->
if (value != null) {
decrypt(value)
if (runCatching { decrypt(value) }.isErr()) {
decrypt(isError = true)
}
}
}
}
dialog.show(supportFragmentManager, "PASSWORD_DIALOG")
}
private fun decrypt(password: String) {
lifecycleScope.launch {
val message = withContext(Dispatchers.IO) { File(fullPath).readBytes().inputStream() }
val result =
withContext(Dispatchers.IO) {
val outputStream = ByteArrayOutputStream()
repository.decrypt(
password,
message,
outputStream,
)
outputStream
}
startAutoDismissTimer()
private suspend fun decrypt(password: String) {
val message = withContext(Dispatchers.IO) { File(fullPath).readBytes().inputStream() }
val result =
withContext(Dispatchers.IO) {
val outputStream = ByteArrayOutputStream()
repository.decrypt(
password,
message,
outputStream,
)
outputStream
}
require(result.size() != 0) { "Incorrect password" }
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 entry = passwordEntryFactory.create(lifecycleScope, result.toByteArray())
passwordEntry = entry
invalidateOptionsMenu()
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.content.DialogInterface
import android.os.Bundle
import android.view.KeyEvent
import android.view.WindowManager
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dev.msfjarvis.aps.R
@ -21,6 +24,7 @@ import kotlinx.coroutines.flow.asStateFlow
class PasswordDialog : DialogFragment() {
private val binding by unsafeLazy { DialogPasswordEntryBinding.inflate(layoutInflater) }
private var isError: Boolean = false
private val _password = MutableStateFlow<String?>(null)
val password = _password.asStateFlow()
@ -28,15 +32,37 @@ class PasswordDialog : DialogFragment() {
val builder = MaterialAlertDialogBuilder(requireContext())
builder.setView(binding.root)
builder.setTitle(R.string.password)
builder.setPositiveButton(android.R.string.ok) { _, _ ->
do {} while (!_password.tryEmit(binding.passwordEditText.text.toString()))
dismiss()
builder.setPositiveButton(android.R.string.ok) { _, _ -> tryEmitPassword() }
val dialog = builder.create()
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) {
super.onCancel(dialog)
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_height="wrap_content"
android:inputType="textPassword" />
<requestFocus />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>