Compare commits

..
5 Commits
Author SHA1 Message Date
Nikolay Edigaryev b83bce4544 tart login: read whole stdin contents instead of just a line (#170) 2022-08-02 16:52:35 +03:00
Nikolay Edigaryev 7d16516b6a OCI: gcr.io fixes (#169)
* Make sure we don't craft an URLComponents with an empty query items

* Prevent auth() infinite loop

* OCI: ignore invalid RFC 3339 formatted dates
2022-08-01 19:24:11 -04:00
Nikolay Edigaryev 56ddd8df75 OCI's HTTP client: prevent the usage of NIO Transport Services (#168) 2022-08-01 18:17:50 -04:00
Fedor Korotkov b1534a05d5 Configure audio devices to pass through the signal from/to the host (#163)
Fixes #161
2022-08-01 17:54:55 +03:00
Nikolay Edigaryev f54e7c2cab tart login: only store one credential per registry (#162) 2022-08-01 09:55:37 +03:00
4 changed files with 40 additions and 26 deletions
+3 -1
View File
@@ -30,7 +30,9 @@ struct Login: AsyncParsableCommand {
if let username = username {
user = username
password = readLine()!
let passwordData = FileHandle.standardInput.readDataToEndOfFile()
password = String(decoding: passwordData, as: UTF8.self)
} else {
(user, password) = try StdinCredentials.retrieve()
}
@@ -35,27 +35,30 @@ class KeychainCredentialsProvider: CredentialsProvider {
func store(host: String, user: String, password: String) throws {
let passwordData = password.data(using: .utf8)
let attributes: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: user,
kSecAttrProtocol as String: kSecAttrProtocolHTTPS,
kSecAttrServer as String: host,
kSecValueData as String: passwordData,
kSecAttrLabel as String: "Tart Credentials",
let key: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrProtocol as String: kSecAttrProtocolHTTPS,
kSecAttrServer as String: host,
kSecAttrLabel as String: "Tart Credentials",
]
let value: [String: Any] = [kSecAttrAccount as String: user,
kSecValueData as String: passwordData,
]
let status = SecItemAdd(attributes as CFDictionary, nil)
let status = SecItemCopyMatching(key as CFDictionary, nil)
switch status {
case errSecItemNotFound:
let status = SecItemAdd(key.merging(value) { (current, _) in current } as CFDictionary, nil)
if status != errSecSuccess {
throw CredentialsProviderError.Failed(message: "Keychain failed to add item: \(status.explanation())")
}
case errSecSuccess:
return
case errSecDuplicateItem:
let status = SecItemUpdate(attributes as CFDictionary,
[kSecValueData as String : passwordData] as CFDictionary)
let status = SecItemUpdate(key as CFDictionary, value as CFDictionary)
if status != errSecSuccess {
throw CredentialsProviderError.Failed(message: "Keychain failed to update item: \(status.explanation())")
}
default:
throw CredentialsProviderError.Failed(message: "Keychain failed to add item: \(status.explanation())")
throw CredentialsProviderError.Failed(message: "Keychain failed to find item: \(status.explanation())")
}
}
}
+17 -12
View File
@@ -2,6 +2,7 @@ import Foundation
import NIOCore
import NIOHTTP1
import AsyncHTTPClient
import NIOPosix
enum RegistryError: Error {
case UnexpectedHTTPStatusCode(when: String, code: UInt, details: String = "")
@@ -38,15 +39,16 @@ struct TokenResponse: Decodable, Authentication {
decoder.keyDecodingStrategy = .convertFromSnakeCase
// RFC3339 date formatter from Apple's documentation[1]
//
// [1]: https://developer.apple.com/documentation/foundation/dateformatter
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [.withInternetDateTime]
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
decoder.dateDecodingStrategy = .formatted(dateFormatter)
decoder.dateDecodingStrategy = .custom { decoder in
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
return dateFormatter.date(from: dateString) ?? Date()
}
return try decoder.decode(TokenResponse.self, from: fromData)
}
@@ -75,8 +77,10 @@ struct TokenResponse: Decodable, Authentication {
}
class Registry {
private let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
private let httpClient = HTTPClient(
eventLoopGroupProvider: .shared(MultiThreadedEventLoopGroup(numberOfThreads: 1))
)
deinit {
try! httpClient.syncShutdown()
}
@@ -226,11 +230,12 @@ class Registry {
_ urlComponents: URLComponents,
headers: Dictionary<String, String> = Dictionary(),
parameters: Dictionary<String, String> = Dictionary(),
body: Data? = nil
body: Data? = nil,
doAuth: Bool = true
) async throws -> HTTPClientResponse {
var urlComponents = urlComponents
if urlComponents.queryItems == nil {
if urlComponents.queryItems == nil && !parameters.isEmpty {
urlComponents.queryItems = []
}
urlComponents.queryItems?.append(contentsOf: parameters.map { key, value -> URLQueryItem in
@@ -312,7 +317,7 @@ class Registry {
headers["Authorization"] = "Basic \(encodedCredentials!)"
}
let response = try await rawRequest(.GET, authenticateURL, headers: headers)
let response = try await rawRequest(.GET, authenticateURL, headers: headers, doAuth: false)
if response.status != .ok {
let body = try await response.body.readTextResponse() ?? ""
throw RegistryError.AuthFailed(why: "received unexpected HTTP status code \(response.status.code) "
+5 -1
View File
@@ -237,7 +237,11 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
// Audio
let soundDeviceConfiguration = VZVirtioSoundDeviceConfiguration()
soundDeviceConfiguration.streams = [VZVirtioSoundDeviceInputStreamConfiguration(), VZVirtioSoundDeviceOutputStreamConfiguration()]
let inputAudioStreamConfiguration = VZVirtioSoundDeviceInputStreamConfiguration()
inputAudioStreamConfiguration.source = VZHostAudioInputStreamSource()
let outputAudioStreamConfiguration = VZVirtioSoundDeviceOutputStreamConfiguration()
outputAudioStreamConfiguration.sink = VZHostAudioOutputStreamSink()
soundDeviceConfiguration.streams = [inputAudioStreamConfiguration, outputAudioStreamConfiguration]
configuration.audioDevices = [soundDeviceConfiguration]
// Keyboard and mouse