Improve Git password/passphrase dialog behavior (#829)
* Reset PasswordFinder retry state after authentication * Memorize password inbetween Git commands
This commit is contained in:
parent
0c01a5bbf9
commit
4172c70c86
3 changed files with 51 additions and 6 deletions
|
@ -12,6 +12,7 @@ import android.os.AsyncTask
|
|||
import com.github.ajalt.timberkt.e
|
||||
import com.zeapo.pwdstore.PasswordStore
|
||||
import com.zeapo.pwdstore.R
|
||||
import com.zeapo.pwdstore.git.config.SshjSessionFactory
|
||||
import net.schmizz.sshj.userauth.UserAuthException
|
||||
import org.eclipse.jgit.api.CommitCommand
|
||||
import org.eclipse.jgit.api.GitCommand
|
||||
|
@ -147,6 +148,7 @@ class GitAsyncTask(
|
|||
if (refreshListOnEnd) {
|
||||
(activity as? PasswordStore)?.resetPasswordList()
|
||||
}
|
||||
(SshSessionFactory.getInstance() as? SshjSessionFactory)?.clearCredentials()
|
||||
SshSessionFactory.setInstance(null)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ package com.zeapo.pwdstore.git.config
|
|||
import android.util.Base64
|
||||
import com.github.ajalt.timberkt.d
|
||||
import com.github.ajalt.timberkt.w
|
||||
import com.zeapo.pwdstore.utils.clear
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.schmizz.sshj.SSHClient
|
||||
|
@ -33,18 +34,48 @@ import kotlin.coroutines.Continuation
|
|||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
sealed class SshAuthData {
|
||||
class Password(val passwordFinder: InteractivePasswordFinder) : SshAuthData()
|
||||
class PublicKeyFile(val keyFile: File, val passphraseFinder: InteractivePasswordFinder) : SshAuthData()
|
||||
class Password(val passwordFinder: InteractivePasswordFinder) : SshAuthData() {
|
||||
override fun clearCredentials() {
|
||||
passwordFinder.clearPassword()
|
||||
}
|
||||
}
|
||||
|
||||
class PublicKeyFile(val keyFile: File, val passphraseFinder: InteractivePasswordFinder) : SshAuthData() {
|
||||
override fun clearCredentials() {
|
||||
passphraseFinder.clearPassword()
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun clearCredentials()
|
||||
}
|
||||
|
||||
abstract class InteractivePasswordFinder : PasswordFinder {
|
||||
|
||||
private var isRetry = false
|
||||
private var shouldRetry = true
|
||||
|
||||
abstract fun askForPassword(cont: Continuation<String?>, isRetry: Boolean)
|
||||
|
||||
private var isRetry = false
|
||||
private var shouldRetry = true
|
||||
private var lastPassword: CharArray? = null
|
||||
|
||||
fun resetForReuse() {
|
||||
isRetry = false
|
||||
shouldRetry = true
|
||||
}
|
||||
|
||||
fun clearPassword() {
|
||||
lastPassword?.clear()
|
||||
lastPassword = null
|
||||
}
|
||||
|
||||
final override fun reqPassword(resource: Resource<*>?): CharArray {
|
||||
if (lastPassword != null && !isRetry) {
|
||||
// This instance successfully authenticated in a previous authentication step and is
|
||||
// now being reused for a new one. We try the previous password so that the user
|
||||
// does not have to type it again.
|
||||
isRetry = true
|
||||
return lastPassword!!
|
||||
}
|
||||
clearPassword()
|
||||
val password = runBlocking(Dispatchers.Main) {
|
||||
suspendCoroutine<String?> { cont ->
|
||||
askForPassword(cont, isRetry)
|
||||
|
@ -52,7 +83,7 @@ abstract class InteractivePasswordFinder : PasswordFinder {
|
|||
}
|
||||
isRetry = true
|
||||
return if (password != null) {
|
||||
password.toCharArray()
|
||||
password.toCharArray().also { lastPassword = it }
|
||||
} else {
|
||||
shouldRetry = false
|
||||
CharArray(0)
|
||||
|
@ -67,6 +98,10 @@ class SshjSessionFactory(private val username: String, private val authData: Ssh
|
|||
override fun getSession(uri: URIish, credentialsProvider: CredentialsProvider?, fs: FS?, tms: Int): RemoteSession {
|
||||
return SshjSession(uri, username, authData, hostKeyFile).connect()
|
||||
}
|
||||
|
||||
fun clearCredentials() {
|
||||
authData.clearCredentials()
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeTofuHostKeyVerifier(hostKeyFile: File): HostKeyVerifier {
|
||||
|
@ -105,9 +140,11 @@ private class SshjSession(private val uri: URIish, private val username: String,
|
|||
when (authData) {
|
||||
is SshAuthData.Password -> {
|
||||
ssh.authPassword(username, authData.passwordFinder)
|
||||
authData.passwordFinder.resetForReuse()
|
||||
}
|
||||
is SshAuthData.PublicKeyFile -> {
|
||||
ssh.authPublickey(username, ssh.loadKeys(authData.keyFile.absolutePath, authData.passphraseFinder))
|
||||
authData.passphraseFinder.resetForReuse()
|
||||
}
|
||||
}
|
||||
return this
|
||||
|
|
|
@ -26,6 +26,12 @@ fun String.splitLines(): Array<String> {
|
|||
return split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
}
|
||||
|
||||
fun CharArray.clear() {
|
||||
forEachIndexed { i, _ ->
|
||||
this[i] = 0.toChar()
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.resolveAttribute(attr: Int): Int {
|
||||
val typedValue = TypedValue()
|
||||
this.theme.resolveAttribute(attr, typedValue, true)
|
||||
|
|
Loading…
Reference in a new issue