Add support for properly dealing with incorrect passwords (#1672)
This commit is contained in:
parent
541bf62038
commit
78a90aacb3
4 changed files with 72 additions and 24 deletions
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue