2
0
MasterPassword/platform-darwin/Source/Mac/MPMacAppDelegate.m

767 lines
31 KiB
Mathematica
Raw Normal View History

2017-04-05 20:56:22 +00:00
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
2017-04-05 20:56:22 +00:00
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
2017-04-05 20:56:22 +00:00
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
2017-04-05 20:56:22 +00:00
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#import "MPMacAppDelegate.h"
2012-05-13 08:24:19 +00:00
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
2020-03-25 17:12:06 +00:00
#import "MPSecrets.h"
2020-04-04 21:34:07 +00:00
#import "mpw-marshal.h"
2020-03-25 17:12:06 +00:00
#import <Carbon/Carbon.h>
#import <ServiceManagement/ServiceManagement.h>
2020-03-25 17:12:06 +00:00
#import <Sentry/Sentry.h>
#import <Countly/Countly.h>
#define LOGIN_HELPER_BUNDLE_ID @"com.lyndir.lhunath.MasterPassword.Mac.LoginHelper"
2014-06-25 00:30:15 +00:00
@implementation MPMacAppDelegate
2013-04-20 18:11:19 +00:00
2012-06-24 14:29:51 +00:00
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wfour-char-constants"
2013-04-20 18:11:19 +00:00
static EventHotKeyID MPShowHotKey = { .signature = 'show', .id = 1 };
static EventHotKeyID MPLockHotKey = { .signature = 'lock', .id = 1 };
2012-06-24 14:29:51 +00:00
#pragma clang diagnostic pop
+ (void)initialize {
2012-06-08 21:46:13 +00:00
[MPMacConfig get];
}
2012-06-08 21:46:13 +00:00
static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) {
// Extract the hotkey ID.
2012-06-08 21:46:13 +00:00
EventHotKeyID hotKeyID;
2013-04-20 18:11:19 +00:00
GetEventParameter( theEvent, kEventParamDirectObject, typeEventHotKeyID,
NULL, sizeof( hotKeyID ), NULL, &hotKeyID );
2012-06-08 21:46:13 +00:00
// Check which hotkey this was.
if (hotKeyID.signature == MPShowHotKey.signature && hotKeyID.id == MPShowHotKey.id) {
[((__bridge MPMacAppDelegate *)userData) showPasswordWindow:nil];
return noErr;
}
if (hotKeyID.signature == MPLockHotKey.signature && hotKeyID.id == MPLockHotKey.id) {
[((__bridge MPMacAppDelegate *)userData) lock:nil];
return noErr;
}
2012-06-08 21:46:13 +00:00
return eventNotHandledErr;
}
2014-06-25 00:30:15 +00:00
#pragma mark - Life
2013-04-20 18:11:19 +00:00
2014-06-25 00:30:15 +00:00
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
2020-03-25 17:12:06 +00:00
@try {
// Sentry
2020-04-04 23:50:37 +00:00
[SentrySDK initWithOptions:@{
@"dsn" : decrypt( sentryDSN ),
2020-03-25 17:12:06 +00:00
#ifdef DEBUG
2020-04-04 23:50:37 +00:00
@"debug" : @(YES),
@"environment": @"Development",
2020-03-25 17:12:06 +00:00
#elif PUBLIC
2020-04-04 23:50:37 +00:00
@"debug" : @(NO),
@"environment": @"Public",
2020-03-25 17:12:06 +00:00
#else
2020-04-04 23:50:37 +00:00
@"debug" : @(NO),
@"environment": @"Private",
#endif
2020-04-04 23:50:37 +00:00
@"enabled" : [MPMacConfig get].sendInfo,
}];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
2020-03-25 17:12:06 +00:00
PearlLogLevel level = PearlLogLevelWarn;
if ([[MPConfig get].sendInfo boolValue])
level = PearlLogLevelDebug;
2020-03-25 17:12:06 +00:00
if (message.level >= level) {
2020-04-04 23:50:37 +00:00
SentryLevel sentryLevel = kSentryLevelInfo;
2020-03-25 17:12:06 +00:00
switch (message.level) {
case PearlLogLevelTrace:
sentryLevel = kSentryLevelNone;
2020-03-25 17:12:06 +00:00
break;
case PearlLogLevelDebug:
2020-04-04 23:50:37 +00:00
sentryLevel = kSentryLevelDebug;
2020-03-25 17:12:06 +00:00
break;
case PearlLogLevelInfo:
2020-04-04 23:50:37 +00:00
sentryLevel = kSentryLevelInfo;
2020-03-25 17:12:06 +00:00
break;
case PearlLogLevelWarn:
2020-04-04 23:50:37 +00:00
sentryLevel = kSentryLevelWarning;
2020-03-25 17:12:06 +00:00
break;
case PearlLogLevelError:
2020-04-04 23:50:37 +00:00
sentryLevel = kSentryLevelError;
2020-03-25 17:12:06 +00:00
break;
case PearlLogLevelFatal:
2020-04-04 23:50:37 +00:00
sentryLevel = kSentryLevelFatal;
2020-03-25 17:12:06 +00:00
break;
}
SentryBreadcrumb *breadcrumb = [[SentryBreadcrumb alloc] initWithLevel:sentryLevel category:@"Pearl"];
breadcrumb.type = @"log";
breadcrumb.message = message.message;
breadcrumb.timestamp = message.occurrence;
breadcrumb.data = @{ @"file": message.fileName, @"line": @(message.lineNumber), @"function": message.function };
2020-04-04 23:50:37 +00:00
[SentrySDK addBreadcrumb:breadcrumb];
2020-03-25 17:12:06 +00:00
}
return YES;
}];
2020-03-25 17:12:06 +00:00
// Countly
CountlyConfig *countlyConfig = [CountlyConfig new];
countlyConfig.host = @"https://countly.lyndir.com";
countlyConfig.appKey = decrypt( countlyKey );
countlyConfig.features = @[ CLYPushNotifications ];
countlyConfig.requiresConsent = YES;
countlyConfig.alwaysUsePOST = YES;
countlyConfig.deviceID = [PearlKeyChain deviceIdentifier];
countlyConfig.secretSalt = decrypt( countlySalt );
#if DEBUG
2020-03-25 17:12:06 +00:00
countlyConfig.pushTestMode = CLYPushTestModeDevelopment;
countlyConfig.enableDebug = YES;
#elif ! PUBLIC
2020-03-25 17:12:06 +00:00
countlyConfig.pushTestMode = CLYPushTestModeTestFlightOrAdHoc;
countlyConfig.enableDebug = NO;
2020-03-25 17:12:06 +00:00
#endif
[Countly.sharedInstance startWithConfig:countlyConfig];
}
@catch (id exception) {
err( @"During Analytics Setup: %@", exception );
}
2014-06-25 00:30:15 +00:00
// Setup delegates and listeners.
[MPConfig get].delegate = self;
__weak id weakSelf = self;
[self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) {
dispatch_async( dispatch_get_main_queue(), ^{
[weakSelf updateMenuItems];
} );
} forKeyPath:@"key" options:0 context:nil];
[self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) {
dispatch_async( dispatch_get_main_queue(), ^{
[weakSelf updateMenuItems];
} );
} forKeyPath:@"activeUser" options:0 context:nil];
// Status item.
2014-10-31 01:05:13 +00:00
self.statusView = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
2014-06-25 00:30:15 +00:00
self.statusView.image = [NSImage imageNamed:@"menu-icon"];
2014-10-31 01:05:13 +00:00
self.statusView.image.template = YES;
2014-06-25 00:30:15 +00:00
self.statusView.menu = self.statusMenu;
self.statusView.target = self;
self.statusView.action = @selector( showMenu );
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, self.storeCoordinator, nil,
^(id self, NSNotification *note) {
PearlMainQueue( ^{
[self updateUsers];
} );
} );
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, self.storeCoordinator, nil,
^(id self, NSNotification *note) {
PearlMainQueue( ^{
[self updateUsers];
} );
} );
PearlAddNotificationObserver( MPCheckConfigNotification, nil, nil,
^(MPMacAppDelegate *self, NSNotification *note) {
[self updateConfigKey:note.object];
} );
2014-06-25 00:30:15 +00:00
[self updateUsers];
2014-06-25 00:30:15 +00:00
// Global hotkey.
EventHotKeyRef hotKeyRef;
EventTypeSpec hotKeyEvents[1] = { { .eventClass = kEventClassKeyboard, .eventKind = kEventHotKeyPressed } };
2017-04-01 04:30:25 +00:00
OSStatus status = InstallApplicationEventHandler( NewEventHandlerUPP( MPHotKeyHander ), GetEventTypeCount( hotKeyEvents ), hotKeyEvents,
(__bridge void *)self, NULL );
2014-06-25 00:30:15 +00:00
if (status != noErr)
err( @"Error installing application event handler: %i", (int)status );
status = RegisterEventHotKey( 35 /* p */, controlKey + cmdKey, MPShowHotKey, GetApplicationEventTarget(), 0, &hotKeyRef );
if (status != noErr)
err( @"Error registering 'show' hotkey: %i", (int)status );
status = RegisterEventHotKey( 35 /* p */, controlKey + optionKey + cmdKey, MPLockHotKey, GetApplicationEventTarget(), 0, &hotKeyRef );
if (status != noErr)
err( @"Error registering 'lock' hotkey: %i", (int)status );
2014-06-25 00:30:15 +00:00
// Initial display.
2014-07-19 02:04:10 +00:00
if ([[MPMacConfig get].firstRun boolValue]) {
[(self.initialWindowController = [[MPInitialWindowController alloc] initWithWindowNibName:@"MPInitialWindow"])
.window makeKeyAndOrderFront:self];
[NSApp activateIgnoringOtherApps:YES];
}
2014-06-25 00:30:15 +00:00
}
2014-06-25 00:30:15 +00:00
- (void)applicationWillResignActive:(NSNotification *)notification {
2014-06-25 00:30:15 +00:00
if (![[MPConfig get].rememberLogin boolValue])
[self lock:nil];
}
2013-04-20 18:11:19 +00:00
2014-06-25 00:30:15 +00:00
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
// Save changes in the application's managed object context before the application terminates.
NSManagedObjectContext *mainContext = [MPMacAppDelegate managedObjectContextForMainThreadIfReady];
if (!mainContext)
2014-06-25 00:30:15 +00:00
return NSTerminateNow;
2013-04-20 18:11:19 +00:00
if (![mainContext commitEditing])
2014-06-25 00:30:15 +00:00
return NSTerminateCancel;
2013-04-20 18:11:19 +00:00
if (![mainContext hasChanges])
2014-06-25 00:30:15 +00:00
return NSTerminateNow;
2013-04-20 18:11:19 +00:00
[mainContext saveToStore];
2014-06-25 00:30:15 +00:00
return NSTerminateNow;
}
2014-06-25 00:30:15 +00:00
#pragma mark - State
- (void)setActiveUser:(MPUserEntity *)activeUser {
[super setActiveUser:activeUser];
if (activeUser)
[MPMacConfig get].usedUserName = activeUser.name;
2014-06-25 00:30:15 +00:00
PearlMainQueue( ^{
[self updateUsers];
} );
}
- (void)setLoginItemEnabled:(BOOL)enabled {
BOOL loginItemEnabled = [self loginItemEnabled];
if (loginItemEnabled != enabled) {
if (SMLoginItemSetEnabled( (__bridge CFStringRef)LOGIN_HELPER_BUNDLE_ID, (Boolean)enabled ) == true)
loginItemEnabled = enabled;
else
2014-06-25 00:30:15 +00:00
wrn( @"Failed to set login item." );
}
2014-06-25 00:30:15 +00:00
self.openAtLoginItem.state = loginItemEnabled? NSOnState: NSOffState;
self.initialWindowController.openAtLoginButton.state = loginItemEnabled? NSOnState: NSOffState;
2014-06-25 00:30:15 +00:00
}
- (BOOL)loginItemEnabled {
// The easy and sane method (SMJobCopyDictionary) can pose problems when the app is sandboxed. -_-
NSArray *jobs = (__bridge_transfer NSArray *)SMCopyAllJobDictionaries( kSMDomainUserLaunchd );
for (NSDictionary *job in jobs)
if ([LOGIN_HELPER_BUNDLE_ID isEqualToString:job[@"Label"]])
2014-10-26 14:39:59 +00:00
return [job[@"OnDemand"] boolValue];
2014-06-25 00:30:15 +00:00
return NO;
}
- (BOOL)isFeatureUnlocked:(NSString *)productIdentifier {
// All features are unlocked for mac versions.
return YES;
}
2014-06-25 00:30:15 +00:00
#pragma mark - Actions
- (void)selectUser:(NSMenuItem *)item {
2013-04-20 18:11:19 +00:00
NSManagedObjectContext *mainContext = [MPMacAppDelegate managedObjectContextForMainThreadIfReady];
self.activeUser = [MPUserEntity existingObjectWithID:[item representedObject] inContext:mainContext];
}
- (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;
2020-04-04 21:34:07 +00:00
NSMutableArray *allExtensions = [NSMutableArray array];
for (MPMarshalFormat format = MPMarshalFormatLast; format >= MPMarshalFormatFirst; --format) {
size_t count = 0;
const char **extensions = mpw_format_extensions( format, &count );
for (int c = 0; c < count; ++c)
[allExtensions addObject:@(extensions[c])];
free( extensions );
}
openPanel.allowedFileTypes = allExtensions;
[NSApp activateIgnoringOtherApps:YES];
if ([openPanel runModal] == NSFileHandlingPanelCancelButton)
return;
NSURL *url = openPanel.URL;
[openPanel close];
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
2017-08-13 02:26:48 +00:00
^(NSData *importedSitesData, NSURLResponse *response, NSError *urlError) {
if (urlError)
[[NSAlert alertWithError:MPError( urlError, @"While reading imported sites from %@.", url )] runModal];
if (!importedSitesData)
return;
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
2017-08-13 02:26:48 +00:00
[self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
__block NSString *masterPassword = nil;
PearlMainQueueWait( ^{
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Unlock"];
[alert addButtonWithTitle:@"Cancel"];
2017-08-13 02:26:48 +00:00
alert.messageText = strf( @"Importing Sites For\n%@", userName );
alert.informativeText = @"Enter the master password used to create this export file.";
alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert layout];
if ([alert runModal] == NSAlertFirstButtonReturn)
masterPassword = ((NSTextField *)alert.accessoryView).stringValue;
} );
return masterPassword;
2017-08-13 02:26:48 +00:00
} askUserPassword:^NSString *(NSString *userName) {
__block NSString *masterPassword = nil;
PearlMainQueueWait( ^{
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Import"];
[alert addButtonWithTitle:@"Cancel"];
2017-08-13 02:26:48 +00:00
alert.messageText = strf( @"Master Password For\n%@", userName );
alert.informativeText = @"Enter the current master password for this user.";
alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert layout];
if ([alert runModal] == NSAlertFirstButtonReturn)
masterPassword = ((NSTextField *)alert.accessoryView).stringValue;
} );
return masterPassword;
2017-08-13 02:26:48 +00:00
} result:^(NSError *error) {
PearlMainQueue( ^{
[self updateUsers];
if (error && !(error.domain == NSCocoaErrorDomain && error.code == NSUserCancelledError))
[[NSAlert alertWithError:error] runModal];
} );
2017-08-13 02:26:48 +00:00
}];
}] resume];
}
- (IBAction)togglePreference:(id)sender {
2012-06-08 21:46:13 +00:00
if (sender == self.diagnosticsItem)
[MPConfig get].sendInfo = @(self.diagnosticsItem.state != NSOnState);
if (sender == self.hidePasswordsItem)
[MPConfig get].hidePasswords = @(self.hidePasswordsItem.state != NSOnState);
if (sender == self.rememberPasswordItem)
[MPConfig get].rememberLogin = @(self.rememberPasswordItem.state != NSOnState);
if (sender == self.openAtLoginItem)
[self setLoginItemEnabled:self.openAtLoginItem.state != NSOnState];
if (sender == self.showFullScreenItem) {
[MPMacConfig get].fullScreen = @(self.showFullScreenItem.state != NSOnState);
[NSApp updateWindows];
}
if (sender == self.savePasswordItem) {
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:context];
if ((activeUser.saveKey = !activeUser.saveKey))
[[MPMacAppDelegate get] storeSavedKeyFor:activeUser];
else
[[MPMacAppDelegate get] forgetSavedKeyFor:activeUser];
[context saveToStore];
}];
}
[MPMacConfig flush];
[self updateMenuItems];
}
- (IBAction)newUser:(NSMenuItem *)sender {
NSAlert *alert = [NSAlert new];
[alert setMessageText:@"New User"];
[alert setInformativeText:@"To begin, enter your full name.\n\n"
@"IMPORTANT: Enter your name correctly, including the right capitalization, "
@"as you would on an official document."];
[alert addButtonWithTitle:@"Create User"];
[alert addButtonWithTitle:@"Cancel"];
NSTextField *nameField = [[NSTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert setAccessoryView:nameField];
[alert layout];
2020-03-25 17:12:06 +00:00
[alert.window makeFirstResponder:nameField];
if ([alert runModal] != NSAlertFirstButtonReturn)
return;
NSString *name = [(NSSecureTextField *)alert.accessoryView stringValue];
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *newUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass( [MPUserEntity class] )
inManagedObjectContext:context];
newUser.name = name;
[context saveToStore];
[self setActiveUser:newUser];
PearlMainQueue( ^{
[self showPasswordWindow:nil];
} );
}];
}
- (IBAction)deleteUser:(NSMenuItem *)sender {
NSAlert *alert = [NSAlert new];
[alert setMessageText:@"Delete User"];
[alert setInformativeText:strf( @"This will delete %@ and all their sites.", self.activeUserForMainThread.name )];
[alert addButtonWithTitle:@"Delete"];
[alert addButtonWithTitle:@"Cancel"];
if ([alert runModal] != NSAlertFirstButtonReturn)
return;
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[context deleteObject:[self activeUserInContext:context]];
[self setActiveUser:nil];
[context saveToStore];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateUsers];
[self showPasswordWindow:nil];
}];
}];
}
- (IBAction)lock:(id)sender {
[self signOut];
}
- (IBAction)terminate:(id)sender {
2017-05-07 22:36:01 +00:00
[self.sitesWindowController close];
self.sitesWindowController = nil;
[NSApp terminate:nil];
}
- (IBAction)showPopup:(id)sender {
2014-10-31 01:05:13 +00:00
[self.statusView popUpStatusItemMenu:self.statusView.menu];
}
2014-06-25 00:30:15 +00:00
- (IBAction)showPasswordWindow:(id)sender {
prof_new( @"showPasswordWindow" );
2014-06-25 00:30:15 +00:00
[NSApp activateIgnoringOtherApps:YES];
2017-04-01 04:30:25 +00:00
prof_rewind( @"activateIgnoringOtherApps" );
2014-06-25 00:30:15 +00:00
// 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];
prof_finish( @"activeUserForMainThread" );
2014-06-25 00:30:15 +00:00
return;
}
prof_rewind( @"activeUserForMainThread" );
2014-06-25 00:30:15 +00:00
// Don't show window if we weren't already running (ie. if we haven't been activated before).
2017-05-07 22:36:01 +00:00
if (!self.sitesWindowController)
self.sitesWindowController = [[MPSitesWindowController alloc] initWithWindowNibName:@"MPSitesWindowController"];
prof_rewind( @"initWithWindow" );
2017-05-07 22:36:01 +00:00
[self.sitesWindowController showWindow:self];
prof_finish( @"showWindow" );
2014-06-25 00:30:15 +00:00
}
2012-06-08 21:46:13 +00:00
2014-06-25 00:30:15 +00:00
#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;
2020-04-04 21:34:07 +00:00
savePanel.nameFieldStringValue = strf( @"%@ (%@).%@", mainActiveUser.name, [exportDateFormatter stringFromDate:[NSDate date]],
@(mpw_format_extension( MPMarshalFormatDefault ) ) );
NSMutableArray *allExtensions = [NSMutableArray array];
size_t count = 0;
const char **extensions = mpw_format_extensions( MPMarshalFormatDefault, &count );
for (int c = 0; c < count; ++c)
[allExtensions addObject:@(extensions[c])];
free( extensions );
savePanel.allowedFileTypes = allExtensions;
[NSApp activateIgnoringOtherApps:YES];
if ([savePanel runModal] == NSFileHandlingPanelCancelButton)
return;
2017-08-13 02:26:48 +00:00
[self exportSitesRevealPasswords:revealPasswords
askExportPassword:^NSString *(NSString *userName) {
return PearlMainQueueAwait( ^id {
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Import"];
[alert addButtonWithTitle:@"Cancel"];
alert.messageText = strf( @"Master Password For\n%@", userName );
alert.informativeText = @"Enter the current master password for this user.";
alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert layout];
if ([alert runModal] == NSAlertFirstButtonReturn)
return ((NSTextField *)alert.accessoryView).stringValue;
else
return nil;
} );
} result:^(NSString *mpsites, NSError *error) {
if (!mpsites || error) {
PearlMainQueue( ^{
[[NSAlert alertWithError:MPError( error, @"Failed to export mpsites." )] runModal];
} );
return;
}
2017-08-13 02:26:48 +00:00
NSError *coordinateError = nil;
[[[NSFileCoordinator alloc] initWithFilePresenter:nil]
coordinateWritingItemAtURL:savePanel.URL options:0 error:&coordinateError byAccessor:^(NSURL *newURL) {
NSError *writeError = nil;
if (![mpsites writeToURL:newURL atomically:NO encoding:NSUTF8StringEncoding error:&writeError])
PearlMainQueue( ^{
2017-08-13 02:26:48 +00:00
[[NSAlert alertWithError:MPError( writeError, @"Could not write to the export file." )] runModal];
} );
2017-08-13 02:26:48 +00:00
}];
if (coordinateError)
PearlMainQueue( ^{
[[NSAlert alertWithError:MPError( coordinateError, @"Could not gain access to the export file." )] runModal];
} );
}];
}
2014-06-25 00:30:15 +00:00
- (void)updateUsers {
BOOL foundSeparator = NO;
for (NSMenuItem *item in [[self.usersItem submenu] itemArray]) {
if (foundSeparator)
[[self.usersItem submenu] removeItem:item];
else if (item.isSeparatorItem)
foundSeparator = YES;
}
2012-06-08 21:46:13 +00:00
NSManagedObjectContext *mainContext = [MPMacAppDelegate managedObjectContextForMainThreadIfReady];
if (!mainContext) {
2014-06-25 00:30:15 +00:00
self.createUserItem.title = @"New User (Not ready)";
self.createUserItem.enabled = NO;
self.createUserItem.toolTip = @"Please wait until the app is fully loaded.";
self.deleteUserItem.title = @"Delete User (Not ready)";
self.deleteUserItem.enabled = NO;
self.deleteUserItem.toolTip = @"Please wait until the app is fully loaded.";
[self.usersItem.submenu addItemWithTitle:@"Loading..." action:NULL keyEquivalent:@""].enabled = NO;
2014-06-25 00:30:15 +00:00
return;
}
MPUserEntity *mainActiveUser = [self activeUserInContext:mainContext];
2014-06-25 00:30:15 +00:00
self.createUserItem.title = @"New User";
self.createUserItem.enabled = YES;
self.createUserItem.toolTip = nil;
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.";
2014-06-25 00:30:15 +00:00
NSError *error = nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ];
NSArray *users = [mainContext executeFetchRequest:fetchRequest error:&error];
2014-06-25 00:30:15 +00:00
if (!users)
MPError( error, @"Failed to load users." );
2014-06-25 00:30:15 +00:00
if (![users count]) {
NSMenuItem *noUsersItem = [self.usersItem.submenu addItemWithTitle:@"No users" action:NULL keyEquivalent:@""];
noUsersItem.enabled = NO;
2014-07-19 02:04:10 +00:00
noUsersItem.toolTip = @"Begin by creating a user.";
2014-06-25 00:30:15 +00:00
}
self.usersItem.state = NSMixedState;
for (MPUserEntity *user in users) {
NSMenuItem *userItem = [[NSMenuItem alloc] initWithTitle:user.name action:@selector( selectUser: ) keyEquivalent:@""];
[userItem setTarget:self];
[userItem setRepresentedObject:user.permanentObjectID];
2014-06-25 00:30:15 +00:00
[[self.usersItem submenu] addItem:userItem];
if (!mainActiveUser && [user.name isEqualToString:[MPMacConfig get].usedUserName])
[super setActiveUser:mainActiveUser = user];
2014-06-25 00:30:15 +00:00
if ([mainActiveUser isEqual:user]) {
2014-06-25 00:30:15 +00:00
userItem.state = NSOnState;
self.usersItem.state = NSOffState;
}
else
userItem.state = NSOffState;
}
if (!mainActiveUser)
[self.sitesWindowController close];
2014-06-25 00:30:15 +00:00
[self updateMenuItems];
}
- (void)showMenu {
[self updateMenuItems];
2014-10-31 01:05:13 +00:00
[self.statusView popUpStatusItemMenu:self.statusView.menu];
}
- (void)updateMenuItems {
MPUserEntity *activeUser = [self activeUserForMainThread];
2017-05-07 22:36:01 +00:00
// if (!(self.showItem.enabled = ![self.sitesWindowController.window isVisible])) {
// self.showItem.title = @"Show (Showing)";
// self.showItem.toolTip = @"Master Password is already showing.";
// }
// else if (!(self.showItem.enabled = (activeUser != nil))) {
// self.showItem.title = @"Show (No user)";
// self.showItem.toolTip = @"First select the user to show passwords for.";
// }
// else {
// self.showItem.title = @"Show";
// self.showItem.toolTip = nil;
// }
if (self.key) {
2013-04-20 18:11:19 +00:00
self.lockItem.title = @"Lock";
self.lockItem.enabled = YES;
self.lockItem.toolTip = nil;
2013-04-20 18:11:19 +00:00
}
else {
self.lockItem.title = @"Lock (Locked)";
self.lockItem.enabled = NO;
self.lockItem.toolTip = @"Master Password is currently locked.";
}
BOOL loginItemEnabled = [self loginItemEnabled];
self.openAtLoginItem.state = loginItemEnabled? NSOnState: NSOffState;
self.showFullScreenItem.state = [[MPMacConfig get].fullScreen boolValue]? NSOnState: NSOffState;
self.initialWindowController.openAtLoginButton.state = loginItemEnabled? NSOnState: NSOffState;
self.rememberPasswordItem.state = [[MPConfig get].rememberLogin boolValue]? NSOnState: NSOffState;
2013-04-21 21:05:59 +00:00
self.savePasswordItem.state = activeUser.saveKey? NSOnState: NSOffState;
if (!activeUser) {
2013-04-20 18:11:19 +00:00
self.savePasswordItem.title = @"Save Password (No user)";
self.savePasswordItem.enabled = NO;
self.savePasswordItem.toolTip = @"First select your user and unlock by showing the Master Password window.";
2013-04-20 18:11:19 +00:00
}
else if (!self.key) {
self.savePasswordItem.title = @"Save Password (Locked)";
self.savePasswordItem.enabled = NO;
self.savePasswordItem.toolTip = @"First unlock by showing the Master Password window.";
2013-04-20 18:11:19 +00:00
}
else {
self.savePasswordItem.title = @"Save Password";
self.savePasswordItem.enabled = YES;
self.savePasswordItem.toolTip = nil;
}
2014-07-19 02:04:10 +00:00
}
2014-06-25 00:30:15 +00:00
#pragma mark - PearlConfigDelegate
2012-06-08 21:46:13 +00:00
2014-06-25 00:30:15 +00:00
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)oldValue {
2012-06-08 21:46:13 +00:00
2014-06-25 00:30:15 +00:00
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:NSStringFromSelector( configKey )];
}
- (void)updateConfigKey:(NSString *)key {
PearlMainQueue( ^{
if (!key || [key isEqualToString:NSStringFromSelector( @selector( sendInfo ) )])
self.diagnosticsItem.state = [[MPConfig get].sendInfo boolValue]? NSOnState: NSOffState;
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;
} );
// Send info
if ([[MPConfig get].sendInfo boolValue]) {
PearlMainQueue( ^{
[Countly.sharedInstance giveConsentForAllFeatures];
[Countly.sharedInstance askForNotificationPermission];
});
if ([PearlLogger get].printLevel > PearlLogLevelInfo)
[PearlLogger get].printLevel = PearlLogLevelInfo;
NSMutableDictionary *prefs = [NSMutableDictionary new];
prefs[@"rememberLogin"] = [MPConfig get].rememberLogin;
prefs[@"sendInfo"] = [MPConfig get].sendInfo;
prefs[@"fullScreen"] = [MPMacConfig get].fullScreen;
prefs[@"firstRun"] = [PearlConfig get].firstRun;
prefs[@"launchCount"] = [PearlConfig get].launchCount;
prefs[@"askForReviews"] = [PearlConfig get].askForReviews;
prefs[@"reviewAfterLaunches"] = [PearlConfig get].reviewAfterLaunches;
prefs[@"reviewedVersion"] = [PearlConfig get].reviewedVersion;
prefs[@"simulator"] = @([PearlDeviceUtils isSimulator]);
prefs[@"encrypted"] = @([PearlDeviceUtils isAppEncrypted]);
prefs[@"platform"] = [PearlDeviceUtils platform];
[SentrySDK.currentHub getClient].options.enabled = @YES;
[SentrySDK configureScope:^(SentryScope *scope) {
for (NSString *pref in prefs.allKeys)
[scope setExtraValue:prefs[pref] forKey:pref];
}];
}
else {
[SentrySDK.currentHub getClient].options.enabled = @NO;
PearlMainQueue( ^{
[Countly.sharedInstance cancelConsentForAllFeatures];
});
}
}
@end