Add option to rename secrets

This commit is contained in:
Dylan Lundy 2021-04-27 00:28:06 +09:30
parent f30d1f802f
commit f2012545ef
8 changed files with 128 additions and 12 deletions

View File

@ -49,10 +49,12 @@ public class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable {
private let _create: (String, Bool) throws -> Void
private let _delete: (AnySecret) throws -> Void
private let _updateName: (AnySecret, String) throws -> Void
public init<SecretStoreType>(modifiable secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable {
_create = { try secretStore.create(name: $0, requiresAuthentication: $1) }
_delete = { try secretStore.delete(secret: $0.base as! SecretStoreType.SecretType) }
_updateName = { try secretStore.update(secret: $0.base as! SecretStoreType.SecretType, name: $1) }
super.init(secretStore)
}
@ -64,4 +66,7 @@ public class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable {
try _delete(secret)
}
public func update(secret: AnySecret, name: String) throws {
try _updateName(secret, name)
}
}

View File

@ -17,6 +17,7 @@ public protocol SecretStoreModifiable: SecretStore {
func create(name: String, requiresAuthentication: Bool) throws
func delete(secret: SecretType) throws
func update(secret: SecretType, name: String) throws
}

View File

@ -68,7 +68,7 @@ extension SecureEnclave {
let deleteAttributes = [
kSecClass: kSecClassKey,
kSecAttrApplicationLabel: secret.id as CFData
] as CFDictionary
] as CFDictionary
let status = SecItemDelete(deleteAttributes)
if status != errSecSuccess {
throw KeychainError(statusCode: status)
@ -76,6 +76,23 @@ extension SecureEnclave {
reloadSecrets()
}
public func update(secret: Secret, name: String) throws {
let updateQuery = [
kSecClass: kSecClassKey,
kSecAttrApplicationLabel: secret.id as CFData
] as CFDictionary
let updatedAttributes = [
kSecAttrLabel: name,
] as CFDictionary
let status = SecItemUpdate(updateQuery, updatedAttributes)
if status != errSecSuccess {
throw KeychainError(statusCode: status)
}
reloadSecrets()
}
public func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> Data {
let context = LAContext()
context.localizedReason = "sign a request from \"\(provenance.origin.displayName)\" using secret \"\(secret.name)\""

View File

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
2C4A9D2F2636FFD3008CC8E2 /* RenameSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4A9D2E2636FFD3008CC8E2 /* RenameSecretView.swift */; };
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
50153E20250AFCB200525160 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E1F250AFCB200525160 /* UpdateView.swift */; };
50153E22250DECA300525160 /* SecretListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListView.swift */; };
@ -222,6 +223,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
2C4A9D2E2636FFD3008CC8E2 /* RenameSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenameSecretView.swift; sourceTree = "<group>"; };
50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
50153E1F250AFCB200525160 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = "<group>"; };
50153E21250DECA300525160 /* SecretListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListView.swift; sourceTree = "<group>"; };
@ -545,6 +547,7 @@
50C385A42407A76D00AF2719 /* SecretDetailView.swift */,
5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */,
50B8550C24138C4F009958AC /* DeleteSecretView.swift */,
2C4A9D2E2636FFD3008CC8E2 /* RenameSecretView.swift */,
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */,
506772C82425BB8500034DED /* NoStoresView.swift */,
50153E1F250AFCB200525160 /* UpdateView.swift */,
@ -1005,6 +1008,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2C4A9D2F2636FFD3008CC8E2 /* RenameSecretView.swift in Sources */,
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,

View File

