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) {
|
if (log.isErrorEnabled) {
|
||||||
log.error(e.message, e)
|
log.error(e.message, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 失败关闭
|
||||||
|
stop()
|
||||||
|
|
||||||
withContext(Dispatchers.Swing) {
|
withContext(Dispatchers.Swing) {
|
||||||
terminal.write("\r\n${ControlCharacters.ESC}[31m")
|
terminal.write("\r\n${ControlCharacters.ESC}[31m")
|
||||||
terminal.write(ExceptionUtils.getRootCauseMessage(e))
|
terminal.write(ExceptionUtils.getRootCauseMessage(e))
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package app.termora
|
package app.termora
|
||||||
|
|
||||||
import app.termora.addons.zmodem.ZModemPtyConnectorAdaptor
|
import app.termora.addons.zmodem.ZModemPtyConnectorAdaptor
|
||||||
|
import app.termora.keyboardinteractive.TerminalUserInteraction
|
||||||
import app.termora.terminal.ControlCharacters
|
import app.termora.terminal.ControlCharacters
|
||||||
import app.termora.terminal.DataKey
|
import app.termora.terminal.DataKey
|
||||||
import app.termora.terminal.PtyConnector
|
import app.termora.terminal.PtyConnector
|
||||||
@@ -24,6 +25,7 @@ import org.apache.sshd.common.util.net.SshdSocketAddress
|
|||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
|
import javax.swing.SwingUtilities
|
||||||
|
|
||||||
|
|
||||||
class SSHTerminalTab(host: Host) : PtyHostTerminalTab(host) {
|
class SSHTerminalTab(host: Host) : PtyHostTerminalTab(host) {
|
||||||
@@ -76,6 +78,9 @@ class SSHTerminalTab(host: Host) : PtyHostTerminalTab(host) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val client = SshClients.openClient(host).also { sshClient = it }
|
val client = SshClients.openClient(host).also { sshClient = it }
|
||||||
|
// keyboard interactive
|
||||||
|
client.userInteraction = TerminalUserInteraction(SwingUtilities.getWindowAncestor(terminalPanel))
|
||||||
|
|
||||||
val sessionListener = MySessionListener()
|
val sessionListener = MySessionListener()
|
||||||
val channelListener = MyChannelListener()
|
val channelListener = MyChannelListener()
|
||||||
|
|
||||||
|
|||||||
@@ -64,9 +64,12 @@ object SshClients {
|
|||||||
} else if (host.authentication.type == AuthenticationType.PublicKey) {
|
} else if (host.authentication.type == AuthenticationType.PublicKey) {
|
||||||
session.keyIdentityProvider = OhKeyPairKeyPairProvider(host.authentication.password)
|
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")
|
throw SshException("Authentication failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return session
|
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
|
package app.termora.transport
|
||||||
|
|
||||||
import app.termora.*
|
import app.termora.*
|
||||||
|
import app.termora.keyboardinteractive.TerminalUserInteraction
|
||||||
import com.formdev.flatlaf.icons.FlatOptionPaneErrorIcon
|
import com.formdev.flatlaf.icons.FlatOptionPaneErrorIcon
|
||||||
import com.formdev.flatlaf.icons.FlatOptionPaneInformationIcon
|
import com.formdev.flatlaf.icons.FlatOptionPaneInformationIcon
|
||||||
import com.jgoodies.forms.builder.FormBuilder
|
import com.jgoodies.forms.builder.FormBuilder
|
||||||
@@ -113,6 +114,10 @@ class SftpFileSystemPanel(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
val client = SshClients.openClient(host).apply { client = this }
|
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 }
|
val session = SshClients.openSession(host, client).apply { session = this }
|
||||||
fileSystem = SftpClientFactory.instance().createSftpFileSystem(session)
|
fileSystem = SftpClientFactory.instance().createSftpFileSystem(session)
|
||||||
session.addCloseFutureListener { onClose() }
|
session.addCloseFutureListener { onClose() }
|
||||||
|
|||||||
Reference in New Issue
Block a user