Added password for ssh keys

This commit is contained in:
Abdelilah El Aissaoui 2021-11-05 04:27:35 +01:00
parent b63614d68d
commit da3f3c8935
7 changed files with 178 additions and 11 deletions

View File

@ -19,7 +19,11 @@ object CredentialsStateManager {
sealed class CredentialsState { sealed class CredentialsState {
object None : CredentialsState() object None : CredentialsState()
object CredentialsRequested : CredentialsState() sealed class CredentialsRequested : CredentialsState()
object SshCredentialsRequested : CredentialsRequested()
object HttpCredentialsRequested : CredentialsRequested()
object CredentialsDenied : CredentialsState() object CredentialsDenied : CredentialsState()
data class CredentialsAccepted(val user: String, val password: String) : CredentialsState() sealed class CredentialsAccepted : CredentialsState()
data class SshCredentialsAccepted(val password: String) : CredentialsAccepted()
data class HttpCredentialsAccepted(val user: String, val password: String) : CredentialsAccepted()
} }

View File

@ -1,32 +1,67 @@
package app.credentials package app.credentials
import org.apache.sshd.agent.SshAgent
import org.apache.sshd.agent.local.AgentImpl
import org.apache.sshd.agent.local.LocalAgentFactory
import org.apache.sshd.client.SshClient import org.apache.sshd.client.SshClient
import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractive
import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory
import org.apache.sshd.client.auth.keyboard.UserInteraction
import org.apache.sshd.client.auth.password.PasswordAuthenticationReporter
import org.apache.sshd.client.auth.password.UserAuthPassword
import org.apache.sshd.client.future.ConnectFuture import org.apache.sshd.client.future.ConnectFuture
import org.apache.sshd.client.session.ClientSession
import org.apache.sshd.common.NamedResource
import org.apache.sshd.common.config.keys.FilePasswordProvider
import org.apache.sshd.common.keyprovider.FileKeyPairProvider
import org.apache.sshd.common.session.SessionContext
import org.eclipse.jgit.transport.RemoteSession import org.eclipse.jgit.transport.RemoteSession
import org.eclipse.jgit.transport.URIish import org.eclipse.jgit.transport.URIish
import java.lang.Exception
import java.security.KeyPair
import java.time.Duration
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider import javax.inject.Provider
private const val DEFAULT_SSH_PORT = 22 private const val DEFAULT_SSH_PORT = 22
class GRemoteSession @Inject constructor( class GRemoteSession @Inject constructor(
private val processProvider: Provider<GProcess>, private val processProvider: Provider<GProcess>,
) : RemoteSession { ) : RemoteSession {
private val credentialsStateManager = CredentialsStateManager
private val client = SshClient.setUpDefaultClient() private val client = SshClient.setUpDefaultClient()
private var connectFuture: ConnectFuture? = null private var connectFuture: ConnectFuture? = null
override fun exec(commandName: String, timeout: Int): Process { override fun exec(commandName: String, timeout: Int): Process {
println(commandName) println(commandName)
val connectFuture = checkNotNull(connectFuture) val connectFuture = checkNotNull(connectFuture)
val session = connectFuture.clientSession val session = connectFuture.clientSession
session.auth().verify()
val auth = session.auth()
auth.addListener { arg0 ->
println("Authentication completed with " + if (arg0.isSuccess) "success" else "failure")
}
session.waitFor(
listOf(
ClientSession.ClientSessionEvent.WAIT_AUTH,
ClientSession.ClientSessionEvent.CLOSED,
ClientSession.ClientSessionEvent.AUTHED
), Duration.ofHours(2)
)
auth.verify()
val process = processProvider.get() val process = processProvider.get()
process.setup(session, commandName) process.setup(session, commandName)
return process return process
} }
override fun disconnect() { override fun disconnect() {
client.close() client.close()
} }
@ -39,6 +74,25 @@ class GRemoteSession @Inject constructor(
} else } else
uri.port uri.port
val filePasswordProvider = object : FilePasswordProvider {
override fun getPassword(session: SessionContext?, resourceKey: NamedResource?, retryIndex: Int): String? {
credentialsStateManager.updateState(CredentialsState.SshCredentialsRequested)
var credentials = credentialsStateManager.currentCredentialsState
while (credentials is CredentialsState.CredentialsRequested) {
// TODO check if support for ED25519 with pwd can be added
credentials = credentialsStateManager.currentCredentialsState
}
return if(credentials !is CredentialsState.SshCredentialsAccepted)
null
else
credentials.password
}
}
client.filePasswordProvider = filePasswordProvider
val connectFuture = client.connect(uri.user, uri.host, port) val connectFuture = client.connect(uri.user, uri.host, port)
connectFuture.await() connectFuture.await()

View File

@ -23,15 +23,14 @@ class HttpCredentialsProvider : CredentialsProvider() {
} }
override fun get(uri: URIish?, vararg items: CredentialItem?): Boolean { override fun get(uri: URIish?, vararg items: CredentialItem?): Boolean {
credentialsStateManager.updateState(CredentialsState.CredentialsRequested) credentialsStateManager.updateState(CredentialsState.HttpCredentialsRequested)
@Suppress("ControlFlowWithEmptyBody")
var credentials = credentialsStateManager.currentCredentialsState var credentials = credentialsStateManager.currentCredentialsState
while (credentials is CredentialsState.CredentialsRequested) { while (credentials is CredentialsState.CredentialsRequested) {
credentials = credentialsStateManager.currentCredentialsState credentials = credentialsStateManager.currentCredentialsState
} }
if(credentials is CredentialsState.CredentialsAccepted) { if(credentials is CredentialsState.HttpCredentialsAccepted) {
val userItem = items.firstOrNull { it?.promptText == "Username" } val userItem = items.firstOrNull { it?.promptText == "Username" }
val passwordItem = items.firstOrNull { it?.promptText == "Password" } val passwordItem = items.firstOrNull { it?.promptText == "Password" }

View File

@ -226,8 +226,12 @@ class GitManager @Inject constructor(
credentialsStateManager.updateState(CredentialsState.CredentialsDenied) credentialsStateManager.updateState(CredentialsState.CredentialsDenied)
} }
fun credentialsAccepted(user: String, password: String) { fun httpCredentialsAccepted(user: String, password: String) {
credentialsStateManager.updateState(CredentialsState.CredentialsAccepted(user, password)) credentialsStateManager.updateState(CredentialsState.HttpCredentialsAccepted(user, password))
}
fun sshCredentialsAccepted(password: String) {
credentialsStateManager.updateState(CredentialsState.SshCredentialsAccepted(password))
} }
suspend fun diffListFromCommit(commit: RevCommit): List<DiffEntry> { suspend fun diffListFromCommit(commit: RevCommit): List<DiffEntry> {
@ -284,6 +288,7 @@ class GitManager @Inject constructor(
try { try {
callback() callback()
} catch (ex: Exception) { } catch (ex: Exception) {
ex.printStackTrace()
errorsManager.addError(newErrorNow(ex, ex.localizedMessage)) errorsManager.addError(newErrorNow(ex, ex.localizedMessage))
} finally { } finally {
_processing.value = false _processing.value = false

View File

@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.diff.DiffEntry import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.lib.Constants
import javax.inject.Inject import javax.inject.Inject
class StatusManager @Inject constructor() { class StatusManager @Inject constructor() {
@ -68,6 +69,13 @@ class StatusManager @Inject constructor() {
loadStatus(git) loadStatus(git)
} }
// suspend fun stageHunk(git: Git) {
//// val repository = git.repository
//// val objectInserter = repository.newObjectInserter()
//
//// objectInserter.insert(Constants.OBJ_BLOB,)
// }
suspend fun unstage(git: Git, diffEntry: DiffEntry) = withContext(Dispatchers.IO) { suspend fun unstage(git: Git, diffEntry: DiffEntry) = withContext(Dispatchers.IO) {
git.reset() git.reset()
.addPath(diffEntry.filePath) .addPath(diffEntry.filePath)

View File

@ -10,6 +10,7 @@ import app.credentials.CredentialsState
import app.git.DiffEntryType import app.git.DiffEntryType
import app.git.GitManager import app.git.GitManager
import app.ui.dialogs.NewBranchDialog import app.ui.dialogs.NewBranchDialog
import app.ui.dialogs.PasswordDialog
import app.ui.dialogs.UserPasswordDialog import app.ui.dialogs.UserPasswordDialog
import openRepositoryDialog import openRepositoryDialog
import org.eclipse.jgit.revwalk.RevCommit import org.eclipse.jgit.revwalk.RevCommit
@ -32,7 +33,7 @@ fun RepositoryOpenPage(gitManager: GitManager, dialogManager: DialogManager) {
val credentialsState by gitManager.credentialsState.collectAsState() val credentialsState by gitManager.credentialsState.collectAsState()
if (credentialsState == CredentialsState.CredentialsRequested) { if (credentialsState == CredentialsState.HttpCredentialsRequested) {
dialogManager.show { dialogManager.show {
UserPasswordDialog( UserPasswordDialog(
onReject = { onReject = {
@ -40,7 +41,20 @@ fun RepositoryOpenPage(gitManager: GitManager, dialogManager: DialogManager) {
dialogManager.dismiss() dialogManager.dismiss()
}, },
onAccept = { user, password -> onAccept = { user, password ->
gitManager.credentialsAccepted(user, password) gitManager.httpCredentialsAccepted(user, password)
dialogManager.dismiss()
}
)
}
} else if (credentialsState == CredentialsState.SshCredentialsRequested) {
dialogManager.show {
PasswordDialog(
onReject = {
gitManager.credentialsDenied()
dialogManager.dismiss()
},
onAccept = { password ->
gitManager.sshCredentialsAccepted(password)
dialogManager.dismiss() dialogManager.dismiss()
} }
) )

View File

@ -0,0 +1,83 @@
package app.ui.dialogs
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusOrder
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun PasswordDialog(
onReject: () -> Unit,
onAccept: (password: String) -> Unit
) {
var passwordField by remember { mutableStateOf("") }
val passwordFieldFocusRequester = remember { FocusRequester() }
val buttonFieldFocusRequester = remember { FocusRequester() }
Column(
modifier = Modifier
.background(MaterialTheme.colors.background),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text(
text = "Introduce your default SSH key's password",
modifier = Modifier
.padding(vertical = 8.dp),
)
OutlinedTextField(
modifier = Modifier.padding(bottom = 8.dp)
.focusOrder(passwordFieldFocusRequester) {
this.next = buttonFieldFocusRequester
}
.width(300.dp),
value = passwordField,
singleLine = true,
label = { Text("Password", fontSize = 14.sp) },
textStyle = TextStyle(fontSize = 14.sp),
onValueChange = {
passwordField = it
},
visualTransformation = PasswordVisualTransformation()
)
Row(
modifier = Modifier
.padding(top = 16.dp)
.align(Alignment.End)
) {
TextButton(
modifier = Modifier.padding(end = 8.dp),
onClick = {
onReject()
}
) {
Text("Cancel")
}
Button(
modifier = Modifier.focusOrder(buttonFieldFocusRequester) {
this.previous = passwordFieldFocusRequester
},
onClick = {
onAccept(passwordField)
}
) {
Text("Ok")
}
}
}
LaunchedEffect(Unit) {
passwordFieldFocusRequester.requestFocus()
}
}