diff --git a/MacPass.xcodeproj/project.pbxproj b/MacPass.xcodeproj/project.pbxproj index 8ff06f6e..cebb6f77 100644 --- a/MacPass.xcodeproj/project.pbxproj +++ b/MacPass.xcodeproj/project.pbxproj @@ -203,6 +203,7 @@ 4C8B36AB17A6ED4B005E1FF1 /* MPOutlineContextMenuDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C8B36AA17A6ED4B005E1FF1 /* MPOutlineContextMenuDelegate.m */; }; 4C8DEAA21C314D2C00D24C32 /* MPTestAutotypeDelay.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C8DEAA11C314D2C00D24C32 /* MPTestAutotypeDelay.m */; }; 4C8E889222C223620002C7C8 /* createdTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 4C8E889122C223620002C7C8 /* createdTemplate.pdf */; }; + 4C8E889422C227270002C7C8 /* plugins.json in Resources */ = {isa = PBXBuildFile; fileRef = 4C8E889322C227270002C7C8 /* plugins.json */; }; 4C8F0C6E1FCEE9B900BE157F /* MPPluginConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C8F0C6D1FCEE9B900BE157F /* MPPluginConstants.m */; }; 4C8F0C711FCEF91400BE157F /* MPPickcharsParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C8F0C701FCEF91400BE157F /* MPPickcharsParser.m */; }; 4C8F0C731FCF1B7A00BE157F /* MPTestPickcharsParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C8F0C721FCF1B7A00BE157F /* MPTestPickcharsParser.m */; }; @@ -720,6 +721,7 @@ 4C8C10061FC489D8003DDD5E /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/PluginPreferences.strings; sourceTree = ""; }; 4C8DEAA11C314D2C00D24C32 /* MPTestAutotypeDelay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTestAutotypeDelay.m; sourceTree = ""; }; 4C8E889122C223620002C7C8 /* createdTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = createdTemplate.pdf; sourceTree = ""; }; + 4C8E889322C227270002C7C8 /* plugins.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = plugins.json; path = Resources/plugins.json; sourceTree = ""; }; 4C8F0C6C1FCEE98900BE157F /* MPPluginConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPPluginConstants.h; sourceTree = ""; }; 4C8F0C6D1FCEE9B900BE157F /* MPPluginConstants.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPPluginConstants.m; sourceTree = ""; }; 4C8F0C6F1FCEF91400BE157F /* MPPickcharsParser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPPickcharsParser.h; sourceTree = ""; }; @@ -1485,6 +1487,7 @@ 4C77E36D15B84A240093A587 /* Supporting Files */ = { isa = PBXGroup; children = ( + 4C8E889322C227270002C7C8 /* plugins.json */, FA13910A1F9CD9EB0033D256 /* Localizable.stringsdict */, 4C888C8E16EB6C91003D34A1 /* Localizable.strings */, 4CB9339716D3A0DD00A13B5D /* Credits.rtf */, @@ -1924,6 +1927,7 @@ 6021FE4B18E13F1D00C3BC51 /* GroupInspectorView.xib in Resources */, 4C3826BF1AD04D8E007D7D67 /* 52_EncryptedTemplate.pdf in Resources */, 4C3826BD1AD04D8E007D7D67 /* 50_FolderTarTemplate.pdf in Resources */, + 4C8E889422C227270002C7C8 /* plugins.json in Resources */, 6021FE3B18E1341900C3BC51 /* EntryInspectorView.xib in Resources */, 4C3826AD1AD04D8E007D7D67 /* 32_FileSystemViewTemplate.pdf in Resources */, 6021FE6118E15D9100C3BC51 /* WorkflowPreferences.xib in Resources */, diff --git a/MacPass/MPPluginRepository.m b/MacPass/MPPluginRepository.m index 09268b09..e272837f 100644 --- a/MacPass/MPPluginRepository.m +++ b/MacPass/MPPluginRepository.m @@ -23,6 +23,7 @@ #import "MPPluginRepository.h" #import "MPConstants.h" #import "MPPluginRepositoryItem.h" +#import "MPSettingsHelper.h" NSString *const MPPluginRepositoryDidUpdateAvailablePluginsNotification = @"com.hicknhack.macpass.MPPluginRepositoryDidInitializeAvailablePluginsNotification"; @@ -56,7 +57,7 @@ NSString *const MPPluginRepositoryDidUpdateAvailablePluginsNotification = @"com. if(self) { _isInitialized = NO; _lastDataFetchTime = NSDate.distantPast.timeIntervalSinceReferenceDate; - [self fetchRepositoryDataCompletionHandler:^(NSArray * _Nonnull availablePlugins) { + [self _fetchRepositoryDataCompletionHandler:^(NSArray * _Nonnull availablePlugins) { self.availablePlugins = availablePlugins; self.isInitialized = YES; }]; @@ -68,7 +69,7 @@ NSString *const MPPluginRepositoryDidUpdateAvailablePluginsNotification = @"com. /* update cache on every read if it's older than 5 minutes */ if((NSDate.timeIntervalSinceReferenceDate - self.lastDataFetchTime) > 60*5 ) { NSLog(@"%@: updating available plugins cache.", self.className); - [self fetchRepositoryDataCompletionHandler:^(NSArray * _Nonnull availablePlugins) { + [self _fetchRepositoryDataCompletionHandler:^(NSArray * _Nonnull availablePlugins) { self.availablePlugins = availablePlugins; }]; } @@ -87,59 +88,91 @@ NSString *const MPPluginRepositoryDidUpdateAvailablePluginsNotification = @"com. } } -- (void)fetchRepositoryDataCompletionHandler:(void (^)(NSArray * _Nonnull))completionHandler { +- (void)_fetchRepositoryDataCompletionHandler:(void (^)(NSArray * _Nonnull))completionHandler { + BOOL allowRemoteConnection = [self _askForPluginRepositoryPermission]; + if(!allowRemoteConnection) { + [self _fetchLocalFallbackRepositoryData:completionHandler]; + return; + } NSString *urlString = NSBundle.mainBundle.infoDictionary[MPBundlePluginRepositoryURLKey]; if(!urlString) { - if(completionHandler) { - completionHandler(@[]); - } + [self _fetchLocalFallbackRepositoryData:completionHandler]; return; } NSURL *jsonURL = [NSURL URLWithString:urlString]; if(!jsonURL) { - if(completionHandler) { - completionHandler(@[]); - } + [self _fetchLocalFallbackRepositoryData:completionHandler]; return; } NSURLSessionTask *downloadTask = [NSURLSession.sharedSession dataTaskWithURL:jsonURL completionHandler:^(NSData * _Nullable jsonData, NSURLResponse * _Nullable response, NSError * _Nullable error) { if(![response isKindOfClass:NSHTTPURLResponse.class]) { - if(completionHandler) { - completionHandler(@[]); - } + [self _fetchLocalFallbackRepositoryData:completionHandler]; return; } NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; if(httpResponse.statusCode != 200 || jsonData.length == 0) { - if(completionHandler) { - completionHandler(@[]); - } + [self _fetchLocalFallbackRepositoryData:completionHandler]; return; } - id jsonRoot = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; - if(!jsonRoot || ![jsonRoot isKindOfClass:NSArray.class]) { - if(completionHandler) { - completionHandler(@[]); - } - return; - } - NSMutableArray *items = [[NSMutableArray alloc] init]; - for(id item in jsonRoot) { - if(![item isKindOfClass:NSDictionary.class]) { - continue; - } - MPPluginRepositoryItem *pluginItem = [MPPluginRepositoryItem pluginItemFromDictionary:item]; - if(pluginItem.isVaid) { - [items addObject:pluginItem]; - } - } + + NSArray *items = [self _parseJSONData:jsonData]; + if(completionHandler) { completionHandler([items copy]); } }]; - [downloadTask resume]; } +- (void)_fetchLocalFallbackRepositoryData:(void (^)(NSArray * _Nonnull))completionHandler { + NSURL *jsonURL = [NSBundle.mainBundle URLForResource:@"plugins" withExtension:@"json"]; + NSData *localJsonData = [NSData dataWithContentsOfURL:jsonURL]; + if(!localJsonData) { + if(completionHandler) { + completionHandler(@[]); + } + } + NSArray *items = [self _parseJSONData:localJsonData]; + if(completionHandler) { + completionHandler(items); + } +} + +- (NSArray *)_parseJSONData:(NSData *)jsonData { + NSError *error; + id jsonRoot = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; + if(!jsonRoot || ![jsonRoot isKindOfClass:NSArray.class]) { + return @[]; + } + NSMutableArray *items = [[NSMutableArray alloc] init]; + for(id item in jsonRoot) { + if(![item isKindOfClass:NSDictionary.class]) { + continue; + } + MPPluginRepositoryItem *pluginItem = [MPPluginRepositoryItem pluginItemFromDictionary:item]; + if(pluginItem.isVaid) { + [items addObject:pluginItem]; + } + } + return [items copy]; +} + +- (BOOL)_askForPluginRepositoryPermission { + if(![NSUserDefaults.standardUserDefaults objectForKey:kMPSettingsKeyAllowRemoteFetchOfPluginRepository]) { + NSAlert *alert = [[NSAlert alloc] init]; + alert.alertStyle = NSAlertStyleWarning; + alert.informativeText = NSLocalizedString(@"ALERT_ASK_FOR_PLUGIN_REPOSITORY_CONNECTION_PERMISSION_INFORMATIVE_TEXT", @"Informative text displayed on the alert that shows up when MacPass asks for permssion to download the plugin repository JSON file"); + alert.messageText = NSLocalizedString(@"ALERT_ASK_FOR_PLUGIN_REPOSITORY_CONNECTION_PERMISSION_MESSAGE", @"Message displayed on the alert that askf for permission to download the plugin repository JSON file"); + alert.showsSuppressionButton = YES; + [alert addButtonWithTitle:NSLocalizedString(@"ALERT_ASK_FOR_PLUGIN_REPOSITORY_DISALLOW_DOWNLOAD", @"Disallow the download of the plugin repository file")]; + [alert addButtonWithTitle:NSLocalizedString(@"ALERT_ASK_FOR_PLUGIN_REPOSITORY_ALLOW_DOWNLOAD", @"Allow the download of the plugin repository file")]; + NSModalResponse repsonse = [alert runModal]; + BOOL allow = (repsonse == NSAlertFirstButtonReturn); + [NSUserDefaults.standardUserDefaults setBool:allow forKey:kMPSettingsKeyAllowRemoteFetchOfPluginRepository]; + return allow; + } + return [NSUserDefaults.standardUserDefaults boolForKey:kMPSettingsKeyAllowRemoteFetchOfPluginRepository]; +} + @end diff --git a/MacPass/MPSettingsHelper.h b/MacPass/MPSettingsHelper.h index 52d70470..a5d68a0a 100644 --- a/MacPass/MPSettingsHelper.h +++ b/MacPass/MPSettingsHelper.h @@ -87,6 +87,8 @@ APPKIT_EXTERN NSString *const kMPSettingsKeyLoadUnsecurePlugins; // I APPKIT_EXTERN NSString *const kMPSettingsKeyDisabledPlugins; // NSArray of bundle identifiers of disabled plugins APPKIT_EXTERN NSString *const kMPSettingsKeyLoadIncompatiblePlugins; // If set to YES incompatible plugins (no version info, marked as incompatible, etc) will be loaded regardless APPKIT_EXTERN NSString *const kMPSettingsKeyHideIncopatiblePluginsWarning; // Do not show an alert, when MacPass encounteres incompatible plugins +APPKIT_EXTERN NSString *const kMPSettingsKeyAllowRemoteFetchOfPluginRepository; // Allow the download of the plugin repository file +APPKIT_EXTERN NSString *const kMPSettingsKeyPluginHideAksForRemoveConnectionPermission; typedef NS_ENUM(NSUInteger, MPFileChangeStrategy) { MPFileChangeStrategyAsk, diff --git a/MacPass/MPSettingsHelper.m b/MacPass/MPSettingsHelper.m index eccbb4e2..7a473bcd 100644 --- a/MacPass/MPSettingsHelper.m +++ b/MacPass/MPSettingsHelper.m @@ -84,6 +84,7 @@ NSString *const kMPSettingsKeyLoadUnsecurePlugins = @"LoadUn NSString *const kMPSettingsKeyLoadIncompatiblePlugins = @"LoadIncompatiblePlugins"; NSString *const kMPSettingsKeyDisabledPlugins = @"DisabledPlugins"; NSString *const kMPSettingsKeyHideIncopatiblePluginsWarning = @"HideIncopatiblePluginsWarning"; +NSString *const kMPSettingsKeyAllowRemoteFetchOfPluginRepository = @"AllowRemoteFetchOfPluginRepository"; /* Deprecated */ NSString *const kMPDeprecatedSettingsKeyRememberKeyFilesForDatabases = @"kMPSettingsKeyRememberKeyFilesForDatabases"; diff --git a/MacPass/Resources/plugins.json b/MacPass/Resources/plugins.json new file mode 100644 index 00000000..ca5ba69a --- /dev/null +++ b/MacPass/Resources/plugins.json @@ -0,0 +1,26 @@ +[ + { + "name": "MacPassHTTP", + "description": "KeePassHTTP support for MacPass", + "download": "https://github.com/MacPass/MacPassHTTP/releases/download/0.3.1/MacPassHTTP.mpplugin-0.3.1.zip", + "source": "https://github.com/MacPass/MacPassHTTP", + "currentVersion": "0.3.1", + "bundleIdentifier": "com.hicknhacksoftware.MacPassHTTP", + "compatibilty" : [ + { + "pluginVersion": "0.2.*", + "minimumHostVersion" : "0.6.1", + "maximumHostVersion" : "0.6.2" + }, + { + "pluginVersion": "0.3", + "minimumHostVersion" : "0.7", + "maximumHostVersion" : "0.7.3" + }, + { + "pluginVersion": "0.3.1", + "minimumHostVersion" : "0.7.4" + } + ] + } +] diff --git a/scripts/update_plugin_repository.sh b/scripts/update_plugin_repository.sh new file mode 100755 index 00000000..6c15a54f --- /dev/null +++ b/scripts/update_plugin_repository.sh @@ -0,0 +1,6 @@ +#!/bin/bash +URL="https://macpassapp.org/data/plugins.json" +MY_FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +DOWNLOAD_FOLDER="${MY_FOLDER}/../MacPass/Resources/" +cd "${DOWNLOAD_FOLDER}" +wget "${URL}"