From 5eddd1ce416e4050374f0e16e877ea081a4ac7dd Mon Sep 17 00:00:00 2001 From: Nikolay Edigaryev Date: Tue, 15 Aug 2023 15:16:33 +0400 Subject: [PATCH] Introduce "tart logout" command (#583) * Introduce "tart logout" * tart login: introduce --no-validate --- Sources/tart/Commands/Login.swift | 17 +++++++++++------ Sources/tart/Commands/Logout.swift | 14 ++++++++++++++ .../KeychainCredentialsProvider.swift | 18 ++++++++++++++++++ Sources/tart/Root.swift | 1 + 4 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 Sources/tart/Commands/Logout.swift diff --git a/Sources/tart/Commands/Login.swift b/Sources/tart/Commands/Login.swift index 748968a..fb6d280 100644 --- a/Sources/tart/Commands/Login.swift +++ b/Sources/tart/Commands/Login.swift @@ -17,6 +17,9 @@ struct Login: AsyncParsableCommand { @Flag(help: "connect to the OCI registry via insecure HTTP protocol") var insecure: Bool = false + @Flag(help: "skip validation of the registry's credentials before logging-in") + var noValidate: Bool = false + func validate() throws { let usernameProvided = username != nil let passwordProvided = passwordStdin @@ -45,12 +48,14 @@ struct Login: AsyncParsableCommand { host: (user, password) ]) - do { - let registry = try Registry(host: host, namespace: "", insecure: insecure, - credentialsProviders: [credentialsProvider]) - try await registry.ping() - } catch { - throw RuntimeError.InvalidCredentials("invalid credentials: \(error)") + if !noValidate { + do { + let registry = try Registry(host: host, namespace: "", insecure: insecure, + credentialsProviders: [credentialsProvider]) + try await registry.ping() + } catch { + throw RuntimeError.InvalidCredentials("invalid credentials: \(error)") + } } try KeychainCredentialsProvider().store(host: host, user: user, password: password) diff --git a/Sources/tart/Commands/Logout.swift b/Sources/tart/Commands/Logout.swift new file mode 100644 index 0000000..03a8202 --- /dev/null +++ b/Sources/tart/Commands/Logout.swift @@ -0,0 +1,14 @@ +import ArgumentParser +import Dispatch +import SwiftUI + +struct Logout: AsyncParsableCommand { + static var configuration = CommandConfiguration(abstract: "Logout from a registry") + + @Argument(help: "host") + var host: String + + func run() async throws { + try KeychainCredentialsProvider().remove(host: host) + } +} diff --git a/Sources/tart/Credentials/KeychainCredentialsProvider.swift b/Sources/tart/Credentials/KeychainCredentialsProvider.swift index fc76634..6705c6e 100644 --- a/Sources/tart/Credentials/KeychainCredentialsProvider.swift +++ b/Sources/tart/Credentials/KeychainCredentialsProvider.swift @@ -61,6 +61,24 @@ class KeychainCredentialsProvider: CredentialsProvider { throw CredentialsProviderError.Failed(message: "Keychain failed to find item: \(status.explanation())") } } + + func remove(host: String) throws { + let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword, + kSecAttrServer as String: host, + kSecAttrLabel as String: "Tart Credentials", + ] + + let status = SecItemDelete(query as CFDictionary) + + switch status { + case errSecSuccess: + return + case errSecItemNotFound: + return + default: + throw CredentialsProviderError.Failed(message: "Failed to remove Keychain item(s): \(status.explanation())") + } + } } extension OSStatus { diff --git a/Sources/tart/Root.swift b/Sources/tart/Root.swift index 1eeacfb..e934596 100644 --- a/Sources/tart/Root.swift +++ b/Sources/tart/Root.swift @@ -16,6 +16,7 @@ struct Root: AsyncParsableCommand { Get.self, List.self, Login.self, + Logout.self, IP.self, Pull.self, Push.self,