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,