diff --git a/Cartfile b/Cartfile index 856c5fbe..a1c662f1 100644 --- a/Cartfile +++ b/Cartfile @@ -1,3 +1,3 @@ github "sparkle-project/Sparkle" ~> 1.18.1 -github "mstarke/KeePassKit" ~> 1.3 -github "mstarke/HNHUi" ~> 1.1 +github "mstarke/KeePassKit" ~> 1.4 +github "mstarke/HNHUi" ~> 1.4 diff --git a/Cartfile.resolved b/Cartfile.resolved index 4cca1211..09eb0e52 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,3 @@ -github "mstarke/HNHUi" "1.2" -github "mstarke/KeePassKit" "1.3" +github "mstarke/HNHUi" "1.4" +github "mstarke/KeePassKit" "1.4" github "sparkle-project/Sparkle" "1.18.1" diff --git a/MacPass/Base.lproj/EntryInspectorView.xib b/MacPass/Base.lproj/EntryInspectorView.xib index 0f357d86..4b306a0d 100644 --- a/MacPass/Base.lproj/EntryInspectorView.xib +++ b/MacPass/Base.lproj/EntryInspectorView.xib @@ -1,7 +1,8 @@ - + - + + @@ -84,10 +85,7 @@ - - - - + @@ -165,8 +163,8 @@ - - + + @@ -174,10 +172,7 @@ - - - - + @@ -241,6 +236,7 @@ + @@ -263,7 +259,7 @@ - + @@ -287,6 +283,7 @@ + @@ -309,6 +306,7 @@ + @@ -323,6 +321,7 @@ + @@ -367,6 +366,7 @@ + @@ -451,6 +451,9 @@ + + + - - + + + + @@ -92,9 +87,8 @@ - - - + + @@ -102,8 +96,7 @@ - - + @@ -113,21 +106,16 @@ - - - - - - + + - - - - - + + - - + + @@ -70,10 +71,10 @@ - - + + - + @@ -103,7 +104,7 @@ - + @@ -121,23 +122,27 @@ + + + + - + diff --git a/MacPass/EntryView.xib b/MacPass/EntryView.xib index 838c9bd0..60f07e1b 100644 --- a/MacPass/EntryView.xib +++ b/MacPass/EntryView.xib @@ -1,7 +1,8 @@ - + - + + @@ -130,11 +131,11 @@ - + - + @@ -251,7 +252,7 @@ - + diff --git a/MacPass/MPCustomFieldTableViewDelegate.m b/MacPass/MPCustomFieldTableViewDelegate.m index bf26f76d..fad4093b 100644 --- a/MacPass/MPCustomFieldTableViewDelegate.m +++ b/MacPass/MPCustomFieldTableViewDelegate.m @@ -33,19 +33,23 @@ [view.labelTextField bind:NSValueBinding toObject:view - withKeyPath:[NSString stringWithFormat:@"%@.%@", NSStringFromSelector(@selector(objectValue)), NSStringFromSelector(@selector(key))] + withKeyPath:@"objectValue.key" options:@{ NSValidatesImmediatelyBindingOption: @YES }]; [view.valueTextField bind:NSValueBinding toObject:view - withKeyPath:[NSString stringWithFormat:@"%@.%@", NSStringFromSelector(@selector(objectValue)), NSStringFromSelector(@selector(value))] + withKeyPath:@"objectValue.value" options:nil]; - - // TODO: Move to public KeePassKit API! - for(NSControl *control in @[view.labelTextField, view.valueTextField, view.removeButton ]) { + [view.protectedButton bind:NSValueBinding + toObject:view + withKeyPath:@"objectValue.isProtected" + options:nil]; + + + for(NSControl *control in @[view.labelTextField, view.valueTextField, view.removeButton, view.protectedButton ]) { [control bind:NSEnabledBinding toObject:view - withKeyPath:[NSString stringWithFormat:@"%@.%@.%@", NSStringFromSelector(@selector(objectValue)), NSStringFromSelector(@selector(entry)), NSStringFromSelector(@selector(isHistory))] - options:@{NSConditionallySetsEditableBindingOption: @NO, NSValueTransformerNameBindingOption: NSNegateBooleanTransformerName}]; + withKeyPath:@"objectValue.isEditable" + options:@{NSConditionallySetsEditableBindingOption: @NO }]; } view.removeButton.target = self.viewController; diff --git a/MacPass/MPDocument.m b/MacPass/MPDocument.m index 8861dd0d..a32d37a8 100644 --- a/MacPass/MPDocument.m +++ b/MacPass/MPDocument.m @@ -445,6 +445,7 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGrou - (BOOL)unlockWithPassword:(NSString *)password keyFileURL:(NSURL *)keyFileURL error:(NSError *__autoreleasing*)error{ + // TODO: Make this API asynchronous self.compositeKey = [[KPKCompositeKey alloc] initWithPassword:password key:keyFileURL]; self.tree = [[KPKTree alloc] initWithData:self.encryptedData key:self.compositeKey error:error]; diff --git a/MacPass/MPDocumentWindowController.m b/MacPass/MPDocumentWindowController.m index d9b9179c..94e63331 100644 --- a/MacPass/MPDocumentWindowController.m +++ b/MacPass/MPDocumentWindowController.m @@ -263,7 +263,7 @@ typedef void (^MPPasswordChangedBlock)(BOOL didChangePassword); openPanel.allowsMultipleSelection = NO; openPanel.canChooseDirectories = NO; openPanel.canChooseFiles = YES; - openPanel.allowedFileTypes = @[(id)kUTTypeXML]; + openPanel.allowedFileTypes = @[(id)kUTTypeXML]; [openPanel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) { if(result == NSFileHandlingPanelOKButton) { [document readXMLfromURL:openPanel.URL]; diff --git a/MacPass/MPEntryInspectorViewController.h b/MacPass/MPEntryInspectorViewController.h index 14e52ff8..3d250e35 100644 --- a/MacPass/MPEntryInspectorViewController.h +++ b/MacPass/MPEntryInspectorViewController.h @@ -21,13 +21,13 @@ // #import "MPViewController.h" - +#import "HNHUi/HNHUi.h" #import @class HNHUIRoundedSecureTextField; @class MPDocument; -@interface MPEntryInspectorViewController : MPViewController +@interface MPEntryInspectorViewController : MPViewController @property (weak) IBOutlet NSSegmentedControl *infoTabControl; diff --git a/MacPass/MPEntryInspectorViewController.m b/MacPass/MPEntryInspectorViewController.m index eb717061..a6dee21a 100644 --- a/MacPass/MPEntryInspectorViewController.m +++ b/MacPass/MPEntryInspectorViewController.m @@ -38,6 +38,7 @@ #import "MPTemporaryFileStorageCenter.h" #import "MPActionHelper.h" #import "MPSettingsHelper.h" +#import "MPPasteBoardController.h" #import "MPArrayController.h" @@ -511,6 +512,41 @@ typedef NS_ENUM(NSUInteger, MPEntryTab) { } +#pragma mark - +#pragma mark HNHUITextFieldDelegate +- (NSMenu *)textField:(NSTextField *)textField textView:(NSTextView *)view menu:(NSMenu *)menu { + return menu; +} + +- (BOOL)textField:(NSTextField *)textField textView:(NSTextView *)textView performAction:(SEL)action { + if(action == @selector(copy:)) { + MPPasteboardOverlayInfoType info = MPPasteboardOverlayInfoCustom; + NSString *value = textField.stringValue; + NSString *name = @""; + if(nil == value) { + return YES; + } + if(textField == self.usernameTextField) { + info = MPPasteboardOverlayInfoUsername; + } + else if(textField == self.passwordTextField) { + info = MPPasteboardOverlayInfoPassword; + } + else if(textField == self.URLTextField) { + info = MPPasteboardOverlayInfoURL; + } + else if(textField == self.uuidTextField) { + name = NSLocalizedString(@"UUID", "Displayed name when uuid field was copied"); + } + else if(textField == self.titleTextField) { + name = NSLocalizedString(@"TITLE", "Displayed name when title field was copied"); + } + [[MPPasteBoardController defaultController] copyObjects:@[value] overlayInfo:info name:name atView:self.view]; + return NO; + } + return YES; +} + - (IBAction)toggleExpire:(NSButton*)sender { if([sender state] == NSOnState && [self.representedEntry.timeInfo.expirationDate isEqualToDate:[NSDate distantFuture]]) { [NSApp sendAction:self.pickExpireDateButton.action to:nil from:self.pickExpireDateButton]; diff --git a/MacPass/MPEntryViewController.m b/MacPass/MPEntryViewController.m index 39623e82..0296f68b 100644 --- a/MacPass/MPEntryViewController.m +++ b/MacPass/MPEntryViewController.m @@ -52,13 +52,6 @@ #define STATUS_BAR_ANIMATION_TIME 0.15 #define EXPIRED_ENTRY_REFRESH_SECONDS 60 -typedef NS_ENUM(NSUInteger, MPOverlayInfoType) { - MPOverlayInfoPassword, - MPOverlayInfoUsername, - MPOverlayInfoURL, - MPOverlayInfoCustom, -}; - NSString *const MPEntryTableIndexColumnIdentifier = @"MPEntryTableIndexColumnIdentifier"; NSString *const MPEntryTableUserNameColumnIdentifier = @"MPUserNameColumnIdentifier"; NSString *const MPEntryTableTitleColumnIdentifier = @"MPTitleColumnIdentifier"; @@ -597,37 +590,6 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; }]; } -#pragma mark Copy/Paste Overlays -- (void)_copyToPasteboard:(NSString *)data overlayInfo:(MPOverlayInfoType)overlayInfoType name:(NSString *)name{ - if(data) { - [MPPasteBoardController.defaultController copyObjects:@[ data ]]; - } - NSImage *infoImage = nil; - NSString *infoText = nil; - switch (overlayInfoType) { - case MPOverlayInfoPassword: - infoImage = [[NSBundle mainBundle] imageForResource:@"00_PasswordTemplate"]; - infoText = NSLocalizedString(@"COPIED_PASSWORD", @"Password was copied to the pasteboard"); - break; - - case MPOverlayInfoURL: - infoImage = [[NSBundle mainBundle] imageForResource:@"01_PackageNetworkTemplate"]; - infoText = NSLocalizedString(@"COPIED_URL", @"URL was copied to the pasteboard"); - break; - - case MPOverlayInfoUsername: - infoImage = [[NSBundle mainBundle] imageForResource:@"09_IdentityTemplate"]; - infoText = NSLocalizedString(@"COPIED_USERNAME", @"Username was copied to the pasteboard"); - break; - - case MPOverlayInfoCustom: - infoImage = [[NSBundle mainBundle] imageForResource:@"00_PasswordTemplate"]; - infoText = [NSString stringWithFormat:NSLocalizedString(@"COPIED_FIELD_%@", "Field name that was copied to the pasteboard"), name]; - break; - } - [[MPOverlayWindowController sharedController] displayOverlayImage:infoImage label:infoText atView:self.view]; -} - #pragma mark Validation - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { /* Validation is solely handled in the document */ @@ -679,16 +641,18 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; - (void)copyPassword:(id)sender { NSArray *nodes = [self currentTargetNodes]; KPKEntry *selectedEntry = nodes.count == 1 ? [nodes.firstObject asEntry] : nil; - if(selectedEntry) { - [self _copyToPasteboard:[selectedEntry.password kpk_finalValueForEntry:selectedEntry] overlayInfo:MPOverlayInfoPassword name:nil]; + NSString *value = [selectedEntry.password kpk_finalValueForEntry:selectedEntry]; + if(value) { + [[MPPasteBoardController defaultController] copyObjects:@[value] overlayInfo:MPPasteboardOverlayInfoPassword name:nil atView:self.view]; } } - (void)copyUsername:(id)sender { NSArray *nodes = [self currentTargetNodes]; KPKEntry *selectedEntry = nodes.count == 1 ? [nodes.firstObject asEntry] : nil; - if(selectedEntry) { - [self _copyToPasteboard:[selectedEntry.username kpk_finalValueForEntry:selectedEntry] overlayInfo:MPOverlayInfoUsername name:nil]; + NSString *value = [selectedEntry.username kpk_finalValueForEntry:selectedEntry]; + if(value) { + [[MPPasteBoardController defaultController] copyObjects:@[value] overlayInfo:MPPasteboardOverlayInfoUsername name:nil atView:self.view]; } } @@ -697,17 +661,21 @@ NSString *const _MPTableSecurCellView = @"PasswordCell"; KPKEntry *selectedEntry = nodes.count == 1 ? [nodes.firstObject asEntry] : nil; if(selectedEntry && [selectedEntry isKindOfClass:[KPKEntry class]]) { NSUInteger index = [sender tag]; - NSAssert((index >= 0) && (index < [selectedEntry.customAttributes count]), @"Index for custom field needs to be valid"); + NSAssert((index >= 0) && (index < selectedEntry.customAttributes.count), @"Index for custom field needs to be valid"); KPKAttribute *attribute = selectedEntry.customAttributes[index]; - [self _copyToPasteboard:attribute.evaluatedValue overlayInfo:MPOverlayInfoCustom name:attribute.key]; + NSString *value = attribute.evaluatedValue; + if(value) { + [[MPPasteBoardController defaultController] copyObjects:@[value] overlayInfo:MPPasteboardOverlayInfoCustom name:attribute.key atView:self.view]; + } } } - (void)copyURL:(id)sender { NSArray *nodes = [self currentTargetNodes]; KPKEntry *selectedEntry = nodes.count == 1 ? [nodes.firstObject asEntry] : nil; - if(selectedEntry) { - [self _copyToPasteboard:[selectedEntry.url kpk_finalValueForEntry:selectedEntry] overlayInfo:MPOverlayInfoURL name:nil]; + NSString *value = [selectedEntry.url kpk_finalValueForEntry:selectedEntry]; + if(value) { + [[MPPasteBoardController defaultController] copyObjects:@[value] overlayInfo:MPPasteboardOverlayInfoURL name:nil atView:self.view]; } } diff --git a/MacPass/MPPasteBoardController.h b/MacPass/MPPasteBoardController.h index 9df63b45..984e30a5 100644 --- a/MacPass/MPPasteBoardController.h +++ b/MacPass/MPPasteBoardController.h @@ -22,6 +22,13 @@ #import +typedef NS_ENUM(NSUInteger, MPPasteboardOverlayInfoType) { + MPPasteboardOverlayInfoPassword, + MPPasteboardOverlayInfoUsername, + MPPasteboardOverlayInfoURL, + MPPasteboardOverlayInfoCustom, +}; + @interface MPPasteBoardController : NSObject /** @@ -51,4 +58,6 @@ FOUNDATION_EXPORT NSString *const MPPasteBoardControllerDidClearClipboard; - (void)copyObjects:(NSArray> *)objects; - (void)copyObjectsWithoutTimeout:(NSArray> *)objects; +- (void)copyObjects:(NSArray> *)objects overlayInfo:(MPPasteboardOverlayInfoType)overlayInfoType name:(NSString *)name atView:(NSView *)view; + @end diff --git a/MacPass/MPPasteBoardController.m b/MacPass/MPPasteBoardController.m index 8f143178..2b0a44d0 100644 --- a/MacPass/MPPasteBoardController.m +++ b/MacPass/MPPasteBoardController.m @@ -22,6 +22,7 @@ #import "MPPasteBoardController.h" #import "MPSettingsHelper.h" +#import "MPOverlayWindowController.h" /* Notifications */ NSString *const MPPasteBoardControllerDidCopyObjects = @"com.hicknhack.macpass.MPPasteBoardControllerDidCopyObjects"; @@ -105,6 +106,37 @@ NSString *const MPPasteBoardControllerDidClearClipboard = @"com.hicknhack.macpas self.isEmpty = NO; } +- (void)copyObjects:(NSArray> *)objects overlayInfo:(MPPasteboardOverlayInfoType)overlayInfoType name:(NSString *)name atView:(NSView *)view{ + if(!objects) { + return; + } + [MPPasteBoardController.defaultController copyObjects:objects]; + NSImage *infoImage = nil; + NSString *infoText = nil; + switch(overlayInfoType) { + case MPPasteboardOverlayInfoPassword: + infoImage = [[NSBundle mainBundle] imageForResource:@"00_PasswordTemplate"]; + infoText = NSLocalizedString(@"COPIED_PASSWORD", @"Password was copied to the pasteboard"); + break; + + case MPPasteboardOverlayInfoURL: + infoImage = [[NSBundle mainBundle] imageForResource:@"01_PackageNetworkTemplate"]; + infoText = NSLocalizedString(@"COPIED_URL", @"URL was copied to the pasteboard"); + break; + + case MPPasteboardOverlayInfoUsername: + infoImage = [[NSBundle mainBundle] imageForResource:@"09_IdentityTemplate"]; + infoText = NSLocalizedString(@"COPIED_USERNAME", @"Username was copied to the pasteboard"); + break; + + case MPPasteboardOverlayInfoCustom: + infoImage = [[NSBundle mainBundle] imageForResource:@"00_PasswordTemplate"]; + infoText = [NSString stringWithFormat:NSLocalizedString(@"COPIED_FIELD_%@", "Field name that was copied to the pasteboard"), name]; + break; + } + [[MPOverlayWindowController sharedController] displayOverlayImage:infoImage label:infoText atView:view]; +} + - (void)_clearPasteboardContents { /* Only clear stuff we might have put there */ if(!self.isEmpty) {