diff --git a/External/Pearl b/External/Pearl index a3f33c5f..59dc05c9 160000 --- a/External/Pearl +++ b/External/Pearl @@ -1 +1 @@ -Subproject commit a3f33c5f0cbafd6910ae55fb313764c18b48ad69 +Subproject commit 59dc05c9cca43504bad026dc4633dbba34b4cc43 diff --git a/MasterPassword/ObjC/MPAppDelegate_Store.h b/MasterPassword/ObjC/MPAppDelegate_Store.h index cd5b5c62..edbc2605 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Store.h +++ b/MasterPassword/ObjC/MPAppDelegate_Store.h @@ -11,13 +11,13 @@ #import "UbiquityStoreManager.h" #import "MPFixable.h" -typedef enum { +typedef NS_ENUM( NSUInteger, MPImportResult ) { MPImportResultSuccess, MPImportResultCancelled, MPImportResultInvalidPassword, MPImportResultMalformedInput, MPImportResultInternalError, -} MPImportResult; +}; @interface MPAppDelegate_Shared(Store) diff --git a/MasterPassword/ObjC/MPAppDelegate_Store.m b/MasterPassword/ObjC/MPAppDelegate_Store.m index 8aea9dad..de1f10d5 100644 --- a/MasterPassword/ObjC/MPAppDelegate_Store.m +++ b/MasterPassword/ObjC/MPAppDelegate_Store.m @@ -784,7 +784,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext [export appendFormat:@"%@ %8ld %8s %20s\t%@\n", [[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], (long)uses, - [PearlString( @"%lu:%lu", (long)type, (unsigned long)version ) UTF8String], [name UTF8String], content + [strf( @"%lu:%lu", (long)type, (unsigned long)version ) UTF8String], [name UTF8String], content ? content: @""]; } diff --git a/MasterPassword/ObjC/Mac/MPElementCollectionView.h b/MasterPassword/ObjC/Mac/MPElementCollectionView.h deleted file mode 100644 index bd9968d0..00000000 --- a/MasterPassword/ObjC/Mac/MPElementCollectionView.h +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) - * - * See the enclosed file LICENSE for license information (LGPLv3). If you did - * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt - * - * @author Maarten Billemont - * @license http://www.gnu.org/licenses/lgpl-3.0.txt - */ - -// -// MPElementCollectionView.h -// MPElementCollectionView -// -// Created by lhunath on 2/11/2014. -// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. -// - -#import -@class MPElementModel; - -@interface MPElementCollectionView : NSCollectionViewItem - -@property (nonatomic) MPElementModel *representedObject; -@property (nonatomic) BOOL counterHidden; -@property (nonatomic) BOOL updateContentHidden; - -- (IBAction)toggleType:(id)sender; -- (IBAction)updateLoginName:(id)sender; -- (IBAction)updateContent:(id)sender; -- (IBAction)delete:(id)sender; - -@end diff --git a/MasterPassword/ObjC/Mac/MPElementCollectionView.m b/MasterPassword/ObjC/Mac/MPElementCollectionView.m deleted file mode 100644 index 25f18976..00000000 --- a/MasterPassword/ObjC/Mac/MPElementCollectionView.m +++ /dev/null @@ -1,225 +0,0 @@ -/** - * Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com) - * - * See the enclosed file LICENSE for license information (LGPLv3). If you did - * not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt - * - * @author Maarten Billemont - * @license http://www.gnu.org/licenses/lgpl-3.0.txt - */ - -// -// MPElementCollectionView.h -// MPElementCollectionView -// -// Created by lhunath on 2/11/2014. -// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved. -// - -#import "MPElementCollectionView.h" -#import "MPElementModel.h" -#import "MPMacAppDelegate.h" -#import "MPAppDelegate_Store.h" -#import "MPPasswordDialogController.h" - -#define MPAlertChangeType @"MPAlertChangeType" -#define MPAlertChangeLogin @"MPAlertChangeLogin" -#define MPAlertChangeContent @"MPAlertChangeContent" -#define MPAlertDeleteSite @"MPAlertDeleteSite" - -@implementation MPElementCollectionView { -} - -@dynamic representedObject; - -- (id)initWithCoder:(NSCoder *)coder { - - if (!(self = [super initWithCoder:coder])) - return nil; - - [self addObserver:self forKeyPath:@"representedObject" options:0 context:nil]; - - return self; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - self.counterHidden = !(MPElementTypeClassGenerated & self.representedObject.type); - self.updateContentHidden = !(MPElementTypeClassStored & self.representedObject.type); - }]; -} - -- (void)dealloc { - - [self removeObserver:self forKeyPath:@"representedObject"]; -} - -- (IBAction)toggleType:(id)sender { - - id algorithm = self.representedObject.algorithm; - NSString *previousType = [algorithm nameOfType:[algorithm previousType:self.representedObject.type]]; - NSString *nextType = [algorithm nameOfType:[algorithm nextType:self.representedObject.type]]; - - [[NSAlert alertWithMessageText:@"Change Password Type" - defaultButton:nextType alternateButton:@"Cancel" otherButton:previousType - informativeTextWithFormat:@"Changing the password type for this site will cause the password to change.\n" - @"You will need to update your account with the new password.\n\n" - @"Changing back to the old type will restore your current password."] - beginSheetModalForWindow:self.view.window modalDelegate:self - didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertChangeType]; -} - -- (IBAction)updateLoginName:(id)sender { - - NSAlert *alert = [NSAlert alertWithMessageText:@"Update Login Name" - defaultButton:@"Update" alternateButton:@"Cancel" otherButton:nil - informativeTextWithFormat:@"Enter the login name for %@:", self.representedObject.siteName]; - NSTextField *passwordField = [[NSTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]; - [alert setAccessoryView:passwordField]; - [alert layout]; - [passwordField becomeFirstResponder]; - [alert beginSheetModalForWindow:self.view.window modalDelegate:self - didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertChangeLogin]; -} - -- (IBAction)updateContent:(id)sender { - - NSAlert *alert = [NSAlert alertWithMessageText:@"Update Password" - defaultButton:@"Update" alternateButton:@"Cancel" otherButton:nil - informativeTextWithFormat:@"Enter the new password for %@:", self.representedObject.siteName]; - NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]; - [alert setAccessoryView:passwordField]; - [alert layout]; - [passwordField becomeFirstResponder]; - [alert beginSheetModalForWindow:self.view.window modalDelegate:self - didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertChangeContent]; -} - -- (IBAction)delete:(id)sender { - - NSAlert *alert = [NSAlert alertWithMessageText:@"Delete Site" - defaultButton:@"Delete" alternateButton:@"Cancel" otherButton:nil - informativeTextWithFormat:@"Are you sure you want to delete the site: %@?", self.representedObject.siteName]; - [alert beginSheetModalForWindow:self.view.window modalDelegate:self - didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertDeleteSite]; -} - -- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { - - if (contextInfo == MPAlertChangeType) { - switch (returnCode) { - case NSAlertDefaultReturn: { - // "Next type" button. - [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { - MPElementEntity *element = [self.representedObject entityInContext:context]; - element = [[MPMacAppDelegate get] changeElement:element saveInContext:context - toType:[element.algorithm nextType:element.type]]; - - self.representedObject = [[MPElementModel alloc] initWithEntity:element]; - }]; - break; - } - - case NSAlertAlternateReturn: { - // "Cancel" button. - break; - } - - case NSAlertOtherReturn: { - // "Previous type" button. - [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { - MPElementEntity *element = [self.representedObject entityInContext:context]; - element = [[MPMacAppDelegate get] changeElement:element saveInContext:context - toType:[element.algorithm previousType:element.type]]; - - self.representedObject = [[MPElementModel alloc] initWithEntity:element]; - }]; - break; - } - - default: - break; - } - - return; - } - if (contextInfo == MPAlertChangeLogin) { - switch (returnCode) { - case NSAlertDefaultReturn: { - // "Update" button. - [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { - MPElementEntity *element = [self.representedObject entityInContext:context]; - element.loginName = [(NSTextField *)alert.accessoryView stringValue]; - [context saveToStore]; - - self.representedObject = [[MPElementModel alloc] initWithEntity:element]; - }]; - break; - } - - case NSAlertAlternateReturn: { - // "Cancel" button. - break; - } - - default: - break; - } - - return; - } - if (contextInfo == MPAlertChangeContent) { - switch (returnCode) { - case NSAlertDefaultReturn: { - // "Update" button. - [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { - MPElementEntity *element = [self.representedObject entityInContext:context]; - [element.algorithm saveContent:[(NSSecureTextField *)alert.accessoryView stringValue] - toElement:element usingKey:[MPMacAppDelegate get].key]; - [context saveToStore]; - - self.representedObject = [[MPElementModel alloc] initWithEntity:element]; - }]; - break; - } - - case NSAlertAlternateReturn: { - // "Cancel" button. - break; - } - - default: - break; - } - - return; - } - if (contextInfo == MPAlertDeleteSite) { - switch (returnCode) { - case NSAlertDefaultReturn: { - // "Delete" button. - [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { - MPElementEntity *element = [self.representedObject entityInContext:context]; - [context deleteObject:element]; - [context saveToStore]; - - [((MPPasswordDialogController *)self.collectionView.window.windowController) updateElements]; - }]; - break; - } - - case NSAlertAlternateReturn: { - // "Cancel" button. - break; - } - - default: - break; - } - - return; - } -} - -@end diff --git a/MasterPassword/ObjC/Mac/MPElementModel.h b/MasterPassword/ObjC/Mac/MPElementModel.h index 2283c8d5..6aaaccde 100644 --- a/MasterPassword/ObjC/Mac/MPElementModel.h +++ b/MasterPassword/ObjC/Mac/MPElementModel.h @@ -25,6 +25,7 @@ @property (nonatomic) MPElementType type; @property (nonatomic) NSString *typeName; @property (nonatomic) NSString *content; +@property (nonatomic) NSString *contentDisplay; @property (nonatomic) NSString *loginName; @property (nonatomic) NSNumber *uses; @property (nonatomic) NSUInteger counter; diff --git a/MasterPassword/ObjC/Mac/MPElementModel.m b/MasterPassword/ObjC/Mac/MPElementModel.m index dd82e60d..bfb2864a 100644 --- a/MasterPassword/ObjC/Mac/MPElementModel.m +++ b/MasterPassword/ObjC/Mac/MPElementModel.m @@ -111,13 +111,21 @@ - (void)updateContent:(MPElementEntity *)entity { - [entity resolveContentUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) { - if ([[MPConfig get].hidePasswords boolValue] && !([NSEvent modifierFlags] & NSAlternateKeyMask)) - result = [result stringByReplacingMatchesOfExpression: - [NSRegularExpression regularExpressionWithPattern:@"." options:0 error:nil] - withTemplate:@"●"]; + static NSRegularExpression *re_anyChar; + static dispatch_once_t once = 0; + dispatch_once( &once, ^{ + re_anyChar = [NSRegularExpression regularExpressionWithPattern:@"." options:0 error:nil]; + } ); - PearlMainQueue( ^{ self.content = result; } ); + [entity resolveContentUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) { + NSString *displayResult = result; + if ([[MPConfig get].hidePasswords boolValue] && !([NSEvent modifierFlags] & NSAlternateKeyMask)) + displayResult = [displayResult stringByReplacingMatchesOfExpression:re_anyChar withTemplate:@"●"]; + + PearlMainQueue( ^{ + self.content = result; + self.contentDisplay = displayResult; + } ); }]; } diff --git a/MasterPassword/ObjC/Mac/MPMacAppDelegate.h b/MasterPassword/ObjC/Mac/MPMacAppDelegate.h index 1df507f7..180920c0 100644 --- a/MasterPassword/ObjC/Mac/MPMacAppDelegate.h +++ b/MasterPassword/ObjC/Mac/MPMacAppDelegate.h @@ -26,8 +26,6 @@ @property(nonatomic, weak) IBOutlet NSMenuItem *createUserItem; @property(nonatomic, weak) IBOutlet NSMenuItem *deleteUserItem; @property(nonatomic, weak) IBOutlet NSMenuItem *usersItem; -@property(nonatomic, weak) IBOutlet NSMenuItem *dialogStyleRegular; -@property(nonatomic, weak) IBOutlet NSMenuItem *dialogStyleHUD; @property(nonatomic, weak) IBOutlet NSButton *openAtLoginButton; @property(nonatomic, weak) IBOutlet NSButton *enableCloudButton; diff --git a/MasterPassword/ObjC/Mac/MPMacAppDelegate.m b/MasterPassword/ObjC/Mac/MPMacAppDelegate.m index a482937c..248f9c49 100644 --- a/MasterPassword/ObjC/Mac/MPMacAppDelegate.m +++ b/MasterPassword/ObjC/Mac/MPMacAppDelegate.m @@ -37,7 +37,7 @@ static EventHotKeyID MPLockHotKey = { .signature = 'lock', .id = 1 }; + (void)initialize { - static dispatch_once_t initialize; + static dispatch_once_t initialize = 0; dispatch_once( &initialize, ^{ [MPMacConfig get]; @@ -71,7 +71,6 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - PearlProfiler *profiler = [PearlProfiler profilerForTask:@"applicationDidFinishLaunching"]; // Setup delegates and listeners. [MPConfig get].delegate = self; __weak id weakSelf = self; @@ -90,7 +89,6 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven [weakSelf updateMenuItems]; } ); } forKeyPath:@"storeManager.cloudAvailable" options:0 context:nil]; - [profiler finishJob:@"observers"]; // Status item. self.statusView = [[RHStatusItemView alloc] initWithStatusBarItem: @@ -99,41 +97,24 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven self.statusView.menu = self.statusMenu; self.statusView.target = self; self.statusView.action = @selector( showMenu ); - [profiler finishJob:@"statusView"]; [[NSNotificationCenter defaultCenter] addObserverForName:USMStoreDidChangeNotification object:nil - queue:[NSOperationQueue mainQueue] usingBlock: - ^(NSNotification *note) { + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { [self updateUsers]; }]; [[NSNotificationCenter defaultCenter] addObserverForName:USMStoreDidImportChangesNotification object:nil - queue:[NSOperationQueue mainQueue] usingBlock: - ^(NSNotification *note) { + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { [self updateUsers]; }]; [[NSNotificationCenter defaultCenter] addObserverForName:MPCheckConfigNotification object:nil - queue:[NSOperationQueue mainQueue] usingBlock: - ^(NSNotification *note) { + queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { NSString *key = note.object; if (!key || [key isEqualToString:NSStringFromSelector( @selector( hidePasswords ) )]) self.hidePasswordsItem.state = [[MPConfig get].hidePasswords boolValue]? NSOnState: NSOffState; if (!key || [key isEqualToString:NSStringFromSelector( @selector( rememberLogin ) )]) self.rememberPasswordItem.state = [[MPConfig get].rememberLogin boolValue]? NSOnState: NSOffState; - if (!key || [key isEqualToString:NSStringFromSelector( @selector( dialogStyleHUD ) )]) { - self.dialogStyleRegular.state = ![[MPMacConfig get].dialogStyleHUD boolValue]? NSOnState: NSOffState; - self.dialogStyleHUD.state = [[MPMacConfig get].dialogStyleHUD boolValue]? NSOnState: NSOffState; - if (![self.passwordWindow.window isVisible]) - self.passwordWindow = nil; - else { - [self.passwordWindow close]; - self.passwordWindow = nil; - [self showPasswordWindow:nil]; - } - } }]; - [profiler finishJob:@"notificationCenter"]; [self updateUsers]; - [profiler finishJob:@"updateUsers"]; // Global hotkey. EventHotKeyRef hotKeyRef; @@ -148,7 +129,6 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven status = RegisterEventHotKey( 35 /* p */, controlKey + optionKey + cmdKey, MPLockHotKey, GetApplicationEventTarget(), 0, &hotKeyRef ); if (status != noErr) err( @"Error registering 'lock' hotkey: %i", (int)status ); - [profiler finishJob:@"hotKey"]; // Initial display. if ([[MPMacConfig get].firstRun boolValue]) { @@ -156,7 +136,6 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven [self.initialWindow.window setLevel:NSFloatingWindowLevel]; [self.initialWindow showWindow:self]; } - [profiler finishJob:@"initial display"]; } - (void)applicationWillResignActive:(NSNotification *)notification { @@ -230,12 +209,113 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven [self signOutAnimated:NO]; - NSError *error = nil; - NSManagedObjectContext *context = [MPMacAppDelegate managedObjectContextForMainThreadIfReady]; - self.activeUser = (MPUserEntity *)[context existingObjectWithID:[item representedObject] error:&error]; + NSManagedObjectContext *mainContext = [MPMacAppDelegate managedObjectContextForMainThreadIfReady]; + self.activeUser = [MPUserEntity existingObjectWithID:[item representedObject] inContext:mainContext]; +} - if (error) - err( @"While looking up selected user: %@", error ); +- (IBAction)exportSitesSecure:(id)sender { + + [self exportSitesAndRevealPasswords:NO]; +} + +- (IBAction)exportSitesReveal:(id)sender { + + [self exportSitesAndRevealPasswords:YES]; +} + +- (IBAction)importSites:(id)sender { + + NSOpenPanel *openPanel = [NSOpenPanel openPanel]; + openPanel.allowsMultipleSelection = NO; + openPanel.canChooseDirectories = NO; + openPanel.title = @"Master Password"; + openPanel.message = @"Locate the Master Password export file to import."; + openPanel.prompt = @"Import"; + openPanel.directoryURL = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject; + openPanel.allowedFileTypes = @[ @"mpsites" ]; + [NSApp activateIgnoringOtherApps:YES]; + if ([openPanel runModal] == NSFileHandlingPanelCancelButton) + return; + + NSURL *url = openPanel.URL; + + PearlNotMainQueue( ^{ + NSError *error; + NSURLResponse *response; + NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] + returningResponse:&response error:&error]; + if (error) + err( @"While reading imported sites from %@: %@", url, error ); + if (!importedSitesData) + return; + + NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding]; + MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) { + __block NSString *masterPassword = nil; + + PearlMainQueueWait( ^{ + NSAlert *alert = [NSAlert new]; + [alert addButtonWithTitle:@"Unlock"]; + [alert addButtonWithTitle:@"Cancel"]; + alert.messageText = @"Import File's Master Password"; + alert.informativeText = strf( @"%@'s export was done using a different master password.\n" + @"Enter that master password to unlock the exported data.", userName ); + alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]; + [alert layout]; + if ([alert runModal] == NSAlertFirstButtonReturn) + masterPassword = ((NSTextField *)alert.accessoryView).stringValue; + } ); + + return masterPassword; + } askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) { + __block NSString *masterPassword = nil; + + PearlMainQueueWait( ^{ + NSAlert *alert = [NSAlert new]; + [alert addButtonWithTitle:@"Import"]; + [alert addButtonWithTitle:@"Cancel"]; + alert.messageText = strf( @"Master Password for\n%@", userName ); + alert.informativeText = strf( @"Imports %lu sites, overwriting %lu.", + (unsigned long)importCount, (unsigned long)deleteCount ); + alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]; + [alert layout]; + if ([alert runModal] == NSAlertFirstButtonReturn) + masterPassword = ((NSTextField *)alert.accessoryView).stringValue; + } ); + + return masterPassword; + }]; + + PearlMainQueue( ^{ + switch (result) { + case MPImportResultSuccess: { + [self updateUsers]; + + NSAlert *alert = [NSAlert new]; + alert.messageText = @"Successfully imported sites."; + [alert runModal]; + break; + } + case MPImportResultCancelled: + break; + case MPImportResultInternalError: + [NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{ + NSLocalizedDescriptionKey : @"Import failed because of an internal error." + }]]; + break; + case MPImportResultMalformedInput: + [NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{ + NSLocalizedDescriptionKey : @"The import doesn't look like a Master Password export." + }]]; + break; + case MPImportResultInvalidPassword: + [NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{ + NSLocalizedDescriptionKey : @"Incorrect master password for the import sites." + }]]; + break; + } + } ); + } ); } - (IBAction)togglePreference:(id)sender { @@ -269,10 +349,6 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven [context saveToStore]; }]; } - if (sender == self.dialogStyleRegular) - [MPMacConfig get].dialogStyleHUD = @NO; - if (sender == self.dialogStyleHUD) - [MPMacConfig get].dialogStyleHUD = @YES; [MPMacConfig flush]; } @@ -381,23 +457,81 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven // If no user, can't activate. if (![self activeUserForMainThread]) { + NSAlert *alert = [NSAlert new]; + alert.messageText = @"No User Selected"; + alert.informativeText = @"Begin by selecting or creating your user from the status menu (●●●|) next to the clock."; + [alert runModal]; [self showPopup:nil]; - [[NSAlert alertWithMessageText:@"No User Selected" defaultButton:[PearlStrings get].commonButtonOkay alternateButton:nil - otherButton:nil informativeTextWithFormat: - @"Begin by selecting or creating your user from the status menu (●●●|) next to the clock."] - runModal]; return; } // Don't show window if we weren't already running (ie. if we haven't been activated before). + PearlProfiler *profiler = [PearlProfiler profilerForTask:@"passwordWindow"]; if (!self.passwordWindow) self.passwordWindow = [[MPPasswordWindowController alloc] initWithWindowNibName:@"MPPasswordWindowController"]; + [profiler finishJob:@"init"]; [self.passwordWindow showWindow:self]; + [profiler finishJob:@"show"]; } #pragma mark - Private +- (void)exportSitesAndRevealPasswords:(BOOL)revealPasswords { + + MPUserEntity *mainActiveUser = [self activeUserForMainThread]; + if (!mainActiveUser) { + NSAlert *alert = [NSAlert new]; + alert.messageText = @"No User Selected"; + alert.informativeText = @"To export your sites, first select the user whose sites to export."; + [alert runModal]; + [self showPopup:nil]; + return; + } + + if (!self.key) { + NSAlert *alert = [NSAlert new]; + alert.messageText = @"User Locked"; + alert.informativeText = @"To export your sites, first unlock your user by opening Master Password."; + [alert runModal]; + [self showPopup:nil]; + return; + } + + NSDateFormatter *exportDateFormatter = [NSDateFormatter new]; + [exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"]; + + NSSavePanel *savePanel = [NSSavePanel savePanel]; + savePanel.title = @"Master Password"; + savePanel.message = @"Pick a location for the export Master Password's sites."; + if (revealPasswords) + savePanel.message = strf( @"%@\nWARNING: Your passwords will be visible. Make sure to always keep the file in a secure location.", + savePanel.message ); + savePanel.prompt = @"Export"; + savePanel.directoryURL = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject; + savePanel.nameFieldStringValue = strf( @"%@ (%@).mpsites", mainActiveUser.name, + [exportDateFormatter stringFromDate:[NSDate date]] ); + savePanel.allowedFileTypes = @[ @"mpsites" ]; + [NSApp activateIgnoringOtherApps:YES]; + if ([savePanel runModal] == NSFileHandlingPanelCancelButton) + return; + + NSError *coordinateError = nil; + NSString *exportedSites = [self exportSitesRevealPasswords:revealPasswords]; + [[[NSFileCoordinator alloc] initWithFilePresenter:nil] coordinateWritingItemAtURL:savePanel.URL options:0 error:&coordinateError + byAccessor:^(NSURL *newURL) { + NSError *writeError = nil; + if (![exportedSites writeToURL:newURL atomically:NO encoding:NSUTF8StringEncoding error:&writeError]) + PearlMainQueue( ^{ + [[NSAlert alertWithError:writeError] runModal]; + } ); + }]; + if (coordinateError) + PearlMainQueue( ^{ + [[NSAlert alertWithError:coordinateError] runModal]; + } ); +} + - (void)updateUsers { [[[self.usersItem submenu] itemArray] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { @@ -405,8 +539,8 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven [[self.usersItem submenu] removeItem:obj]; }]; - NSManagedObjectContext *context = [MPMacAppDelegate managedObjectContextForMainThreadIfReady]; - if (!context) { + NSManagedObjectContext *mainContext = [MPMacAppDelegate managedObjectContextForMainThreadIfReady]; + if (!mainContext) { self.createUserItem.title = @"New User (Not ready)"; self.createUserItem.enabled = NO; self.createUserItem.toolTip = @"Please wait until the app is fully loaded."; @@ -418,20 +552,20 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven return; } - MPUserEntity *activeUser = [self activeUserInContext:context]; + MPUserEntity *mainActiveUser = [self activeUserInContext:mainContext]; self.createUserItem.title = @"New User"; self.createUserItem.enabled = YES; self.createUserItem.toolTip = nil; - self.deleteUserItem.title = activeUser? @"Delete User": @"Delete User (None Selected)"; - self.deleteUserItem.enabled = activeUser != nil; - self.deleteUserItem.toolTip = activeUser? nil: @"First select the user to delete."; + self.deleteUserItem.title = mainActiveUser? @"Delete User": @"Delete User (None Selected)"; + self.deleteUserItem.enabled = mainActiveUser != nil; + self.deleteUserItem.toolTip = mainActiveUser? nil: @"First select the user to delete."; NSError *error = nil; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )]; fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ]; - NSArray *users = [context executeFetchRequest:fetchRequest error:&error]; + NSArray *users = [mainContext executeFetchRequest:fetchRequest error:&error]; if (!users) err( @"Failed to load users: %@", error ); @@ -449,10 +583,10 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven [userItem setRepresentedObject:[user objectID]]; [[self.usersItem submenu] addItem:userItem]; - if (!activeUser && [user.name isEqualToString:[MPMacConfig get].usedUserName]) - [super setActiveUser:activeUser = user]; + if (!mainActiveUser && [user.name isEqualToString:[MPMacConfig get].usedUserName]) + [super setActiveUser:mainActiveUser = user]; - if ([activeUser isEqual:user]) { + if ([mainActiveUser isEqual:user]) { userItem.state = NSOnState; self.usersItem.state = NSOffState; } diff --git a/MasterPassword/ObjC/Mac/MPMacConfig.h b/MasterPassword/ObjC/Mac/MPMacConfig.h index 9540e648..c3a208fa 100644 --- a/MasterPassword/ObjC/Mac/MPMacConfig.h +++ b/MasterPassword/ObjC/Mac/MPMacConfig.h @@ -11,6 +11,5 @@ @interface MPMacConfig : MPConfig @property(nonatomic, retain) NSString *usedUserName; -@property(nonatomic, retain) NSNumber *dialogStyleHUD; @end diff --git a/MasterPassword/ObjC/Mac/MPMacConfig.m b/MasterPassword/ObjC/Mac/MPMacConfig.m index 693f8d26..4ce6931d 100644 --- a/MasterPassword/ObjC/Mac/MPMacConfig.m +++ b/MasterPassword/ObjC/Mac/MPMacConfig.m @@ -9,7 +9,6 @@ @implementation MPMacConfig @dynamic usedUserName; -@dynamic dialogStyleHUD; - (id)init { @@ -18,7 +17,6 @@ [self.defaults registerDefaults:@{ NSStringFromSelector( @selector(iTunesID) ) : @"510296984", - NSStringFromSelector( @selector(dialogStyleHUD) ) : @NO, }]; return self; diff --git a/MasterPassword/ObjC/Mac/MPPasswordDialogController.h b/MasterPassword/ObjC/Mac/MPPasswordDialogController.h deleted file mode 100644 index 78587a2e..00000000 --- a/MasterPassword/ObjC/Mac/MPPasswordDialogController.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// MPPasswordDialogController.h -// MasterPassword-Mac -// -// Created by Maarten Billemont on 04/03/12. -// Copyright (c) 2012 Lyndir. All rights reserved. -// - -#import -@class MPElementModel; - -@interface MPPasswordDialogController : NSWindowController - -@property(nonatomic, strong) NSMutableArray *elements; -@property(nonatomic, strong) NSIndexSet *elementSelectionIndexes; -@property(nonatomic, weak) IBOutlet NSTextField *siteField; -@property(nonatomic, weak) IBOutlet NSView *contentContainer; -@property(nonatomic, weak) IBOutlet NSTextField *userLabel; -@property(nonatomic, weak) IBOutlet NSCollectionView *siteCollectionView; - -- (void)updateElements; - -@end diff --git a/MasterPassword/ObjC/Mac/MPPasswordDialogController.m b/MasterPassword/ObjC/Mac/MPPasswordDialogController.m deleted file mode 100644 index d510a3de..00000000 --- a/MasterPassword/ObjC/Mac/MPPasswordDialogController.m +++ /dev/null @@ -1,478 +0,0 @@ -// -// MPPasswordDialogController.m -// MasterPassword-Mac -// -// Created by Maarten Billemont on 04/03/12. -// Copyright (c) 2012 Lyndir. All rights reserved. -// - -#import "MPPasswordDialogController.h" -#import "MPMacAppDelegate.h" -#import "MPAppDelegate_Key.h" -#import "MPAppDelegate_Store.h" -#import "MPElementModel.h" - -#define MPAlertUnlockMP @"MPAlertUnlockMP" -#define MPAlertIncorrectMP @"MPAlertIncorrectMP" -#define MPAlertCreateSite @"MPAlertCreateSite" - -@interface MPPasswordDialogController() - -@property(nonatomic) BOOL inProgress; - -@property(nonatomic, strong) NSOperationQueue *backgroundQueue; -@property(nonatomic, strong) NSAlert *loadingDataAlert; -@property(nonatomic) BOOL closing; - -@end - -@implementation MPPasswordDialogController - -#pragma mark - Life - -- (void)windowDidLoad { - - if ([[MPMacConfig get].dialogStyleHUD boolValue]) { - self.window.styleMask = NSHUDWindowMask | NSTitledWindowMask | NSUtilityWindowMask | NSClosableWindowMask; - self.userLabel.textColor = [NSColor whiteColor]; - } - else { - self.window.styleMask = NSTexturedBackgroundWindowMask | NSResizableWindowMask | NSTitledWindowMask | NSClosableWindowMask; - self.userLabel.textColor = [NSColor controlTextColor]; - } - - self.backgroundQueue = [NSOperationQueue new]; - self.backgroundQueue.maxConcurrentOperationCount = 1; - - [[MPMacAppDelegate get] addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) { -// [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { -// if (![MPAlgorithmDefault migrateUser:[[MPMacAppDelegate get] activeUserInContext:moc]]) -// [NSAlert alertWithMessageText:@"Migration Needed" defaultButton:@"OK" alternateButton:nil otherButton:nil -// informativeTextWithFormat:@"Certain sites require explicit migration to get updated to the latest version of the " -// @"Master Password algorithm. For these sites, a migration button will appear. Migrating these sites will cause " -// @"their passwords to change. You'll need to update your profile for that site with the new password."]; -// [moc saveToStore]; -// }]; - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - [self ensureLoadedAndUnlockedOrCloseIfLoggedOut:YES]; - [self updateElements]; - }]; - } forKeyPath:@"key" options:NSKeyValueObservingOptionInitial context:nil]; - [[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window - queue:[NSOperationQueue mainQueue] usingBlock: - ^(NSNotification *note) { - [self ensureLoadedAndUnlockedOrCloseIfLoggedOut:NO]; - [self.siteField selectText:nil]; - [self updateElements]; - }]; - [[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window - queue:[NSOperationQueue mainQueue] usingBlock: - ^(NSNotification *note) { - NSWindow *sheet = [self.window attachedSheet]; - if (sheet) - [NSApp endSheet:sheet]; - - [NSApp hide:nil]; - self.closing = NO; - }]; - [[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil - queue:[NSOperationQueue mainQueue] usingBlock: - ^(NSNotification *note) { - self.userLabel.stringValue = @""; - self.siteField.stringValue = @""; - self.elements = nil; - - [self ensureLoadedAndUnlockedOrCloseIfLoggedOut:YES]; - }]; - [[NSNotificationCenter defaultCenter] addObserverForName:MPSignedInNotification object:nil - queue:[NSOperationQueue mainQueue] usingBlock: - ^(NSNotification *note) { - self.userLabel.stringValue = PearlString( @"%@'s password for:", - [[MPMacAppDelegate get] activeUserForMainThread].name ); - }]; - [[NSNotificationCenter defaultCenter] addObserverForName:USMStoreDidChangeNotification object:nil - queue:[NSOperationQueue mainQueue] usingBlock: - ^(NSNotification *note) { - [self ensureLoadedAndUnlockedOrCloseIfLoggedOut:NO]; - }]; - - [super windowDidLoad]; -} - -- (void)close { - - self.closing = YES; - [super close]; -} - -#pragma mark - NSAlert - -- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { - - if (contextInfo == MPAlertIncorrectMP) { - [self close]; - return; - } - if (contextInfo == MPAlertUnlockMP) { - switch (returnCode) { - case NSAlertFirstButtonReturn: { - // "Unlock" button. - self.contentContainer.alphaValue = 0; - self.inProgress = YES; - - NSString *password = [(NSSecureTextField *)alert.accessoryView stringValue]; - [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { - MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc]; - NSString *userName = activeUser.name; - BOOL success = [[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc - usingMasterPassword:password]; - self.inProgress = NO; - - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - if (success) - self.contentContainer.alphaValue = 1; - else { - [[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{ - NSLocalizedDescriptionKey : PearlString( @"Incorrect master password for user %@", userName ) - }]] beginSheetModalForWindow:self.window modalDelegate:self - didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertIncorrectMP]; - } - }]; - }]; - break; - } - - case NSAlertSecondButtonReturn: { - // "Change" button. - NSAlert *alert_ = [NSAlert new]; - [alert_ addButtonWithTitle:@"Update"]; - [alert_ addButtonWithTitle:@"Cancel"]; - [alert_ setMessageText:@"Changing Master Password"]; - [alert_ setInformativeText:@"This will allow you to log in with a different master password.\n\n" - @"Note that you will only see the sites and passwords for the master password you log in with.\n" - @"If you log in with a different master password, your current sites will be unavailable.\n\n" - @"You can always change back to your current master password later.\n" - @"Your current sites and passwords will then become available again."]; - - if ([alert_ runModal] == NSAlertFirstButtonReturn) { - [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { - MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:context]; - activeUser.keyID = nil; - [[MPMacAppDelegate get] forgetSavedKeyFor:activeUser]; - [[MPMacAppDelegate get] signOutAnimated:YES]; - [context saveToStore]; - }]; - } - break; - } - - case NSAlertThirdButtonReturn: { - // "Cancel" button. - [self close]; - break; - } - - default: - break; - } - - return; - } - if (contextInfo == MPAlertCreateSite) { - switch (returnCode) { - case NSAlertFirstButtonReturn: { - // "Create" button. - [[MPMacAppDelegate get] addElementNamed:[self.siteField stringValue] completion:^(MPElementEntity *element) { - if (element) - PearlMainQueue( ^{ [self updateElements]; } ); - }]; - break; - } - case NSAlertThirdButtonReturn: - // "Cancel" button. - break; - default: - break; - } - } -} - -#pragma mark - NSCollectionViewDelegate - -#pragma mark - NSTextFieldDelegate - -- (void)doCommandBySelector:(SEL)commandSelector { - - if (commandSelector == @selector(insertNewline:)) - [self useSite]; -} - -- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector { - - if (commandSelector == @selector(cancel:)) - [self close]; - if (commandSelector == @selector(moveUp:)) - self.elementSelectionIndexes = - [NSIndexSet indexSetWithIndex:MAX(self.elementSelectionIndexes.firstIndex, (NSUInteger)1) - 1]; - if (commandSelector == @selector(moveDown:)) - self.elementSelectionIndexes = - [NSIndexSet indexSetWithIndex:MIN(self.elementSelectionIndexes.firstIndex + 1, self.elements.count - 1)]; - if (commandSelector == @selector(moveLeft:)) - [[self selectedView].animator setBoundsOrigin:NSZeroPoint]; - if (commandSelector == @selector(moveRight:)) - [[self selectedView].animator setBoundsOrigin:NSMakePoint( self.siteCollectionView.frame.size.width / 2, 0 )]; - if (commandSelector == @selector(insertNewline:)) - [self useSite]; - else - return NO; - - return YES; -} - -- (void)controlTextDidChange:(NSNotification *)note { - - if (note.object != self.siteField) - return; - - // Update the site content as the site name changes. - if ([[NSApp currentEvent] type] == NSKeyDown && - [[[NSApp currentEvent] charactersIgnoringModifiers] isEqualToString:@"\r"]) { // Return while completing. - [self useSite]; - return; - } - -// if ([[NSApp currentEvent] type] == NSKeyDown && -// [[[NSApp currentEvent] charactersIgnoringModifiers] characterAtIndex:0] == 0x1b) { // Escape while completing. -// [self trySiteWithAction:NO]; -// return; -// } - - [self updateElements]; -} - -#pragma mark - Private - -- (BOOL)ensureLoadedAndUnlockedOrCloseIfLoggedOut:(BOOL)closeIfLoggedOut { - - if (![self ensureStoreLoaded]) - return NO; - - if (self.closing || self.inProgress || !self.window.isKeyWindow) - return NO; - - return [self ensureUnlocked:closeIfLoggedOut]; -} - -- (BOOL)ensureStoreLoaded { - - if ([MPMacAppDelegate managedObjectContextForMainThreadIfReady]) { - // Store loaded. - if (self.loadingDataAlert.window) - [NSApp endSheet:self.loadingDataAlert.window]; - - return YES; - } - - [self.loadingDataAlert = [NSAlert alertWithMessageText:@"Opening Your Data" defaultButton:@"..." alternateButton:nil otherButton:nil - informativeTextWithFormat:@""] - beginSheetModalForWindow:self.window modalDelegate:self - didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:nil]; - - return NO; -} - -- (BOOL)ensureUnlocked:(BOOL)closeIfLoggedOut { - - __block BOOL unlocked = NO; - [MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *moc) { - MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:moc]; - NSString *userName = activeUser.name; - if (!activeUser) { - // No user to sign in with. - if (closeIfLoggedOut) - [self close]; - return; - } - if ([MPMacAppDelegate get].key) { - // Already logged in. - unlocked = YES; - return; - } - if (activeUser.saveKey && closeIfLoggedOut) { - // App was locked, don't instantly unlock again. - [self close]; - return; - } - if ([[MPMacAppDelegate get] signInAsUser:activeUser saveInContext:moc usingMasterPassword:nil]) { - // Loaded the key from the keychain. - unlocked = YES; - return; - } - - // Ask the user to set the key through his master password. - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - if ([MPMacAppDelegate get].key) - return; - - [self.siteField setStringValue:@""]; - - NSAlert *alert = [NSAlert new]; - [alert addButtonWithTitle:@"Unlock"]; - [alert addButtonWithTitle:@"Change"]; - [alert addButtonWithTitle:@"Cancel"]; - [alert setMessageText:@"Master Password is locked."]; - [alert setInformativeText:PearlString( @"The master password is required to unlock the application for:\n\n%@", userName )]; - - NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]; - [alert setAccessoryView:passwordField]; - [alert layout]; - [alert beginSheetModalForWindow:self.window modalDelegate:self - didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertUnlockMP]; - [passwordField becomeFirstResponder]; - }]; - }]; - - return unlocked; -} - -- (void)updateElements { - - if (![MPMacAppDelegate get].key) { - self.elements = nil; - return; - } - - NSString *query = [self.siteField.currentEditor string]; - [MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) { - NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )]; - fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO]]; - fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@", - query, query, [[MPMacAppDelegate get] activeUserInContext:context]]; - - NSError *error = nil; - NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error]; - if (!siteResults) { - err(@"While fetching elements for completion: %@", error); - return; - } - - NSMutableArray *newElements = [NSMutableArray arrayWithCapacity:[siteResults count]]; - for (MPElementEntity *element in siteResults) - [newElements addObject:[[MPElementModel alloc] initWithEntity:element]]; - self.elements = newElements; - if (!self.selectedElement) - self.elementSelectionIndexes = [newElements count]? [NSIndexSet indexSetWithIndex:0]: nil; - }]; -} - -- (NSUInteger)selectedIndex { - - if (!self.elementSelectionIndexes) - return NSNotFound; - NSUInteger selectedIndex = self.elementSelectionIndexes.firstIndex; - if (selectedIndex >= self.elements.count) - return NSNotFound; - - return selectedIndex; -} - -- (NSBox *)selectedView { - - NSUInteger selectedIndex = [self selectedIndex]; - if (selectedIndex == NSNotFound) - return nil; - - return (NSBox *)[self.siteCollectionView itemAtIndex:selectedIndex].view; -} - -- (MPElementModel *)selectedElement { - - NSUInteger selectedIndex = [self selectedIndex]; - if (selectedIndex == NSNotFound) - return nil; - - return (MPElementModel *)self.elements[selectedIndex]; -} - -- (void)setSelectedElement:(MPElementModel *)element { - - self.elementSelectionIndexes = [NSIndexSet indexSetWithIndex:[self.elements indexOfObject:element]]; -} - -- (void)useSite { - - MPElementModel *selectedElement = [self selectedElement]; - if (selectedElement) { - // Performing action while content is available. Copy it. - [self copyContent:selectedElement.content]; - - [self close]; - - NSUserNotification *notification = [NSUserNotification new]; - notification.title = @"Password Copied"; - if (selectedElement.loginName.length) - notification.subtitle = PearlString( @"%@ at %@", selectedElement.loginName, selectedElement.siteName ); - else - notification.subtitle = selectedElement.siteName; - [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; - } - else { - NSString *siteName = [self.siteField stringValue]; - if ([siteName length]) - // Performing action without content but a site name is written. - [self createNewSite:siteName]; - } -} - -- (void)copyContent:(NSString *)content { - - [[NSPasteboard generalPasteboard] declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; - if (![[NSPasteboard generalPasteboard] setString:content forType:NSPasteboardTypeString]) { - wrn(@"Couldn't copy password to pasteboard."); - return; - } - - [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) { - [[self.selectedElement entityInContext:moc] use]; - [moc saveToStore]; - }]; -} - -- (void)createNewSite:(NSString *)siteName { - - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - NSAlert *alert = [NSAlert new]; - [alert addButtonWithTitle:@"Create"]; - [alert addButtonWithTitle:@"Cancel"]; - [alert setMessageText:@"Create site?"]; - [alert setInformativeText:PearlString( @"Do you want to create a new site named:\n\n%@", siteName )]; - [alert beginSheetModalForWindow:self.window modalDelegate:self - didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertCreateSite]; - }]; -} - -#pragma mark - KVO - -- (void)setElementSelectionIndexes:(NSIndexSet *)elementSelectionIndexes { - - // First reset bounds. - PearlMainQueue(^{ - NSUInteger selectedIndex = self.elementSelectionIndexes.firstIndex; - if (selectedIndex != NSNotFound && selectedIndex < self.elements.count) - [[self selectedView].animator setBoundsOrigin:NSZeroPoint]; - } ); - - _elementSelectionIndexes = elementSelectionIndexes; -} - -- (void)insertObject:(MPElementModel *)model inElementsAtIndex:(NSUInteger)index { - - [self.elements insertObject:model atIndex:index]; -} - -- (void)removeObjectFromElementsAtIndex:(NSUInteger)index { - - [self.elements removeObjectAtIndex:index]; -} - -@end diff --git a/MasterPassword/ObjC/Mac/MPPasswordDialogController.xib b/MasterPassword/ObjC/Mac/MPPasswordDialogController.xib deleted file mode 100644 index bcdee008..00000000 --- a/MasterPassword/ObjC/Mac/MPPasswordDialogController.xib +++ /dev/null @@ -1,380 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NSNegateBoolean - - - - - - diff --git a/MasterPassword/ObjC/Mac/MPPasswordWindowController.xib b/MasterPassword/ObjC/Mac/MPPasswordWindowController.xib index 97536265..45c31f49 100644 --- a/MasterPassword/ObjC/Mac/MPPasswordWindowController.xib +++ b/MasterPassword/ObjC/Mac/MPPasswordWindowController.xib @@ -1,8 +1,7 @@ - + - - + @@ -25,7 +24,7 @@ - + @@ -66,14 +65,13 @@ - + - - + @@ -93,11 +91,11 @@ - + - + @@ -118,11 +116,20 @@ - + - + + + + + + + + + + @@ -324,11 +331,9 @@ - - @@ -353,7 +358,7 @@ NSNegateBoolean - + @@ -364,7 +369,6 @@ - @@ -384,7 +388,6 @@ - @@ -408,6 +410,11 @@ + + + NSNegateBoolean + + @@ -417,26 +424,25 @@ NSNegateBoolean - - - NSNegateBoolean - - - - + + + + NSNegateBoolean + + @@ -446,16 +452,10 @@ NSNegateBoolean - - - NSNegateBoolean - - - @@ -466,6 +466,11 @@ + + + NSNegateBoolean + + @@ -475,26 +480,25 @@ NSNegateBoolean - - - NSNegateBoolean - - - - + + + + NSNegateBoolean + + @@ -513,22 +517,16 @@ NSNegateBoolean - - - NSNegateBoolean - - - - + @@ -606,7 +604,6 @@ - diff --git a/MasterPassword/ObjC/Mac/MasterPassword-Info.plist b/MasterPassword/ObjC/Mac/MasterPassword-Info.plist index 765a5fb4..e6df7def 100644 --- a/MasterPassword/ObjC/Mac/MasterPassword-Info.plist +++ b/MasterPassword/ObjC/Mac/MasterPassword-Info.plist @@ -8,6 +8,29 @@ en CFBundleDisplayName Master Password + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + mpsites + + CFBundleTypeIconFile + + CFBundleTypeIconFiles + + CFBundleTypeName + Master Password sites + CFBundleTypeRole + Viewer + LSHandlerRank + Owner + LSItemContentTypes + + com.lyndir.lhunath.MasterPassword.sites + + + CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile @@ -24,6 +47,19 @@ [auto] CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.lyndir.lhunath.MasterPassword + CFBundleURLSchemes + + com.lyndir.lhunath.MasterPassword + + + CFBundleVersion [auto] LSApplicationCategoryType @@ -38,5 +74,27 @@ MainMenu NSPrincipalClass NSApplication + UTExportedTypeDeclarations + + + UTTypeDescription + Master Password sites + UTTypeIconFile + + UTTypeIdentifier + com.lyndir.lhunath.MasterPassword.sites + UTTypeSize320IconFile + + UTTypeSize64IconFile + + UTTypeTagSpecification + + public.filename-extension + + mpsites + + + + diff --git a/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj b/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj index 4fc51e25..e87d3574 100644 --- a/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj +++ b/MasterPassword/ObjC/Mac/MasterPassword-Mac.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ 93D3970BCF85F7902E611168 /* PearlProfiler.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DB3A8ADED08C39A6228 /* PearlProfiler.m */; }; 93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; }; 93D39C5789EFA607CF788082 /* MPElementModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E73BF5CBF8E5B005CD3 /* MPElementModel.m */; }; - 93D39C7C2BE7C0E0763B0177 /* MPElementCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D394495528B10D1B61A2C3 /* MPElementCollectionView.m */; }; 93D39D304F73B3BBA031522A /* PearlProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D394EEFF5BF555A55AF361 /* PearlProfiler.h */; }; 93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; }; 93D39F833DEC1C89B2F795AC /* MPPasswordWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */; }; @@ -64,10 +63,7 @@ DA5E5D021724A667003798D8 /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CB11724A667003798D8 /* MPUserEntity.m */; }; DA5E5D031724A667003798D8 /* MPMacAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CB41724A667003798D8 /* MPMacAppDelegate.m */; }; DA5E5D041724A667003798D8 /* MPMacConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CB61724A667003798D8 /* MPMacConfig.m */; }; - DA5E5D051724A667003798D8 /* MPPasswordDialogController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CB81724A667003798D8 /* MPPasswordDialogController.m */; }; - DA5E5D061724A667003798D8 /* MPPasswordDialogController.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5CB91724A667003798D8 /* MPPasswordDialogController.xib */; }; DA5E5D081724A667003798D8 /* MasterPassword.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5CBF1724A667003798D8 /* MasterPassword.entitlements */; }; - DA5E5D091724A667003798D8 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5CC01724A667003798D8 /* Credits.rtf */; }; DA5E5D0A1724A667003798D8 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5CC21724A667003798D8 /* InfoPlist.strings */; }; DA5E5D0B1724A667003798D8 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5CC41724A667003798D8 /* MainMenu.xib */; }; DA5E5D0C1724A667003798D8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CC61724A667003798D8 /* main.m */; }; @@ -312,9 +308,7 @@ 93D39240B5143E01F0B75E96 /* MPElementModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementModel.h; sourceTree = ""; }; 93D392C3918763B3B72CF366 /* MPPasswordWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordWindowController.h; sourceTree = ""; }; 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = ""; }; - 93D394495528B10D1B61A2C3 /* MPElementCollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementCollectionView.m; sourceTree = ""; }; 93D394EEFF5BF555A55AF361 /* PearlProfiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PearlProfiler.h; path = ../../../External/Pearl/Pearl/PearlProfiler.h; sourceTree = ""; }; - 93D3960D320FF8A072B092E3 /* MPElementCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementCollectionView.h; sourceTree = ""; }; 93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = ""; }; 93D3977484534E99F9BA579D /* MPPasswordWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordWindow.h; sourceTree = ""; }; 93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordWindowController.m; sourceTree = ""; }; @@ -393,13 +387,9 @@ DA5E5CB41724A667003798D8 /* MPMacAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMacAppDelegate.m; sourceTree = ""; }; DA5E5CB51724A667003798D8 /* MPMacConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMacConfig.h; sourceTree = ""; }; DA5E5CB61724A667003798D8 /* MPMacConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMacConfig.m; sourceTree = ""; }; - DA5E5CB71724A667003798D8 /* MPPasswordDialogController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordDialogController.h; sourceTree = ""; }; - DA5E5CB81724A667003798D8 /* MPPasswordDialogController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordDialogController.m; sourceTree = ""; }; - DA5E5CB91724A667003798D8 /* MPPasswordDialogController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPPasswordDialogController.xib; sourceTree = ""; }; DA5E5CBA1724A667003798D8 /* MasterPassword-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "MasterPassword-Info.plist"; sourceTree = ""; }; DA5E5CBE1724A667003798D8 /* MasterPassword-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MasterPassword-Prefix.pch"; sourceTree = ""; }; DA5E5CBF1724A667003798D8 /* MasterPassword.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = MasterPassword.entitlements; sourceTree = ""; }; - DA5E5CC11724A667003798D8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; DA5E5CC31724A667003798D8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; DA5E5CC51724A667003798D8 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; DA5E5CC61724A667003798D8 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; @@ -1206,19 +1196,13 @@ DA5E5CB41724A667003798D8 /* MPMacAppDelegate.m */, DA5E5CB51724A667003798D8 /* MPMacConfig.h */, DA5E5CB61724A667003798D8 /* MPMacConfig.m */, - DA5E5CB71724A667003798D8 /* MPPasswordDialogController.h */, - DA5E5CB81724A667003798D8 /* MPPasswordDialogController.m */, - DA5E5CB91724A667003798D8 /* MPPasswordDialogController.xib */, DA5E5CBA1724A667003798D8 /* MasterPassword-Info.plist */, DA5E5CBE1724A667003798D8 /* MasterPassword-Prefix.pch */, DA5E5CBF1724A667003798D8 /* MasterPassword.entitlements */, - DA5E5CC01724A667003798D8 /* Credits.rtf */, DA5E5CC21724A667003798D8 /* InfoPlist.strings */, DA5E5CC41724A667003798D8 /* MainMenu.xib */, DA5E5CC61724A667003798D8 /* main.m */, DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */, - 93D394495528B10D1B61A2C3 /* MPElementCollectionView.m */, - 93D3960D320FF8A072B092E3 /* MPElementCollectionView.h */, 93D39E73BF5CBF8E5B005CD3 /* MPElementModel.m */, 93D39240B5143E01F0B75E96 /* MPElementModel.h */, DA2508F019511D3600AC23F1 /* MPPasswordWindowController.xib */, @@ -2396,9 +2380,7 @@ DACA296F1705DF81002C6C22 /* Crashlytics.plist in Resources */, DACA29731705E1A8002C6C22 /* ciphers.plist in Resources */, DACA29741705E1A8002C6C22 /* dictionary.lst in Resources */, - DA5E5D061724A667003798D8 /* MPPasswordDialogController.xib in Resources */, DA5E5D081724A667003798D8 /* MasterPassword.entitlements in Resources */, - DA5E5D091724A667003798D8 /* Credits.rtf in Resources */, DA5E5D0A1724A667003798D8 /* InfoPlist.strings in Resources */, DA5E5D0B1724A667003798D8 /* MainMenu.xib in Resources */, DA5E5D551724F9C8003798D8 /* MasterPassword.iconset in Resources */, @@ -2479,10 +2461,8 @@ DA5E5D021724A667003798D8 /* MPUserEntity.m in Sources */, DA5E5D031724A667003798D8 /* MPMacAppDelegate.m in Sources */, DA5E5D041724A667003798D8 /* MPMacConfig.m in Sources */, - DA5E5D051724A667003798D8 /* MPPasswordDialogController.m in Sources */, DA5E5D0C1724A667003798D8 /* main.m in Sources */, DA5E5D0D1724A667003798D8 /* MasterPassword.xcdatamodeld in Sources */, - 93D39C7C2BE7C0E0763B0177 /* MPElementCollectionView.m in Sources */, 93D39C5789EFA607CF788082 /* MPElementModel.m in Sources */, 93D39F833DEC1C89B2F795AC /* MPPasswordWindowController.m in Sources */, 93D390C676DF52DA7E459F19 /* MPPasswordWindow.m in Sources */, @@ -2560,14 +2540,6 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - DA5E5CC01724A667003798D8 /* Credits.rtf */ = { - isa = PBXVariantGroup; - children = ( - DA5E5CC11724A667003798D8 /* en */, - ); - name = Credits.rtf; - sourceTree = ""; - }; DA5E5CC21724A667003798D8 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( @@ -2798,7 +2770,6 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_OBJC_ARC = YES; CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements; - CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -2818,7 +2789,6 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_OBJC_ARC = YES; CODE_SIGN_ENTITLEMENTS = MasterPassword.entitlements; - CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/MasterPassword/ObjC/Mac/MasterPassword.entitlements b/MasterPassword/ObjC/Mac/MasterPassword.entitlements index 3bce57e2..ddc76204 100644 --- a/MasterPassword/ObjC/Mac/MasterPassword.entitlements +++ b/MasterPassword/ObjC/Mac/MasterPassword.entitlements @@ -15,5 +15,7 @@ HL3Q45LX9N.com.lyndir.lhunath.MasterPassword.Mac + com.apple.security.files.user-selected.read-write + diff --git a/MasterPassword/ObjC/Mac/en.lproj/Credits.rtf b/MasterPassword/ObjC/Mac/en.lproj/Credits.rtf deleted file mode 100644 index 46576ef2..00000000 --- a/MasterPassword/ObjC/Mac/en.lproj/Credits.rtf +++ /dev/null @@ -1,29 +0,0 @@ -{\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} -{\colortbl;\red255\green255\blue255;} -\paperw9840\paperh8400 -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural - -\f0\b\fs24 \cf0 Engineering: -\b0 \ - Some people\ -\ - -\b Human Interface Design: -\b0 \ - Some other people\ -\ - -\b Testing: -\b0 \ - Hopefully not nobody\ -\ - -\b Documentation: -\b0 \ - Whoever\ -\ - -\b With special thanks to: -\b0 \ - Mom\ -} diff --git a/MasterPassword/ObjC/Mac/en.lproj/MainMenu.xib b/MasterPassword/ObjC/Mac/en.lproj/MainMenu.xib index 7725f6a8..dc0d7b07 100644 --- a/MasterPassword/ObjC/Mac/en.lproj/MainMenu.xib +++ b/MasterPassword/ObjC/Mac/en.lproj/MainMenu.xib @@ -17,8 +17,6 @@ - - @@ -97,7 +95,7 @@ - + @@ -142,25 +140,53 @@ - + - + - + - + - + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + diff --git a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m index 2906e143..45d5fb82 100644 --- a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m +++ b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m @@ -182,7 +182,7 @@ dispatch_group_enter( importPasswordGroup ); dispatch_async( dispatch_get_main_queue(), ^{ [PearlAlert showAlertWithTitle:@"Import File's Master Password" - message:PearlString( @"%@'s export was done using a different master password.\n" + message:strf( @"%@'s export was done using a different master password.\n" @"Enter that master password to unlock the exported data.", userName ) viewStyle:UIAlertViewStyleSecureTextInput initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) { @@ -405,6 +405,7 @@ [PearlInfoPlist get].CFBundleShortVersionString, [PearlInfoPlist get].CFBundleVersion ); + NSString *exportedSites = [self exportSitesRevealPasswords:revealPasswords]; NSDateFormatter *exportDateFormatter = [NSDateFormatter new]; [exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];