From 29a6c39c1fc7f05491d412c52772b7fc7d586e4b Mon Sep 17 00:00:00 2001 From: michael starke Date: Thu, 16 Nov 2017 17:34:03 +0100 Subject: [PATCH] Added rudementary support to add plugins via the plugin settings tab --- MacPass/Base.lproj/PluginSettings.xib | 46 +++++++++++++----- MacPass/MPPluginHost.h | 2 + MacPass/MPPluginHost.m | 31 +++++++++++- MacPass/MPPluginSettingsController.h | 2 + MacPass/MPPluginSettingsController.m | 68 ++++++++++++++++++++++++++- MacPass/NSApplication+MPAdditions.h | 4 +- MacPass/NSApplication+MPAdditions.m | 8 ++++ MacPass/NSError+Messages.h | 1 + 8 files changed, 144 insertions(+), 18 deletions(-) diff --git a/MacPass/Base.lproj/PluginSettings.xib b/MacPass/Base.lproj/PluginSettings.xib index 79c80ff5..d9198d1f 100644 --- a/MacPass/Base.lproj/PluginSettings.xib +++ b/MacPass/Base.lproj/PluginSettings.xib @@ -1,14 +1,15 @@ - + - + + @@ -18,24 +19,24 @@ - + - + - + - + If enabled, only properly signed Plugins will be loaded. Keep in mind, that Plugins have full access to your data! Changes take affect on restart. @@ -44,13 +45,13 @@ - + - + - + @@ -106,23 +107,42 @@ + + + + + + + + + + + + + + + + + - - - + + + + + diff --git a/MacPass/MPPluginHost.h b/MacPass/MPPluginHost.h index af910b59..fe4f09b7 100644 --- a/MacPass/MPPluginHost.h +++ b/MacPass/MPPluginHost.h @@ -38,4 +38,6 @@ FOUNDATION_EXPORT NSString *const MPPluginHostPluginBundleIdentifiyerKey; - (instancetype)init NS_UNAVAILABLE; +- (BOOL)installPluginAtURL:(NSURL *)url error:(NSError *__autoreleasing *)error; + @end diff --git a/MacPass/MPPluginHost.m b/MacPass/MPPluginHost.m index a22ba333..d3d1bfda 100644 --- a/MacPass/MPPluginHost.m +++ b/MacPass/MPPluginHost.m @@ -26,6 +26,8 @@ #import "NSApplication+MPAdditions.h" #import "MPSettingsHelper.h" +#import "NSError+Messages.h" + #import "KeePassKit/KeePassKit.h" @@ -35,7 +37,7 @@ NSString *const MPPluginHostDidLoadPlugin = @"comt.hicknhack.macpass.MPPluginHos NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBundleIdentifiyerKey"; -@interface MPPluginHost () +@interface MPPluginHost () @property (strong) NSMutableArray *mutablePlugins; @property (nonatomic) BOOL loadUnsecurePlugins; @@ -80,6 +82,31 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun return [self.mutablePlugins copy]; } +- (BOOL)installPluginAtURL:(NSURL *)url error:(NSError *__autoreleasing *)error { + if(![self _validURL:url]) { + if(error) { + *error = [NSError errorWithCode:MPErrorInvalidPlugin description:NSLocalizedString(@"ERROR_INVALID_PLUGIN", @"Error description given when adding an invalid plugin")]; + } + return NO; + } + NSString *fileName; + if(![url getResourceValue:&fileName forKey:NSURLNameKey error:error]) { + return NO; + } + NSURL *appSupportURL = [NSApp applicationSupportDirectoryURL:YES]; + NSURL *destinationURL = [appSupportURL URLByAppendingPathComponent:fileName]; + NSFileManager.defaultManager.delegate = self; + return [NSFileManager.defaultManager moveItemAtURL:url toURL:destinationURL error:error]; +} + +#pragma mark - NSFileManagerDelegate + +- (BOOL)fileManager:(NSFileManager *)fileManager shouldProceedAfterError:(NSError *)error movingItemAtURL:(NSURL *)srcURL toURL:(NSURL *)dstURL { + return NO; +} + +#pragma mark - Plugin Loading + - (void)_loadPlugins { NSURL *appSupportDir = [NSApp applicationSupportDirectoryURL:YES]; NSError *error; @@ -131,7 +158,7 @@ NSString *const MPPluginHostPluginBundleIdentifiyerKey = @"MPPluginHostPluginBun } if(![pluginBundle loadAndReturnError:&error]) { - NSLog(@"Bunlde Loading Error %@ %@", error.localizedDescription, error.localizedFailureReason); + NSLog(@"Bundle Loading Error %@ %@", error.localizedDescription, error.localizedFailureReason); continue; } diff --git a/MacPass/MPPluginSettingsController.h b/MacPass/MPPluginSettingsController.h index f3626b60..8a4ce96d 100644 --- a/MacPass/MPPluginSettingsController.h +++ b/MacPass/MPPluginSettingsController.h @@ -25,4 +25,6 @@ @interface MPPluginSettingsController : MPViewController +- (IBAction)addOrRemovePlugin:(id)sender; + @end diff --git a/MacPass/MPPluginSettingsController.m b/MacPass/MPPluginSettingsController.m index ea91e71a..2a442f05 100644 --- a/MacPass/MPPluginSettingsController.m +++ b/MacPass/MPPluginSettingsController.m @@ -26,11 +26,21 @@ #import "MPSettingsHelper.h" +#import "NSApplication+MPAdditions.h" + +#import + +typedef NS_ENUM(NSUInteger, MPPluginSegmentType) { + MPAddPluginSegment = 0, + MPRemovePluginSegment = 1 +}; + @interface MPPluginSettingsController () @property (weak) IBOutlet NSTableView *pluginTableView; @property (weak) IBOutlet NSView *settingsView; @property (weak) IBOutlet NSButton *loadInsecurePlugsinCheckButton; +@property (weak) IBOutlet NSSegmentedControl *addRemovePluginsControl; @end @@ -55,12 +65,14 @@ - (void)viewDidLoad { self.pluginTableView.delegate = self; self.pluginTableView.dataSource = self; + [self.addRemovePluginsControl setEnabled:NO forSegment:MPRemovePluginSegment]; [self.loadInsecurePlugsinCheckButton bind:NSValueBinding toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyLoadUnsecurePlugins] options:nil]; + } - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { @@ -98,7 +110,61 @@ - (void)tableViewSelectionDidChange:(NSNotification *)notification { NSTableView *table = notification.object; - [self showSettingsForPlugin:[self pluginForRow:table.selectedRow]]; + MPPlugin *plugin = [self pluginForRow:table.selectedRow]; + [self.addRemovePluginsControl setEnabled:(nil != plugin) forSegment:MPRemovePluginSegment]; + [self showSettingsForPlugin:plugin]; +} + +- (IBAction)addOrRemovePlugin:(id)sender { + if(sender != self.addRemovePluginsControl) { + return; + } + switch(self.addRemovePluginsControl.selectedSegment) { + case MPAddPluginSegment: + [self showAddPluginPanel]; + break; + case MPRemovePluginSegment: + break; + default: + break; + } +} + +- (void)showAddPluginPanel { + NSOpenPanel *openPanel = [NSOpenPanel openPanel]; + openPanel.allowedFileTypes = @[kMPPluginFileExtension]; + openPanel.allowsMultipleSelection = NO; + openPanel.canChooseFiles = YES; + openPanel.canChooseDirectories = NO; + [openPanel beginSheetModalForWindow:self.view.window completionHandler:^(NSModalResponse result) { + if(NSModalResponseOK) { + if(openPanel.URLs.count == 1) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self _addPlugin:openPanel.URLs.firstObject]; + }); + } + } + }]; +} + +- (void)_addPlugin:(NSURL *)bundleURL { + NSError *error; + if(![[MPPluginHost sharedHost] installPluginAtURL:bundleURL error:&error]) { + [NSApp presentError:error modalForWindow:self.view.window delegate:nil didPresentSelector:NULL contextInfo:NULL]; + } + else { + NSAlert *alert = [[NSAlert alloc] init]; + alert.alertStyle = NSAlertStyleInformational; + alert.messageText = NSLocalizedString(@"ALERT_MESSAGE_TEXT_PLUGIN_INSTALLED_SUGGEST_RESTART", "Alert message text when a plugin was successfully installed"); + alert.informativeText = NSLocalizedString(@"ALERT_INFORMATIVE_TEXT_PLUGIN_INSTALLED_SUGGEST_RESTART", "ALert informative text when a plugin was sucessfully installed"); + [alert addButtonWithTitle:NSLocalizedString(@"CANCEL", @"Cancel button in plugin installed, request restart alert")]; + [alert addButtonWithTitle:NSLocalizedString(@"RESTART", @"Restart button in plugin installed, request restart alert")]; + [alert beginSheetModalForWindow:self.view.window completionHandler:^(NSModalResponse returnCode) { + if(returnCode == NSAlertSecondButtonReturn) { + [NSApp relaunchAfterDelay:3]; + } + }]; + } } @end diff --git a/MacPass/NSApplication+MPAdditions.h b/MacPass/NSApplication+MPAdditions.h index e34f5f0b..49b0b415 100644 --- a/MacPass/NSApplication+MPAdditions.h +++ b/MacPass/NSApplication+MPAdditions.h @@ -29,8 +29,8 @@ NS_ASSUME_NONNULL_BEGIN @property (copy, readonly) NSString *applicationName; @property (copy, readonly, nullable) NSURL *applicationSupportDirectoryURL; -- (NSURL * _Nullable)applicationSupportDirectoryURL:(BOOL)create; - +- (NSURL *_Nullable)applicationSupportDirectoryURL:(BOOL)create; +- (void)relaunchAfterDelay:(CGFloat)seconds; @end NS_ASSUME_NONNULL_END diff --git a/MacPass/NSApplication+MPAdditions.m b/MacPass/NSApplication+MPAdditions.m index d7048417..d06e61d6 100644 --- a/MacPass/NSApplication+MPAdditions.m +++ b/MacPass/NSApplication+MPAdditions.m @@ -52,4 +52,12 @@ return nil; } +- (void)relaunchAfterDelay:(CGFloat)seconds { + NSTask *task = [[NSTask alloc] init]; + task.launchPath = @"/bin/sh"; + task.arguments = @[ @"-c", [NSString stringWithFormat:@"sleep %f; open \"%@\"", seconds, NSBundle.mainBundle.bundlePath] ]; + [task launch]; + [self terminate:nil]; +} + @end diff --git a/MacPass/NSError+Messages.h b/MacPass/NSError+Messages.h index bfff8373..a7d2cbdf 100644 --- a/MacPass/NSError+Messages.h +++ b/MacPass/NSError+Messages.h @@ -26,6 +26,7 @@ FOUNDATION_EXPORT NSString *const MPErrorDomain; typedef NS_ENUM(NSInteger, MPErrorCodes) { MPErrorNoPasswordOrKeyFile = 10000, + MPErrorInvalidPlugin }; @interface NSError (Messages)