@ -42,7 +42,6 @@ extension Preview {
}
class StoreModifiable: Store, SecretStoreModifiable {
override var name: String { "Modifiable Preview Store" }
func create(name: String, requiresAuthentication: Bool) throws {
@ -50,8 +49,10 @@ extension Preview {
func delete(secret: Preview.Secret) throws {
}
}
func update(secret: Preview.Secret, name: String) throws {
}
}
}
extension Preview {

View File

@ -0,0 +1,55 @@
//
// RenameSecretView.swift
// Secretive
//
// Created by Diesal11 on 26/4/21.
// Copyright © 2021 Max Goedjen. All rights reserved.
//
import SwiftUI
import SecretKit
struct RenameSecretView<StoreType: SecretStoreModifiable>: View {
@ObservedObject var store: StoreType
let secret: StoreType.SecretType
var dismissalBlock: (_ renamed: Bool) -> ()
@State private var newName = ""
var body: some View {
VStack {
HStack {
Image(nsImage: NSApp.applicationIconImage)
.resizable()
.frame(width: 64, height: 64)
.padding()
VStack {
HStack {
Text("Type your new name for \"\(secret.name)\" below.")
Spacer()
}
HStack {
TextField(secret.name, text: $newName).focusable()
}
}
}
HStack {
Spacer()
Button("Save", action: rename)
.disabled(newName.count == 0)
.keyboardShortcut(.return)
Button("Cancel") {
dismissalBlock(false)
}.keyboardShortcut(.cancelAction)
}
}
.padding()
.frame(minWidth: 400)
}
func rename() {
try? store.update(secret: secret, name: newName)
dismissalBlock(true)
}
}

View File

@ -6,8 +6,12 @@ struct SecretListView: View {
@ObservedObject var store: AnySecretStore
@Binding var activeSecret: AnySecret.ID?
@Binding var deletingSecret: AnySecret?
@Binding var renamingSecret: AnySecret?
@State var renamingText: String = ""
var deletedSecret: (AnySecret) -> Void
var renamedSecret: (AnySecret) -> Void
var body: some View {
ForEach(store.secrets) { secret in
@ -15,17 +19,29 @@ struct SecretListView: View {
Text(secret.name)
}.contextMenu {
if store is AnySecretStoreModifiable {
Button(action: { rename(secret: secret) }) {
Text("Rename")
}
Button(action: { delete(secret: secret) }) {
Text("Delete")
}
}
}
.popover(isPresented: .constant(deletingSecret == secret)) {
.popover(isPresented: .constant(deletingSecret == secret || renamingSecret == secret)) {
if let modifiable = store as? AnySecretStoreModifiable {
DeleteSecretView(store: modifiable, secret: secret) { deleted in
deletingSecret = nil
if deleted {
deletedSecret(AnySecret(secret))
if deletingSecret == secret {
DeleteSecretView(store: modifiable, secret: secret) { deleted in
deletingSecret = nil
if deleted {
deletedSecret(secret)
}
}
} else if renamingSecret == secret {
RenameSecretView(store: modifiable, secret: secret) { renamed in
renamingSecret = nil
if renamed {
renamedSecret(secret)
}
}
}
}
@ -34,8 +50,11 @@ struct SecretListView: View {
}
}
func rename<SecretType: Secret>(secret: SecretType) {
renamingSecret = AnySecret(secret)
}
func delete<SecretType: Secret>(secret: SecretType) {
deletingSecret = AnySecret(secret)
}
}

View File

@ -7,10 +7,19 @@ struct StoreListView: View {
@State private var activeSecret: AnySecret.ID?
@State private var deletingSecret: AnySecret?
@State private var renamingSecret: AnySecret?
@EnvironmentObject private var storeList: SecretStoreList
var body: some View {
let secretDeleted = { (secret: AnySecret) in
activeSecret = nextDefaultSecret
}
let secretRenamed = { (secret: AnySecret) in
activeSecret = nextDefaultSecret
}
NavigationView {
List(selection: $activeSecret) {
ForEach(storeList.stores) { store in
@ -19,9 +28,14 @@ struct StoreListView: View {
if store.secrets.isEmpty {
EmptyStoreView(store: store, activeSecret: $activeSecret)
} else {
SecretListView(store: store, activeSecret: $activeSecret, deletingSecret: $deletingSecret, deletedSecret: { _ in
activeSecret = nextDefaultSecret
})
SecretListView(
store: store,
activeSecret: $activeSecret,
deletingSecret: $deletingSecret,
renamingSecret: $renamingSecret,
deletedSecret: secretDeleted,
renamedSecret: secretRenamed
)
}
}
}