diff --git a/KeePassKit b/KeePassKit
index 8008d620..428a53af 160000
--- a/KeePassKit
+++ b/KeePassKit
@@ -1 +1 @@
-Subproject commit 8008d6209f3ee95d66c5ac00e661a27b15a458a7
+Subproject commit 428a53af6c68fc4d8c5602fe953ce5dca6ae06cd
diff --git a/MacPass/Base.lproj/MainMenu.xib b/MacPass/Base.lproj/MainMenu.xib
index b7fd19ba..b01e4e18 100644
--- a/MacPass/Base.lproj/MainMenu.xib
+++ b/MacPass/Base.lproj/MainMenu.xib
@@ -235,6 +235,15 @@
+
1260
+
showPreferences:
@@ -932,6 +949,7 @@
+
@@ -1272,6 +1290,11 @@
+
+ 1261
+
+
+
@@ -1291,6 +1314,7 @@
com.apple.InterfaceBuilder.CocoaPlugin
com.apple.InterfaceBuilder.CocoaPlugin
com.apple.InterfaceBuilder.CocoaPlugin
+ com.apple.InterfaceBuilder.CocoaPlugin
com.apple.InterfaceBuilder.CocoaPlugin
com.apple.InterfaceBuilder.CocoaPlugin
com.apple.InterfaceBuilder.CocoaPlugin
@@ -1346,7 +1370,7 @@
- 1260
+ 1263
@@ -1407,6 +1431,7 @@
id
id
+ id
id
@@ -1418,6 +1443,10 @@
exportDatabase:
id
+
+ lock:
+ id
+
showDatabaseSettings:
id
diff --git a/MacPass/MPActionHelper.h b/MacPass/MPActionHelper.h
index 0aec7b4d..b53ee40a 100644
--- a/MacPass/MPActionHelper.h
+++ b/MacPass/MPActionHelper.h
@@ -9,9 +9,9 @@
#import
typedef NS_ENUM(NSUInteger, MPActionType) {
+ MPUnkownAction, // Netural element to be used for returns
MPActionAddEntry, // Add an new entry
MPActionAddGroup, // Add a new group
- MPActionEdit, // Edit entry or group
MPActionDelete, // Delete entry or group
MPActionCopyUsername, // copy username to pasteboard
MPActionCopyPassword, // copy password to pasteboard
@@ -25,5 +25,6 @@ typedef NS_ENUM(NSUInteger, MPActionType) {
@interface MPActionHelper : NSObject
+ (SEL)actionOfType:(MPActionType)type;
++ (MPActionType)typeForAction:(SEL)action;
@end
diff --git a/MacPass/MPActionHelper.m b/MacPass/MPActionHelper.m
index ab87bc88..b16bd45f 100644
--- a/MacPass/MPActionHelper.m
+++ b/MacPass/MPActionHelper.m
@@ -10,25 +10,42 @@
@implementation MPActionHelper
-+ (SEL)actionOfType:(MPActionType)type {
++ (NSDictionary *)_actionDictionary {
static NSDictionary *actionDict;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
actionDict = @{
- @(MPActionAddEntry) : @"createEntry:",
- @(MPActionAddGroup) : @"createGroup:",
- @(MPActionCopyPassword) : @"copyPassword:",
- @(MPActionCopyURL) : @"copyURL:",
- @(MPActionCopyUsername) : @"copyUsername:",
- @(MPActionDelete) : @"deleteNode:",
- @(MPActionEdit) : @"editEntry:",
- @(MPActionOpenURL) : @"openURL:",
- @(MPActionToggleInspector) : @"toggleInspector:",
- @(MPActionLock) : @"lock:",
- @(MPActionEmptyTrash) : @"emptyTrash:"
- };
+ @(MPActionAddEntry) : @"createEntry:",
+ @(MPActionAddGroup) : @"createGroup:",
+ @(MPActionCopyPassword) : @"copyPassword:",
+ @(MPActionCopyURL) : @"copyURL:",
+ @(MPActionCopyUsername) : @"copyUsername:",
+ @(MPActionDelete) : @"deleteNode:",
+ @(MPActionOpenURL) : @"openURL:",
+ @(MPActionToggleInspector) : @"toggleInspector:",
+ @(MPActionLock) : @"lock:",
+ @(MPActionEmptyTrash) : @"emptyTrash:"
+ };
});
+ return actionDict;
+}
+
++ (SEL)actionOfType:(MPActionType)type {
+ NSDictionary *actionDict = [self _actionDictionary];
return NSSelectorFromString(actionDict[@(type)]);
}
++ (MPActionType)typeForAction:(SEL)action {
+ NSString *selectorString = NSStringFromSelector(action);
+ NSArray *selectors = [[self _actionDictionary] allValues];
+ NSUInteger index = [selectors indexOfObject:selectorString];
+ if(index == NSNotFound) {
+ return MPUnkownAction;
+ }
+ NSArray *keys = [[self _actionDictionary] allKeysForObject:selectorString];
+ NSAssert([keys count] == 1, @"There should only be one object for the specified key");
+ return [[keys lastObject] integerValue];
+}
+
+
@end
diff --git a/MacPass/MPDocument.h b/MacPass/MPDocument.h
index a4fcd720..ca75ea97 100644
--- a/MacPass/MPDocument.h
+++ b/MacPass/MPDocument.h
@@ -42,6 +42,7 @@ APPKIT_EXTERN NSString *const MPDocumentGroupKey;
@property (strong, readonly, nonatomic) KdbTree *tree;
@property (weak, readonly, nonatomic) KdbGroup *root;
@property (readonly, strong) MPRootAdapter *rootAdapter;
+@property (weak, readonly) KdbGroup *trash;
@property (nonatomic, copy) NSString *password;
@property (nonatomic, strong) NSURL *key;
@@ -68,6 +69,8 @@ APPKIT_EXTERN NSString *const MPDocumentGroupKey;
- (void)useGroupAsTrash:(KdbGroup *)group;
- (void)useGroupAsTemplate:(KdbGroup *)group;
+- (BOOL)isItemTrashed:(id)item;
+
#pragma mark Export
- (void)writeXMLToURL:(NSURL *)url;
diff --git a/MacPass/MPDocument.m b/MacPass/MPDocument.m
index 44fcd015..9d11e1f4 100644
--- a/MacPass/MPDocument.m
+++ b/MacPass/MPDocument.m
@@ -65,7 +65,6 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGroupKey";
@property (strong) NSURL *lockFileURL;
@property (readonly) BOOL useTrash;
-@property (weak, readonly) KdbGroup *trash;
@property (strong) IBOutlet NSView *warningView;
@property (weak) IBOutlet NSImageView *warningViewImage;
@@ -307,6 +306,26 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGroupKey";
return nil;
}
+- (BOOL)isItemTrashed:(id)item {
+ BOOL validItem = [item isKindOfClass:[KdbEntry class]] || [item isKindOfClass:[KdbGroup class]];
+ if(!item) {
+ return NO;
+ }
+ if(item == self.trash) {
+ return NO; // No need to look further as this is the trashcan
+ }
+ if(validItem) {
+ BOOL isTrashed = NO;
+ id parent = [item parent];
+ while( parent && !isTrashed ) {
+ isTrashed = (parent == self.trash);
+ parent = [parent parent];
+ }
+ return isTrashed;
+ }
+ return NO;
+}
+
- (void)useGroupAsTrash:(KdbGroup *)group {
if(self.useTrash) {
Kdb4Group *groupv4 = (Kdb4Group *)group;
@@ -449,6 +468,9 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGroupKey";
#pragma mark Actions
- (void)emptyTrash:(id)sender {
+ if(self.version != MPDatabaseVersion4) {
+ return; // We have no trash on those file types
+ }
NSAlert *alert = [[NSAlert alloc] init];
[alert setAlertStyle:NSWarningAlertStyle];
[alert setMessageText:NSLocalizedString(@"WARNING_ON_EMPTY_TRASH_TITLE", "")];
@@ -474,6 +496,7 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGroupKey";
BOOL hasEntries = [self.trash.entries count] > 0;
return (hasEntries || hasGroups);
}
+
return [super validateUserInterfaceItem:anItem];
}
@@ -517,7 +540,24 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGroupKey";
for(KdbGroup *group in [self.trash childGroups]) {
[[self undoManager] removeAllActionsWithTarget:group];
}
+ [self _cleanTrashedBinaries];
[self.trash clear];
}
+- (void)_cleanTrashedBinaries {
+ NSMutableSet *clearKeys = [[NSMutableSet alloc] initWithCapacity:20];
+ NSMutableArray *clearBinaries = [[NSMutableArray alloc] initWithCapacity:[self.treeV4.binaries count]];
+ for(Kdb4Entry *entry in [self.trash childEntries]) {
+ for(BinaryRef *binaryRef in entry.binaries) {
+ [clearKeys addObject:@(binaryRef.ref)];
+ }
+ }
+ for(Binary *binary in self.treeV4.binaries) {
+ if([clearKeys containsObject:@(binary.binaryId)]) {
+ [clearBinaries addObject:binary];
+ }
+ }
+ [self.treeV4.binaries removeObjectsInArray:clearBinaries];
+}
+
@end
diff --git a/MacPass/MPDocumentWindowController.h b/MacPass/MPDocumentWindowController.h
index b381b69f..c4d68702 100644
--- a/MacPass/MPDocumentWindowController.h
+++ b/MacPass/MPDocumentWindowController.h
@@ -32,6 +32,12 @@ APPKIT_EXTERN NSString *const MPCurrentItemChangedNotification;
@property (readonly, unsafe_unretained) KdbGroup *currentGroup;
@property (readonly, unsafe_unretained) KdbEntry *currentEntry;
+/**
+ @param action The action that should be validatet
+ @param item The item that the action affects. Pass nil to fall back for default item
+ @returns YES if the action is valid, NO otherwise
+ */
+- (BOOL)validateAction:(SEL)action forItem:(id)item;
- (void)showEntries;
- (void)showPasswordInput;
@@ -40,7 +46,7 @@ APPKIT_EXTERN NSString *const MPCurrentItemChangedNotification;
- (IBAction)showDatabaseSettings:(id)sender;
- (IBAction)exportDatabase:(id)sender;
-- (void)lock:(id)sender;
+- (IBAction)lock:(id)sender;
- (void)createGroup:(id)sender;
- (void)toggleInspector:(id)sender;
diff --git a/MacPass/MPDocumentWindowController.m b/MacPass/MPDocumentWindowController.m
index b2550046..5ba56dc2 100644
--- a/MacPass/MPDocumentWindowController.m
+++ b/MacPass/MPDocumentWindowController.m
@@ -192,6 +192,9 @@ NSString *const MPCurrentItemChangedNotification = @"com.hicknhack.macpass.MPCur
if(itemAction == @selector(exportDatabase:)) {
enabled = (nil != document.treeV4);
}
+ if(itemAction == [MPActionHelper actionOfType:MPActionDelete]) {
+ enabled &= (nil != _currentItem) && (_currentItem != document.trash);
+ }
enabled &= !( !document.decrypted || document.isLocked || document.isReadOnly );
return enabled;
@@ -202,20 +205,55 @@ NSString *const MPCurrentItemChangedNotification = @"com.hicknhack.macpass.MPCur
if(!document.decrypted || document.isLocked || document.isReadOnly) {
return NO;
}
- SEL itemAction = [theItem action];
- if( itemAction == [MPActionHelper actionOfType:MPActionLock]) {
- return document.hasPasswordOrKey;
+ MPActionType actionType = [MPActionHelper typeForAction:[theItem action]];
+ switch (actionType) {
+ case MPActionAddGroup:
+ case MPActionAddEntry:
+ return (nil != _outlineViewController.selectedGroup);
+ case MPActionDelete: {
+ BOOL valid = (nil != _currentItem);
+ valid &= (_currentItem != document.trash);
+ valid &= ![document isItemTrashed:_currentItem];
+ return valid;
+ }
+ case MPActionLock:
+ return document.hasPasswordOrKey;
+
+ case MPActionToggleInspector:
+ return (nil != [_splitView superview]);
+
+ default:
+ return YES;
}
- if(itemAction == [MPActionHelper actionOfType:MPActionAddEntry]) {
- return (nil != _outlineViewController.selectedGroup);
+ return YES;
+}
+
+- (BOOL)validateAction:(SEL)action forItem:(id)item {
+ MPDocument *document = [self document];
+ if(!document.decrypted || document.isLocked || document.isReadOnly) {
+ return NO;
}
- if(itemAction == [MPActionHelper actionOfType:MPActionDelete]) {
- return (nil != _currentItem);
+ MPActionType actionType = [MPActionHelper typeForAction:action];
+ switch (actionType) {
+ case MPActionAddGroup:
+ case MPActionAddEntry:
+ // test if Group is in trash
+ return (nil != _outlineViewController.selectedGroup);
+ case MPActionDelete: {
+ BOOL valid = (nil != _currentItem);
+ valid &= (_currentItem != document.trash);
+ valid &= ![document isItemTrashed:_currentItem];
+ return valid;
+ }
+ case MPActionLock:
+ return document.hasPasswordOrKey;
+
+ case MPActionToggleInspector:
+ return (nil != [_splitView superview]);
+
+ default:
+ return YES;
}
- if(itemAction == [MPActionHelper actionOfType:MPActionToggleInspector]) {
- return (nil != [_splitView superview]);
- }
-
return YES;
}
@@ -235,7 +273,7 @@ NSString *const MPCurrentItemChangedNotification = @"com.hicknhack.macpass.MPCur
[self _showDatabaseSetting:MPDatabaseSettingsTabGeneral];
}
-- (void)lock:(id)sender {
+- (IBAction)lock:(id)sender {
MPDocument *document = [self document];
if(!document.hasPasswordOrKey) {
return; // Document needs a password/keyfile to be lockable
diff --git a/MacPass/MPEntryViewController.m b/MacPass/MPEntryViewController.m
index dc1532a0..7e490708 100644
--- a/MacPass/MPEntryViewController.m
+++ b/MacPass/MPEntryViewController.m
@@ -513,6 +513,11 @@ NSString *const _toggleFilterUsernameButton = @"SearchUsername";
[document deleteEntry:entry];
}
+#pragma mark Validation
+- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
+ return YES;
+}
+
- (void)_toggleFilterSpace:(id)sender {
NSButton *button = sender;
NSNumber *value = self.filterButtonToMode[[button identifier]];
diff --git a/MacPass/MPOutlineViewController.m b/MacPass/MPOutlineViewController.m
index 8868d6df..869c7f73 100644
--- a/MacPass/MPOutlineViewController.m
+++ b/MacPass/MPOutlineViewController.m
@@ -137,6 +137,29 @@ NSString *const _MPOutlinveViewHeaderViewIdentifier = @"HeaderCell";
}
}
+#pragma mark Validation
+
+- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
+ MPActionType actionType = [MPActionHelper typeForAction:[menuItem action]];
+ switch(actionType) {
+ case MPActionAddEntry:
+ case MPActionAddGroup:
+ case MPActionDelete: {
+ MPDocument *document = [[self windowController] document];
+ id selected = [self _clickedOrSelectedGroup];
+ if(!selected) {
+ return NO;
+ }
+ if(selected == document.trash) {
+ return NO;
+ }
+ return ![document isItemTrashed:selected];
+ }
+ default:
+ return YES; // We are only validated for three targets
+ }
+}
+
#pragma mark -
#pragma mark Actions
diff --git a/MacPass/MPToolbarDelegate.m b/MacPass/MPToolbarDelegate.m
index d8686fbe..1bf08f96 100644
--- a/MacPass/MPToolbarDelegate.m
+++ b/MacPass/MPToolbarDelegate.m
@@ -17,7 +17,6 @@
NSString *const MPToolbarItemLock = @"TOOLBAR_LOCK";
NSString *const MPToolbarItemAddGroup = @"TOOLBAR_ADD_GROUP";
NSString *const MPToolbarItemAddEntry = @"TOOLBAR_ADD_ENTRY";
-NSString *const MPToolbarItemEdit = @"TOOLBAR_EDIT";
NSString *const MPToolbarItemDelete =@"TOOLBAR_DELETE";
NSString *const MPToolbarItemAction = @"TOOLBAR_ACTION";
NSString *const MPToolbarItemInspector = @"TOOLBAR_INSPECTOR";
@@ -135,7 +134,6 @@ NSString *const MPToolbarItemInspector = @"TOOLBAR_INSPECTOR";
MPToolbarItemAddEntry: NSLocalizedString(@"ADD_ENTRY", @""),
MPToolbarItemAddGroup: NSLocalizedString(@"ADD_GROUP", @""),
MPToolbarItemDelete: NSLocalizedString(@"DELETE", @""),
- MPToolbarItemEdit: NSLocalizedString(@"EDIT", @""),
MPToolbarItemInspector: NSLocalizedString(@"INSPECTOR", @"")
};
return labelDict[identifier];
@@ -146,7 +144,6 @@ NSString *const MPToolbarItemInspector = @"TOOLBAR_INSPECTOR";
MPToolbarItemAddEntry: @(MPActionAddEntry),
MPToolbarItemAddGroup: @(MPActionAddGroup),
MPToolbarItemDelete: @(MPActionDelete),
- MPToolbarItemEdit: @(MPActionEdit),
MPToolbarItemInspector: @(MPActionToggleInspector)
};
MPActionType actionType = (MPActionType)[actionDict[identifier] integerValue];