291 lines
13 KiB
Swift
291 lines
13 KiB
Swift
import Foundation
|
|
import TermindDomain
|
|
|
|
import struct TermindDomain.Host
|
|
|
|
/// Memory-only Custom Identity fields shared by the native Host editors.
|
|
///
|
|
/// Passwords live here only for the lifetime of an editor. This value is never
|
|
/// persisted, synchronized, logged, or given a textual representation.
|
|
@MainActor
|
|
public struct HostCustomIdentityDraft {
|
|
public var sshUsername = ""
|
|
public var sshPassword = ""
|
|
public var sshKeyID: SSHKeyRef.ID?
|
|
public var telnetUsername = ""
|
|
public var telnetPassword = ""
|
|
public private(set) var usesCustomTelnetSelection = false
|
|
public var usesCustomProxy = false
|
|
public var proxyPassword = ""
|
|
public var usesCustomTelnetProxy = false
|
|
public var telnetProxyPassword = ""
|
|
|
|
private var sshIdentityID: Identity.ID?
|
|
private var telnetIdentityID: Identity.ID?
|
|
private var proxyIdentityID: Identity.ID?
|
|
private var telnetProxyIdentityID: Identity.ID?
|
|
private var sshHasConfiguredPassword = false
|
|
private var telnetHasConfiguredPassword = false
|
|
private var proxyHasConfiguredPassword = false
|
|
private var telnetProxyHasConfiguredPassword = false
|
|
private var sshPasswordAvailability: SecretAvailability = .notConfigured
|
|
private var telnetPasswordAvailability: SecretAvailability = .notConfigured
|
|
private var proxyPasswordAvailability: SecretAvailability = .notConfigured
|
|
private var telnetProxyPasswordAvailability: SecretAvailability = .notConfigured
|
|
|
|
public init() {}
|
|
|
|
public init(host: Host, library: HostLibraryModel) {
|
|
let ssh = library.customIdentity(for: host.id, purpose: .ssh)
|
|
sshIdentityID = ssh?.id
|
|
sshUsername = ssh?.username ?? ""
|
|
sshKeyID = ssh?.keyID
|
|
sshHasConfiguredPassword = ssh?.usesPassword == true
|
|
if let ssh {
|
|
sshPasswordAvailability = library.passwordAvailability(for: ssh)
|
|
if sshPasswordAvailability == .available {
|
|
sshPassword = (try? library.editablePassword(for: ssh)) ?? ""
|
|
}
|
|
}
|
|
|
|
let telnet = library.customIdentity(for: host.id, purpose: .telnet)
|
|
telnetIdentityID = telnet?.id
|
|
telnetUsername = telnet?.username ?? ""
|
|
telnetHasConfiguredPassword = telnet?.usesPassword == true
|
|
if let telnet {
|
|
telnetPasswordAvailability = library.passwordAvailability(for: telnet)
|
|
if telnetPasswordAvailability == .available {
|
|
telnetPassword = (try? library.editablePassword(for: telnet)) ?? ""
|
|
}
|
|
}
|
|
usesCustomTelnetSelection = host.telnetIdentityID == telnet?.id && telnet != nil
|
|
|
|
let proxy = library.customIdentity(for: host.id, purpose: .proxy)
|
|
proxyIdentityID = proxy?.id
|
|
proxyHasConfiguredPassword = proxy?.usesPassword == true
|
|
if let proxy {
|
|
proxyPasswordAvailability = library.passwordAvailability(for: proxy)
|
|
if host.proxy?.identityID == proxy.id {
|
|
usesCustomProxy = true
|
|
if proxyPasswordAvailability == .available {
|
|
proxyPassword = (try? library.editablePassword(for: proxy)) ?? ""
|
|
}
|
|
}
|
|
}
|
|
|
|
let telnetProxy = library.customIdentity(for: host.id, purpose: .telnetProxy)
|
|
telnetProxyIdentityID = telnetProxy?.id
|
|
telnetProxyHasConfiguredPassword = telnetProxy?.usesPassword == true
|
|
if let telnetProxy {
|
|
telnetProxyPasswordAvailability = library.passwordAvailability(for: telnetProxy)
|
|
if host.telnetProxy?.identityID == telnetProxy.id {
|
|
usesCustomTelnetProxy = true
|
|
if telnetProxyPasswordAvailability == .available {
|
|
telnetProxyPassword = (try? library.editablePassword(for: telnetProxy)) ?? ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public var sshPasswordIsEditable: Bool {
|
|
!sshHasConfiguredPassword || sshPasswordAvailability == .available
|
|
}
|
|
|
|
public var proxyPasswordIsEditable: Bool {
|
|
!proxyHasConfiguredPassword || proxyPasswordAvailability == .available
|
|
}
|
|
|
|
public var telnetPasswordIsEditable: Bool {
|
|
!telnetHasConfiguredPassword || telnetPasswordAvailability == .available
|
|
}
|
|
|
|
public var telnetProxyPasswordIsEditable: Bool {
|
|
!telnetProxyHasConfiguredPassword || telnetProxyPasswordAvailability == .available
|
|
}
|
|
|
|
public func usesCustomSSH(for host: Host) -> Bool {
|
|
guard let identityID = host.identityID else { return true }
|
|
return identityID == sshIdentityID
|
|
}
|
|
|
|
public func selectCustomSSH(on host: inout Host) {
|
|
host.identityID = sshIdentityID
|
|
}
|
|
|
|
public func selectSSHIdentity(_ identity: Identity, on host: inout Host) {
|
|
host.identityID = identity.id
|
|
}
|
|
|
|
public func usesCustomTelnet(for host: Host) -> Bool {
|
|
usesCustomTelnetSelection && (host.telnetIdentityID == nil || host.telnetIdentityID == telnetIdentityID)
|
|
}
|
|
|
|
public mutating func selectCustomTelnet(on host: inout Host) {
|
|
usesCustomTelnetSelection = true
|
|
host.telnetIdentityID = telnetIdentityID
|
|
}
|
|
|
|
public mutating func clearTelnetIdentity(on host: inout Host) {
|
|
usesCustomTelnetSelection = false
|
|
host.telnetIdentityID = nil
|
|
}
|
|
|
|
public mutating func selectTelnetIdentity(_ identity: Identity, on host: inout Host) {
|
|
usesCustomTelnetSelection = false
|
|
host.telnetIdentityID = identity.id
|
|
}
|
|
|
|
public mutating func clearProxyIdentity(on host: inout Host) {
|
|
usesCustomProxy = false
|
|
host.proxy?.identityID = nil
|
|
host.proxy?.username = ""
|
|
}
|
|
|
|
public mutating func selectCustomProxy(on host: inout Host, using library: HostLibraryModel) {
|
|
usesCustomProxy = true
|
|
let identity = library.customIdentity(for: host.id, purpose: .proxy)
|
|
proxyIdentityID = identity?.id
|
|
proxyHasConfiguredPassword = identity?.usesPassword == true
|
|
proxyPasswordAvailability = identity.map(library.passwordAvailability) ?? .notConfigured
|
|
host.proxy?.identityID = identity?.id
|
|
host.proxy?.username = identity?.username ?? ""
|
|
proxyPassword = ""
|
|
if let identity, proxyPasswordAvailability == .available {
|
|
proxyPassword = (try? library.editablePassword(for: identity)) ?? ""
|
|
}
|
|
}
|
|
|
|
public mutating func selectProxyIdentity(_ identity: Identity, on host: inout Host) {
|
|
usesCustomProxy = false
|
|
host.proxy?.identityID = identity.id
|
|
host.proxy?.username = identity.username
|
|
}
|
|
|
|
public mutating func clearTelnetProxyIdentity(on host: inout Host) {
|
|
usesCustomTelnetProxy = false
|
|
host.telnetProxy?.identityID = nil
|
|
host.telnetProxy?.username = ""
|
|
}
|
|
|
|
public mutating func selectCustomTelnetProxy(on host: inout Host, using library: HostLibraryModel) {
|
|
usesCustomTelnetProxy = true
|
|
let identity = library.customIdentity(for: host.id, purpose: .telnetProxy)
|
|
telnetProxyIdentityID = identity?.id
|
|
telnetProxyHasConfiguredPassword = identity?.usesPassword == true
|
|
telnetProxyPasswordAvailability = identity.map(library.passwordAvailability) ?? .notConfigured
|
|
host.telnetProxy?.identityID = identity?.id
|
|
host.telnetProxy?.username = identity?.username ?? ""
|
|
telnetProxyPassword = ""
|
|
if let identity, telnetProxyPasswordAvailability == .available {
|
|
telnetProxyPassword = (try? library.editablePassword(for: identity)) ?? ""
|
|
}
|
|
}
|
|
|
|
public mutating func selectTelnetProxyIdentity(_ identity: Identity, on host: inout Host) {
|
|
usesCustomTelnetProxy = false
|
|
host.telnetProxy?.identityID = identity.id
|
|
host.telnetProxy?.username = identity.username
|
|
}
|
|
|
|
public func isSSHValid(for host: Host) -> Bool {
|
|
guard host.sshEnabled else { return true }
|
|
return !usesCustomSSH(for: host)
|
|
|| (!sshUsername.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
|| (sshPassword.isEmpty && sshKeyID == nil))
|
|
}
|
|
|
|
public func isProxyValid(for host: Host) -> Bool {
|
|
guard host.sshEnabled else { return true }
|
|
guard let proxy = host.proxy else { return true }
|
|
guard usesCustomProxy else { return proxy.isValid }
|
|
|
|
let username = proxy.username.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let hasPassword = !proxyPassword.isEmpty || proxyHasConfiguredPassword
|
|
let hasAuthentication = !username.isEmpty || hasPassword
|
|
return !proxy.hostname.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
&& (1...65_535).contains(proxy.port)
|
|
&& (!hasAuthentication || (!username.isEmpty && hasPassword))
|
|
}
|
|
|
|
public func isTelnetValid(for host: Host) -> Bool {
|
|
guard host.telnetEnabled else { return true }
|
|
guard (1...65_535).contains(host.telnetPort) else { return false }
|
|
guard usesCustomTelnet(for: host) else { return true }
|
|
let username = telnetUsername.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
return !username.isEmpty && (!telnetPassword.isEmpty || telnetHasConfiguredPassword)
|
|
}
|
|
|
|
public func isTelnetProxyValid(for host: Host) -> Bool {
|
|
guard host.telnetEnabled, let proxy = host.telnetProxy else { return true }
|
|
guard usesCustomTelnetProxy else { return proxy.isValid }
|
|
let username = proxy.username.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let hasPassword = !telnetProxyPassword.isEmpty || telnetProxyHasConfiguredPassword
|
|
let hasAuthentication = !username.isEmpty || hasPassword
|
|
return !proxy.hostname.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
&& (1...65_535).contains(proxy.port)
|
|
&& (!hasAuthentication || (!username.isEmpty && hasPassword))
|
|
}
|
|
|
|
public mutating func save(into host: inout Host, using library: HostLibraryModel) throws {
|
|
if usesCustomSSH(for: host) {
|
|
let identity = try library.saveCustomIdentity(
|
|
ownerHostID: host.id, purpose: .ssh, displayName: host.name,
|
|
username: sshUsername, password: sshPassword, keyID: sshKeyID)
|
|
host.identityID = identity?.id
|
|
sshIdentityID = identity?.id
|
|
sshHasConfiguredPassword = identity?.usesPassword == true
|
|
sshPasswordAvailability = identity.map(library.passwordAvailability) ?? .notConfigured
|
|
}
|
|
|
|
if usesCustomTelnet(for: host), host.telnetEnabled {
|
|
let identity = try library.saveCustomIdentity(
|
|
ownerHostID: host.id, purpose: .telnet, displayName: "\(host.name) Telnet",
|
|
username: telnetUsername, password: telnetPassword, keyID: nil)
|
|
host.telnetIdentityID = identity?.id
|
|
telnetIdentityID = identity?.id
|
|
telnetHasConfiguredPassword = identity?.usesPassword == true
|
|
telnetPasswordAvailability = identity.map(library.passwordAvailability) ?? .notConfigured
|
|
}
|
|
|
|
if usesCustomProxy, let proxy = host.proxy {
|
|
let identity = try library.saveCustomIdentity(
|
|
ownerHostID: host.id, purpose: .proxy, displayName: "\(host.name) Proxy",
|
|
username: proxy.username, password: proxyPassword, keyID: nil)
|
|
host.proxy?.identityID = identity?.id
|
|
proxyIdentityID = identity?.id
|
|
proxyHasConfiguredPassword = identity?.usesPassword == true
|
|
proxyPasswordAvailability = identity.map(library.passwordAvailability) ?? .notConfigured
|
|
} else if host.proxy == nil {
|
|
_ = try library.saveCustomIdentity(
|
|
ownerHostID: host.id, purpose: .proxy, displayName: "",
|
|
username: "", password: "", keyID: nil)
|
|
proxyIdentityID = nil
|
|
proxyHasConfiguredPassword = false
|
|
proxyPasswordAvailability = .notConfigured
|
|
}
|
|
|
|
if usesCustomTelnetProxy, let proxy = host.telnetProxy {
|
|
let identity = try library.saveCustomIdentity(
|
|
ownerHostID: host.id, purpose: .telnetProxy,
|
|
displayName: "\(host.name) Telnet Proxy",
|
|
username: proxy.username, password: telnetProxyPassword, keyID: nil)
|
|
host.telnetProxy?.identityID = identity?.id
|
|
telnetProxyIdentityID = identity?.id
|
|
telnetProxyHasConfiguredPassword = identity?.usesPassword == true
|
|
telnetProxyPasswordAvailability = identity.map(library.passwordAvailability) ?? .notConfigured
|
|
} else if host.telnetProxy == nil {
|
|
_ = try library.saveCustomIdentity(
|
|
ownerHostID: host.id, purpose: .telnetProxy, displayName: "",
|
|
username: "", password: "", keyID: nil)
|
|
telnetProxyIdentityID = nil
|
|
telnetProxyHasConfiguredPassword = false
|
|
telnetProxyPasswordAvailability = .notConfigured
|
|
}
|
|
|
|
guard library.saveHost(host) else {
|
|
throw TermindSSHError.library(
|
|
library.storageError ?? String(localized: "The host could not be saved."))
|
|
}
|
|
}
|
|
}
|