From 53c3ba83b9881163b86f0481f3e6dd9688ebc5ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Heidekr=C3=BCger?= Date: Thu, 29 Feb 2024 19:54:33 +0100 Subject: [PATCH 1/3] Add SettingsStore --- .../Secretive/Helpers/SettingsHelper.swift | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 Sources/Secretive/Helpers/SettingsHelper.swift diff --git a/Sources/Secretive/Helpers/SettingsHelper.swift b/Sources/Secretive/Helpers/SettingsHelper.swift new file mode 100644 index 0000000..8b6ce64 --- /dev/null +++ b/Sources/Secretive/Helpers/SettingsHelper.swift @@ -0,0 +1,60 @@ +// +// SettingsHelper.swift +// Secretive +// +// Created by Paul Heidekrüger on 27.02.24. +// Copyright © 2024 Max Goedjen. All rights reserved. +// + +import Foundation + +class SettingsStore { + static let service = "com.maxgoedjen.Secretive" +} + +extension SettingsStore { + static func set(key: String, value: String) -> Bool { + let valueData = value.data(using: String.Encoding.utf8)! + + if let keyVal = get(key: key) { + if keyVal == value { + return true + } + + let updateQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword, + kSecAttrServer as String: service] + let attributes: [String: Any] = [kSecAttrAccount as String: key, + kSecValueData as String: valueData] + // FIXME: Make this non-blocking as described here: https://developer.apple.com/documentation/security/1393617-secitemupdate + let status = SecItemUpdate(updateQuery as CFDictionary, attributes as CFDictionary) + guard status == errSecSuccess else { + print("Couldn't update item in keychain. " + status.description) + return false + } + } else { + let addquery: [String: Any] = [kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecAttrServer as String: service, + kSecValueData as String: valueData] + // FIXME: Make this non-blocking as described here: https://developer.apple.com/documentation/security/1401659-secitemadd + let status = SecItemAdd(addquery as CFDictionary, nil) + guard status == errSecSuccess else { + print("Couldn't add item to keychain. " + status.description) + return false + } + } + return true + } + + static func get(key: String) -> String? { + let getquery: [String: Any] = [kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecAttrServer as String: service, + kSecMatchLimit as String: kSecMatchLimitOne, + kSecReturnData as String: true] + var item: CFTypeRef? + let status = SecItemCopyMatching(getquery as CFDictionary, &item) + + return status == errSecSuccess ? String(decoding: item as! Data, as: UTF8.self) : nil + } +} From a1424d3663ea8bab66a84154d433a58eb362f09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Heidekr=C3=BCger?= Date: Thu, 29 Feb 2024 19:55:20 +0100 Subject: [PATCH 2/3] Use keychain instead of UserDefaults in JustUpdatedChecker.swift --- Sources/Secretive/Controllers/JustUpdatedChecker.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Sources/Secretive/Controllers/JustUpdatedChecker.swift b/Sources/Secretive/Controllers/JustUpdatedChecker.swift index 4c86f68..ee0dceb 100644 --- a/Sources/Secretive/Controllers/JustUpdatedChecker.swift +++ b/Sources/Secretive/Controllers/JustUpdatedChecker.swift @@ -15,14 +15,11 @@ class JustUpdatedChecker: ObservableObject, JustUpdatedCheckerProtocol { } func check() { - let lastBuild = UserDefaults.standard.object(forKey: Constants.previousVersionUserDefaultsKey) as? String ?? "None" + let lastBuild = SettingsStore.get(key: Constants.previousVersionUserDefaultsKey) ?? "None" let currentBuild = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String - UserDefaults.standard.set(currentBuild, forKey: Constants.previousVersionUserDefaultsKey) + SettingsStore.set(key: Constants.previousVersionUserDefaultsKey, value: currentBuild) justUpdated = lastBuild != currentBuild } - - - } extension JustUpdatedChecker { From fabd1a0cff8f71158602a2d7d937a73e4076beda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Heidekr=C3=BCger?= Date: Thu, 29 Feb 2024 19:56:23 +0100 Subject: [PATCH 3/3] Add ability disable the SSH comment via settings --- Sources/Secretive.xcodeproj/project.pbxproj | 9 +++ Sources/Secretive/App.swift | 3 + Sources/Secretive/Localizable.xcstrings | 15 +++++ .../Secretive/Views/SecretDetailView.swift | 9 ++- Sources/Secretive/Views/SettingsView.swift | 57 +++++++++++++++++++ 5 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 Sources/Secretive/Views/SettingsView.swift diff --git a/Sources/Secretive.xcodeproj/project.pbxproj b/Sources/Secretive.xcodeproj/project.pbxproj index 6a67328..f944608 100644 --- a/Sources/Secretive.xcodeproj/project.pbxproj +++ b/Sources/Secretive.xcodeproj/project.pbxproj @@ -53,6 +53,8 @@ 50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; }; 50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; }; 50E9CF422B51D596004AB36D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 500B93C22B478D8400E157DE /* Localizable.xcstrings */; }; + DA140C502B8DF70500948F81 /* SettingsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA140C4F2B8DF70500948F81 /* SettingsHelper.swift */; }; + DA22A3402B712A57004D45DD /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA22A33F2B712A57004D45DD /* SettingsView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -152,6 +154,9 @@ 50B8550C24138C4F009958AC /* DeleteSecretView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteSecretView.swift; sourceTree = ""; }; 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStoreView.swift; sourceTree = ""; }; 50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = ""; }; + DA140C4F2B8DF70500948F81 /* SettingsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHelper.swift; sourceTree = ""; }; + DA22A33C2B6EB835004D45DD /* SecretiveRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SecretiveRelease.entitlements; sourceTree = ""; }; + DA22A33F2B712A57004D45DD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -192,6 +197,7 @@ isa = PBXGroup; children = ( 50033AC227813F1700253856 /* BundleIDs.swift */, + DA140C4F2B8DF70500948F81 /* SettingsHelper.swift */, ); path = Helpers; sourceTree = ""; @@ -282,6 +288,7 @@ 50153E1F250AFCB200525160 /* UpdateView.swift */, 5066A6C12516F303004B5A36 /* SetupView.swift */, 5066A6C72516FE6E004B5A36 /* CopyableView.swift */, + DA22A33F2B712A57004D45DD /* SettingsView.swift */, ); path = Views; sourceTree = ""; @@ -495,6 +502,7 @@ 5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */, 50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */, 5066A6F7251829B1004B5A36 /* ShellConfigurationController.swift in Sources */, + DA22A3402B712A57004D45DD /* SettingsView.swift in Sources */, 50033AC327813F1700253856 /* BundleIDs.swift in Sources */, 508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */, 50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */, @@ -506,6 +514,7 @@ 50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */, 50617D8323FCE48E0099B055 /* App.swift in Sources */, 506772C92425BB8500034DED /* NoStoresView.swift in Sources */, + DA140C502B8DF70500948F81 /* SettingsHelper.swift in Sources */, 50153E22250DECA300525160 /* SecretListItemView.swift in Sources */, 508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */, 508A58AA241E06B40069DC07 /* PreviewUpdater.swift in Sources */, diff --git a/Sources/Secretive/App.swift b/Sources/Secretive/App.swift index 81555ab..d40521e 100644 --- a/Sources/Secretive/App.swift +++ b/Sources/Secretive/App.swift @@ -62,6 +62,9 @@ struct Secretive: App { } SidebarCommands() } + Settings { + SettingsView() + } } } diff --git a/Sources/Secretive/Localizable.xcstrings b/Sources/Secretive/Localizable.xcstrings index 2f06bcd..9742fc4 100644 --- a/Sources/Secretive/Localizable.xcstrings +++ b/Sources/Secretive/Localizable.xcstrings @@ -1808,6 +1808,9 @@ } } } + }, + "General" : { + }, "no_secure_storage_description" : { "localizations" : { @@ -1946,6 +1949,9 @@ } } } + }, + "None" : { + }, "persist_authentication_accept_button" : { "comment" : "When the user authorizes an action using a secret that requires unlock, they're shown a notification offering to leave the secret unlocked for a set period of time. This is the title for the notification.", @@ -2443,6 +2449,9 @@ } } } + }, + "Settings" : { + }, "setup_agent_activity_monitor_description" : { "localizations" : { @@ -3168,6 +3177,12 @@ } } } + }, + "SSH Public Key Comment" : { + + }, + "SSH public keys can be extended with an arbitrary comment string without changing the meaning of the key." : { + }, "unnamed_secret" : { "extractionState" : "manual", diff --git a/Sources/Secretive/Views/SecretDetailView.swift b/Sources/Secretive/Views/SecretDetailView.swift index aefe49d..319d668 100644 --- a/Sources/Secretive/Views/SecretDetailView.swift +++ b/Sources/Secretive/Views/SecretDetailView.swift @@ -4,6 +4,7 @@ import SecretKit struct SecretDetailView: View { @State var secret: SecretType + @AppStorage("com.maxgoedjen.Secretive.commentStyle") var style: CommentStyle = .keyAndHost private let keyWriter = OpenSSHKeyWriter() private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: NSHomeDirectory().replacingOccurrences(of: Bundle.main.hostBundleID, with: Bundle.main.agentBundleID)) @@ -42,9 +43,13 @@ struct SecretDetailView: View { } var keyString: String { - keyWriter.openSSHString(secret: secret, comment: "\(dashedKeyName)@\(dashedHostName)") + switch style { + case CommentStyle.none: + keyWriter.openSSHString(secret: secret, comment: "") + default: + keyWriter.openSSHString(secret: secret, comment: "\(dashedKeyName)@\(dashedHostName)") + } } - } #if DEBUG diff --git a/Sources/Secretive/Views/SettingsView.swift b/Sources/Secretive/Views/SettingsView.swift new file mode 100644 index 0000000..b0bc065 --- /dev/null +++ b/Sources/Secretive/Views/SettingsView.swift @@ -0,0 +1,57 @@ +// +// SettingsView.swift +// Secretive +// +// Created by Paul Heidekrüger on 05.02.24. +// Copyright © 2024 Max Goedjen. All rights reserved. +// + +import SwiftUI + +enum CommentStyle: String, CaseIterable, Identifiable { + case keyAndHost, none + var id: Self { self } +} + +struct GeneralSettingsView: View { + @AppStorage("com.maxgoedjen.Secretive.commentStyle") var selectedCommentStyle: CommentStyle = .keyAndHost + + var body: some View { + VStack(alignment: .leading) { + Section(footer: Text("SSH public keys can be extended with an arbitrary comment string without changing the meaning of the key.") + .font(.caption) + .fontWeight(.light)) { + Picker("SSH Public Key Comment", selection: $selectedCommentStyle) { + Text("Default").tag(CommentStyle.keyAndHost) + Text("None").tag(CommentStyle.none) + } + .pickerStyle(DefaultPickerStyle()) + } + } + .padding(20) + .frame(width: 350, height: 100) + .navigationTitle("Settings") + } +} + + +struct SettingsView: View { + private enum Tabs: Hashable { + case general + } + var body: some View { + TabView { + GeneralSettingsView() + .tabItem { + Label("General", systemImage: "gear") + } + .tag(Tabs.general) + } + .padding(20) + .frame(width: 500, height: 200) + } +} + +#Preview { + SettingsView() +}