Skip to content
Snippets Groups Projects
Commit 16774fd2 authored by Bruno Muniz's avatar Bruno Muniz :apple:
Browse files

Refactor sftp service

parent 9f3daf5b
Branches
Tags v4.2.0 v4.3.0
2 merge requests!54Releasing 1.1.4,!42Adding SFTP as a service to backup/restore
...@@ -216,7 +216,7 @@ extension BackupService { ...@@ -216,7 +216,7 @@ extension BackupService {
} }
if sftpService.isAuthorized() { if sftpService.isAuthorized() {
sftpService.fetchMetadata({ [weak settings] in sftpService.fetchMetadata { [weak settings] in
guard let settings = settings else { return } guard let settings = settings else { return }
guard let metadata = try? $0.get()?.backup else { guard let metadata = try? $0.get()?.backup else {
...@@ -229,7 +229,7 @@ extension BackupService { ...@@ -229,7 +229,7 @@ extension BackupService {
date: metadata.date, date: metadata.date,
size: metadata.size size: metadata.size
) )
}) }
} }
if dropboxService.isAuthorized() { if dropboxService.isAuthorized() {
......
import Shout
import Socket
import Keychain
import Foundation
import DependencyInjection
public struct SFTPAuthenticator {
public var authenticate: (String, String, String) throws -> Void
public func callAsFunction(host: String, username: String, password: String) throws {
try authenticate(host, username, password)
}
}
extension SFTPAuthenticator {
static let mock = SFTPAuthenticator { host, username, password in
print("^^^ Requested authentication on sftp service.")
print("^^^ Host: \(host)")
print("^^^ Username: \(username)")
print("^^^ Password: \(password)")
}
static let live = SFTPAuthenticator { host, username, password in
do {
try SSH.connect(
host: host,
port: 22,
username: username,
authMethod: SSHPassword(password)) { ssh in
_ = try ssh.openSftp()
let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
try keychain.store(key: .host, value: host)
try keychain.store(key: .pwd, value: password)
try keychain.store(key: .username, value: username)
}
} catch {
if let error = error as? SSHError {
print(error.kind)
print(error.message)
print(error.description)
} else if let error = error as? Socket.Error {
print(error.errorCode)
print(error.description)
print(error.errorReason)
print(error.localizedDescription)
} else {
print(error.localizedDescription)
}
throw error
}
}
}
...@@ -4,7 +4,9 @@ import Keychain ...@@ -4,7 +4,9 @@ import Keychain
import Foundation import Foundation
import DependencyInjection import DependencyInjection
public struct SFTPServiceBackupDownloader { public typealias SFTPDownloadResult = (Result<Data, Error>) -> Void
public struct SFTPDownloader {
public var download: (String, @escaping SFTPDownloadResult) -> Void public var download: (String, @escaping SFTPDownloadResult) -> Void
public func callAsFunction(path: String, completion: @escaping SFTPDownloadResult) { public func callAsFunction(path: String, completion: @escaping SFTPDownloadResult) {
...@@ -12,13 +14,13 @@ public struct SFTPServiceBackupDownloader { ...@@ -12,13 +14,13 @@ public struct SFTPServiceBackupDownloader {
} }
} }
extension SFTPServiceBackupDownloader { extension SFTPDownloader {
static let mock = SFTPServiceBackupDownloader { path, _ in static let mock = SFTPDownloader { path, _ in
print("^^^ Requested backup download on sftp service.") print("^^^ Requested backup download on sftp service.")
print("^^^ Path: \(path)") print("^^^ Path: \(path)")
} }
static let live = SFTPServiceBackupDownloader { path, completion in static let live = SFTPDownloader { path, completion in
DispatchQueue.global().async { DispatchQueue.global().async {
do { do {
let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
......
import Shout
import Socket
import Models
import Keychain
import Foundation
import DependencyInjection
public typealias SFTPFetchResult = (Result<RestoreSettings?, Error>) -> Void
public struct SFTPFetcher {
public var fetch: (@escaping SFTPFetchResult) -> Void
public func callAsFunction(completion: @escaping SFTPFetchResult) {
fetch(completion)
}
}
extension SFTPFetcher {
static let mock = SFTPFetcher { _ in
print("^^^ Requested backup metadata on sftp service.")
}
static let live = SFTPFetcher { completion in
DispatchQueue.global().async {
do {
let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
let host = try keychain.get(key: .host)
let password = try keychain.get(key: .pwd)
let username = try keychain.get(key: .username)
let ssh = try SSH(host: host!, port: 22)
try ssh.authenticate(username: username!, password: password!)
let sftp = try ssh.openSftp()
if let files = try? sftp.listFiles(in: "backup"),
let backup = files.filter({ file in file.0 == "backup.xxm" }).first {
completion(.success(.init(
backup: .init(
id: "backup/backup.xxm",
date: backup.value.lastModified,
size: Float(backup.value.size)
),
cloudService: .sftp
)))
return
}
completion(.success(nil))
} catch {
if let error = error as? SSHError {
print(error.kind)
print(error.message)
print(error.description)
} else if let error = error as? Socket.Error {
print(error.errorCode)
print(error.description)
print(error.errorReason)
print(error.localizedDescription)
} else {
print(error.localizedDescription)
}
completion(.failure(error))
}
}
}
}
import Shout import Shout
import Socket import Socket
import Models
import Keychain import Keychain
import Foundation import Foundation
import DependencyInjection import DependencyInjection
public struct SFTPServiceBackupUploader { public typealias SFTPUploadResult = (Result<Backup, Error>) -> Void
public struct SFTPUploader {
public var upload: (URL, @escaping SFTPUploadResult) -> Void public var upload: (URL, @escaping SFTPUploadResult) -> Void
public func callAsFunction(url: URL, completion: @escaping SFTPUploadResult) { public func callAsFunction(url: URL, completion: @escaping SFTPUploadResult) {
...@@ -12,15 +15,15 @@ public struct SFTPServiceBackupUploader { ...@@ -12,15 +15,15 @@ public struct SFTPServiceBackupUploader {
} }
} }
extension SFTPServiceBackupUploader { extension SFTPUploader {
static let mock = SFTPServiceBackupUploader( static let mock = SFTPUploader(
upload: { url, _ in upload: { url, _ in
print("^^^ Requested upload on sftp service") print("^^^ Requested upload on sftp service")
print("^^^ URL path: \(url.path)") print("^^^ URL path: \(url.path)")
} }
) )
static let live = SFTPServiceBackupUploader { url, completion in static let live = SFTPUploader { url, completion in
DispatchQueue.global().async { DispatchQueue.global().async {
do { do {
let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
......
import UIKit import UIKit
import Shout
import Socket
import Models
import Combine
import Keychain import Keychain
import Foundation
import Presentation import Presentation
import DependencyInjection import DependencyInjection
public typealias SFTPDownloadResult = (Result<Data, Error>) -> Void
public typealias SFTPUploadResult = (Result<Backup, Error>) -> Void
public typealias SFTPFetchResult = (Result<RestoreSettings?, Error>) -> Void
public typealias SFTPAuthorizationParams = (UIViewController, () -> Void) public typealias SFTPAuthorizationParams = (UIViewController, () -> Void)
public struct SFTPService { public struct SFTPService {
public var isAuthorized: () -> Bool public var isAuthorized: () -> Bool
public var fetchMetadata: (@escaping SFTPFetchResult) -> Void public var fetchMetadata: SFTPFetcher
public var uploadBackup: SFTPServiceBackupUploader public var uploadBackup: SFTPUploader
public var authorizeFlow: (SFTPAuthorizationParams) -> Void public var authorizeFlow: (SFTPAuthorizationParams) -> Void
public var authenticate: (String, String, String) throws -> Void public var authenticate: SFTPAuthenticator
public var downloadBackup: SFTPServiceBackupDownloader public var downloadBackup: SFTPDownloader
} }
public extension SFTPService { public extension SFTPService {
static var mock = SFTPService( static var mock = SFTPService(
isAuthorized: { isAuthorized: { true },
print("^^^ Requested auth status on sftp service") fetchMetadata: .mock,
return true
},
fetchMetadata: { completion in
print("^^^ Requested backup metadata on sftp service.")
completion(.success(nil))
},
uploadBackup: .mock, uploadBackup: .mock,
authorizeFlow: { (_, completion) in authorizeFlow: { (_, completion) in completion() },
print("^^^ Requested authorizing flow on sftp service.") authenticate: .mock,
completion()
},
authenticate: { host, username, password in
print("^^^ Requested authentication on sftp service.")
print("^^^ Host: \(host)")
print("^^^ Username: \(username)")
print("^^^ Password: \(password)")
},
downloadBackup: .mock downloadBackup: .mock
) )
...@@ -57,87 +35,13 @@ public extension SFTPService { ...@@ -57,87 +35,13 @@ public extension SFTPService {
return false return false
}, },
fetchMetadata: { completion in fetchMetadata: .live,
DispatchQueue.global().async {
do {
let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
let host = try keychain.get(key: .host)
let password = try keychain.get(key: .pwd)
let username = try keychain.get(key: .username)
let ssh = try SSH(host: host!, port: 22)
try ssh.authenticate(username: username!, password: password!)
let sftp = try ssh.openSftp()
if let files = try? sftp.listFiles(in: "backup"),
let backup = files.filter({ file in file.0 == "backup.xxm" }).first {
completion(.success(.init(
backup: .init(
id: "backup/backup.xxm",
date: backup.value.lastModified,
size: Float(backup.value.size)
),
cloudService: .sftp
)))
return
}
completion(.success(nil))
} catch {
if let error = error as? SSHError {
print(error.kind)
print(error.message)
print(error.description)
} else if let error = error as? Socket.Error {
print(error.errorCode)
print(error.description)
print(error.errorReason)
print(error.localizedDescription)
} else {
print(error.localizedDescription)
}
completion(.failure(error))
}
}
},
uploadBackup: .live , uploadBackup: .live ,
authorizeFlow: { controller, completion in authorizeFlow: { controller, completion in
var pushPresenter: Presenting = PushPresenter() var pushPresenter: Presenting = PushPresenter()
pushPresenter.present(SFTPController(completion), from: controller) pushPresenter.present(SFTPController(completion), from: controller)
}, },
authenticate: { host, username, password in authenticate: .live,
do {
try SSH.connect(
host: host,
port: 22,
username: username,
authMethod: SSHPassword(password)) { ssh in
_ = try ssh.openSftp()
let keychain = try DependencyInjection.Container.shared.resolve() as KeychainHandling
try keychain.store(key: .host, value: host)
try keychain.store(key: .pwd, value: password)
try keychain.store(key: .username, value: username)
}
} catch {
if let error = error as? SSHError {
print(error.kind)
print(error.message)
print(error.description)
} else if let error = error as? Socket.Error {
print(error.errorCode)
print(error.description)
print(error.errorReason)
print(error.localizedDescription)
} else {
print(error.localizedDescription)
}
throw error
}
},
downloadBackup: .live downloadBackup: .live
) )
} }
...@@ -54,7 +54,12 @@ final class SFTPViewModel { ...@@ -54,7 +54,12 @@ final class SFTPViewModel {
DispatchQueue.global().async { [weak self] in DispatchQueue.global().async { [weak self] in
guard let self = self else { return } guard let self = self else { return }
do { do {
try self.service.authenticate(host, username, password) try self.service.authenticate(
host: host,
username: username,
password: password
)
self.hudSubject.send(.none) self.hudSubject.send(.none)
self.authSubject.send(()) self.authSubject.send(())
} catch { } catch {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment