mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: support keyboard-interactive
This commit is contained in:
@@ -61,6 +61,10 @@ abstract class PtyHostTerminalTab(
|
||||
if (log.isErrorEnabled) {
|
||||
log.error(e.message, e)
|
||||
}
|
||||
|
||||
// 失败关闭
|
||||
stop()
|
||||
|
||||
withContext(Dispatchers.Swing) {
|
||||
terminal.write("\r\n${ControlCharacters.ESC}[31m")
|
||||
terminal.write(ExceptionUtils.getRootCauseMessage(e))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.termora
|
||||
|
||||
import app.termora.addons.zmodem.ZModemPtyConnectorAdaptor
|
||||
import app.termora.keyboardinteractive.TerminalUserInteraction
|
||||
import app.termora.terminal.ControlCharacters
|
||||
import app.termora.terminal.DataKey
|
||||
import app.termora.terminal.PtyConnector
|
||||
@@ -24,6 +25,7 @@ import org.apache.sshd.common.util.net.SshdSocketAddress
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.nio.charset.StandardCharsets
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
|
||||
class SSHTerminalTab(host: Host) : PtyHostTerminalTab(host) {
|
||||
@@ -76,6 +78,9 @@ class SSHTerminalTab(host: Host) : PtyHostTerminalTab(host) {
|
||||
}
|
||||
|
||||
val client = SshClients.openClient(host).also { sshClient = it }
|
||||
// keyboard interactive
|
||||
client.userInteraction = TerminalUserInteraction(SwingUtilities.getWindowAncestor(terminalPanel))
|
||||
|
||||
val sessionListener = MySessionListener()
|
||||
val channelListener = MyChannelListener()
|
||||
|
||||
|
||||
@@ -64,9 +64,12 @@ object SshClients {
|
||||
} else if (host.authentication.type == AuthenticationType.PublicKey) {
|
||||
session.keyIdentityProvider = OhKeyPairKeyPairProvider(host.authentication.password)
|
||||
}
|
||||
if (!session.auth().verify(timeout).await(timeout)) {
|
||||
|
||||
val verifyTimeout = Duration.ofSeconds(timeout.seconds * 5)
|
||||
if (!session.auth().verify(verifyTimeout).await(verifyTimeout)) {
|
||||
throw SshException("Authentication failed")
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package app.termora.keyboardinteractive
|
||||
|
||||
import app.termora.DialogWrapper
|
||||
import app.termora.I18n
|
||||
import app.termora.OutlinePasswordField
|
||||
import app.termora.OutlineTextField
|
||||
import com.formdev.flatlaf.FlatClientProperties
|
||||
import com.jgoodies.forms.builder.FormBuilder
|
||||
import com.jgoodies.forms.layout.FormLayout
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import java.awt.Dimension
|
||||
import java.awt.Window
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.text.JTextComponent
|
||||
|
||||
class KeyboardInteractiveDialog(
|
||||
owner: Window,
|
||||
private val prompt: String,
|
||||
echo: Boolean
|
||||
) : DialogWrapper(owner) {
|
||||
|
||||
private val textField = (if (echo) OutlineTextField() else OutlinePasswordField()) as JTextComponent
|
||||
|
||||
|
||||
init {
|
||||
isModal = true
|
||||
isResizable = true
|
||||
controlsVisible = false
|
||||
title = I18n.getString("termora.new-host.title")
|
||||
|
||||
init()
|
||||
pack()
|
||||
size = Dimension(300, size.height)
|
||||
setLocationRelativeTo(null)
|
||||
|
||||
}
|
||||
|
||||
override fun createCenterPanel(): JComponent {
|
||||
val formMargin = "4dlu"
|
||||
val layout = FormLayout(
|
||||
"left:pref, $formMargin, default:grow",
|
||||
"pref, $formMargin, pref, $formMargin"
|
||||
)
|
||||
|
||||
var rows = 1
|
||||
val step = 2
|
||||
return FormBuilder.create().layout(layout).padding("$formMargin, $formMargin, 0, $formMargin")
|
||||
.add(prompt).xy(1, rows)
|
||||
.add(textField).xy(3, rows).apply { rows += step }
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun doCancelAction() {
|
||||
textField.text = StringUtils.EMPTY
|
||||
super.doCancelAction()
|
||||
}
|
||||
|
||||
override fun doOKAction() {
|
||||
if (textField.text.isBlank()) {
|
||||
textField.putClientProperty(FlatClientProperties.OUTLINE, FlatClientProperties.OUTLINE_ERROR)
|
||||
textField.requestFocusInWindow()
|
||||
return
|
||||
}
|
||||
super.doOKAction()
|
||||
}
|
||||
|
||||
fun getText(): String {
|
||||
isModal = true
|
||||
isVisible = true
|
||||
return textField.text
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package app.termora.keyboardinteractive
|
||||
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.apache.sshd.client.auth.keyboard.UserInteraction
|
||||
import org.apache.sshd.client.session.ClientSession
|
||||
import java.awt.Window
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
class TerminalUserInteraction(
|
||||
private val owner: Window
|
||||
) : UserInteraction {
|
||||
|
||||
|
||||
override fun interactive(
|
||||
session: ClientSession?,
|
||||
name: String?,
|
||||
instruction: String?,
|
||||
lang: String?,
|
||||
prompt: Array<out String>,
|
||||
echo: BooleanArray
|
||||
): Array<String> {
|
||||
val passwords = Array(prompt.size) { StringUtils.EMPTY }
|
||||
|
||||
SwingUtilities.invokeAndWait {
|
||||
for (i in prompt.indices) {
|
||||
val dialog = KeyboardInteractiveDialog(
|
||||
owner,
|
||||
prompt[i],
|
||||
true
|
||||
)
|
||||
dialog.title = instruction ?: name ?: StringUtils.EMPTY
|
||||
passwords[i] = dialog.getText()
|
||||
if (passwords[i].isBlank()) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (passwords.last().isBlank()) {
|
||||
throw IllegalStateException("User interaction was cancelled.")
|
||||
}
|
||||
|
||||
if (passwords.all { it.isEmpty() }) {
|
||||
return emptyArray()
|
||||
}
|
||||
|
||||
return passwords
|
||||
}
|
||||
|
||||
override fun getUpdatedPassword(session: ClientSession?, prompt: String?, lang: String?): String {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.termora.transport
|
||||
|
||||
import app.termora.*
|
||||
import app.termora.keyboardinteractive.TerminalUserInteraction
|
||||
import com.formdev.flatlaf.icons.FlatOptionPaneErrorIcon
|
||||
import com.formdev.flatlaf.icons.FlatOptionPaneInformationIcon
|
||||
import com.jgoodies.forms.builder.FormBuilder
|
||||
@@ -113,6 +114,10 @@ class SftpFileSystemPanel(
|
||||
|
||||
try {
|
||||
val client = SshClients.openClient(host).apply { client = this }
|
||||
withContext(Dispatchers.Swing) {
|
||||
client.userInteraction =
|
||||
TerminalUserInteraction(SwingUtilities.getWindowAncestor(this@SftpFileSystemPanel))
|
||||
}
|
||||
val session = SshClients.openSession(host, client).apply { session = this }
|
||||
fileSystem = SftpClientFactory.instance().createSftpFileSystem(session)
|
||||
session.addCloseFutureListener { onClose() }
|
||||
|
||||
Reference in New Issue
Block a user