From 4ea50e41d7d1cfecb82ac446d115177a1332bd8b Mon Sep 17 00:00:00 2001 From: michael starke Date: Fri, 25 Sep 2015 16:53:32 +0200 Subject: [PATCH] Enhanced Autotype Autotype now looks in all open documents for matches Autotype now uses the same matching as KeePass Settings for autotype are now enabled in the UI since they are used now Signed-off-by: michael starke --- DDHotKey | 2 +- MacPass/Base.lproj/IntegrationSettings.xib | 140 +++++++++++---------- MacPass/MPAutotypeDaemon.m | 63 +++++----- MacPass/MPDocument+Autotype.m | 71 +++++++---- MacPass/MPIntegrationSettingsController.h | 1 + MacPass/MPIntegrationSettingsController.m | 6 +- MacPass/MPSettingsHelper.h | 7 +- MacPass/MPSettingsHelper.m | 2 + 8 files changed, 167 insertions(+), 125 deletions(-) diff --git a/DDHotKey b/DDHotKey index e6846463..dfab50eb 160000 --- a/DDHotKey +++ b/DDHotKey @@ -1 +1 @@ -Subproject commit e6846463e2fedd6742a5f10633e7cc1069555924 +Subproject commit dfab50eb22782850ec836a92d56d16bdb6b5912a diff --git a/MacPass/Base.lproj/IntegrationSettings.xib b/MacPass/Base.lproj/IntegrationSettings.xib index a4b05ac3..62d67e0c 100644 --- a/MacPass/Base.lproj/IntegrationSettings.xib +++ b/MacPass/Base.lproj/IntegrationSettings.xib @@ -1,8 +1,9 @@ - + - + + @@ -14,7 +15,8 @@ - + + @@ -22,16 +24,16 @@ - + - - + + - - + - - + + @@ -88,85 +90,95 @@ - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + - - - + + @@ -176,12 +188,12 @@ - - - - - - + + + + + + diff --git a/MacPass/MPAutotypeDaemon.m b/MacPass/MPAutotypeDaemon.m index 1da809b0..1b23c796 100644 --- a/MacPass/MPAutotypeDaemon.m +++ b/MacPass/MPAutotypeDaemon.m @@ -109,8 +109,8 @@ NSString *const kMPProcessIdentifierKey = @"kMPProcessIdentifierKey"; #pragma mark - #pragma mark Actions - (void)performAutotypeWithSelectedMatch:(id)sender { - NSMenuItem *item = [self.matchSelectionButton selectedItem]; - MPAutotypeContext *context = [item representedObject]; + NSMenuItem *item = self.matchSelectionButton.selectedItem; + MPAutotypeContext *context = item.representedObject; [self.matchSelectionWindow orderOut:self]; [self _performAutotypeForContext:context]; } @@ -131,17 +131,16 @@ NSString *const kMPProcessIdentifierKey = @"kMPProcessIdentifierKey"; return; // We do not perform Autotype on ourselves } - MPDocument *document = [self _findAutotypeDocument]; - if(!document) { + NSArray *documents = [self _findAutotypeDocuments]; + if(documents.count == 0) { /* We do not have a document. This can be a) there is none - nothing happens b) there is at least one, but locked - we get called again after the document has been unlocked */ return; } - - MPAutotypeContext *context = [self _autotypeContextInDocument:document forWindowTitle:self.targetWindowTitle preferredEntry:entryOrNil]; - /* TODO: that's popping up if the multi selection dialog goes up! */ + MPAutotypeContext *context = [self _autotypeContextForDocuments:documents forWindowTitle:self.targetWindowTitle preferredEntry:entryOrNil]; + /* TODO: that's popping up if the mulit seleciton dialog goes up! */ if(!entryOrNil) { NSImage *appIcon = [[NSApplication sharedApplication] applicationIconImage]; NSString *label = context ? NSLocalizedString(@"AUTOTYPE_OVERLAY_SINGLE_MATCH", "") : NSLocalizedString(@"AUTOTYPE_OVERLAY_NO_MATCH", ""); @@ -150,36 +149,40 @@ NSString *const kMPProcessIdentifierKey = @"kMPProcessIdentifierKey"; [self _performAutotypeForContext:context]; } -- (MPDocument *)_findAutotypeDocument { +- (NSArray *)_findAutotypeDocuments { + NSArray *documents = [NSApp orderedDocuments]; - MPDocument *currentDocument = nil; - for(MPDocument *openDocument in documents) { - if(NO == openDocument.encrypted) { - currentDocument = openDocument; - break; - } - } - BOOL hasOpenDocuments = [documents count] > 0; - if(!currentDocument && hasOpenDocuments) { + NSPredicate *filterPredicate = [NSPredicate predicateWithBlock:^BOOL(id _Nonnull evaluatedObject, NSDictionary * _Nullable bindings) { + MPDocument *document = evaluatedObject; + return document.encrypted;}]; + NSArray *unlockedDocuments = [documents filteredArrayUsingPredicate:filterPredicate]; + /* We look for all unlocked documents, if all open documents are locked, we pop the front most and try to search again */ + if(unlockedDocuments.count == 0 && documents.count > 0) { [NSApp activateIgnoringOtherApps:YES]; [[NSApp mainWindow] makeKeyAndOrderFront:self]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didUnlockDatabase:) name:MPDocumentDidUnlockDatabaseNotification object:nil]; } - return currentDocument; + return unlockedDocuments; } -- (MPAutotypeContext *)_autotypeContextInDocument:(MPDocument *)document forWindowTitle:(NSString *)windowTitle preferredEntry:(KPKEntry *)entry { +- (MPAutotypeContext *)_autotypeContextForDocuments:(NSArray *)documents forWindowTitle:(NSString *)windowTitle preferredEntry:(KPKEntry *)entry { /* Query the document to generate a autotype command list for the window title We do not care where this came form, just get the autotype commands */ - NSArray *autotypeCandidates = [document autotypContextsForWindowTitle:windowTitle preferredEntry:entry]; - NSUInteger candidates = [autotypeCandidates count]; + NSMutableArray *autotypeCandidates = [[NSMutableArray alloc] init]; + for(MPDocument *document in documents) { + NSArray *contexts = [document autotypContextsForWindowTitle:windowTitle preferredEntry:entry]; + if(contexts ) { + [autotypeCandidates addObjectsFromArray:contexts]; + } + } + NSUInteger candidates = autotypeCandidates.count; if(candidates == 0) { return nil; } if(candidates == 1 ) { - return [autotypeCandidates lastObject]; + return autotypeCandidates.lastObject; } [self _presentSelectionWindow:autotypeCandidates]; return nil; // Nothing to do, we get called back by the window @@ -191,11 +194,11 @@ NSString *const kMPProcessIdentifierKey = @"kMPProcessIdentifierKey"; } if([self _orderApplicationToFront:self.targetPID]) { /* Sleep a bit after the app was activated */ - /* TODO - we can use a saver way and use a notification to check if the app actually was activated */ + /* TODO - we can use a saver way and use a notification to chekc if the app actally was activated */ usleep(1 * NSEC_PER_MSEC); } dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSArray *commands = [MPAutotypeCommand commandsForContext:context]; + NSArray *commands = [MPAutotypeCommand commandsForContext:context]; for(MPAutotypeCommand *command in commands) { [command execute]; } @@ -230,7 +233,7 @@ NSString *const kMPProcessIdentifierKey = @"kMPProcessIdentifierKey"; NSArray *currentWindows = CFBridgingRelease(CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID)); for(NSDictionary *windowDict in currentWindows) { NSString *windowTitle = windowDict[(NSString *)kCGWindowName]; - if([windowTitle length] <= 0) { + if(windowTitle.length <= 0) { continue; } NSNumber *processId = windowDict[(NSString *)kCGWindowOwnerPID]; @@ -247,12 +250,12 @@ NSString *const kMPProcessIdentifierKey = @"kMPProcessIdentifierKey"; - (void)_presentSelectionWindow:(NSArray *)candidates { if(!self.matchSelectionWindow) { [[NSBundle mainBundle] loadNibNamed:@"AutotypeCandidateSelectionWindow" owner:self topLevelObjects:nil]; - [self.matchSelectionWindow setLevel:NSFloatingWindowLevel]; + self.matchSelectionWindow.level = NSFloatingWindowLevel; } NSMenu *associationMenu = [[NSMenu alloc] init]; [associationMenu addItemWithTitle:NSLocalizedString(@"SELECT_AUTOTYPE_CANDIDATE", "") action:NULL keyEquivalent:@""]; [associationMenu addItem:[NSMenuItem separatorItem]]; - [associationMenu setAutoenablesItems:NO]; + associationMenu.autoenablesItems = NO; for(MPAutotypeContext *context in candidates) { NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:context.entry.title action:0 keyEquivalent:@""]; [item setRepresentedObject:context]; @@ -263,12 +266,12 @@ NSString *const kMPProcessIdentifierKey = @"kMPProcessIdentifierKey"; for(NSString *value in attributes) { NSMenuItem *valueItem = [[NSMenuItem alloc] initWithTitle:value action:NULL keyEquivalent:@""]; - [valueItem setIndentationLevel:1]; - [valueItem setEnabled:NO]; + valueItem.indentationLevel = 1; + valueItem.enabled = NO; [associationMenu addItem:valueItem]; } } - [self.matchSelectionButton setMenu:associationMenu]; + self.matchSelectionButton.menu = associationMenu; [self.matchSelectionWindow makeKeyAndOrderFront:self]; [NSApp activateIgnoringOtherApps:YES]; } diff --git a/MacPass/MPDocument+Autotype.m b/MacPass/MPDocument+Autotype.m index 1ab9f386..0d5cd7ee 100644 --- a/MacPass/MPDocument+Autotype.m +++ b/MacPass/MPDocument+Autotype.m @@ -23,11 +23,14 @@ #import "MPDocument.h" #import "MPAutotypeContext.h" +#import "KPKNode.h" #import "KPKGroup.h" #import "KPKEntry.h" #import "KPKAutotype.h" #import "KPKWindowAssociation.h" +#import "MPSettingsHelper.h" + @implementation MPDocument (Autotype) + (BOOL)isCandidateForMalformedAutotype:(id)item { @@ -51,37 +54,58 @@ return nil; } BOOL usePreferredEntry = (nil != entry); + /* We might get a preferred entry from other documents, if so, stop searching and return */ + if(usePreferredEntry && entry.rootGroup != self.root) { + return nil; + } NSArray *autotypeEntries = usePreferredEntry ? [[NSArray alloc] initWithObjects:entry, nil] : [self.root autotypeableChildEntries]; - NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:MAX(1,ceil([autotypeEntries count] / 4.0))]; + NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:MAX(1,ceil(autotypeEntries.count / 4.0))]; + + BOOL matchTitle = [[NSUserDefaults standardUserDefaults] boolForKey:kMPSettingsKeyAutotypeMatchTitle]; + BOOL matchURL = [[NSUserDefaults standardUserDefaults] boolForKey:kMPSettingsKeyAutotypeMatchURL]; + BOOL matchHost = [[NSUserDefaults standardUserDefaults] boolForKey:kMPSettingsKeyAutotypeMatchHost]; + BOOL matchTags = [[NSUserDefaults standardUserDefaults] boolForKey:kMPSettingsKeyAutotypeMatchTags]; + MPAutotypeContext *context; for(KPKEntry *entry in autotypeEntries) { - /* TODO: - - KeePass for Windows has the following options for matching: - Title is contained - URL is contained - Host component is contained - A tag is contained - - */ - /* Test for entry title in window title */ - NSRange titleRange = [windowTitle rangeOfString:entry.title options:NSCaseInsensitiveSearch]; - /* Test for window title in entry title */ - if (titleRange.location == NSNotFound || titleRange.length == 0) { - titleRange = [entry.title rangeOfString:windowTitle options:NSCaseInsensitiveSearch]; - } - if(titleRange.location != NSNotFound && titleRange.length != 0) { - context = [[MPAutotypeContext alloc] initWithEntry:entry andSequence:entry.autotype.defaultKeystrokeSequence]; - } /* search in Autotype entries for match */ - else { - KPKWindowAssociation *association = [entry.autotype windowAssociationMatchingWindowTitle:windowTitle]; - context = [[MPAutotypeContext alloc] initWithWindowAssociation:association]; - } + KPKWindowAssociation *association = [entry.autotype windowAssociationMatchingWindowTitle:windowTitle]; + context = [[MPAutotypeContext alloc] initWithWindowAssociation:association]; if(context.valid) { [contexts addObject:context]; + continue; // association did match + } + BOOL foundMatch = NO; + /* Test for entry title in window title */ + if(matchTitle && !foundMatch) { + foundMatch = [windowTitle rangeOfString:entry.title options:NSCaseInsensitiveSearch].length != 0 || [entry.title rangeOfString:windowTitle options:NSCaseInsensitiveSearch].length != 0; + } + /* test for URL */ + if(matchURL && !foundMatch) { + foundMatch = [windowTitle rangeOfString:entry.url options:NSCaseInsensitiveSearch].length != 0; + } + /* test for host */ + if(matchHost && foundMatch) { + //TODO: + } + /* test for tags */ + if(matchTags && !context.valid) { + NSArray *tags = [entry.tags componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@".:;"]]; + for(NSString *tag in tags) { + foundMatch = ([windowTitle rangeOfString:tag options:NSCaseInsensitiveSearch].length != 0); + if(foundMatch) { + break; + } + } + } + if(foundMatch) { + context = [[MPAutotypeContext alloc] initWithDefaultSequenceForEntry:entry]; + if(context.valid) { + [contexts addObject:context]; + } } } + /* Fall back to preferred Entry if no match was found */ if(contexts.count == 0 && usePreferredEntry) { context = [[MPAutotypeContext alloc] initWithEntry:entry andSequence:entry.autotype.defaultKeystrokeSequence]; @@ -116,5 +140,4 @@ [self _flattenGroup:childGroup toArray:array]; } } - @end diff --git a/MacPass/MPIntegrationSettingsController.h b/MacPass/MPIntegrationSettingsController.h index 9430a137..0167d4af 100644 --- a/MacPass/MPIntegrationSettingsController.h +++ b/MacPass/MPIntegrationSettingsController.h @@ -21,6 +21,7 @@ @property (weak) IBOutlet DDHotKeyTextField *hotKeyTextField; @property (weak) IBOutlet NSTextField *hotkeyWarningTextField; +@property (weak) IBOutlet NSButton *matchTitleCheckBox; @property (weak) IBOutlet NSButton *matchURLCheckBox; @property (weak) IBOutlet NSButton *matchHostCheckBox; @property (weak) IBOutlet NSButton *matchTagsCheckBox; diff --git a/MacPass/MPIntegrationSettingsController.m b/MacPass/MPIntegrationSettingsController.m index 94b4759f..e2088508 100644 --- a/MacPass/MPIntegrationSettingsController.m +++ b/MacPass/MPIntegrationSettingsController.m @@ -50,11 +50,11 @@ [self.hotKeyTextField bind:NSEnabledBinding toObject:defaultsController withKeyPath:enableGlobalAutotypeKeyPath options:nil]; self.hotKeyTextField.delegate = self; - /* + + [self.matchTitleCheckBox bind:NSValueBinding toObject:defaultsController withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyAutotypeMatchTitle ] options:nil]; + [self.matchURLCheckBox bind:NSValueBinding toObject:defaultsController withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyAutotypeMatchURL] options:nil]; [self.matchHostCheckBox bind:NSValueBinding toObject:defaultsController withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyAutotypeMatchHost] options:nil]; [self.matchTagsCheckBox bind:NSValueBinding toObject:defaultsController withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyAutotypeMatchTags] options:nil]; - [self.matchURLCheckBox bind:NSValueBinding toObject:defaultsController withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyAutotypeMatchURL] options:nil]; - */ [self.sendCommandForControlCheckBox bind:NSValueBinding toObject:defaultsController withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeySendCommandForControlKey] options:nil]; diff --git a/MacPass/MPSettingsHelper.h b/MacPass/MPSettingsHelper.h index 0e5a147b..4ece7f62 100644 --- a/MacPass/MPSettingsHelper.h +++ b/MacPass/MPSettingsHelper.h @@ -50,9 +50,10 @@ APPKIT_EXTERN NSString *const kMPSettingsKeySendCommandForControlKey; // APPKIT_EXTERN NSString *const kMPSettingsKeyEnableGlobalAutotype; // Is Global Autotype enabled? APPKIT_EXTERN NSString *const kMPSettingsKeyGlobalAutotypeKeyDataKey; // The stored Data for the user defined global autotype key APPKIT_EXTERN NSString *const kMPSettingsKeyDefaultGlobalAutotypeSequence; // Default sequence used for Autotype -APPKIT_EXTERN NSString *const kMPSettingsKeyAutotypeMatchURL; // Autotype lookup included entry URL -APPKIT_EXTERN NSString *const kMPSettingsKeyAutotypeMatchHost; // Autotype lookup included host part of entry URL -APPKIT_EXTERN NSString *const kMPSettingsKeyAutotypeMatchTags; // Autotype lookup included tags for entries +APPKIT_EXTERN NSString *const kMPSettingsKeyAutotypeMatchTitle; // Autotype lookup includes entry title +APPKIT_EXTERN NSString *const kMPSettingsKeyAutotypeMatchURL; // Autotype lookup includes entry URL +APPKIT_EXTERN NSString *const kMPSettingsKeyAutotypeMatchHost; // Autotype lookup includes host part of entry URL +APPKIT_EXTERN NSString *const kMPSettingsKeyAutotypeMatchTags; // Autotype lookup includes tags for entries /* Search */ APPKIT_EXTERN NSString *const kMPSettingsKeyEntrySearchFilterContext; diff --git a/MacPass/MPSettingsHelper.m b/MacPass/MPSettingsHelper.m index e141b7ea..59422a6a 100644 --- a/MacPass/MPSettingsHelper.m +++ b/MacPass/MPSettingsHelper.m @@ -38,6 +38,7 @@ NSString *const kMPSettingsKeySendCommandForControlKey = @"SendCo NSString *const kMPSettingsKeyEnableGlobalAutotype = @"EnableGlobalAutotype"; NSString *const kMPSettingsKeyGlobalAutotypeKeyDataKey = @"GlobalAutotypeKeyDataKey"; NSString *const kMPSettingsKeyDefaultGlobalAutotypeSequence = @"DefaultGlobalAutotypeSequence"; +NSString *const kMPSettingsKeyAutotypeMatchTitle = @"AutotypeMatchTitle"; NSString *const kMPSettingsKeyAutotypeMatchURL = @"AutotypeMatchURL"; NSString *const kMPSettingsKeyAutotypeMatchHost = @"AutotypeMatchHost"; NSString *const kMPSettingsKeyAutotypeMatchTags = @"AutotypeMatchTags"; @@ -108,6 +109,7 @@ NSString *const kMPDeprecatedSettingsKeyEntrySearchFilterMode = @"En kMPSettingsKeyEnableGlobalAutotype: @NO, kMPSettingsKeyGlobalAutotypeKeyDataKey: [[DDHotKey defaultHotKey] keyData], kMPSettingsKeyDefaultGlobalAutotypeSequence: @"{USERNAME}{TAB}{PASSWORD}{ENTER}", + kMPSettingsKeyAutotypeMatchTitle: @YES, kMPSettingsKeyAutotypeMatchURL: @NO, kMPSettingsKeyAutotypeMatchHost: @NO, kMPSettingsKeyAutotypeMatchTags: @NO,