From b161b579ff62e4a9e1740b8bb49485746270fe75 Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Sat, 25 Jul 2015 10:41:44 -0500 Subject: [PATCH 01/59] Initial commit, working GitHub 2FA. -- Only SMS has been tested. -- Unfortunate GET --> POST --> GET flow needed when removing token. --- .../AutoPkg Task/LGAutoPkgErrorHandler.h | 9 +- .../AutoPkg Task/LGAutoPkgErrorHandler.m | 72 +++++++--- AutoPkgr/Models/AutoPkg Task/LGAutoPkgTask.m | 133 ++++++++++++------ AutoPkgr/Utility/LGError.h | 2 + 4 files changed, 154 insertions(+), 62 deletions(-) diff --git a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.h b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.h index 266a300f..5059be75 100644 --- a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.h +++ b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.h @@ -30,7 +30,12 @@ typedef NS_ENUM(NSInteger, LGAutoPkgErrorCodes) { kLGAutoPkgErrorNoRecipes = 255, kLGAutoPkgErrorRepoModification = -2, - kLGAutoPkgErrorNeedsRepair = -1 + kLGAutoPkgErrorNeedsRepair = -1, +}; + +typedef NS_ENUM(NSInteger, LGAutoPkgGitHubAPIError) { + kLGAutoPkgErrorGHApi2FAAuthRequired = 401, + kLGAutoPkgErrorAPITokenNotOnRemote = 600, }; typedef NS_OPTIONS(NSInteger, LGAutoPkgVerb) { @@ -64,4 +69,6 @@ typedef NS_OPTIONS(NSInteger, LGAutoPkgVerb) { - (instancetype)initWithVerb:(LGAutoPkgVerb)verb; - (NSError *)errorWithExitCode:(NSInteger)exitCode; ++ (NSError *)errorWithGitHubAPIErrorCode:(LGAutoPkgGitHubAPIError)statusCode; + @end diff --git a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.m b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.m index dbb217e1..9d827d81 100644 --- a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.m +++ b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.m @@ -21,7 +21,7 @@ #import "LGAutoPkgErrorHandler.h" #import "LGLogger.h" -NSString * LGAutoPkgLocalizedString(NSString *key, NSString *comment) +NSString *LGAutoPkgLocalizedString(NSString *key, NSString *comment) { return [[NSBundle mainBundle] localizedStringForKey:key value:key @@ -34,41 +34,62 @@ switch (verb) { case kLGAutoPkgRun: - message = NSLocalizedStringFromTable(@"Error running recipes", @"LocalizableAutoPkg", nil); + message = NSLocalizedStringFromTable(@"Error running recipes", @"LocalizableAutoPkg", nil); break; case kLGAutoPkgListRecipes: - message = NSLocalizedStringFromTable(@"Error encountered listing recipes", @"LocalizableAutoPkg", nil); + message = NSLocalizedStringFromTable(@"Error encountered listing recipes", @"LocalizableAutoPkg", nil); break; case kLGAutoPkgMakeOverride: - message = NSLocalizedStringFromTable(@"Error creating recipe override file", @"LocalizableAutoPkg", nil); + message = NSLocalizedStringFromTable(@"Error creating recipe override file", @"LocalizableAutoPkg", nil); break; case kLGAutoPkgSearch: - message = NSLocalizedStringFromTable(@"Error encountered searching for recipe", @"LocalizableAutoPkg", nil); + message = NSLocalizedStringFromTable(@"Error encountered searching for recipe", @"LocalizableAutoPkg", nil); break; case kLGAutoPkgRepoAdd: - message = NSLocalizedStringFromTable(@"Error adding repo", @"LocalizableAutoPkg", nil); + message = NSLocalizedStringFromTable(@"Error adding repo", @"LocalizableAutoPkg", nil); break; case kLGAutoPkgRepoDelete: - message = NSLocalizedStringFromTable(@"Error removing repo", @"LocalizableAutoPkg", nil); + message = NSLocalizedStringFromTable(@"Error removing repo", @"LocalizableAutoPkg", nil); break; case kLGAutoPkgRepoUpdate: - message = NSLocalizedStringFromTable(@"Error updating repo", @"LocalizableAutoPkg", nil); + message = NSLocalizedStringFromTable(@"Error updating repo", @"LocalizableAutoPkg", nil); break; case kLGAutoPkgRepoList: - message = NSLocalizedStringFromTable(@"Error encountered listing repos", @"LocalizableAutoPkg", nil); + message = NSLocalizedStringFromTable(@"Error encountered listing repos", @"LocalizableAutoPkg", nil); break; case kLGAutoPkgVersion: - message = NSLocalizedStringFromTable(@"Error getting AutoPkg version", @"LocalizableAutoPkg", nil); + message = NSLocalizedStringFromTable(@"Error getting AutoPkg version", @"LocalizableAutoPkg", nil); break; case kLGAutoPkgUndefinedVerb: default: - message = NSLocalizedStringFromTable(@"AutoPkgr encountered an error", @"LocalizableAutoPkg", nil); + message = NSLocalizedStringFromTable(@"AutoPkgr encountered an error", @"LocalizableAutoPkg", nil); break; } return message; } +static NSDictionary *errorFromAutoPkgGitHubAPICode(LGAutoPkgGitHubAPIError code) +{ + NSDictionary *userInfo = nil; + switch (code) { + case kLGAutoPkgErrorGHApi2FAAuthRequired: { + userInfo = @{ NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"Two factor authentication is required", @"LocalizableAutoPkg", nil), + NSLocalizedRecoverySuggestionErrorKey : NSLocalizedStringFromTable(@"Your GitHub account has two factor authentication enabled. If you did not recieve a code on your mobile device, check your account's security settings and confirm the information there is correct.", @"LocalizableAutoPkg", nil) }; + break; + } + case kLGAutoPkgErrorAPITokenNotOnRemote: { + userInfo = @{ NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"API Token not found.", @"LocalizableAutoPkg", nil), + NSLocalizedRecoverySuggestionErrorKey : NSLocalizedStringFromTable(@"A GitHub API token matching the local one was not found on the remote. You may need to remove it manually. If you've just added the token, you may need to wait a minute to delete it.", @"LocalizableAutoPkg", nil) }; + } break; + default: { + userInfo = @{ NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"There was a problem communicating with GitHub", @"LocalizableAutoPkg", nil) }; + break; + } + } + return userInfo; +} + NSString *maskPasswordInString(NSString *string) { NSError *error; @@ -117,16 +138,16 @@ - (instancetype)initWithVerb:(LGAutoPkgVerb)verb _pipe = pipe; [pipe.fileHandleForReading setReadabilityHandler:^(NSFileHandle *fh) { - NSData *data = fh.availableData; - if (data) { - NSString *str = [[NSMutableString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSData *data = fh.availableData; + if (data) { + NSString *str = [[NSMutableString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - if (!_errorStrings) { - _errorStrings = [[NSMutableOrderedSet alloc] init]; - } - - [_errorStrings addObject:str]; - } + if (!_errorStrings) { + _errorStrings = [[NSMutableOrderedSet alloc] init]; + } + + [_errorStrings addObject:str]; + } }]; } return self; @@ -197,7 +218,7 @@ - (NSError *)errorWithExitCode:(NSInteger)exitCode } // autopkg run exits 255 if no recipe specified else if (_verb == kLGAutoPkgRun && exitCode == kLGAutoPkgErrorNoRecipes) { - errorDetails = LGAutoPkgLocalizedString(@"No recipes specified.", nil) ; + errorDetails = LGAutoPkgLocalizedString(@"No recipes specified.", nil); } } @@ -218,4 +239,13 @@ - (NSError *)errorWithExitCode:(NSInteger)exitCode return error; } ++ (NSError *)errorWithGitHubAPIErrorCode:(LGAutoPkgGitHubAPIError)statusCode +{ + NSDictionary *userInfo = errorFromAutoPkgGitHubAPICode(statusCode); + NSError *error = [NSError errorWithDomain:[[NSBundle mainBundle] bundleIdentifier] ?: @"autopkg" + code:statusCode + userInfo:userInfo]; + return error; +} + @end diff --git a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgTask.m b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgTask.m index e98aad57..8b9d1716 100644 --- a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgTask.m +++ b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgTask.m @@ -1017,59 +1017,53 @@ + (BOOL)apiTokenFileExists:(NSString *__autoreleasing *)file return (access(tokenFile.UTF8String, R_OK) == 0); } -+ (void)generateGitHubAPIToken:(NSString *)username password:(NSString *)password reply:(void (^)(NSError *))reply ++ (void)generateGitHubAPIToken:(NSString *)username password:(NSString *)password reply:(void (^)(NSError *))reply{ + [self generateGitHubAPIToken:username password:password twoFactorCode:nil reply:reply]; +} + ++ (void)generateGitHubAPIToken:(NSString *)username password:(NSString *)password twoFactorCode:(NSString *)twoFactorCode reply:(void (^)(NSError *))reply { NSString *tokenFile = nil; if (![self apiTokenFileExists:&tokenFile]) { - - // Headers & Parameters - NSDictionary *headers = @{ - @"Accept" : @"application/vnd.github.v3+json", - @"User-Agent" : @"AutoPkg", - }; + AFHTTPRequestOperationManager *manager = [self tokenRequestManager:username password:password twoFactorCode:twoFactorCode]; NSDictionary *parameters = @{ @"note" : @"AutoPkg CLI" }; - // Networking - AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:[NSURL URLWithString:@"https://api.github.com"]]; - - // Request Serializer - manager.requestSerializer = [AFJSONRequestSerializer serializer]; - [manager.requestSerializer setAuthorizationHeaderFieldWithUsername:username password:password]; - [headers enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - [manager.requestSerializer setValue:obj forHTTPHeaderField:key]; - }]; - - // Response serializer - manager.responseSerializer = [AFJSONResponseSerializer serializer]; - [manager POST:@"/authorizations" parameters:parameters success:^(AFHTTPRequestOperation *operation, NSDictionary *responseObject) { NSError *error = nil; NSString *tokenString = responseObject[@"token"]; if (tokenString.length){ [tokenString writeToFile:tokenFile atomically:YES encoding:NSUTF8StringEncoding error:&error]; - reply(error); - } else { - reply(error); } + reply(error); + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { - reply(operation.response ? [LGError errorWithResponse:operation.response] : error); + NSDictionary *headers = operation.response.allHeaderFields; + if (operation.response.statusCode == 401 && [headers[@"X-GitHub-OTP"] hasPrefix:@"required"]) { + NSString *tfa = [self requestTwoFactorCode]; + if (tfa.length) { + [self generateGitHubAPIToken:username password:password twoFactorCode:tfa reply:reply]; + } else { + reply([LGAutoPkgErrorHandler errorWithGitHubAPIErrorCode:kLGAutoPkgErrorGHApi2FAAuthRequired]); + } + } else { + reply(operation.response ? [LGError errorWithResponse:operation.response] : error); + } }]; } else { reply(nil); } } -+ (void)deleteGitHubAPIToken:(NSString *)username password:(NSString *)password reply:(void (^)(NSError *))reply -{ - AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:[NSURL URLWithString:@"https://api.github.com"]]; ++ (void)deleteGitHubAPIToken:(NSString *)username password:(NSString *)password reply:(void (^)(NSError *))reply{ + [self deleteGitHubAPIToken:username password:password twoFactorCode:nil reply:reply]; +} - // Request Serializer - manager.requestSerializer = [AFJSONRequestSerializer serializer]; - [manager.requestSerializer setAuthorizationHeaderFieldWithUsername:username - password:password]; ++ (void)deleteGitHubAPIToken:(NSString *)username password:(NSString *)password twoFactorCode:(NSString *)twoFactorCode reply:(void (^)(NSError *))reply +{ + AFHTTPRequestOperationManager *manager = [self tokenRequestManager:username password:password twoFactorCode:twoFactorCode]; [manager GET:@"/authorizations" parameters:nil success:^(AFHTTPRequestOperation *operation, NSArray *responseObject) { // Get Auth ID @@ -1101,20 +1095,79 @@ + (void)deleteGitHubAPIToken:(NSString *)username password:(NSString *)password reply(operation.response ? [LGError errorWithResponse:operation.response] : error); }]; } else { - NSString *message = LGAutoPkgLocalizedString(@"API Token not found.", nil); - NSString *suggestion = LGAutoPkgLocalizedString(@"A GitHub API token matching the local one was not found on the remote. You may need to remove it manually. If you've just added the token, you may need to wait a minute to delete it.", nil); - - NSError *error = [NSError errorWithDomain:[[NSProcessInfo processInfo] processName] - code:-2 - userInfo:@{NSLocalizedDescriptionKey : message, - NSLocalizedRecoverySuggestionErrorKey : suggestion}]; - reply(error); + reply([LGAutoPkgErrorHandler errorWithGitHubAPIErrorCode:kLGAutoPkgErrorAPITokenNotOnRemote]); } } failure:^(AFHTTPRequestOperation *operation, NSError *error) { - reply(operation.response ? [LGError errorWithResponse:operation.response] : error); + NSDictionary *headers = operation.response.allHeaderFields; + if (operation.response.statusCode == 401 && [headers[@"X-GitHub-OTP"] hasPrefix:@"required"]) { + // GitHub only seems to send 2FA with POST request, not GET (as of7/25/15 ) + // So we need to loop over again this time specifying POST. + [manager POST:@"/authorizations" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {} failure:^(AFHTTPRequestOperation *operation, NSError *error) { + NSString *tfa = [self requestTwoFactorCode]; + if (tfa.length) { + [self deleteGitHubAPIToken:username password:password twoFactorCode:tfa reply:reply]; + } else { + reply([LGAutoPkgErrorHandler errorWithGitHubAPIErrorCode:kLGAutoPkgErrorGHApi2FAAuthRequired]); + } + }]; + + } else { + reply(operation.response ? [LGError errorWithResponse:operation.response] : error); + } }]; } ++ (AFHTTPRequestOperationManager *)tokenRequestManager:(NSString *)username password:(NSString *)password twoFactorCode:(NSString *)twoFactorCode{ + AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:[NSURL URLWithString:@"https://api.github.com"]]; + + // Request Serializer + manager.requestSerializer = [AFJSONRequestSerializer serializer]; + + // Headers & Parameters + NSDictionary *headers = @{ + @"Accept" : @"application/vnd.github.v3+json", + @"User-Agent" : @"AutoPkg", + }; + + [manager.requestSerializer setAuthorizationHeaderFieldWithUsername:username + password:password]; + + [headers enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + [manager.requestSerializer setValue:obj forHTTPHeaderField:key]; + }]; + + if (twoFactorCode) { + [manager.requestSerializer setValue:twoFactorCode forHTTPHeaderField:@"X-GitHub-OTP"]; + } + + // Response serializer + manager.responseSerializer = [AFJSONResponseSerializer serializer]; + + return manager; +} + ++ (NSString *)requestTwoFactorCode{ + NSString *password; + NSString *promptString = NSLocalizedString(@"Please enter the two factor authentication code.", nil); + + NSAlert *alert = [NSAlert alertWithMessageText:promptString + defaultButton:@"OK" + alternateButton:@"Cancel" + otherButton:nil + informativeTextWithFormat:@""]; + + NSTextField *input = [[NSTextField alloc] init]; + [input setFrame:NSMakeRect(0, 0, 300, 24)]; + [alert setAccessoryView:input]; + + NSInteger button = [alert runModal]; + if (button == NSAlertDefaultReturn) { + [input validateEditing]; + password = [input stringValue]; + } + return password; +} + #pragma mark-- Convenience Initializers -- + (LGAutoPkgTask *)runRecipesTask:(NSArray *)recipes { diff --git a/AutoPkgr/Utility/LGError.h b/AutoPkgr/Utility/LGError.h index 069a7bb2..136d6be0 100644 --- a/AutoPkgr/Utility/LGError.h +++ b/AutoPkgr/Utility/LGError.h @@ -58,6 +58,8 @@ typedef NS_ENUM(NSInteger, LGErrorAutoPkgCodes) { /** AutoPkg returns 255 if no recipe is specified */ kLGErrorAutoPkgNoRecipes = 255, + + }; @interface LGError : NSObject From 2d6072f010af255686996ee92ec356456813938b Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Sat, 25 Jul 2015 15:34:25 -0500 Subject: [PATCH 02/59] Add 6 text fields to alert's accessory view. (6 digits) --- AutoPkgr.xcodeproj/project.pbxproj | 8 + .../AutoPkg Task/LGAutoPkgErrorHandler.h | 4 +- AutoPkgr/Models/AutoPkg Task/LGAutoPkgTask.m | 446 +++++++++--------- .../UI Subclasses/LGTwoFactorAuthAlert.h | 24 + .../UI Subclasses/LGTwoFactorAuthAlert.m | 86 ++++ 5 files changed, 346 insertions(+), 222 deletions(-) create mode 100644 AutoPkgr/Views-Controllers-XIB/UI Subclasses/LGTwoFactorAuthAlert.h create mode 100644 AutoPkgr/Views-Controllers-XIB/UI Subclasses/LGTwoFactorAuthAlert.m diff --git a/AutoPkgr.xcodeproj/project.pbxproj b/AutoPkgr.xcodeproj/project.pbxproj index 06ea1ed3..544992bb 100644 --- a/AutoPkgr.xcodeproj/project.pbxproj +++ b/AutoPkgr.xcodeproj/project.pbxproj @@ -180,6 +180,8 @@ BEC1258B19F04703006696C4 /* LGRepoTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0BABE8196E560000A136BB /* LGRepoTableViewController.m */; }; BEC1258C19F049A1006696C4 /* LGTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = BE32178D19EDA15400A92E5A /* LGTableView.m */; }; BEC2024B1B600FF100F3BC2A /* LocalizableJSSImporter.strings in Resources */ = {isa = PBXBuildFile; fileRef = BEC202491B600FF100F3BC2A /* LocalizableJSSImporter.strings */; }; + BEC949791B63FDA3005641EB /* LGTwoFactorAuthAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = BEC949781B63FDA3005641EB /* LGTwoFactorAuthAlert.m */; }; + BEC9497A1B63FDA3005641EB /* LGTwoFactorAuthAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = BEC949781B63FDA3005641EB /* LGTwoFactorAuthAlert.m */; }; BECDAB8C1B249C0400544388 /* NSButton+colored.m in Sources */ = {isa = PBXBuildFile; fileRef = BECDAB891B249A2900544388 /* NSButton+colored.m */; }; BECDAB8D1B249C0B00544388 /* NSButton+colored.m in Sources */ = {isa = PBXBuildFile; fileRef = BECDAB891B249A2900544388 /* NSButton+colored.m */; }; BECDAB951B24C29600544388 /* LGMunkiIntegrationView.m in Sources */ = {isa = PBXBuildFile; fileRef = BECDAB931B24C29600544388 /* LGMunkiIntegrationView.m */; }; @@ -488,6 +490,8 @@ BEBF7B151A4894AC00E9967F /* LGVersioner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LGVersioner.h; sourceTree = ""; }; BEBF7B161A4894AC00E9967F /* LGVersioner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LGVersioner.m; sourceTree = ""; }; BEC2024A1B600FF100F3BC2A /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/LocalizableJSSImporter.strings; sourceTree = ""; }; + BEC949771B63FDA3005641EB /* LGTwoFactorAuthAlert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LGTwoFactorAuthAlert.h; sourceTree = ""; }; + BEC949781B63FDA3005641EB /* LGTwoFactorAuthAlert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LGTwoFactorAuthAlert.m; sourceTree = ""; }; BECDAB881B249A2900544388 /* NSButton+colored.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSButton+colored.h"; sourceTree = ""; }; BECDAB891B249A2900544388 /* NSButton+colored.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSButton+colored.m"; sourceTree = ""; }; BECDAB921B24C29600544388 /* LGMunkiIntegrationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LGMunkiIntegrationView.h; sourceTree = ""; }; @@ -1023,6 +1027,8 @@ BE81F2411B22C211007D16C4 /* LGViewWindowController.xib */, BE32178C19EDA15400A92E5A /* LGTableView.h */, BE32178D19EDA15400A92E5A /* LGTableView.m */, + BEC949771B63FDA3005641EB /* LGTwoFactorAuthAlert.h */, + BEC949781B63FDA3005641EB /* LGTwoFactorAuthAlert.m */, BE4DD5451B126E2800854FD8 /* LGSchedulePickerMenu.h */, BE4DD5461B126E2800854FD8 /* LGSchedulePickerMenu.m */, BE5E559F1B1B59D50025CFD3 /* LGTableCellViews.h */, @@ -1545,6 +1551,7 @@ BEC1258C19F049A1006696C4 /* LGTableView.m in Sources */, 6A53625D1988BE59008A949C /* LGTestPort.m in Sources */, BEFC931D1995F0710074C938 /* LGError.m in Sources */, + BEC949791B63FDA3005641EB /* LGTwoFactorAuthAlert.m in Sources */, BEEFE6C21AEBCC7C00882C89 /* LGIntegrationManager.m in Sources */, BEEFE6CE1AEC761F00882C89 /* LGJSSImporterIntegration.m in Sources */, BE05CE8219DAF6320089068B /* LGAutoPkgrAuthorizer.m in Sources */, @@ -1635,6 +1642,7 @@ BE38EA9D1B0CFEA6009FCBEB /* LGScheduleViewController.m in Sources */, BEFC3C8B1A3DF16700C789E9 /* NSString+cleaned.m in Sources */, BEEFE6CB1AEC760900882C89 /* LGAutoPkgIntegration.m in Sources */, + BEC9497A1B63FDA3005641EB /* LGTwoFactorAuthAlert.m in Sources */, BEFC3C8C1A3DF16700C789E9 /* NSTextField+safeStringValue.m in Sources */, BEFC3C8E1A3DF16700C789E9 /* NSArray+filtered.m in Sources */, BEB9AF2C1B41A01900016D98 /* LGSlackNotificationView.m in Sources */, diff --git a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.h b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.h index 5059be75..b754a963 100644 --- a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.h +++ b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.h @@ -34,8 +34,8 @@ typedef NS_ENUM(NSInteger, LGAutoPkgErrorCodes) { }; typedef NS_ENUM(NSInteger, LGAutoPkgGitHubAPIError) { - kLGAutoPkgErrorGHApi2FAAuthRequired = 401, - kLGAutoPkgErrorAPITokenNotOnRemote = 600, + kLGAutoPkgErrorGHApi2FAAuthRequired = 1000, + kLGAutoPkgErrorAPITokenNotOnRemote, }; typedef NS_OPTIONS(NSInteger, LGAutoPkgVerb) { diff --git a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgTask.m b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgTask.m index 8b9d1716..7f6f908d 100644 --- a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgTask.m +++ b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgTask.m @@ -23,6 +23,8 @@ #import "LGAutoPkgResultHandler.h" #import "LGHostInfo.h" #import "LGVersioner.h" +#import "LGTwoFactorAuthAlert.h" + #import "BSDProcessInfo.h" #import "NSData+taskData.h" @@ -165,8 +167,8 @@ - (void)runRecipes:(NSArray *)recipes } - (void)runRecipes:(NSArray *)recipes - withInteraction:(BOOL)withInteraction - reply:(void (^)(NSDictionary *, NSError *))reply + withInteraction:(BOOL)withInteraction + reply:(void (^)(NSDictionary *, NSError *))reply { LGAutoPkgTask *task = [LGAutoPkgTask runRecipesTask:recipes]; task.replyReportBlock = reply; @@ -312,7 +314,7 @@ - (void)main __weak typeof(self) weakSelf = self; [self.task setTerminationHandler:^(NSTask *task) { - [weakSelf didCompleteTaskExecution]; + [weakSelf didCompleteTaskExecution]; }]; // Since NSTask can raise for unexpected reasons, @@ -320,14 +322,13 @@ - (void)main @try { [self.task launch]; } - @catch (NSException *exception) - { + @catch (NSException *exception) { NSString *message = LGAutoPkgLocalizedString(@"A fatal error occurred when trying to run AutoPkg", nil); - NSString *suggestion = LGAutoPkgLocalizedString( @"If you repeatedly see this message please report it. The full scope of the error is in the system.log, make sure to include that in the report", nil); + NSString *suggestion = LGAutoPkgLocalizedString(@"If you repeatedly see this message please report it. The full scope of the error is in the system.log, make sure to include that in the report", nil); NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : message, - NSLocalizedRecoverySuggestionErrorKey : suggestion}; + NSLocalizedRecoverySuggestionErrorKey : suggestion }; NSLog(@"[AutoPkgr EXCEPTION] %@ %@", exception.reason, exception.userInfo); @@ -382,7 +383,7 @@ - (void)didCompleteTaskExecution if (_verb & (kLGAutoPkgRepoAdd | kLGAutoPkgRepoDelete | kLGAutoPkgRepoUpdate)) { // Post a notification for objects watching for modified repos. dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:kLGNotificationReposModified object:nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:kLGNotificationReposModified object:nil]; }); } @@ -519,63 +520,63 @@ - (void)configureFileHandles BOOL verbose = [[NSUserDefaults standardUserDefaults] boolForKey:@"verboseAutoPkgRun"]; [[standardOutput fileHandleForReading] setReadabilityHandler:^(NSFileHandle *handle) { - NSData *data = handle.availableData; - - if (data.length) { - NSString *message = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; - - if (isInteractive && data.taskData_isInteractive) { - DevLog(@"Prompting for interaction: %@", message); - return [self interactiveAlertWithMessage:message]; - } - - [_versioner parseString:message]; - if ([progressPredicate evaluateWithObject:message]) { - NSString *fullMessage; - if (_verb == kLGAutoPkgRepoUpdate) { - fullMessage = [NSString stringWithFormat:@"Updating %@", [message lastPathComponent]]; - } else { - int cntStr = (int)round(count) + 1; - int totStr = (int)round(total); - fullMessage = [[NSString stringWithFormat:@"(%d/%d) %@", cntStr, totStr, message] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; - } - - double progress = ((count/total) * 100); - - LGAutoPkgTaskResponseObject *response = [[LGAutoPkgTaskResponseObject alloc] init]; - response.progressMessage = fullMessage; - response.progress = progress; - count++; - - [(NSObject *)_taskStatusDelegate performSelectorOnMainThread:@selector(didReceiveStatusUpdate:) withObject:response waitUntilDone:NO]; - - // If verboseAutoPkgRun is not enabled, log the limited message here. - if (!verbose) { - NSLog(@"%@",message); - } - } - // If verboseAutoPkgRun is enabled, log everything generated by autopkg run -v. - if (verbose) { - NSLog(@"%@",message); - } - } + NSData *data = handle.availableData; + + if (data.length) { + NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + + if (isInteractive && data.taskData_isInteractive) { + DevLog(@"Prompting for interaction: %@", message); + return [self interactiveAlertWithMessage:message]; + } + + [_versioner parseString:message]; + if ([progressPredicate evaluateWithObject:message]) { + NSString *fullMessage; + if (_verb == kLGAutoPkgRepoUpdate) { + fullMessage = [NSString stringWithFormat:@"Updating %@", [message lastPathComponent]]; + } else { + int cntStr = (int)round(count) + 1; + int totStr = (int)round(total); + fullMessage = [[NSString stringWithFormat:@"(%d/%d) %@", cntStr, totStr, message] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + } + + double progress = ((count / total) * 100); + + LGAutoPkgTaskResponseObject *response = [[LGAutoPkgTaskResponseObject alloc] init]; + response.progressMessage = fullMessage; + response.progress = progress; + count++; + + [(NSObject *)_taskStatusDelegate performSelectorOnMainThread:@selector(didReceiveStatusUpdate:) withObject:response waitUntilDone:NO]; + + // If verboseAutoPkgRun is not enabled, log the limited message here. + if (!verbose) { + NSLog(@"%@", message); + } + } + // If verboseAutoPkgRun is enabled, log everything generated by autopkg run -v. + if (verbose) { + NSLog(@"%@", message); + } + } }]; } } else { // In order to prevent maxing out the stdout buffer collect the data progressively // even thought the data returned is usually small. [[standardOutput fileHandleForReading] setReadabilityHandler:^(NSFileHandle *fh) { - NSData *data = [fh availableData]; - if (data.length) { - if (isInteractive && data.taskData_isInteractive) { - return [self interactiveAlertWithMessage:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]; - } - - if (!_standardOutData) { - _standardOutData = [[NSMutableData alloc] init ]; - } - [_standardOutData appendData:data]; - } + NSData *data = [fh availableData]; + if (data.length) { + if (isInteractive && data.taskData_isInteractive) { + return [self interactiveAlertWithMessage:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]; + } + + if (!_standardOutData) { + _standardOutData = [[NSMutableData alloc] init]; + } + [_standardOutData appendData:data]; + } }]; } } @@ -804,26 +805,25 @@ - (void)interactiveAlertWithMessage:(NSString *)message { /* Eventually there may be more ways to interact, for now it's only to search github for a recipe's repo */ [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - NSString *title = LGAutoPkgLocalizedString(@"Could not find the parent recipe" - , nil); - - NSString *cleanedMessage = [message stringByReplacingOccurrencesOfString:@"[y/n]:" - withString:@""]; - NSAlert *alert = [NSAlert alertWithMessageText:title - defaultButton:@"Yes" - alternateButton:@"No" - otherButton:nil - informativeTextWithFormat:@"%@", cleanedMessage]; - - NSString *results; - // Don't forget the newline char! - if ([alert runModal] == NSAlertDefaultReturn) { - results = @"y\n"; - } else { - results = @"n\n"; - } - - [[self.task.standardInput fileHandleForWriting] writeData:[results dataUsingEncoding:NSUTF8StringEncoding]]; + NSString *title = LGAutoPkgLocalizedString(@"Could not find the parent recipe", nil); + + NSString *cleanedMessage = [message stringByReplacingOccurrencesOfString:@"[y/n]:" + withString:@""]; + NSAlert *alert = [NSAlert alertWithMessageText:title + defaultButton:@"Yes" + alternateButton:@"No" + otherButton:nil + informativeTextWithFormat:@"%@", cleanedMessage]; + + NSString *results; + // Don't forget the newline char! + if ([alert runModal] == NSAlertDefaultReturn) { + results = @"y\n"; + } else { + results = @"n\n"; + } + + [[self.task.standardInput fileHandleForWriting] writeData:[results dataUsingEncoding:NSUTF8StringEncoding]]; }]; } @@ -838,7 +838,7 @@ + (void)runRecipes:(NSArray *)recipes __weak typeof(task) weakTask = task; [task launchInBackground:^(NSError *error) { - reply(weakTask.report,error); + reply(weakTask.report, error); }]; } @@ -851,7 +851,7 @@ + (void)runRecipeList:(NSString *)recipeList __weak typeof(task) weakTask = task; [task launchInBackground:^(NSError *error) { - reply(weakTask.report,error); + reply(weakTask.report, error); }]; } @@ -860,11 +860,11 @@ + (void)search:(NSString *)recipe reply:(void (^)(NSArray *results, NSError *err LGAutoPkgTask *task = [LGAutoPkgTask searchTask:recipe]; __weak typeof(task) weakTask = task; [task launchInBackground:^(NSError *error) { - NSArray *results; - if (!error) { - results = [weakTask results]; - } - reply(results, error); + NSArray *results; + if (!error) { + results = [weakTask results]; + } + reply(results, error); }]; } @@ -884,17 +884,17 @@ + (void)makeOverride:(NSString *)recipe name:(NSString *)name reply:(void (^)(NS task.arguments = args; __weak typeof(task) weakTask = task; [task launchInBackground:^(NSError *error) { - typeof(task) strongTask = weakTask; - NSMutableString *path = nil; - if(!error){ - path = [strongTask.standardOutString.trimmed mutableCopy]; - [path deleteCharactersInRange:[path rangeOfString:@"Override file saved to "]]; - if ((path.length > 2) && ([path characterAtIndex:path.length-1] == '.')) { - [path deleteCharactersInRange:NSMakeRange(path.length-1, 1)]; - } - } - - reply(path, error); + typeof(task) strongTask = weakTask; + NSMutableString *path = nil; + if (!error) { + path = [strongTask.standardOutString.trimmed mutableCopy]; + [path deleteCharactersInRange:[path rangeOfString:@"Override file saved to "]]; + if ((path.length > 2) && ([path characterAtIndex:path.length - 1] == '.')) { + [path deleteCharactersInRange:NSMakeRange(path.length - 1, 1)]; + } + } + + reply(path, error); }]; } @@ -913,8 +913,8 @@ + (void)info:(NSString *)recipe reply:(void (^)(NSString *info, NSError *error)) LGAutoPkgTask *task = [[LGAutoPkgTask alloc] initWithArguments:@[ @"info", recipe ]]; __weak typeof(task) weakTask = task; [task launchInBackground:^(NSError *error) { - typeof(task) strongTask = weakTask; - reply(strongTask.standardOutString, error); + typeof(task) strongTask = weakTask; + reply(strongTask.standardOutString, error); }]; } @@ -930,7 +930,7 @@ + (void)repoAdd:(NSString *)repo reply:(void (^)(NSError *))reply } task.arguments = @[ @"repo-add", repo ]; [task launchInBackground:^(NSError *error) { - reply(error); + reply(error); }]; } @@ -940,7 +940,7 @@ + (void)repoRemove:(NSString *)repo reply:(void (^)(NSError *))reply LGAutoPkgTask *task = [[LGAutoPkgTask alloc] init]; task.arguments = @[ @"repo-delete", repo ]; [task launchInBackground:^(NSError *error) { - reply(error); + reply(error); }]; } @@ -950,7 +950,7 @@ + (void)repoUpdate:(void (^)(NSString *, double taskProgress))progress LGAutoPkgTask *task = [[LGAutoPkgTask alloc] initWithArguments:@[ @"repo-update", @"all" ]]; task.progressUpdateBlock = progress; [task launchInBackground:^(NSError *error) { - reply(error); + reply(error); }]; } @@ -979,27 +979,7 @@ + (NSString *)processorInfo:(NSString *)processor } #pragma mark - Other -+ (NSString *)version -{ - NSString *version; - - NSTask *task = [[NSTask alloc] init]; - - task = task; - task.launchPath = @"/usr/bin/python"; - task.arguments = @[ autopkg(), @"version" ]; - task.standardOutput = [NSPipe pipe]; - [task launch]; - [task waitUntilExit]; - - NSData *data = [[task.standardOutput fileHandleForReading] availableData]; - if (data.length) { - version = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - } - - return [version stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] ?: @"0.0.0"; -} - +#pragma mark-- GitHub API token -- + (NSString *)apiToken { NSString *tokenFile = nil; @@ -1017,7 +997,8 @@ + (BOOL)apiTokenFileExists:(NSString *__autoreleasing *)file return (access(tokenFile.UTF8String, R_OK) == 0); } -+ (void)generateGitHubAPIToken:(NSString *)username password:(NSString *)password reply:(void (^)(NSError *))reply{ ++ (void)generateGitHubAPIToken:(NSString *)username password:(NSString *)password reply:(void (^)(NSError *))reply +{ [self generateGitHubAPIToken:username password:password twoFactorCode:nil reply:reply]; } @@ -1030,34 +1011,38 @@ + (void)generateGitHubAPIToken:(NSString *)username password:(NSString *)passwor NSDictionary *parameters = @{ @"note" : @"AutoPkg CLI" }; - [manager POST:@"/authorizations" parameters:parameters success:^(AFHTTPRequestOperation *operation, NSDictionary *responseObject) { - NSError *error = nil; - NSString *tokenString = responseObject[@"token"]; + [manager POST:@"/authorizations" + parameters:parameters + success:^(AFHTTPRequestOperation *operation, NSDictionary *responseObject) { + NSError *error = nil; + NSString *tokenString = responseObject[@"token"]; + + if (tokenString.length) { + [tokenString writeToFile:tokenFile atomically:YES encoding:NSUTF8StringEncoding error:&error]; + } + reply(error); - if (tokenString.length){ - [tokenString writeToFile:tokenFile atomically:YES encoding:NSUTF8StringEncoding error:&error]; - } - reply(error); - - } failure:^(AFHTTPRequestOperation *operation, NSError *error) { - NSDictionary *headers = operation.response.allHeaderFields; - if (operation.response.statusCode == 401 && [headers[@"X-GitHub-OTP"] hasPrefix:@"required"]) { - NSString *tfa = [self requestTwoFactorCode]; - if (tfa.length) { - [self generateGitHubAPIToken:username password:password twoFactorCode:tfa reply:reply]; - } else { - reply([LGAutoPkgErrorHandler errorWithGitHubAPIErrorCode:kLGAutoPkgErrorGHApi2FAAuthRequired]); - } - } else { - reply(operation.response ? [LGError errorWithResponse:operation.response] : error); } - }]; + failure:^(AFHTTPRequestOperation *operation, NSError *error) { + NSDictionary *headers = operation.response.allHeaderFields; + if (operation.response.statusCode == 401 && [headers[@"X-GitHub-OTP"] hasPrefix:@"required"]) { + NSString *tfa = [self promptForTwoFactorAuthCode]; + if (tfa.length) { + [self generateGitHubAPIToken:username password:password twoFactorCode:tfa reply:reply]; + } else { + reply([LGAutoPkgErrorHandler errorWithGitHubAPIErrorCode:kLGAutoPkgErrorGHApi2FAAuthRequired]); + } + } else { + reply(operation.response ? [LGError errorWithResponse:operation.response] : error); + } + }]; } else { reply(nil); } } -+ (void)deleteGitHubAPIToken:(NSString *)username password:(NSString *)password reply:(void (^)(NSError *))reply{ ++ (void)deleteGitHubAPIToken:(NSString *)username password:(NSString *)password reply:(void (^)(NSError *))reply +{ [self deleteGitHubAPIToken:username password:password twoFactorCode:nil reply:reply]; } @@ -1065,107 +1050,107 @@ + (void)deleteGitHubAPIToken:(NSString *)username password:(NSString *)password { AFHTTPRequestOperationManager *manager = [self tokenRequestManager:username password:password twoFactorCode:twoFactorCode]; - [manager GET:@"/authorizations" parameters:nil success:^(AFHTTPRequestOperation *operation, NSArray *responseObject) { - // Get Auth ID - __block NSDictionary *autoPkgTokenDict = nil; - NSString *token = [self apiToken]; - NSString *tokenLastEight = [token substringFromIndex:token.length - 8]; + [manager GET:@"/authorizations" + parameters:nil + success:^(AFHTTPRequestOperation *operation, NSArray *responseObject) { + // Get Auth ID + __block NSDictionary *autoPkgTokenDict = nil; + NSString *token = [self apiToken]; + NSString *tokenLastEight = [token substringFromIndex:token.length - 8]; - [responseObject enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) { + [responseObject enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) { if ([@"AutoPkg CLI" isEqualToString:obj[@"note"]] && - [tokenLastEight isEqualToString:obj[@"token_last_eight"]]) { + [tokenLastEight isEqualToString:obj[@"token_last_eight"]]) { autoPkgTokenDict = obj; *stop = YES; } - }]; - - if (autoPkgTokenDict) { - NSString *delete = [NSString stringWithFormat:@"/authorizations/%@", autoPkgTokenDict[@"id"]]; - - /* Delete the token from the remote */ - [manager DELETE:delete parameters:@{} success:^(AFHTTPRequestOperation *operation, id responseObject) { - // remove the file - NSString *tokenFile = nil; - NSError *error = nil; - if ([self apiTokenFileExists:&tokenFile]) { - [[NSFileManager defaultManager] removeItemAtPath:tokenFile error:&error]; - } - reply(error); - } failure:^(AFHTTPRequestOperation *operation, NSError *error) { - reply(operation.response ? [LGError errorWithResponse:operation.response] : error); - }]; - } else { - reply([LGAutoPkgErrorHandler errorWithGitHubAPIErrorCode:kLGAutoPkgErrorAPITokenNotOnRemote]); - } - } failure:^(AFHTTPRequestOperation *operation, NSError *error) { - NSDictionary *headers = operation.response.allHeaderFields; - if (operation.response.statusCode == 401 && [headers[@"X-GitHub-OTP"] hasPrefix:@"required"]) { - // GitHub only seems to send 2FA with POST request, not GET (as of7/25/15 ) - // So we need to loop over again this time specifying POST. - [manager POST:@"/authorizations" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {} failure:^(AFHTTPRequestOperation *operation, NSError *error) { - NSString *tfa = [self requestTwoFactorCode]; - if (tfa.length) { - [self deleteGitHubAPIToken:username password:password twoFactorCode:tfa reply:reply]; - } else { - reply([LGAutoPkgErrorHandler errorWithGitHubAPIErrorCode:kLGAutoPkgErrorGHApi2FAAuthRequired]); - } - }]; - - } else { - reply(operation.response ? [LGError errorWithResponse:operation.response] : error); + }]; + + if (autoPkgTokenDict) { + NSString *delete = [NSString stringWithFormat:@"/authorizations/%@", autoPkgTokenDict[@"id"]]; + + /* Delete the token from the remote */ + [manager DELETE:delete + parameters:@{} + success:^(AFHTTPRequestOperation *operation, id responseObject) { + // remove the file + NSString *tokenFile = nil; + NSError *error = nil; + if ([self apiTokenFileExists:&tokenFile]) { + [[NSFileManager defaultManager] removeItemAtPath:tokenFile error:&error]; + } + reply(error); + } + failure:^(AFHTTPRequestOperation *operation, NSError *error) { + reply(operation.response ? [LGError errorWithResponse:operation.response] : error); + }]; + } else { + reply([LGAutoPkgErrorHandler errorWithGitHubAPIErrorCode:kLGAutoPkgErrorAPITokenNotOnRemote]); + } } - }]; + failure:^(AFHTTPRequestOperation *operation, NSError *error) { + + NSDictionary *headers = operation.response.allHeaderFields; + if (operation.response.statusCode == 401 && [headers[@"X-GitHub-OTP"] hasPrefix:@"required"]) { + // GitHub only seems to send 2FA with POST request, not GET (as of7/25/15 ) + // Do the request again this time specifying POST. + [manager POST:@"/authorizations" + parameters:nil + success:^(AFHTTPRequestOperation *operation, id responseObject) { + // We should never get here, but in case we do send a reply so the UI doesn't hang. + reply(nil); + } + failure:^(AFHTTPRequestOperation *operation, NSError *error) { + NSString *newTwoFactorCode = [self promptForTwoFactorAuthCode]; + if (newTwoFactorCode.length) { + [self deleteGitHubAPIToken:username password:password twoFactorCode:newTwoFactorCode reply:reply]; + } else { + reply([LGAutoPkgErrorHandler errorWithGitHubAPIErrorCode:kLGAutoPkgErrorGHApi2FAAuthRequired]); + } + }]; + } else { + reply(operation.response ? [LGError errorWithResponse:operation.response] : error); + } + }]; } -+ (AFHTTPRequestOperationManager *)tokenRequestManager:(NSString *)username password:(NSString *)password twoFactorCode:(NSString *)twoFactorCode{ ++ (AFHTTPRequestOperationManager *)tokenRequestManager:(NSString *)username password:(NSString *)password twoFactorCode:(NSString *)twoFactorCode +{ AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:[NSURL URLWithString:@"https://api.github.com"]]; - // Request Serializer + manager.responseSerializer = [AFJSONResponseSerializer serializer]; manager.requestSerializer = [AFJSONRequestSerializer serializer]; - // Headers & Parameters - NSDictionary *headers = @{ - @"Accept" : @"application/vnd.github.v3+json", - @"User-Agent" : @"AutoPkg", - }; - [manager.requestSerializer setAuthorizationHeaderFieldWithUsername:username password:password]; - [headers enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - [manager.requestSerializer setValue:obj forHTTPHeaderField:key]; + NSDictionary *defaultHeaders = @{ + @"Accept" : @"application/vnd.github.v3+json", + @"User-Agent" : @"AutoPkg", + }; + + [defaultHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + [manager.requestSerializer setValue:obj forHTTPHeaderField:key]; }]; if (twoFactorCode) { [manager.requestSerializer setValue:twoFactorCode forHTTPHeaderField:@"X-GitHub-OTP"]; } - // Response serializer - manager.responseSerializer = [AFJSONResponseSerializer serializer]; - return manager; } -+ (NSString *)requestTwoFactorCode{ - NSString *password; - NSString *promptString = NSLocalizedString(@"Please enter the two factor authentication code.", nil); - - NSAlert *alert = [NSAlert alertWithMessageText:promptString - defaultButton:@"OK" - alternateButton:@"Cancel" - otherButton:nil - informativeTextWithFormat:@""]; - - NSTextField *input = [[NSTextField alloc] init]; - [input setFrame:NSMakeRect(0, 0, 300, 24)]; - [alert setAccessoryView:input]; ++ (NSString *)promptForTwoFactorAuthCode +{ + NSString *code; + LGTwoFactorAuthAlert *alert = [[LGTwoFactorAuthAlert alloc] init]; - NSInteger button = [alert runModal]; - if (button == NSAlertDefaultReturn) { - [input validateEditing]; - password = [input stringValue]; + NSModalResponse button = [alert runModal]; + if (button == NSAlertFirstButtonReturn) { + code = [alert authorizatoinCode]; } - return password; + + return code; } #pragma mark-- Convenience Initializers -- @@ -1224,6 +1209,27 @@ + (LGAutoPkgTask *)repoDeleteTask:(NSString *)repo; } #pragma mark-- Other Methods -- ++ (NSString *)version +{ + NSString *version; + + NSTask *task = [[NSTask alloc] init]; + + task = task; + task.launchPath = @"/usr/bin/python"; + task.arguments = @[ autopkg(), @"version" ]; + task.standardOutput = [NSPipe pipe]; + [task launch]; + [task waitUntilExit]; + + NSData *data = [[task.standardOutput fileHandleForReading] availableData]; + if (data.length) { + version = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + } + + return [version stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] ?: @"0.0.0"; +} + + (BOOL)instanceIsRunning { NSArray *runningArgs = @[ autopkg(), @"run", @"--recipe-list" ]; diff --git a/AutoPkgr/Views-Controllers-XIB/UI Subclasses/LGTwoFactorAuthAlert.h b/AutoPkgr/Views-Controllers-XIB/UI Subclasses/LGTwoFactorAuthAlert.h new file mode 100644 index 00000000..155cebd2 --- /dev/null +++ b/AutoPkgr/Views-Controllers-XIB/UI Subclasses/LGTwoFactorAuthAlert.h @@ -0,0 +1,24 @@ +// +// LGTwoFactorAuthAlert.h +// AutoPkgr +// +// Created by Eldon on 7/25/15. +// Copyright 2014-2015 The Linde Group, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#import + +@interface LGTwoFactorAuthAlert : NSAlert +- (NSString *)authorizatoinCode; +@end diff --git a/AutoPkgr/Views-Controllers-XIB/UI Subclasses/LGTwoFactorAuthAlert.m b/AutoPkgr/Views-Controllers-XIB/UI Subclasses/LGTwoFactorAuthAlert.m new file mode 100644 index 00000000..cb3d7165 --- /dev/null +++ b/AutoPkgr/Views-Controllers-XIB/UI Subclasses/LGTwoFactorAuthAlert.m @@ -0,0 +1,86 @@ +// +// LGTwoFactorAuthAlert.m +// AutoPkgr +// +// Created by Eldon on 7/25/15. +// Copyright 2014-2015 The Linde Group, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "LGTwoFactorAuthAlert.h" + +static int PADDING = 5; +static int TFSIZE = 30; +static int NUMBER_OF_FIELDS = 6; + +@interface LGTwoFactorAuthAlert () +@end + +@implementation LGTwoFactorAuthAlert + +- (instancetype)init +{ + + if (self = [super init]) { + self.messageText = NSLocalizedString(@"Enter your two-factor authentication code", @"NSAlert messageText when requesting 2FA code."); + + self.informativeText = NSLocalizedString(@"Two-factor authentication is enabled for your account. Enter your authentication code to verify your identity.", @"NSAlert informativeText when requesting 2FA code."); + + [self addButtonWithTitle:@"OK"]; + [self addButtonWithTitle:@"Cancel"]; + + self.accessoryView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 250, TFSIZE + PADDING)]; + + int position = round((self.accessoryView.frame.size.width - (NUMBER_OF_FIELDS * (PADDING + TFSIZE))) / 2); + + for (int i = 0; i < NUMBER_OF_FIELDS; i++) { + NSTextField *input = [[NSTextField alloc] init]; + input.delegate = self; + + input.alignment = NSCenterTextAlignment; + input.formatter = [[NSNumberFormatter alloc] init]; + + [input.formatter setMaximumSignificantDigits:1]; + input.frame = NSMakeRect(position, 0, TFSIZE, TFSIZE); + input.font = [NSFont systemFontOfSize:18]; + + [self.accessoryView addSubview:input]; + position = (position + TFSIZE + PADDING); + } + } + return self; +} + +- (NSString *)authorizatoinCode +{ + NSMutableString *string = [[NSMutableString alloc] initWithCapacity:NUMBER_OF_FIELDS]; + for (NSTextField *textFiled in self.accessoryView.subviews) { + if ([textFiled isKindOfClass:[NSTextField class]]) { + [string appendString:textFiled.stringValue]; + } + } + return string.copy; +} + +- (void)controlTextDidChange:(NSNotification *)notification +{ + NSTextField *textFiled = notification.object; + NSNumber *n = [[NSNumberFormatter new] numberFromString:textFiled.stringValue]; + + if (textFiled.stringValue.length && n) { + [self.window selectKeyViewFollowingView:textFiled]; + } +} + +@end From 9c8c7f177baab33a7d1eed31c5a08cdd107abdc4 Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Sun, 26 Jul 2015 13:36:09 -0500 Subject: [PATCH 03/59] Address some initial possible issues. -- Move from dispatch queue to NSOperationQueue -- fix initializer. -- Check for self.connection prior to setting relay.connection in helper. --- AutoPkgr/Models/AutoPkg Task/LGAutoPkgRecipe.m | 3 ++- .../Email & Notifications/LGEmailNotification.m | 1 + .../Notification Manager/LGNotificationManager.m | 7 +++---- .../LGIntegration Base/LGIntegration.m | 15 ++++++--------- AutoPkgr/Supporting Files/main.m | 8 +------- Privileged Helper/LGAutoPkgrHelper.m | 2 +- 6 files changed, 14 insertions(+), 22 deletions(-) diff --git a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRecipe.m b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRecipe.m index e28c4387..f3e2daa2 100644 --- a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRecipe.m +++ b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRecipe.m @@ -79,8 +79,9 @@ - (instancetype)initWithRecipeFile:(NSURL *)recipeFile isOverride:(BOOL)isOverri _enabledInitialized = -1; [_identifierURLStore setObject:_recipeFileURL forKey:_Identifier]; + return self; } - return self; + return nil; } - (NSString *)Description diff --git a/AutoPkgr/Models/Email & Notifications/LGEmailNotification.m b/AutoPkgr/Models/Email & Notifications/LGEmailNotification.m index 9681ae9a..4ed29428 100644 --- a/AutoPkgr/Models/Email & Notifications/LGEmailNotification.m +++ b/AutoPkgr/Models/Email & Notifications/LGEmailNotification.m @@ -172,6 +172,7 @@ - (void)sendEmailNotification:(NSString *)subject message:(NSString *)message te MCOSMTPSession *session = [[MCOSMTPSession alloc] init]; session.hostname = credential.server; session.port = (int)credential.port; + session.timeout = 15; if (credential.user && credential.password) { session.username = credential.user; diff --git a/AutoPkgr/Models/Email & Notifications/Notification Manager/LGNotificationManager.m b/AutoPkgr/Models/Email & Notifications/Notification Manager/LGNotificationManager.m index daeb435b..2601e6b8 100644 --- a/AutoPkgr/Models/Email & Notifications/Notification Manager/LGNotificationManager.m +++ b/AutoPkgr/Models/Email & Notifications/Notification Manager/LGNotificationManager.m @@ -60,10 +60,9 @@ - (instancetype)initWithReportDictionary:(NSDictionary *)dictionary errors:(NSEr - (void)sendEnabledNotifications:(void (^)(NSError *))complete; { - dispatch_queue_t autopkgr_notification_manager_queue; - autopkgr_notification_manager_queue = dispatch_queue_create("com.lindegroup.autopkgr.notification.manager.queue", DISPATCH_QUEUE_CONCURRENT); - dispatch_async(autopkgr_notification_manager_queue, ^{ + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; + [queue addOperationWithBlock:^{ NSMutableArray *enabledServices = [NSMutableArray arrayWithCapacity:serviceClasses().count]; @@ -135,7 +134,7 @@ - (void)sendEnabledNotifications:(void (^)(NSError *))complete; completedService(); }]; } - }); + }]; } - (NSError *)processedError diff --git a/AutoPkgr/Models/Integrations/LGIntegration Base/LGIntegration.m b/AutoPkgr/Models/Integrations/LGIntegration Base/LGIntegration.m index ccb5795c..c7ff8bd8 100644 --- a/AutoPkgr/Models/Integrations/LGIntegration Base/LGIntegration.m +++ b/AutoPkgr/Models/Integrations/LGIntegration Base/LGIntegration.m @@ -132,14 +132,13 @@ - (void)dealloc _progressUpdateBlock = nil; _replyErrorBlock = nil; - /* Repoint so we don't loose reference to the _infoUpdateBlockDict after dealloc */ + DevLog(@"Starting Dealloc %@", self); NSMutableDictionary *releaseDict = _infoUpdateBlocksDict; - dispatch_async(autopkgr_integration_synchronizer_queue(), ^{ - [releaseDict enumerateKeysAndObjectsUsingBlock:^(void (^infoUpdate)(LGIntegrationInfo *), id obj, BOOL * stop) { + [releaseDict enumerateKeysAndObjectsUsingBlock:^(void (^infoUpdate)(LGIntegrationInfo *), id obj, BOOL * stop) { infoUpdate = nil; - }]; - [releaseDict removeAllObjects]; - }); + }]; + [releaseDict removeAllObjects]; + DevLog(@"Done Dealloc %@", self); } - (instancetype)init @@ -261,8 +260,8 @@ - (void)getInfo:(void (^)(LGIntegrationInfo *))reply _isRefreshing = YES; void (^updateInfoHandlers)() = ^() { dispatch_async(autopkgr_integration_synchronizer_queue(), ^{ + self.info = [[LGIntegrationInfo alloc] initWithIntegration:self]; if (reply || _infoUpdateHandler) { - self.info = [[LGIntegrationInfo alloc] initWithIntegration:self]; dispatch_async(dispatch_get_main_queue(), ^{ if (_infoUpdateHandler) { _infoUpdateHandler(_info); @@ -271,8 +270,6 @@ - (void)getInfo:(void (^)(LGIntegrationInfo *))reply reply(_info); } }); - } else { - self.info = [[LGIntegrationInfo alloc] initWithIntegration:self]; } _isRefreshing = NO; }); diff --git a/AutoPkgr/Supporting Files/main.m b/AutoPkgr/Supporting Files/main.m index 583515b8..6b9f001a 100755 --- a/AutoPkgr/Supporting Files/main.m +++ b/AutoPkgr/Supporting Files/main.m @@ -45,6 +45,7 @@ int main(int argc, const char *argv[]) error:nil state:kLGAutoPkgProgressStart]; + [manager setProgressUpdateBlock:^(NSString *message, double progress) { [[helper.connection remoteObjectProxy] sendMessageToMainApplication:message progress:progress @@ -61,12 +62,6 @@ int main(int argc, const char *argv[]) state:kLGAutoPkgProgressComplete]; // Wrap up the progress messaging... completionMessageSent = YES; - if (!completionMessageSent) { - [[helper.connection remoteObjectProxy] sendMessageToMainApplication:nil - progress:100 - error:nil - state:kLGAutoPkgProgressComplete]; - } LGNotificationManager *notifier = [[LGNotificationManager alloc] initWithReportDictionary:report errors:error]; @@ -80,7 +75,6 @@ int main(int argc, const char *argv[]) [[NSRunLoop currentRunLoop] run]; return 0; - } else { return NSApplicationMain(argc, argv); } diff --git a/Privileged Helper/LGAutoPkgrHelper.m b/Privileged Helper/LGAutoPkgrHelper.m index 07187fd9..9d3044f1 100644 --- a/Privileged Helper/LGAutoPkgrHelper.m +++ b/Privileged Helper/LGAutoPkgrHelper.m @@ -499,7 +499,7 @@ - (void)uninstall:(NSData *)authData removeKeychains:(BOOL)removeKeychains packa #pragma mark - IPC communication from background run - (void)registerMainApplication:(void (^)(BOOL resign))resign; { - if(!self.relayConnection){ + if(self.connection && !self.relayConnection){ self.relayConnection = self.connection; self.relayConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(LGProgressDelegate)]; _resign = resign; From 7f373f96af9a4cb9b578906c4f4688165c027d56 Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Sun, 26 Jul 2015 13:40:04 -0500 Subject: [PATCH 04/59] Lower timeout for GitHub Api call and check response status code as well as error. --- AutoPkgr/Utility/LGGitHubJSONLoader.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AutoPkgr/Utility/LGGitHubJSONLoader.m b/AutoPkgr/Utility/LGGitHubJSONLoader.m index 4bb19ae8..a49aac63 100644 --- a/AutoPkgr/Utility/LGGitHubJSONLoader.m +++ b/AutoPkgr/Utility/LGGitHubJSONLoader.m @@ -185,10 +185,10 @@ + (NSArray *)getJSONFromURL:(NSString *)aUrl // Create the NSURLRequest object with the given URL NSURLRequest *req = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy - timeoutInterval:15.0]; + timeoutInterval:5.0]; // Initialize our response and error objects - NSURLResponse *resp; + NSHTTPURLResponse *resp; NSError *error = nil; // Get the JSON data @@ -196,7 +196,7 @@ + (NSArray *)getJSONFromURL:(NSString *)aUrl returningResponse:&resp error:&error]; - if (error) { + if (error || resp.statusCode != 200) { NSLog(@"NSURLConnection error when attempting to get JSON data from the GitHub API. Error: %@.", error); return nil; } From f8ff4d4cd26c4ef7655102b14fd8646b88e1e975 Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Sun, 26 Jul 2015 14:06:00 -0500 Subject: [PATCH 05/59] Clean up helper. Cast self.connection to local NSXPCConnection to avoid mutation. --- Privileged Helper/LGAutoPkgrHelper.m | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/Privileged Helper/LGAutoPkgrHelper.m b/Privileged Helper/LGAutoPkgrHelper.m index 9d3044f1..0ee178ae 100644 --- a/Privileged Helper/LGAutoPkgrHelper.m +++ b/Privileged Helper/LGAutoPkgrHelper.m @@ -101,19 +101,18 @@ - (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConne __weak typeof(newConnection) weakConnection = newConnection; // If all connections are invalidated on the remote side, shutdown the helper. newConnection.invalidationHandler = ^() { - __strong typeof(newConnection) strongConnection = weakConnection; - if ([strongConnection isEqualTo:self.relayConnection] && _resign) { + if ([weakConnection isEqualTo:self.relayConnection] && _resign) { _resign(YES); } - [self.connections removeObject:strongConnection]; + [self.connections removeObject:weakConnection]; if (!self.connections.count) { [self quitHelper:^(BOOL success) {}]; } }; - [newConnection resume]; [self.connections addObject:newConnection]; + [newConnection resume]; syslog(LOG_INFO, "Connection Success..."); return YES; @@ -131,6 +130,8 @@ - (NSXPCConnection *)connection #pragma mark - Password - (void)getKeychainKey:(void (^)(NSString *, NSError *))reply { + NSXPCConnection *connection = self.connection; + // Password used to decrypt the keyFile. This password is stored in the System.keychain. NSString *encryptionPassword = nil; @@ -153,11 +154,11 @@ - (void)getKeychainKey:(void (^)(NSString *, NSError *))reply NSError *error = nil; // Effective user id used to determine location of the user's AutoPkgr keychain. - uid_t euid = self.connection.effectiveUserIdentifier; + uid_t euid = connection.effectiveUserIdentifier; struct passwd *pw = getpwuid(euid); NSAssert(euid != 0, @"The euid of the connection should never be 0!"); if (euid == 0) { - [self.connection invalidate]; + [connection invalidate]; return; } @@ -354,14 +355,16 @@ - (void)installPackageFromPath:(NSString *)path reply:(void (^)(NSError *error))reply; { NSError *error; + NSXPCConnection *connection = self.connection; + error = [LGAutoPkgrAuthorizer checkAuthorization:authData command:_cmd]; if (error != nil) { return reply(error); } - self.connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(LGProgressDelegate)]; + connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(LGProgressDelegate)]; - [self.connection.remoteObjectProxy bringAutoPkgrToFront]; + [connection.remoteObjectProxy bringAutoPkgrToFront]; __block double progress = 75.00; @@ -378,7 +381,7 @@ - (void)installPackageFromPath:(NSString *)path progress ++; NSString *message = data.taskData_splitLines.firstObject; if (message.length && ![message isEqualToString:@"#"]) { - [self.connection.remoteObjectProxy updateProgress:[message stringByReplacingOccurrencesOfString:@"installer: " withString:@""] + [connection.remoteObjectProxy updateProgress:[message stringByReplacingOccurrencesOfString:@"installer: " withString:@""] progress:progress]; } @@ -499,8 +502,9 @@ - (void)uninstall:(NSData *)authData removeKeychains:(BOOL)removeKeychains packa #pragma mark - IPC communication from background run - (void)registerMainApplication:(void (^)(BOOL resign))resign; { - if(self.connection && !self.relayConnection){ - self.relayConnection = self.connection; + NSXPCConnection *connection = self.connection; + if(connection && !self.relayConnection){ + self.relayConnection = connection; self.relayConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(LGProgressDelegate)]; _resign = resign; } else { From 55aff4e1cc1b8711f6e9ea9a5a5588d0d72fb495 Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Sun, 26 Jul 2015 16:38:21 -0500 Subject: [PATCH 06/59] Store keychain key in memory [ 10.8 only ]. --- Privileged Helper/LGAutoPkgrHelper.m | 147 ++++++++++++++++----------- 1 file changed, 86 insertions(+), 61 deletions(-) diff --git a/Privileged Helper/LGAutoPkgrHelper.m b/Privileged Helper/LGAutoPkgrHelper.m index 0ee178ae..7d033290 100644 --- a/Privileged Helper/LGAutoPkgrHelper.m +++ b/Privileged Helper/LGAutoPkgrHelper.m @@ -56,6 +56,9 @@ @interface LGAutoPkgrHelper () @implementation LGAutoPkgrHelper { void (^_resign)(BOOL); + +@private + NSString *_keyChainKey; } - (id)init @@ -94,21 +97,22 @@ - (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConne // We have one method that can handle multiple input types NSSet *acceptedClasses = [NSSet setWithObjects:[AHLaunchJobSchedule class], - [NSNumber class], nil]; + [NSNumber class], nil]; [newConnection.exportedInterface setClasses:acceptedClasses forSelector:@selector(scheduleRun:user:program:authorization:reply:) argumentIndex:0 ofReply:NO]; __weak typeof(newConnection) weakConnection = newConnection; // If all connections are invalidated on the remote side, shutdown the helper. newConnection.invalidationHandler = ^() { - if ([weakConnection isEqualTo:self.relayConnection] && _resign) { - _resign(YES); - } - - [self.connections removeObject:weakConnection]; - if (!self.connections.count) { - [self quitHelper:^(BOOL success) {}]; - } + if ([weakConnection isEqualTo:self.relayConnection] && _resign) { + _resign(YES); + } + + [self.connections removeObject:weakConnection]; + if (!self.connections.count) { + [self quitHelper:^(BOOL success){ + }]; + } }; [self.connections addObject:newConnection]; @@ -131,6 +135,28 @@ - (NSXPCConnection *)connection - (void)getKeychainKey:(void (^)(NSString *, NSError *))reply { NSXPCConnection *connection = self.connection; +#if DEBUG + syslog(LOG_ALERT, "Connection querying keychain %s : EUID: %d", connection.description.UTF8String, connection.effectiveUserIdentifier); +#endif + // Effective user id used to determine location of the user's AutoPkgr keychain. + uid_t euid = connection.effectiveUserIdentifier; + struct passwd *pw = getpwuid(euid); + if (euid == 0) { + [connection invalidate]; + return; + } + + // 10.8 has displayed instability on some systems when accessing the system keychain + // in rapid succession. So reluctantly we need to bypass direct calls to direct calls + // to the Security framewor in this method, and simply store the keyChain data in memory + // for the life of the helper. + // @note the getKeychain: call is still protected by codesign checking done when accepting new connecions so has "almost" the same level of security. + if (floor(NSFoundationVersionNumber) < NSFoundationVersionNumber10_9) { + if (_keyChainKey) { + syslog(LOG_ALERT, "[ 10.8 ] Helper Found keychain key in memory."); + return reply(_keyChainKey, nil); + } + } // Password used to decrypt the keyFile. This password is stored in the System.keychain. NSString *encryptionPassword = nil; @@ -153,15 +179,6 @@ - (void)getKeychainKey:(void (^)(NSString *, NSError *))reply // Error. NSError *error = nil; - // Effective user id used to determine location of the user's AutoPkgr keychain. - uid_t euid = connection.effectiveUserIdentifier; - struct passwd *pw = getpwuid(euid); - NSAssert(euid != 0, @"The euid of the connection should never be 0!"); - if (euid == 0) { - [connection invalidate]; - return; - } - // Attributes used to set permissions on the keyFile and it's parent directory. NSDictionary *const attributes = @{ NSFilePosixPermissions : [NSNumber numberWithShort:0700], // Owner read-write, others no access. @@ -284,7 +301,11 @@ - (void)getKeychainKey:(void (^)(NSString *, NSError *))reply } helper_reply: - + if (floor(NSFoundationVersionNumber) < NSFoundationVersionNumber10_9) { + if (password) { + _keyChainKey = password; + } + } reply(password, error); } @@ -308,7 +329,8 @@ - (void)scheduleRun:(AHLaunchJobSchedule *)scheduleOrInterval if (!error) { // Check if the launch path and user are valid, and that the timer has a sensible minimum. if ([self launchPathIsValid:program error:&error] && - [self userIsValid:user error:&error]) { + [self userIsValid:user + error:&error]) { AHLaunchJob *job = [AHLaunchJob new]; job.Program = program; job.Label = kLGAutoPkgrLaunchDaemonPlist; @@ -316,7 +338,7 @@ - (void)scheduleRun:(AHLaunchJobSchedule *)scheduleOrInterval if ([scheduleOrInterval isKindOfClass:[AHLaunchJobSchedule class]]) { job.StartCalendarInterval = scheduleOrInterval; - } else if ([scheduleOrInterval isKindOfClass:[NSNumber class]]){ + } else if ([scheduleOrInterval isKindOfClass:[NSNumber class]]) { job.StartInterval = [(NSNumber *)scheduleOrInterval integerValue]; } @@ -326,7 +348,7 @@ - (void)scheduleRun:(AHLaunchJobSchedule *)scheduleOrInterval /* Setting __CFPREFERENCES_AVOID_DAEMON helps preferences sync * between the background run managed by launchd and the main * app running in a Acqua session. */ - job.EnvironmentVariables = @{@"__CFPREFERENCES_AVOID_DAEMON" : @"1"}; + job.EnvironmentVariables = @{ @"__CFPREFERENCES_AVOID_DAEMON" : @"1" }; [[AHLaunchCtl sharedController] add:job toDomain:kAHGlobalLaunchDaemon error:&error]; } @@ -339,8 +361,8 @@ - (void)removeScheduleWithAuthorization:(NSData *)authData reply:(void (^)(NSErr { NSError *error = nil; -// NSError *error = [LGAutoPkgrAuthorizer checkAuthorization:authData -// command:_cmd]; + // NSError *error = [LGAutoPkgrAuthorizer checkAuthorization:authData + // command:_cmd]; if (!error) { [[AHLaunchCtl sharedController] remove:kLGAutoPkgrLaunchDaemonPlist fromDomain:kAHGlobalLaunchDaemon error:&error]; @@ -376,27 +398,27 @@ - (void)installPackageFromPath:(NSString *)path task.standardError = task.standardOutput; [[task.standardOutput fileHandleForReading] setReadabilityHandler:^(NSFileHandle *fh) { - NSData *data = fh.availableData; - if (data.length) { - progress ++; - NSString *message = data.taskData_splitLines.firstObject; - if (message.length && ![message isEqualToString:@"#"]) { - [connection.remoteObjectProxy updateProgress:[message stringByReplacingOccurrencesOfString:@"installer: " withString:@""] - progress:progress]; - } - - } + NSData *data = fh.availableData; + if (data.length) { + progress++; + NSString *message = data.taskData_splitLines.firstObject; + if (message.length && ![message isEqualToString:@"#"]) { + [connection.remoteObjectProxy updateProgress:[message stringByReplacingOccurrencesOfString:@"installer: " withString:@""] + progress:progress]; + } + } }]; [task setTerminationHandler:^(NSTask *endTask) { - NSError *error = [LGError errorFromTask:endTask]; - reply(error); + NSError *error = [LGError errorFromTask:endTask]; + reply(error); }]; [task launch]; } -- (void)uninstallPackagesWithIdentifiers:(NSArray *)identifiers authorization:(NSData *)authData reply:(uninstallPackageReplyBlock)reply{ +- (void)uninstallPackagesWithIdentifiers:(NSArray *)identifiers authorization:(NSData *)authData reply:(uninstallPackageReplyBlock)reply +{ NSError *error; error = [LGAutoPkgrAuthorizer checkAuthorization:authData command:_cmd]; @@ -408,12 +430,13 @@ - (void)uninstallPackagesWithIdentifiers:(NSArray *)identifiers authorization:(N LGPackageRemover *remover = [[LGPackageRemover alloc] init]; remover.dryRun = NO; - [remover removePackagesWithIdentifiers:identifiers progress:^(NSString *message, double progress) { + [remover removePackagesWithIdentifiers:identifiers + progress:^(NSString *message, double progress) { #if DEBUG - syslog(LOG_INFO, "[UNINSTALLER]: %s", message.UTF8String); + syslog(LOG_INFO, "[UNINSTALLER]: %s", message.UTF8String); #endif - // TODO: send progress updates - } reply:reply]; + // TODO: send progress updates + } reply:reply]; } #pragma mark - Life Cycle @@ -447,7 +470,8 @@ - (void)uninstall:(NSData *)authData reply:(void (^)(NSError *))reply; reply(error); } -- (void)uninstall:(NSData *)authData removeKeychains:(BOOL)removeKeychains packages:(NSArray *)packageIDs reply:(void (^)(NSError *))reply { +- (void)uninstall:(NSData *)authData removeKeychains:(BOOL)removeKeychains packages:(NSArray *)packageIDs reply:(void (^)(NSError *))reply +{ NSError *error; /*//////////////////////////////////////////////////////////////////// @@ -468,17 +492,17 @@ - (void)uninstall:(NSData *)authData removeKeychains:(BOOL)removeKeychains packa NSArray *keyFiles = [[manager contentsOfDirectoryAtPath:kLGEncryptedKeysParentDirectory error:nil] - filteredArrayUsingPredicate:predicate]; + filteredArrayUsingPredicate:predicate]; if (keyFiles.count) { NSString *encryptedKeyFile = [NSString stringWithFormat:@"%@/UID_%d", - kLGEncryptedKeysParentDirectory, - self.connection.effectiveUserIdentifier]; + kLGEncryptedKeysParentDirectory, + self.connection.effectiveUserIdentifier]; if (keyFiles.count == 1) { /* If the count is 1 there's only one user * remove the whole directory & common encryption key */ if ([manager fileExistsAtPath:encryptedKeyFile]) { - if([manager removeItemAtPath:kLGEncryptedKeysParentDirectory error:nil]){ + if ([manager removeItemAtPath:kLGEncryptedKeysParentDirectory error:nil]) { /* Remove keychain item. */ AHKeychainItem *item = [self commonDecryptionKeychainItem]; [[AHKeychain systemKeychain] deleteItem:item error:&error]; @@ -494,7 +518,7 @@ - (void)uninstall:(NSData *)authData removeKeychains:(BOOL)removeKeychains packa /*//////////////////////////////////////////////////////////////////// // Remove Integrations // ////////////////////////////////////////////////////////////////////*/ - // TODO: remove selected packages... + // TODO: remove selected packages... reply(error); } @@ -503,7 +527,7 @@ - (void)uninstall:(NSData *)authData removeKeychains:(BOOL)removeKeychains packa - (void)registerMainApplication:(void (^)(BOOL resign))resign; { NSXPCConnection *connection = self.connection; - if(connection && !self.relayConnection){ + if (connection && !self.relayConnection) { self.relayConnection = connection; self.relayConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(LGProgressDelegate)]; _resign = resign; @@ -512,27 +536,28 @@ - (void)registerMainApplication:(void (^)(BOOL resign))resign; } } -- (void)sendMessageToMainApplication:(NSString *)message progress:(double)progress error:(NSError *)error state:(LGBackgroundTaskProgressState)state; +- (void)sendMessageToMainApplication:(NSString *)message progress:(double)progress error:(NSError *)error state:(LGBackgroundTaskProgressState)state; { if (self.relayConnection) { switch (state) { - case kLGAutoPkgProgressStart: - [self.relayConnection.remoteObjectProxy startProgressWithMessage:message]; - break; - case kLGAutoPkgProgressProcessing: - [self.relayConnection.remoteObjectProxy updateProgress:message progress:progress]; - break; - case kLGAutoPkgProgressComplete: - [self.relayConnection.remoteObjectProxy stopProgress:error]; - break; - default: - break; + case kLGAutoPkgProgressStart: + [self.relayConnection.remoteObjectProxy startProgressWithMessage:message]; + break; + case kLGAutoPkgProgressProcessing: + [self.relayConnection.remoteObjectProxy updateProgress:message progress:progress]; + break; + case kLGAutoPkgProgressComplete: + [self.relayConnection.remoteObjectProxy stopProgress:error]; + break; + default: + break; } } } #pragma mark - Private -- (AHKeychainItem *)commonDecryptionKeychainItem { +- (AHKeychainItem *)commonDecryptionKeychainItem +{ AHKeychainItem *item = [[AHKeychainItem alloc] init]; item.service = @"AutoPkgr Common Decryption"; item.account = @"com.lindegroup.AutoPkgr.decryption.key"; From 76c7f3c170011765263c44c5e992f592bec48d02 Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Sun, 26 Jul 2015 16:38:42 -0500 Subject: [PATCH 07/59] More specific logging. --- AutoPkgr/Models/AutoPkg Task/LGAutoPkgRecipe.m | 2 +- AutoPkgr/Models/Email & Notifications/LGEmailNotification.m | 3 +++ AutoPkgr/Models/Email & Notifications/LGSlackNotification.m | 2 +- AutoPkgr/Utility/LGAutoPkgSchedule.m | 2 +- .../Views-Controllers-XIB/Panels-Popovers/LGRecipeSearch.m | 4 ++-- .../LGRecipeTableViewController.m | 2 +- .../Tab Views/LGNotificationsViewController.m | 2 +- 7 files changed, 10 insertions(+), 7 deletions(-) diff --git a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRecipe.m b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRecipe.m index f3e2daa2..2d16932b 100644 --- a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRecipe.m +++ b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRecipe.m @@ -417,7 +417,7 @@ + (BOOL)removeRecipeFromRecipeList:(NSString *)recipe NSString *recipe_list = [recipes.array componentsJoinedByString:@"\n"]; if (![recipe_list writeToFile:[self defaultRecipeList] atomically:YES encoding:NSUTF8StringEncoding error:&error]) { - NSLog(@"%@", error); + NSLog(@"Error writing file: %@", error.localizedDescription); return NO; } return YES; diff --git a/AutoPkgr/Models/Email & Notifications/LGEmailNotification.m b/AutoPkgr/Models/Email & Notifications/LGEmailNotification.m index 4ed29428..ec67ace4 100644 --- a/AutoPkgr/Models/Email & Notifications/LGEmailNotification.m +++ b/AutoPkgr/Models/Email & Notifications/LGEmailNotification.m @@ -149,6 +149,9 @@ - (void)sendEmailNotification:(NSString *)subject message:(NSString *)message te NSString *fullSubject = quick_formatString(@"%@ on %@", subject, [NSHost currentHost].localizedName); void (^didCompleteSendOperation)(NSError *) = ^(NSError *error) { + if (error) { + NSLog(@"Error sending email: %@", error); + } if (self.notificatonComplete) { self.notificatonComplete(error); } diff --git a/AutoPkgr/Models/Email & Notifications/LGSlackNotification.m b/AutoPkgr/Models/Email & Notifications/LGSlackNotification.m index c2c667b4..57b18516 100644 --- a/AutoPkgr/Models/Email & Notifications/LGSlackNotification.m +++ b/AutoPkgr/Models/Email & Notifications/LGSlackNotification.m @@ -126,7 +126,7 @@ - (void)sendMessageWithParameters:(NSDictionary *)parameters [manager POST:webHookURL parameters:[self baseParameters:parameters] success:^(AFHTTPRequestOperation *operation, id responseObject) { self.notificatonComplete(nil); } failure:^(AFHTTPRequestOperation *operation, NSError *error) { - NSLog(@"%@", operation.responseString); + NSLog(@"Error sending Slack notification: %@", operation.responseString); self.notificatonComplete([LGError errorWithResponse:operation.response]); }]; } diff --git a/AutoPkgr/Utility/LGAutoPkgSchedule.m b/AutoPkgr/Utility/LGAutoPkgSchedule.m index ece6e4bb..47fcf215 100644 --- a/AutoPkgr/Utility/LGAutoPkgSchedule.m +++ b/AutoPkgr/Utility/LGAutoPkgSchedule.m @@ -75,7 +75,7 @@ + (void)startAutoPkgSchedule:(BOOL)start scheduleOrInterval:(id)scheduleOrInterv NSString *program = [[NSProcessInfo processInfo] arguments].firstObject; [[helper.connection remoteObjectProxyWithErrorHandler:^(NSError *error) { - NSLog(@"%@", error); + NSLog(@"[%@] %@ ",[self class], error); reply(error); }] scheduleRun:scheduleOrInterval user:NSUserName() diff --git a/AutoPkgr/Views-Controllers-XIB/Panels-Popovers/LGRecipeSearch.m b/AutoPkgr/Views-Controllers-XIB/Panels-Popovers/LGRecipeSearch.m index 3e1866e9..c540630b 100644 --- a/AutoPkgr/Views-Controllers-XIB/Panels-Popovers/LGRecipeSearch.m +++ b/AutoPkgr/Views-Controllers-XIB/Panels-Popovers/LGRecipeSearch.m @@ -88,7 +88,7 @@ - (IBAction)searchNow:(NSSearchField *)sender [_progressIndicator setHidden:YES]; if (error) { _limitMessage.hidden = NO; - NSLog(@"%@",error); + NSLog(@"Search error: %@",error); } else { _limitMessage.hidden = YES; _searchResults = results; @@ -113,7 +113,7 @@ - (IBAction)addRecipeAndRepo:(NSButton *)sender sender.enabled = !(error == nil); if (error) { [self.limitMessage fadeOut_withString:error.localizedDescription]; - NSLog(@"%@", error.localizedDescription); + NSLog(@"Error adding repo: %@", error.localizedDescription); } else { _currentlyInstalledRepos = [LGAutoPkgTask repoList]; [_searchTable reloadData]; diff --git a/AutoPkgr/Views-Controllers-XIB/Recipes & Repos Table Controllers/LGRecipeTableViewController.m b/AutoPkgr/Views-Controllers-XIB/Recipes & Repos Table Controllers/LGRecipeTableViewController.m index c077e76c..daf94a15 100644 --- a/AutoPkgr/Views-Controllers-XIB/Recipes & Repos Table Controllers/LGRecipeTableViewController.m +++ b/AutoPkgr/Views-Controllers-XIB/Recipes & Repos Table Controllers/LGRecipeTableViewController.m @@ -167,7 +167,7 @@ - (void)runRecipeFromMenu:(NSMenuItem *)item [_runTaskDictionary setObject:runTask forKey:recipe.Name]; runTask.progressUpdateBlock = ^(NSString *message, double progress) { - NSLog(@"%@", message); + NSLog(@"Run status: %@", message); }; NSIndexSet *rowIdxSet = [[NSIndexSet alloc] initWithIndex:recipeRow]; diff --git a/AutoPkgr/Views-Controllers-XIB/Tab Views/LGNotificationsViewController.m b/AutoPkgr/Views-Controllers-XIB/Tab Views/LGNotificationsViewController.m index 22273de3..f51286ff 100644 --- a/AutoPkgr/Views-Controllers-XIB/Tab Views/LGNotificationsViewController.m +++ b/AutoPkgr/Views-Controllers-XIB/Tab Views/LGNotificationsViewController.m @@ -198,7 +198,7 @@ - (void)savePassword:(void (^)(NSError *error))reply if (!error) { [self updateKeychainPassword:nil]; } else { - NSLog(@"%@", error.localizedDescription); + NSLog(@"Error resetting password [%ld]: %@", error.code, error.localizedDescription); } }]; } else { From e1156430f8b400c0b2ffefbda918bb9ab960da2c Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Sun, 26 Jul 2015 20:10:01 -0500 Subject: [PATCH 08/59] Move password retrieval code to main thread, add a lock when calling Security framework. --- Privileged Helper/LGAutoPkgrHelper.m | 276 ++++++++++++++------------- 1 file changed, 144 insertions(+), 132 deletions(-) diff --git a/Privileged Helper/LGAutoPkgrHelper.m b/Privileged Helper/LGAutoPkgrHelper.m index 7d033290..1d634eee 100644 --- a/Privileged Helper/LGAutoPkgrHelper.m +++ b/Privileged Helper/LGAutoPkgrHelper.m @@ -135,178 +135,190 @@ - (NSXPCConnection *)connection - (void)getKeychainKey:(void (^)(NSString *, NSError *))reply { NSXPCConnection *connection = self.connection; + + dispatch_async(dispatch_get_main_queue(), ^{ + NSAssert([NSThread isMainThread], @"Not main thread"); + #if DEBUG - syslog(LOG_ALERT, "Connection querying keychain %s : EUID: %d", connection.description.UTF8String, connection.effectiveUserIdentifier); + syslog(LOG_ALERT, "Connection querying keychain %s : EUID: %d", connection.description.UTF8String, connection.effectiveUserIdentifier); #endif - // Effective user id used to determine location of the user's AutoPkgr keychain. - uid_t euid = connection.effectiveUserIdentifier; - struct passwd *pw = getpwuid(euid); - if (euid == 0) { - [connection invalidate]; - return; - } + // Effective user id used to determine location of the user's AutoPkgr keychain. + uid_t euid = connection.effectiveUserIdentifier; + struct passwd *pw = getpwuid(euid); + if (euid == 0) { + [connection invalidate]; + return; + } - // 10.8 has displayed instability on some systems when accessing the system keychain - // in rapid succession. So reluctantly we need to bypass direct calls to direct calls - // to the Security framewor in this method, and simply store the keyChain data in memory - // for the life of the helper. - // @note the getKeychain: call is still protected by codesign checking done when accepting new connecions so has "almost" the same level of security. - if (floor(NSFoundationVersionNumber) < NSFoundationVersionNumber10_9) { - if (_keyChainKey) { - syslog(LOG_ALERT, "[ 10.8 ] Helper Found keychain key in memory."); - return reply(_keyChainKey, nil); + // 10.8 has displayed instability on some systems when accessing the system keychain + // in rapid succession. So reluctantly we need to bypass direct calls to direct calls + // to the Security framewor in this method, and simply store the keyChain data in memory + // for the life of the helper. + // @note the getKeychain: call is still protected by codesign checking done when accepting new connecions so has "almost" the same level of security. + if (floor(NSFoundationVersionNumber) < NSFoundationVersionNumber10_9) { + if (_keyChainKey) { + syslog(LOG_ALERT, "[ 10.8 ] Helper Found keychain key in memory."); + return reply(_keyChainKey, nil); + } } - } - // Password used to decrypt the keyFile. This password is stored in the System.keychain. - NSString *encryptionPassword = nil; + // Password used to decrypt the keyFile. This password is stored in the System.keychain. + NSString *encryptionPassword = nil; - // Path to current user's keyFile. Encoded with AES256 using the encryption password. - NSString *encryptedKeyFile = nil; + // Path to current user's keyFile. Encoded with AES256 using the encryption password. + NSString *encryptedKeyFile = nil; - // Data representing the keyFile. This data is encoded - NSData *encryptedKeyFileData = nil; + // Data representing the keyFile. This data is encoded + NSData *encryptedKeyFileData = nil; - // The decoded raw data from the keyFile representing the password for the user's keychain. - NSData *passwordData = nil; + // The decoded raw data from the keyFile representing the password for the user's keychain. + NSData *passwordData = nil; - // The password for the user's keychain (in string form). - NSString *password = nil; + // The password for the user's keychain (in string form). + NSString *password = nil; - // Path to the user's AutoPkgr keychain. Located at ~/Library/Keychain/AutoPkgr.keychain. - NSString *appKeychainPath = nil; + // Path to the user's AutoPkgr keychain. Located at ~/Library/Keychain/AutoPkgr.keychain. + NSString *appKeychainPath = nil; - // Error. - NSError *error = nil; + // Error. + NSError *error = nil; + + // Attributes used to set permissions on the keyFile and it's parent directory. + NSDictionary *const attributes = @{ + NSFilePosixPermissions : [NSNumber numberWithShort:0700], // Owner read-write, others no access. + NSFileOwnerAccountID : @(0), // Root + NSFileGroupOwnerAccountID : @(0), // Wheel + }; - // Attributes used to set permissions on the keyFile and it's parent directory. - NSDictionary *const attributes = @{ - NSFilePosixPermissions : [NSNumber numberWithShort:0700], // Owner read-write, others no access. - NSFileOwnerAccountID : @(0), // Root - NSFileGroupOwnerAccountID : @(0), // Wheel - }; + NSFileManager *manager = [NSFileManager defaultManager]; + + encryptedKeyFile = [NSString stringWithFormat:@"%@/UID_%d", kLGEncryptedKeysParentDirectory, euid]; - NSFileManager *manager = [NSFileManager defaultManager]; + /*/////////////////////////////////////////////////////////////// + // Get the encryption password from the System.keychain // + ///////////////////////////////////////////////////////////////*/ + AHKeychainItem *item = [self commonDecryptionKeychainItem]; - encryptedKeyFile = [NSString stringWithFormat:@"%@/UID_%d", kLGEncryptedKeysParentDirectory, euid]; + BOOL newEncryptionPassword = NO; - /*/////////////////////////////////////////////////////////////// - // Get the encryption password from the System.keychain // - ///////////////////////////////////////////////////////////////*/ - AHKeychainItem *item = [self commonDecryptionKeychainItem]; + NSRecursiveLock *lock = [[NSRecursiveLock alloc] init]; - BOOL newEncryptionPassword = NO; + [lock lock]; + AHKeychain *keychain = [AHKeychain systemKeychain]; + BOOL getSuccess = [keychain getItem:item error:&error]; + [lock unlock]; - if ([[AHKeychain systemKeychain] getItem:item error:&error]) { - // We found the encryption password. - encryptionPassword = item.password; - } else if (error.code == errSecItemNotFound) { - // The item was not found in the keychain. Create it now. + if (getSuccess) { + // We found the encryption password. + encryptionPassword = item.password; + } else if (error.code == errSecItemNotFound) { + // The item was not found in the keychain. Create it now. - // Reset the error so it doesn't inadvertently pass back the wrong message. - error = nil; + // Reset the error so it doesn't inadvertently pass back the wrong message. + error = nil; - // Generate a new encryption password. - encryptionPassword = [[NSProcessInfo processInfo] globallyUniqueString]; - item.password = encryptionPassword; + // Generate a new encryption password. + encryptionPassword = [[NSProcessInfo processInfo] globallyUniqueString]; + item.password = encryptionPassword; - if ([[AHKeychain systemKeychain] saveItem:item error:&error]) { - // Success, a new common encryption was generated. - newEncryptionPassword = YES; + if ([[AHKeychain systemKeychain] saveItem:item error:&error]) { + // Success, a new common encryption was generated. + newEncryptionPassword = YES; + } else { + // If we can't create the keychain return now. There's nothing more to be done. + goto helper_reply; + } } else { - // If we can't create the keychain return now. There's nothing more to be done. + // some other error occurred when trying to find the item ??? goto helper_reply; } - } else { - // some other error occurred when trying to find the item ??? - goto helper_reply; - } - appKeychainPath = [NSString stringWithFormat:@"%s/Library/Keychains/AutoPkgr.keychain", pw->pw_dir]; + appKeychainPath = [NSString stringWithFormat:@"%s/Library/Keychains/AutoPkgr.keychain", pw->pw_dir]; - // Check for an old version of the keychainKey. - BOOL keyFileExists = [manager fileExistsAtPath:encryptedKeyFile]; + // Check for an old version of the keychainKey. + BOOL keyFileExists = [manager fileExistsAtPath:encryptedKeyFile]; - // Check to see if user's AutoPkgr keychain exists. - BOOL usersKeychainExists = [manager fileExistsAtPath:appKeychainPath]; + // Check to see if user's AutoPkgr keychain exists. + BOOL usersKeychainExists = [manager fileExistsAtPath:appKeychainPath]; - // If the user's AutoPkgr keychain has been deleted, try to remove the keyFile and start fresh. - BOOL check1 = !usersKeychainExists && keyFileExists; + // If the user's AutoPkgr keychain has been deleted, try to remove the keyFile and start fresh. + BOOL check1 = !usersKeychainExists && keyFileExists; - // If a new encryption key was generated, but an old keyFile exists. - BOOL check2 = keyFileExists && newEncryptionPassword; + // If a new encryption key was generated, but an old keyFile exists. + BOOL check2 = keyFileExists && newEncryptionPassword; - // If either condition is true, remove the old keyFile. - if (check1 || check2) { - syslog(LOG_ALERT, "Removing unusable keyFile..."); - if (![manager removeItemAtPath:encryptedKeyFile error:nil]) { - syslog(LOG_ALERT, "There was a problem removing the encrypted key file"); + // If either condition is true, remove the old keyFile. + if (check1 || check2) { + syslog(LOG_ALERT, "Removing unusable keyFile..."); + if (![manager removeItemAtPath:encryptedKeyFile error:nil]) { + syslog(LOG_ALERT, "There was a problem removing the encrypted key file"); + } } - } - /*//////////////////////////////////////////////////////////////////// - // Decrypt the keyFile in a root protected space // - ////////////////////////////////////////////////////////////////////*/ - if (![manager fileExistsAtPath:encryptedKeyFile]) { - // The keyFile does not exist, create one now. + /*//////////////////////////////////////////////////////////////////// + // Decrypt the keyFile in a root protected space // + ////////////////////////////////////////////////////////////////////*/ + if (![manager fileExistsAtPath:encryptedKeyFile]) { + // The keyFile does not exist, create one now. - BOOL isDir; - BOOL directoryExists = [manager fileExistsAtPath:kLGEncryptedKeysParentDirectory isDirectory:&isDir]; + BOOL isDir; + BOOL directoryExists = [manager fileExistsAtPath:kLGEncryptedKeysParentDirectory isDirectory:&isDir]; - if (!directoryExists) { - if (![manager createDirectoryAtPath:kLGEncryptedKeysParentDirectory withIntermediateDirectories:NO attributes:attributes error:&error]) { - // If we can't create this directory something is wrong, return now. - syslog(LOG_ALERT, "[ERROR] Could not create the parent directory for the encrypted key files."); + if (!directoryExists) { + if (![manager createDirectoryAtPath:kLGEncryptedKeysParentDirectory withIntermediateDirectories:NO attributes:attributes error:&error]) { + // If we can't create this directory something is wrong, return now. + syslog(LOG_ALERT, "[ERROR] Could not create the parent directory for the encrypted key files."); + goto helper_reply; + } + } else if (directoryExists && !isDir) { + // The path exists but is not a directory, escape!. + syslog(LOG_ALERT, "[ERROR] The %s exists, but it is not a directory, it needs to be repaired.", kLGEncryptedKeysParentDirectory.UTF8String); goto helper_reply; } - } else if (directoryExists && !isDir) { - // The path exists but is not a directory, escape!. - syslog(LOG_ALERT, "[ERROR] The %s exists, but it is not a directory, it needs to be repaired.", kLGEncryptedKeysParentDirectory.UTF8String); - goto helper_reply; - } - - // Generate some random data to use as the password for the user's keychain. - passwordData = [RNCryptor randomDataOfLength:48]; - // Encrypt the random data into AES256. - encryptedKeyFileData = [RNEncryptor encryptData:passwordData - withSettings:kRNCryptorAES256Settings - password:encryptionPassword - error:&error]; + // Generate some random data to use as the password for the user's keychain. + passwordData = [RNCryptor randomDataOfLength:48]; - // Write the encrypted data to the keyFile. - [encryptedKeyFileData writeToFile:encryptedKeyFile atomically:YES]; + // Encrypt the random data into AES256. + encryptedKeyFileData = [RNEncryptor encryptData:passwordData + withSettings:kRNCryptorAES256Settings + password:encryptionPassword + error:&error]; - } else { - // The keyFile is there. - - // Read in the encrypted data of the keyFile. - encryptedKeyFileData = [NSData dataWithContentsOfFile:encryptedKeyFile]; + // Write the encrypted data to the keyFile. + [encryptedKeyFileData writeToFile:encryptedKeyFile atomically:YES]; - // Decrypt the data. - passwordData = [RNDecryptor decryptData:encryptedKeyFileData - withSettings:kRNCryptorAES256Settings - password:encryptionPassword - error:&error]; - } - - // Reset the attributes of the file to root only access. - if (![manager setAttributes:attributes ofItemAtPath:encryptedKeyFile error:nil]) { - syslog(LOG_ALERT, "[ERROR] A problem was encountered updating keyFile's permissions."); - } - - if (passwordData) { - // set the password as the data description. - password = passwordData.description; - } - -helper_reply: - if (floor(NSFoundationVersionNumber) < NSFoundationVersionNumber10_9) { - if (password) { - _keyChainKey = password; + } else { + // The keyFile is there. + + // Read in the encrypted data of the keyFile. + encryptedKeyFileData = [NSData dataWithContentsOfFile:encryptedKeyFile]; + + // Decrypt the data. + passwordData = [RNDecryptor decryptData:encryptedKeyFileData + withSettings:kRNCryptorAES256Settings + password:encryptionPassword + error:&error]; } - } - reply(password, error); + + // Reset the attributes of the file to root only access. + if (![manager setAttributes:attributes ofItemAtPath:encryptedKeyFile error:nil]) { + syslog(LOG_ALERT, "[ERROR] A problem was encountered updating keyFile's permissions."); + } + + if (passwordData) { + // set the password as the data description. + password = passwordData.description; + } + + helper_reply: + if (floor(NSFoundationVersionNumber) < NSFoundationVersionNumber10_9) { + if (password) { + _keyChainKey = password; + } + } + reply(password, error); + }); } #pragma mark - AutoPkgr Schedule From d646667772b9acdb03759898dd542bfd42557c4e Mon Sep 17 00:00:00 2001 From: Elliot Jordan Date: Mon, 27 Jul 2015 10:23:19 -0700 Subject: [PATCH 09/59] Added comment to gitignore. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index dfd58023..03d3cf0a 100644 --- a/.gitignore +++ b/.gitignore @@ -27,5 +27,7 @@ DerivedData *.xcuserstate mailcore2/unittest/data *.orig + +# Markdown preview README.html CHANGELOG.html From 8ae4c0737330b6beb6b08f39ad7d9ae5e9cc9bb1 Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Mon, 27 Jul 2015 16:29:15 -0500 Subject: [PATCH 10/59] Move proxy checks out of token if statement. Closes #399. --- .../LGAutoPkgIntegrationView.m | 27 +++++++++---------- .../LGAutoPkgIntegrationView.xib | 9 ++++--- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGAutoPkgIntegrationView.m b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGAutoPkgIntegrationView.m index b46b47fb..9ea9d482 100644 --- a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGAutoPkgIntegrationView.m +++ b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGAutoPkgIntegrationView.m @@ -51,22 +51,21 @@ - (void)awakeFromNib if ([LGAutoPkgTask apiTokenFileExists:nil]) { _generateAPITokenBT.title = kLGTokenRevokeButonTitle; _generateAPITokenBT.action = @selector(deleteAPIToken:); + } - LGDefaults *defaults = [LGDefaults standardUserDefaults]; - - BOOL systemProxyCheck = [defaults boolForKey:@"useSystemProxies"]; - NSString *proxyCheck = [defaults objectForKey:@"HTTP_PROXY"]; - NSString *proxyCheck2 = [defaults objectForKey:@"HTTPS_PROXY"]; - - if (systemProxyCheck) { - [self.proxySelectionMatrix selectCellAtRow:1 column:0]; - } else if(proxyCheck || proxyCheck2){ - [self.proxySelectionMatrix selectCellAtRow:2 column:0]; - self.useCustomProxies = YES; - } else { - [self.proxySelectionMatrix selectCellAtRow:0 column:0]; - } + LGDefaults *defaults = [LGDefaults standardUserDefaults]; + BOOL systemProxyCheck = [defaults boolForKey:@"useSystemProxies"]; + NSString *proxyCheck = [defaults objectForKey:@"HTTP_PROXY"]; + NSString *proxyCheck2 = [defaults objectForKey:@"HTTPS_PROXY"]; + + if (systemProxyCheck) { + [self.proxySelectionMatrix selectCellAtRow:1 column:0]; + } else if(proxyCheck || proxyCheck2){ + [self.proxySelectionMatrix selectCellAtRow:2 column:0]; + self.useCustomProxies = YES; + } else { + [self.proxySelectionMatrix selectCellAtRow:0 column:0]; } } diff --git a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGAutoPkgIntegrationView.xib b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGAutoPkgIntegrationView.xib index 732d2763..9bc83f34 100644 --- a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGAutoPkgIntegrationView.xib +++ b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGAutoPkgIntegrationView.xib @@ -49,8 +49,9 @@ - + + http://username:password@proxy:8080 @@ -65,8 +66,9 @@ - + + http://username:password@proxy:8080 @@ -81,8 +83,9 @@ - + + .local,10.0.0.0/24,.mylocaldomain.com From 0fd5224c57c0b17d26c1ce108af815ffed3622b8 Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Mon, 27 Jul 2015 16:34:12 -0500 Subject: [PATCH 11/59] Update CHANGELOG.md --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c58fd127..27aa8fab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,11 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). - - +## [1.3.1 Unreleased][unreleased] +### Added +### Changed +### Fixed +- Fixed a bug that would cause Proxy Settings to be displayed incorrectly. ## [1.3] - 2015-05-08 From 219735db8d681e7e22fb22bd8ed8b84837f4a3da Mon Sep 17 00:00:00 2001 From: Elliot Jordan Date: Tue, 28 Jul 2015 08:12:08 -0700 Subject: [PATCH 12/59] Updated changelog, version bump to 1.3.1-alpha. --- AutoPkgr/Supporting Files/AutoPkgr-Info.plist | 4 ++-- CHANGELOG.md | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/AutoPkgr/Supporting Files/AutoPkgr-Info.plist b/AutoPkgr/Supporting Files/AutoPkgr-Info.plist index b7094578..b0f0419c 100755 --- a/AutoPkgr/Supporting Files/AutoPkgr-Info.plist +++ b/AutoPkgr/Supporting Files/AutoPkgr-Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3 + 1.3.1-alpha CFBundleSignature ???? CFBundleVersion - 1002 + 1003 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/CHANGELOG.md b/CHANGELOG.md index 27aa8fab..60d0c2d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,14 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## [1.3.1 Unreleased][unreleased] +## [Unreleased][unreleased] + ### Added + ### Changed + ### Fixed -- Fixed a bug that would cause Proxy Settings to be displayed incorrectly. +- Fixed a bug that would cause proxy settings to be displayed incorrectly. (#399) ## [1.3] - 2015-05-08 From 48fb288a090de18140742eb0733b46d464224edd Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Tue, 28 Jul 2015 11:53:34 -0500 Subject: [PATCH 13/59] Dispatch all keychain calls to a serial queue. --- Privileged Helper/LGAutoPkgrHelper.m | 47 +++++++++++++++++----------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/Privileged Helper/LGAutoPkgrHelper.m b/Privileged Helper/LGAutoPkgrHelper.m index 1d634eee..6259cc49 100644 --- a/Privileged Helper/LGAutoPkgrHelper.m +++ b/Privileged Helper/LGAutoPkgrHelper.m @@ -45,6 +45,17 @@ // Parent directory for all of the keyFiles. Each AutoPkgr user has a unique file. static NSString *const kLGEncryptedKeysParentDirectory = @"/var/db/.AutoPkgrKeys"; +static dispatch_queue_t autopkgr_kc_access_synchronizer_queue() +{ + static dispatch_queue_t dispatch_queue; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + dispatch_queue = dispatch_queue_create("com.lindegroup.autopkgr.helper.kcaccess.queue", DISPATCH_QUEUE_SERIAL); + }); + + return dispatch_queue; +} + @interface LGAutoPkgrHelper () @property (atomic, strong, readwrite) NSXPCListener *listener; @property (readonly) NSXPCConnection *connection; @@ -134,22 +145,10 @@ - (NSXPCConnection *)connection #pragma mark - Password - (void)getKeychainKey:(void (^)(NSString *, NSError *))reply { + dispatch_queue_t replyQueueu = dispatch_get_current_queue(); NSXPCConnection *connection = self.connection; - dispatch_async(dispatch_get_main_queue(), ^{ - NSAssert([NSThread isMainThread], @"Not main thread"); - -#if DEBUG - syslog(LOG_ALERT, "Connection querying keychain %s : EUID: %d", connection.description.UTF8String, connection.effectiveUserIdentifier); -#endif - // Effective user id used to determine location of the user's AutoPkgr keychain. - uid_t euid = connection.effectiveUserIdentifier; - struct passwd *pw = getpwuid(euid); - if (euid == 0) { - [connection invalidate]; - return; - } - + dispatch_sync(autopkgr_kc_access_synchronizer_queue(), ^{ // 10.8 has displayed instability on some systems when accessing the system keychain // in rapid succession. So reluctantly we need to bypass direct calls to direct calls // to the Security framewor in this method, and simply store the keyChain data in memory @@ -161,6 +160,16 @@ - (void)getKeychainKey:(void (^)(NSString *, NSError *))reply return reply(_keyChainKey, nil); } } +#if DEBUG + syslog(LOG_ALERT, "Connection querying keychain %s : EUID: %d", connection.description.UTF8String, connection.effectiveUserIdentifier); +#endif + // Effective user id used to determine location of the user's AutoPkgr keychain. + uid_t euid = connection.effectiveUserIdentifier; + struct passwd *pw = getpwuid(euid); + if (euid == 0) { + [connection invalidate]; + return; + } // Password used to decrypt the keyFile. This password is stored in the System.keychain. NSString *encryptionPassword = nil; @@ -201,12 +210,8 @@ - (void)getKeychainKey:(void (^)(NSString *, NSError *))reply BOOL newEncryptionPassword = NO; - NSRecursiveLock *lock = [[NSRecursiveLock alloc] init]; - - [lock lock]; AHKeychain *keychain = [AHKeychain systemKeychain]; BOOL getSuccess = [keychain getItem:item error:&error]; - [lock unlock]; if (getSuccess) { // We found the encryption password. @@ -317,7 +322,11 @@ - (void)getKeychainKey:(void (^)(NSString *, NSError *))reply _keyChainKey = password; } } - reply(password, error); + + dispatch_async(replyQueueu, ^{ + syslog(LOG_ALERT, "Sending keychain key to AutoPkgr."); + reply(password, error); + }); }); } From b0ea694edc75d2195ab0425eaea2591dd9c472a8 Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Tue, 28 Jul 2015 12:00:45 -0500 Subject: [PATCH 14/59] Fix a condition where in memory launchd would not be reloaded when schedule interval changed. --- CHANGELOG.md | 1 + Privileged Helper/LGAutoPkgrHelper.m | 5 +++++ Privileged Helper/helper-Info.plist | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60d0c2d8..d41c3dd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. This projec ### Fixed - Fixed a bug that would cause proxy settings to be displayed incorrectly. (#399) +- Fixed a bug where launchd would not reload in memory schedule. ## [1.3] - 2015-05-08 diff --git a/Privileged Helper/LGAutoPkgrHelper.m b/Privileged Helper/LGAutoPkgrHelper.m index 07187fd9..5583a3ab 100644 --- a/Privileged Helper/LGAutoPkgrHelper.m +++ b/Privileged Helper/LGAutoPkgrHelper.m @@ -327,6 +327,11 @@ - (void)scheduleRun:(AHLaunchJobSchedule *)scheduleOrInterval * app running in a Acqua session. */ job.EnvironmentVariables = @{@"__CFPREFERENCES_AVOID_DAEMON" : @"1"}; + if (jobIsRunning(job.Label, kAHGlobalLaunchDaemon)) { + syslog(LOG_ALERT, "Reloading current schedule."); + [[AHLaunchCtl sharedController] unload:job.Label inDomain:kAHGlobalLaunchDaemon error:nil]; + } + [[AHLaunchCtl sharedController] add:job toDomain:kAHGlobalLaunchDaemon error:&error]; } } diff --git a/Privileged Helper/helper-Info.plist b/Privileged Helper/helper-Info.plist index 2d8902ab..4154f209 100644 --- a/Privileged Helper/helper-Info.plist +++ b/Privileged Helper/helper-Info.plist @@ -9,10 +9,10 @@ CFBundleName com.lindegroup.AutoPkgr.helper CFBundleVersion - 1.3.2 + 1.3.3 SMAuthorizedClients - identifier "com.lindegroup.AutoPkgr" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = JVY2ZR6SEF + identifier "com.lindegroup.AutoPkgr" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = JVY2ZR6SEF From 9cf0ad6f5fd1a5df079cc59cf10f2dcaf1b6dcf3 Mon Sep 17 00:00:00 2001 From: Elliot Jordan Date: Tue, 28 Jul 2015 14:31:46 -0700 Subject: [PATCH 15/59] Updated changelog. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d41c3dd9..1236fb07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ All notable changes to this project will be documented in this file. This projec ### Fixed - Fixed a bug that would cause proxy settings to be displayed incorrectly. (#399) -- Fixed a bug where launchd would not reload in memory schedule. +- Fixed a bug where schedule changes would not reload in-membory launchd schedule. ## [1.3] - 2015-05-08 From c826df4e1608a7d36994fbadee3ffd056b0fa9e3 Mon Sep 17 00:00:00 2001 From: Elliot Jordan Date: Tue, 28 Jul 2015 14:32:09 -0700 Subject: [PATCH 16/59] =?UTF-8?q?AMExporter=20=E2=86=92=20AMExport.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AutoPkgr/Models/Integrations/LGAbsoluteManageIntegration.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AutoPkgr/Models/Integrations/LGAbsoluteManageIntegration.m b/AutoPkgr/Models/Integrations/LGAbsoluteManageIntegration.m index 13ab31e7..7c7851c3 100644 --- a/AutoPkgr/Models/Integrations/LGAbsoluteManageIntegration.m +++ b/AutoPkgr/Models/Integrations/LGAbsoluteManageIntegration.m @@ -39,7 +39,7 @@ + (NSString *)name + (NSString *)shortName { - return @"AMExporter"; + return @"AMExport"; } + (NSString *)credits { return @"Copyright 2014 Thomas Burgin\nhttp://www.apache.org/licenses/LICENSE-2.0"; From 489d4453121b421f335977130be88f308aa49bdd Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Tue, 28 Jul 2015 20:33:39 -0500 Subject: [PATCH 17/59] Fix a bug that would prevent JSS_REPO default from getting set. --- .../Integration Views/LGJSSImporterIntegrationView.m | 3 ++- CHANGELOG.md | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m index 818cdb88..39bb654e 100644 --- a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m +++ b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m @@ -321,9 +321,10 @@ - (NSArray *)evaluateJSSRepoDictionaries:(NSDictionary *)distributionPoints // and we'll strip them out here. NSPredicate *customDistPointsPredicate = [NSPredicate predicateWithFormat:@"not %K == nil", kLGJSSDistPointTypeKey]; + NSArray *customDistPoints = [_defaults.JSSRepos filteredArrayUsingPredicate:customDistPointsPredicate]; - newRepos = customDistPoints.mutableCopy; + newRepos = customDistPoints.mutableCopy ?: [NSMutableArray new]; if (dictArray) { for (NSDictionary *repo in dictArray) { diff --git a/CHANGELOG.md b/CHANGELOG.md index 1236fb07..fdd0de62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,9 @@ All notable changes to this project will be documented in this file. This projec ### Fixed - Fixed a bug that would cause proxy settings to be displayed incorrectly. (#399) -- Fixed a bug where schedule changes would not reload in-membory launchd schedule. - +- Fixed a bug where schedule changes would not reload in-memory launchd schedule. +- Fixed a bug that would prevent JSS_REPOS defaults from getting set. +- ## [1.3] - 2015-05-08 ### Added From 713ab8d8c78378aac936fca0924f7059f768c7ee Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Wed, 29 Jul 2015 11:08:06 -0500 Subject: [PATCH 18/59] Update LGAutoPkgVerb NS_OPTIONS so -isNetworkOperation is correctly assessed. --- .../Models/AutoPkg Task/LGAutoPkgErrorHandler.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.h b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.h index 266a300f..dbef25a3 100644 --- a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.h +++ b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgErrorHandler.h @@ -43,17 +43,17 @@ typedef NS_OPTIONS(NSInteger, LGAutoPkgVerb) { kLGAutoPkgInfo = 1 << 4, // repo verbs - kLGAutoPkgRepoAdd = 1 << 10, - kLGAutoPkgRepoDelete = 1 << 11, - kLGAutoPkgRepoUpdate = 1 << 12, - kLGAutoPkgRepoList = 1 << 13, + kLGAutoPkgRepoAdd = 1 << 5, + kLGAutoPkgRepoDelete = 1 << 6, + kLGAutoPkgRepoUpdate = 1 << 7, + kLGAutoPkgRepoList = 1 << 8, // processor verbs - kLGAutoPkgProcessorInfo = 1 << 20, - kLGAutoPkgListProcessors = 1 << 21, + kLGAutoPkgProcessorInfo = 1 << 9, + kLGAutoPkgListProcessors = 1 << 10, // other verbs - kLGAutoPkgVersion = 1 << 30, + kLGAutoPkgVersion = 1 << 11, }; @interface LGAutoPkgErrorHandler : NSObject From 184b0c51618dba526cb1c38a7203264b4f45db75 Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Wed, 29 Jul 2015 11:09:12 -0500 Subject: [PATCH 19/59] Unit Test for isNetworkOperation --- AutoPkgrTests/AutoPkgrTests.m | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/AutoPkgrTests/AutoPkgrTests.m b/AutoPkgrTests/AutoPkgrTests.m index c66eca25..372af620 100644 --- a/AutoPkgrTests/AutoPkgrTests.m +++ b/AutoPkgrTests/AutoPkgrTests.m @@ -27,6 +27,7 @@ #import "LGAutoPkgTask.h" #import "LGAutoPkgReport.h" +#import "LGAutoPkgErrorHandler.h" #import "LGPasswords.h" #import "LGServerCredentials.h" @@ -396,7 +397,35 @@ - (void)testNotificationManager { } -- (void)testSlackNotification { +- (void)testIsNetworkOpCheck { + // Are + XCTAssertTrue([self isNetworkOperation:kLGAutoPkgRun], @"Run should be "); + XCTAssertTrue([self isNetworkOperation:kLGAutoPkgSearch], @"Search should be"); + XCTAssertTrue([self isNetworkOperation:kLGAutoPkgRepoAdd], @"Repo Add should be"); + XCTAssertTrue([self isNetworkOperation:kLGAutoPkgRepoUpdate], @"Repo Update should be"); + + // Are not. + XCTAssertFalse([self isNetworkOperation:kLGAutoPkgMakeOverride], @"Repo List should not be"); + XCTAssertFalse([self isNetworkOperation:kLGAutoPkgInfo], @"Repo List should not be"); + XCTAssertFalse([self isNetworkOperation:kLGAutoPkgRepoDelete], @"Repo List should not be"); + XCTAssertFalse([self isNetworkOperation:kLGAutoPkgProcessorInfo], @"Repo List should not be"); + XCTAssertFalse([self isNetworkOperation:kLGAutoPkgListProcessors], @"Repo List should not be"); + + XCTAssertFalse([self isNetworkOperation:kLGAutoPkgRepoList], @"Repo List should not be"); + XCTAssertFalse([self isNetworkOperation:kLGAutoPkgVersion], @"Version should not be"); +} + +- (BOOL)isNetworkOperation:(LGAutoPkgVerb)verb +{ + NSInteger ck = (kLGAutoPkgRepoAdd | kLGAutoPkgRepoUpdate | kLGAutoPkgRun | kLGAutoPkgSearch ); + + BOOL isNetworkOperation = verb & ck; + + return isNetworkOperation; +} + + +-(void)testSlackNotification { idnotification = [[LGSlackNotification alloc] initWithReport:[self notificationReport]]; XCTestExpectation *expectation = [self expectationWithDescription:quick_formatString(@"Test %@", [notification.class serviceDescription])]; From df36bbd46bb544faf5f2d2312d65b0c78401ca44 Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Wed, 29 Jul 2015 11:15:49 -0500 Subject: [PATCH 20/59] Correct host property used in trustStore. --- .../LGServerCredentials.m | 18 +++++++++++++++--- .../LGJSSImporterIntegrationView.m | 13 ++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/AutoPkgr/Models/Passwords-Credentials/LGServerCredentials.m b/AutoPkgr/Models/Passwords-Credentials/LGServerCredentials.m index 3bf11f76..087106b9 100644 --- a/AutoPkgr/Models/Passwords-Credentials/LGServerCredentials.m +++ b/AutoPkgr/Models/Passwords-Credentials/LGServerCredentials.m @@ -130,6 +130,14 @@ - (NSURL *)serverURL return _serverURL; } +- (NSString *)host { + return self.serverURL.host; +} + +- (NSString *)protocol { + return self.serverURL.scheme; +} + - (NSInteger)port { return (_port != 0) ? _port : self.serverURL.port.integerValue; @@ -202,12 +210,13 @@ - (void)handleCertificateTrustChallenge:(NSURLAuthenticationChallenge *)challeng } } + [[LGCertificateStore sharedStorage] setTrust:_sslTrustSetting forHost:self.host]; + if (proceed) { NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; - [[LGCertificateStore sharedStorage] setTrust:_sslTrustSetting forHost:self.host]; [[LGCertificateStore sharedStorage] setChallenge:challenge forHost:self.host]; } else { @@ -229,8 +238,9 @@ - (void)checkCredentialsForPath:(NSString *)path reply:(void (^)(LGHTTPCredentia } // String encode the path. - path = [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - NSURL *url = [NSURL URLWithString:path relativeToURL:self.serverURL]; + NSString *fullURL = [[self.server stringByAppendingPathComponent:path] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + + NSURL *url = [NSURL URLWithString:fullURL]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.timeoutInterval = 10.0; @@ -269,10 +279,12 @@ - (void)checkCredentialsForPath:(NSString *)path reply:(void (^)(LGHTTPCredentia }]; [op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { + DLog(@"Successfully checked credentials for %@ at %@", self.user, self.server); purgeProtectedSpace(); reply(self, authWasChallenged ? kLGCredentialChallengeSuccess : kLGCredentialsNotChallenged, nil); } failure:^(AFHTTPRequestOperation *operation, NSError *error) { purgeProtectedSpace(); + DLog(@"Credential verification failed for %@ at %@. Error: %@", self.user, self.server, error.localizedDescription); reply(self, kLGCredentialChallengeFailed, error); }]; diff --git a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m index 39bb654e..f5ad989d 100644 --- a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m +++ b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m @@ -82,7 +82,7 @@ - (IBAction)credentialsChanged:(NSTextField *)sender // have an issue when the base url is double slashed. Though theoritically // it doesn't make sense, it's an easy enough fix to apply here by looking // for a trailing slash and simply removing that. - NSString *url = sender.stringValue.trailingSlashRemoved; + NSString *url = sender.stringValue; sender.stringValue = url; if (!_portTester) { @@ -94,19 +94,21 @@ - (IBAction)credentialsChanged:(NSTextField *)sender [self stopStatusUpdate:nil]; // If we got a redirect, update the sender to the new url. if (redirect.length && ([url isEqualToString:redirect] == NO)) { - sender.stringValue = redirect.trailingSlashRemoved; + sender.stringValue = redirect; } }]; } // if all settings have been removed clear out the JSS_REPOS key too. - if (!_jssAPIPasswordTF.safe_stringValue && !_jssAPIUsernameTF.safe_stringValue && !_jssURLTF.safe_stringValue) { + else if (!_jssAPIPasswordTF.safe_stringValue && !_jssAPIUsernameTF.safe_stringValue && !_jssURLTF.safe_stringValue) { _defaults.JSSRepos = nil; _jssStatusLight.image = [NSImage LGStatusNone]; _jssStatusLight.hidden = YES; [self saveDefaults]; - } else if (![_defaults.JSSAPIPassword isEqualToString:_jssAPIPasswordTF.safe_stringValue] || ![_defaults.JSSAPIUsername isEqualToString:_jssAPIUsernameTF.safe_stringValue] || ![_defaults.JSSURL isEqualToString:_jssURLTF.safe_stringValue]) { + } + + else if (![_defaults.JSSAPIPassword isEqualToString:_jssAPIPasswordTF.safe_stringValue] || ![_defaults.JSSAPIUsername isEqualToString:_jssAPIUsernameTF.safe_stringValue] || ![_defaults.JSSURL isEqualToString:_jssURLTF.safe_stringValue]) { // Update server status and reset the target action to check credentials... _jssStatusLight.image = [NSImage LGStatusPartiallyAvailable]; @@ -192,6 +194,7 @@ - (void)stopStatusUpdate:(NSError *)error [_jssStatusSpinner setHidden:YES]; [_jssStatusSpinner stopAnimation:self]; if (error) { + NSLog(@"JSS Error: [%ld] %@", error.code, error.localizedDescription); [self.integration.progressDelegate stopProgress:error]; [_jssStatusLight setImage:[NSImage LGStatusUnavailable]]; } @@ -281,7 +284,7 @@ - (void)confirmDisableSSLVerify @"LocalizableJSSImporter", @"button title to disable ssl verificaiton"); - NSString *infoText = NSLocalizedStringFromTable(@"This is most likely due to the certificate being self signed. Choosing \"Disable SSL Verification\" may be required for JSSImporter to function properly.", + NSString *infoText = NSLocalizedStringFromTable(@"This is possibly due to the certificate being self signed. Choosing \"Disable SSL Verification\" may be required for JSSImporter to function properly. If you are sure the JSS is using a trusted certificate signed by public Certificate Authority keep SSL verification enabled.", @"LocalizableJSSImporter", nil); From 39dcb4a1abfba029fe22950bdf6b0b34c4cf9553 Mon Sep 17 00:00:00 2001 From: Elliot Jordan Date: Wed, 29 Jul 2015 09:46:39 -0700 Subject: [PATCH 21/59] Updated changelog. --- CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdd0de62..8cea9142 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,16 @@ All notable changes to this project will be documented in this file. This projec ## [Unreleased][unreleased] -### Added +### Added +- Now supports GitHub two-factor authentication for generation of API tokens. (#393) -### Changed +### Changed ### Fixed - Fixed a bug that would cause proxy settings to be displayed incorrectly. (#399) - Fixed a bug where schedule changes would not reload in-memory launchd schedule. - Fixed a bug that would prevent JSS_REPOS defaults from getting set. -- + ## [1.3] - 2015-05-08 ### Added From 07d9254e1ce91020219ce9146881b2f835752bd6 Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Wed, 29 Jul 2015 16:50:06 -0500 Subject: [PATCH 22/59] Big simplify --- .../Integrations/LGJSSImporterIntegration.h | 2 - .../Integrations/LGJSSImporterIntegration.m | 43 ++---- AutoPkgr/Utility/LGHTTPRequest.h | 3 + AutoPkgr/Utility/LGHTTPRequest.m | 51 ++++++- .../LGJSSImporterIntegrationView.m | 135 ++++++++---------- .../LGJSSImporterIntegrationView.xib | 25 ++-- .../Tab Views/LGIntegrationsViewController.m | 1 + 7 files changed, 133 insertions(+), 127 deletions(-) diff --git a/AutoPkgr/Models/Integrations/LGJSSImporterIntegration.h b/AutoPkgr/Models/Integrations/LGJSSImporterIntegration.h index 2236af96..8d1a22ef 100644 --- a/AutoPkgr/Models/Integrations/LGJSSImporterIntegration.h +++ b/AutoPkgr/Models/Integrations/LGJSSImporterIntegration.h @@ -41,8 +41,6 @@ extern NSString* const kLGJSSDistPointTypeKey; + (instancetype)standardUserDefaults __attribute__((unavailable("Cannot use the shared object in this subclass."))); -@property (copy, nonatomic) LGHTTPCredential *jssCredentials; - @property (copy, nonatomic) NSString *JSSURL; @property (copy, nonatomic) NSString *JSSAPIUsername; @property (copy, nonatomic) NSString *JSSAPIPassword; diff --git a/AutoPkgr/Models/Integrations/LGJSSImporterIntegration.m b/AutoPkgr/Models/Integrations/LGJSSImporterIntegration.m index d3fc51f5..91f70be4 100644 --- a/AutoPkgr/Models/Integrations/LGJSSImporterIntegration.m +++ b/AutoPkgr/Models/Integrations/LGJSSImporterIntegration.m @@ -104,6 +104,14 @@ - (NSString *)installedVersion return _installedVersion; } +- (void)customInstallActions:(void (^)(NSError *))reply { + LGJSSImporterDefaults *defaults = [[LGJSSImporterDefaults alloc] init]; + NSNumber *verifySSL = [defaults autoPkgDomainObject:@"JSS_VERIFY_SSL"]; + if (!verifySSL) { + defaults.JSSVerifySSL = YES; + } +} + - (void)customUninstallActions:(void (^)(NSError *))reply { // Clear out the defaults... LGJSSImporterDefaults *defaults = [[LGJSSImporterDefaults alloc] init]; @@ -121,41 +129,6 @@ - (void)customUninstallActions:(void (^)(NSError *))reply { #pragma mark - LGDefaults category implementation for JSSImporter Interface @implementation LGJSSImporterDefaults -@synthesize jssCredentials = _jssCredentials; - - --(void)setJssCredentials:(LGHTTPCredential *)jssCredentials { - _jssCredentials = jssCredentials; - [self configureCredentialSaveBlock:jssCredentials]; - [jssCredentials save]; -} - -- (LGHTTPCredential *)jssCredentials { - if (!_jssCredentials) { - _jssCredentials = [[LGHTTPCredential alloc] init]; - - _jssCredentials.server = self.JSSURL; - _jssCredentials.user = self.JSSAPIUsername; - _jssCredentials.password = self.JSSAPIPassword; - - /* If the BOOL verifySSL is set to true set this to OS implicit trust. - * Otherwise treat the trust setting as if it was confirmed by the user */ - _jssCredentials.sslTrustSetting = self.JSSVerifySSL ? kLGSSLTrustOSImplicitTrust : kLGSSLTrustUserConfirmedTrust; - - [self configureCredentialSaveBlock:_jssCredentials]; - } - return _jssCredentials; -} - -- (void)configureCredentialSaveBlock:(LGHTTPCredential *)credential { - if (!credential.saveBlock) { - credential.saveBlock = ^(LGHTTPCredential *cred) { - self.JSSAPIUsername = cred.user; - self.JSSAPIPassword = cred.password; - self.JSSURL = cred.server; - }; - } -} - (NSString *)JSSURL { diff --git a/AutoPkgr/Utility/LGHTTPRequest.h b/AutoPkgr/Utility/LGHTTPRequest.h index 4bfc14b9..24001bd2 100644 --- a/AutoPkgr/Utility/LGHTTPRequest.h +++ b/AutoPkgr/Utility/LGHTTPRequest.h @@ -27,4 +27,7 @@ - (void)retrieveDistributionPoints:(LGHTTPCredential *)credential reply:(void (^)(NSDictionary *distributionPoints, NSError *error))reply; +- (void)retrieveDistributionPoints2:(LGHTTPCredential *)credential + reply:(void (^)(NSDictionary *distributionPoints, NSError *error))reply; + @end diff --git a/AutoPkgr/Utility/LGHTTPRequest.m b/AutoPkgr/Utility/LGHTTPRequest.m index 10015890..44ef3caf 100644 --- a/AutoPkgr/Utility/LGHTTPRequest.m +++ b/AutoPkgr/Utility/LGHTTPRequest.m @@ -36,11 +36,50 @@ - (void)dealloc [self resetCredentials]; } +- (void)retrieveDistributionPoints2:(LGHTTPCredential *)credential + reply:(void (^)(NSDictionary *distributionPoints, NSError *error))reply; +{ + NSLog(@"server = %@", credential.serverURL); + + AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:credential.serverURL]; + + manager.requestSerializer = [AFJSONRequestSerializer serializer]; + [manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Accept"]; + + manager.requestSerializer.timeoutInterval = 5.0; + manager.securityPolicy = [AFSecurityPolicy defaultPolicy]; + if (credential.sslTrustSetting & (kLGSSLTrustUserExplicitTrust | kLGSSLTrustUserConfirmedTrust)) { + // Even in the event the user has the certificate set to trust in their keychain + // that setting doesn't seem to get picked up by python-jss' request module so set verify to NO + manager.securityPolicy.allowInvalidCertificates = YES; + manager.securityPolicy.validatesCertificateChain = NO; + } + manager.credential = credential.credential; + manager.responseSerializer = [AFJSONResponseSerializer serializer]; + + // As of 7/29/15 JAMF cloud is returning json data as text/plain + // so handle that too. + [manager.responseSerializer setAcceptableContentTypes:[NSSet setWithObjects: + @"application/json", + @"text/plain", nil]]; + + NSString *fullPath = [credential.serverURL.path + stringByAppendingPathComponent:@"JSSResource/distributionpoints"]; + + [manager GET:fullPath parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { + reply(responseObject, nil); + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { + reply(nil, error); + }]; +} + - (void)retrieveDistributionPoints:(LGHTTPCredential *)credential reply:(void (^)(NSDictionary *distributionPoints, NSError *error))reply; { // Setup the request - NSString *distPointAddress = [credential.server stringByAppendingPathComponent:@"JSSResource/distributionpoints"]; + NSMutableString *distPointAddress = [credential.server.trailingSlashRemoved mutableCopy]; + [ distPointAddress appendString:@"/JSSResource/distributionpoints" ]; + NSURL *url = [NSURL URLWithString:distPointAddress]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; @@ -62,6 +101,16 @@ - (void)retrieveDistributionPoints:(LGHTTPCredential *)credential operation.securityPolicy = policy; + [operation setWillSendRequestForAuthenticationChallengeBlock:^(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge) { + if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){ + [credential handleCertificateTrustChallenge:challenge reply:^(LGSSLTrustSettings trust) {}]; + } else if (credential.credential && challenge.previousFailureCount < 1) { + [challenge.sender useCredential:credential.credential forAuthenticationChallenge:challenge]; + } else { + [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; + } + }]; + [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { NSError *error = nil; diff --git a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m index f5ad989d..b2a5afe1 100644 --- a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m +++ b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m @@ -43,6 +43,29 @@ return predicate; } +@interface LGJSSImporterIntegrationView () +@property (strong) IBOutlet LGTableView *jssDistributionPointTableView; +@property (weak) IBOutlet NSTextField *jssURLTF; +@property (weak) IBOutlet NSTextField *jssAPIUsernameTF; +@property (weak) IBOutlet NSTextField *jssAPIPasswordTF; +@property (weak) IBOutlet NSButton *jssReloadServerBT; +@property (weak) IBOutlet NSProgressIndicator *jssStatusSpinner; +@property (weak) IBOutlet NSImageView *jssStatusLight; + +@property (weak) IBOutlet NSButton *jssEditDistPointBT; +@property (weak) IBOutlet NSButton *jssRemoveDistPointBT; +@property (weak) IBOutlet NSButton *jssUseMasterJDS; +@property (weak) IBOutlet NSButton *jssVerifySSLBT; + +@property (weak) NSWindow *modalWindow; + +- (IBAction)addDistributionPoint:(id)sender; +- (IBAction)removeDistributionPoint:(id)sender; +- (IBAction)editDistributionPoint:(id)sender; +- (IBAction)enableMasterJDS:(NSButton *)sender; + +@end + @implementation LGJSSImporterIntegrationView { LGJSSImporterDefaults *_defaults; LGTestPort *_portTester; @@ -68,103 +91,45 @@ - (void)awakeFromNib _jssAPIPasswordTF.safe_stringValue = _defaults.JSSAPIPassword; _jssURLTF.safe_stringValue = _defaults.JSSURL; - _jssReloadServerBT.action = @selector(testCredentials:); - _jssReloadServerBT.title = @"Verify"; + _jssReloadServerBT.action = @selector(getDistributionPoints:); + _jssReloadServerBT.title = @"Connect"; + + _jssAPIPasswordTF.delegate = self; + _jssAPIUsernameTF.delegate = self; + _jssURLTF.delegate = self; + + _jssVerifySSLBT.state = _defaults.JSSVerifySSL; [_jssDistributionPointTableView reloadData]; } #pragma mark - IBActions -- (IBAction)credentialsChanged:(NSTextField *)sender +- (IBAction)verifySSL:(NSButton *)sender { - if ([sender isEqualTo:_jssURLTF] && ![_defaults.JSSURL isEqualToString:sender.stringValue]) { - // There have been reports that old style cloud hosted JSS - // have an issue when the base url is double slashed. Though theoritically - // it doesn't make sense, it's an easy enough fix to apply here by looking - // for a trailing slash and simply removing that. - NSString *url = sender.stringValue; - sender.stringValue = url; - - if (!_portTester) { - _portTester = [[LGTestPort alloc] init]; - } - [self startStatusUpdate]; - - [_portTester testServerURL:url reply:^(BOOL reachable, NSString *redirect) { - [self stopStatusUpdate:nil]; - // If we got a redirect, update the sender to the new url. - if (redirect.length && ([url isEqualToString:redirect] == NO)) { - sender.stringValue = redirect; - } - }]; - } - - // if all settings have been removed clear out the JSS_REPOS key too. - else if (!_jssAPIPasswordTF.safe_stringValue && !_jssAPIUsernameTF.safe_stringValue && !_jssURLTF.safe_stringValue) { - _defaults.JSSRepos = nil; - _jssStatusLight.image = [NSImage LGStatusNone]; - _jssStatusLight.hidden = YES; - [self saveDefaults]; - - } - - else if (![_defaults.JSSAPIPassword isEqualToString:_jssAPIPasswordTF.safe_stringValue] || ![_defaults.JSSAPIUsername isEqualToString:_jssAPIUsernameTF.safe_stringValue] || ![_defaults.JSSURL isEqualToString:_jssURLTF.safe_stringValue]) { - - // Update server status and reset the target action to check credentials... - _jssStatusLight.image = [NSImage LGStatusPartiallyAvailable]; - _jssReloadServerBT.action = @selector(testCredentials:); - _jssReloadServerBT.title = @"Verify"; - } + _defaults.JSSVerifySSL = _jssVerifySSLBT.state; } -- (IBAction)testCredentials:(id)sender -{ - [self startStatusUpdate]; - - LGHTTPCredential *jssCredentials = [[LGHTTPCredential alloc] init]; - jssCredentials.server = _jssURLTF.stringValue; - jssCredentials.user = _jssAPIUsernameTF.stringValue; - jssCredentials.password = _jssAPIPasswordTF.stringValue; - - [jssCredentials checkCredentialsForPath:@"/JSSResource/distributionpoints" reply:^(LGHTTPCredential *aCredential, LGCredentialChallengeCode status, NSError *error) { - switch (status) { - case kLGCredentialChallengeSuccess: - if (aCredential.sslTrustSetting == kLGSSLTrustStatusUnknown) { - [self confirmDisableSSLVerify]; - } else if (aCredential.sslTrustSetting & (kLGSSLTrustOSImplicitTrust | kLGSSLTrustUserExplicitTrust)){ - _defaults.JSSVerifySSL = YES; - } else { - _defaults.JSSVerifySSL = NO; - } +- (LGHTTPCredential *)jssCredentials { + LGHTTPCredential *credentials = [[LGHTTPCredential alloc] initWithServer:_defaults.JSSURL + user:_defaults.JSSAPIUsername + password:_defaults.JSSAPIPassword]; - _defaults.jssCredentials = aCredential; - [_jssStatusLight setImage:[NSImage LGStatusAvailable]]; - - // Reassign - _jssReloadServerBT.action = @selector(getDistributionPoints:); - _jssReloadServerBT.title = @"Connect"; - - break; - case kLGCredentialsNotChallenged: - [_jssStatusLight setImage:[NSImage LGStatusUnavailable]]; - NSLog(@"The credentials for the JSS were never challenged. please check that the url you've set is correct"); - break; - default: - break; - } - - [self stopStatusUpdate:error]; - }]; + credentials.sslTrustSetting = _defaults.JSSVerifySSL ? kLGSSLTrustOSImplicitTrust : kLGSSLTrustUserConfirmedTrust; + + return credentials; } - (IBAction)getDistributionPoints:(id)sender { LGHTTPRequest *request = [[LGHTTPRequest alloc] init]; - [request retrieveDistributionPoints:_defaults.jssCredentials + + [request retrieveDistributionPoints2:[self jssCredentials] reply:^(NSDictionary *distributionPoints, NSError *error) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ if (!error) { [_jssStatusLight setImage:[NSImage LGStatusAvailable]]; + } else { + [NSApp presentError:error]; } [self stopStatusUpdate:error]; @@ -465,6 +430,18 @@ - (void)editDistributionPoint:(id)sender } } +#pragma mark - Text Field delegate +- (void)controlTextDidChange:(NSNotification *)notification { + NSTextField *obj = notification.object; + if ([obj isEqualTo:_jssURLTF]) { + _defaults.JSSURL = obj.safe_stringValue; + } else if ([obj isEqualTo:_jssAPIUsernameTF]) { + _defaults.JSSAPIUsername = obj.safe_stringValue; + } else if ([obj isEqualTo:_jssAPIPasswordTF]) { + _defaults.JSSAPIPassword = obj.safe_stringValue; + } +} + #pragma mark - Panel didEnd Selectors - (void)didClosePreferencePanel { diff --git a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.xib b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.xib index 74ef82f8..cdddc99f 100644 --- a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.xib +++ b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.xib @@ -17,6 +17,7 @@ + @@ -33,9 +34,6 @@ - - - @@ -63,9 +61,6 @@ NSAllRomanInputSourcesLocaleIdentifier - - - @@ -74,9 +69,6 @@ - - - @@ -250,7 +242,17 @@ - + + + + @@ -263,6 +265,7 @@ + @@ -289,8 +292,10 @@ + + diff --git a/AutoPkgr/Views-Controllers-XIB/Tab Views/LGIntegrationsViewController.m b/AutoPkgr/Views-Controllers-XIB/Tab Views/LGIntegrationsViewController.m index 85b04066..e3e6a28c 100644 --- a/AutoPkgr/Views-Controllers-XIB/Tab Views/LGIntegrationsViewController.m +++ b/AutoPkgr/Views-Controllers-XIB/Tab Views/LGIntegrationsViewController.m @@ -81,6 +81,7 @@ - (void)configure:(NSButton *)sender if (viewClass) { LGBaseIntegrationViewController *integrationView = [[viewClass alloc] initWithIntegration:integration]; + integrationView.integration.progressDelegate = self.progressDelegate; _integrationWindowController = [[LGIntegrationWindowController alloc] initWithViewController:integrationView]; From db157ed094ab2077f94b4b7956f579d1c1c8bd41 Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Thu, 30 Jul 2015 08:47:16 -0500 Subject: [PATCH 23/59] Fixes duplicate repos in listed in table. Closes issue #406. --- AutoPkgr/Models/AutoPkg Task/LGAutoPkgRepo.m | 32 +++++++++++++++++--- AutoPkgr/Models/AutoPkg Task/LGAutoPkgTask.m | 2 +- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRepo.m b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRepo.m index 5b244dcb..a04c3546 100644 --- a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRepo.m +++ b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRepo.m @@ -76,9 +76,19 @@ - (instancetype)initWithCloneURL:(NSString *)cloneURL } #pragma mark - Accessors +- (NSString *)description { + return self.cloneURL.absoluteString; +} + - (BOOL)isInstalled { - NSPredicate *repoPredicate = [NSPredicate predicateWithFormat:@"%K CONTAINS[cd] %@", kLGAutoPkgRepoURLKey, self.cloneURL.absoluteString]; + NSMutableString *repoURL = self.cloneURL.absoluteString.mutableCopy; + if ([repoURL.pathExtension isEqualToString:@"git"]) { + [repoURL deleteCharactersInRange:NSMakeRange(repoURL.length - 4, 4)]; + } + + NSPredicate *repoPredicate = [NSPredicate predicateWithFormat:@"%K CONTAINS[cd] %@", kLGAutoPkgRepoURLKey, repoURL]; + return [[_activeRepos filteredArrayUsingPredicate:repoPredicate] count]; } @@ -245,19 +255,33 @@ + (void)commonRepos:(void (^)(NSArray *))reply { void (^constructCommonRepos)() = ^() { _activeRepos = [LGAutoPkgTask repoList]; - NSMutableArray *commonRepos = [_popularRepos mutableCopy]; + NSMutableArray *commonRepos = [NSMutableArray arrayWithArray:_popularRepos]; [_activeRepos enumerateObjectsUsingBlock:^(NSDictionary *activeRepo, NSUInteger idx, BOOL *stop) { + NSString *repoURL = activeRepo[kLGAutoPkgRepoURLKey]; + NSMutableString *normalizedRepo = repoURL.mutableCopy; + + if (![normalizedRepo.pathExtension isEqualToString:@"git"]) { + // Using -stringByAppendingPathComponent: on a url here + // results in the scheme getting mangled from https://xxx to https:/xxx + // so just use -stringByAppendingString: string. + [normalizedRepo appendString:@".git"]; + } NSPredicate *pred = [NSPredicate predicateWithFormat:@"%K.absoluteString == %@", NSStringFromSelector(@selector(cloneURL)), - activeRepo[kLGAutoPkgRepoURLKey]]; + normalizedRepo]; + + NSArray *matches = [_popularRepos filteredArrayUsingPredicate:pred]; - if ([_popularRepos filteredArrayUsingPredicate:pred].count == 0) { + if (matches.count == 0) { LGAutoPkgRepo *repo = nil; if ((repo = [[self alloc] initWithAutoPkgDictionary:activeRepo])) { [commonRepos addObject:repo]; } + } if (matches.count == 1 && ![repoURL isEqualToString:normalizedRepo]) { + LGAutoPkgRepo *repo = matches.firstObject; + repo->_cloneURL = [NSURL URLWithString:repoURL]; } }]; diff --git a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgTask.m b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgTask.m index 7f6f908d..a8eccc62 100644 --- a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgTask.m +++ b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgTask.m @@ -926,7 +926,7 @@ + (void)repoAdd:(NSString *)repo reply:(void (^)(NSError *))reply LGAutoPkgTask *task = [[LGAutoPkgTask alloc] init]; // Make sure everything that comes in has the .git extension. if (![repo.pathExtension isEqualToString:@"git"]) { - repo = [repo stringByAppendingPathExtension:@"git"]; + repo = [repo stringByAppendingString:@".git"]; } task.arguments = @[ @"repo-add", repo ]; [task launchInBackground:^(NSError *error) { From d9c4af96cecfa151039962f221ffde3868b784b7 Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Thu, 30 Jul 2015 13:11:00 -0500 Subject: [PATCH 24/59] Simplify JSSImporter defaults handling. Don't try to correct anything. --- AutoPkgr/Utility/LGHTTPRequest.h | 4 ++ AutoPkgr/Utility/LGHTTPRequest.m | 7 +- .../LGJSSImporterIntegrationView.h | 19 ----- .../LGJSSImporterIntegrationView.m | 70 +++++++++++++------ 4 files changed, 55 insertions(+), 45 deletions(-) diff --git a/AutoPkgr/Utility/LGHTTPRequest.h b/AutoPkgr/Utility/LGHTTPRequest.h index 24001bd2..1dff3fa9 100644 --- a/AutoPkgr/Utility/LGHTTPRequest.h +++ b/AutoPkgr/Utility/LGHTTPRequest.h @@ -25,6 +25,10 @@ @interface LGHTTPRequest : NSObject - (void)retrieveDistributionPoints:(LGHTTPCredential *)credential + reply:(void (^)(NSDictionary *distributionPoints, NSError *error))reply; + +- (void)retrieveDistributionPoints:(LGHTTPCredential *)credential + redirect:(void (^)(NSString *redirect))redirect reply:(void (^)(NSDictionary *distributionPoints, NSError *error))reply; - (void)retrieveDistributionPoints2:(LGHTTPCredential *)credential diff --git a/AutoPkgr/Utility/LGHTTPRequest.m b/AutoPkgr/Utility/LGHTTPRequest.m index 44ef3caf..8d1b305b 100644 --- a/AutoPkgr/Utility/LGHTTPRequest.m +++ b/AutoPkgr/Utility/LGHTTPRequest.m @@ -58,7 +58,8 @@ - (void)retrieveDistributionPoints2:(LGHTTPCredential *)credential manager.responseSerializer = [AFJSONResponseSerializer serializer]; // As of 7/29/15 JAMF cloud is returning json data as text/plain - // so handle that too. + // But add `application/json` to be future ready, for more approperiate + // Content-Types [manager.responseSerializer setAcceptableContentTypes:[NSSet setWithObjects: @"application/json", @"text/plain", nil]]; @@ -74,15 +75,15 @@ - (void)retrieveDistributionPoints2:(LGHTTPCredential *)credential } - (void)retrieveDistributionPoints:(LGHTTPCredential *)credential - reply:(void (^)(NSDictionary *distributionPoints, NSError *error))reply; + reply:(void (^)(NSDictionary *distributionPoints, NSError *error))reply { - // Setup the request NSMutableString *distPointAddress = [credential.server.trailingSlashRemoved mutableCopy]; [ distPointAddress appendString:@"/JSSResource/distributionpoints" ]; NSURL *url = [NSURL URLWithString:distPointAddress]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + [request setValue:@"application/xml" forHTTPHeaderField:@"Accept"]; request.timeoutInterval = 5.0; diff --git a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.h b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.h index eb011cd5..d5f7e351 100644 --- a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.h +++ b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.h @@ -25,23 +25,4 @@ @interface LGJSSImporterIntegrationView : LGBaseIntegrationViewController -@property (strong) IBOutlet LGTableView *jssDistributionPointTableView; -@property (weak) IBOutlet NSTextField *jssURLTF; -@property (weak) IBOutlet NSTextField *jssAPIUsernameTF; -@property (weak) IBOutlet NSTextField *jssAPIPasswordTF; -@property (weak) IBOutlet NSButton *jssReloadServerBT; -@property (weak) IBOutlet NSProgressIndicator *jssStatusSpinner; -@property (weak) IBOutlet NSImageView *jssStatusLight; - -@property (weak) IBOutlet NSButton *jssEditDistPointBT; -@property (weak) IBOutlet NSButton *jssRemoveDistPointBT; -@property (weak) IBOutlet NSButton *jssUseMasterJDS; - -@property (weak) NSWindow *modalWindow; - -- (IBAction)addDistributionPoint:(id)sender; -- (IBAction)removeDistributionPoint:(id)sender; -- (IBAction)editDistributionPoint:(id)sender; -- (IBAction)enableMasterJDS:(NSButton *)sender; - @end diff --git a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m index b2a5afe1..cf17361f 100644 --- a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m +++ b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGJSSImporterIntegrationView.m @@ -124,22 +124,22 @@ - (IBAction)getDistributionPoints:(id)sender LGHTTPRequest *request = [[LGHTTPRequest alloc] init]; [request retrieveDistributionPoints2:[self jssCredentials] - reply:^(NSDictionary *distributionPoints, NSError *error) { - [[NSOperationQueue mainQueue] addOperationWithBlock:^{ - if (!error) { - [_jssStatusLight setImage:[NSImage LGStatusAvailable]]; - } else { - [NSApp presentError:error]; - } - - [self stopStatusUpdate:error]; - NSArray *cleanedArray = [self evaluateJSSRepoDictionaries:distributionPoints]; - if (cleanedArray) { - _defaults.JSSRepos = cleanedArray; - [_jssDistributionPointTableView reloadData]; - } - }]; - }]; + reply:^(NSDictionary *distributionPoints, NSError *error) { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + if (!error) { + [_jssStatusLight setImage:[NSImage LGStatusAvailable]]; + } else if (error){ + [NSApp presentError:error]; + } + + [self stopStatusUpdate:error]; + NSArray *cleanedArray = [self evaluateJSSRepoDictionaries:distributionPoints]; + if (cleanedArray) { + _defaults.JSSRepos = cleanedArray; + [_jssDistributionPointTableView reloadData]; + } + }]; + }]; } #pragma mark - Progress @@ -312,13 +312,6 @@ - (NSArray *)evaluateJSSRepoDictionaries:(NSDictionary *)distributionPoints return newRepos.copy; } -- (void)saveDefaults -{ - _defaults.JSSAPIPassword = _jssAPIPasswordTF.safe_stringValue; - _defaults.JSSAPIUsername = _jssAPIUsernameTF.safe_stringValue; - _defaults.JSSURL = _jssURLTF.safe_stringValue; -} - - (NSString *)promptForSharePassword:(NSString *)shareName { NSLog(@"Prompting for password for distribution point: %@", shareName); @@ -432,6 +425,8 @@ - (void)editDistributionPoint:(id)sender #pragma mark - Text Field delegate - (void)controlTextDidChange:(NSNotification *)notification { + _jssStatusLight.image = [NSImage LGStatusPartiallyAvailable]; + NSTextField *obj = notification.object; if ([obj isEqualTo:_jssURLTF]) { _defaults.JSSURL = obj.safe_stringValue; @@ -442,6 +437,35 @@ - (void)controlTextDidChange:(NSNotification *)notification { } } +- (void)controlTextDidEndEditing:(NSNotification *)obj { + if ([_jssURLTF isEqualTo:obj.object]) { + [self startStatusUpdate]; + LGTestPort *tester = [[LGTestPort alloc] init]; + [tester testServerURL:_jssURLTF.stringValue reply:^(BOOL reachable, NSString *redirectedURL) { + if (redirectedURL) { + NSAlert *alert = [[NSAlert alloc] init]; + NSString *title = NSLocalizedStringFromTable(@"The server redirected the requested URL.", + @"LocalizableJSSImporter", + @"alert title when server sends a redirect for JSS"); + + NSString *informativeText = NSLocalizedStringFromTable(@"The server redirected the request to\n\n%@\n\nYou should consider using this for the JSS url.", + @"LocalizableJSSImporter", + @"informativeText when server sends a redirect."); + alert.messageText = title; + alert.informativeText = quick_formatString(informativeText, redirectedURL); + [alert addButtonWithTitle:@"Use suggested URL"]; + [alert addButtonWithTitle:@"Ignore"]; + + if([alert runModal] == NSAlertFirstButtonReturn){ + _jssURLTF.stringValue = redirectedURL; + _defaults.JSSURL = redirectedURL; + } + } + [self stopStatusUpdate:nil]; + }]; + } +} + #pragma mark - Panel didEnd Selectors - (void)didClosePreferencePanel { From af1b212be4fcaf99f915377d541fe7cf03594658 Mon Sep 17 00:00:00 2001 From: Elliot Jordan Date: Thu, 30 Jul 2015 12:14:39 -0700 Subject: [PATCH 25/59] Updated change log. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cea9142..c2000fea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. This projec ### Changed ### Fixed +- Adjusted a few things that should improve AutoPkgr's behavior when running on Mac OS X 10.8. - Fixed a bug that would cause proxy settings to be displayed incorrectly. (#399) - Fixed a bug where schedule changes would not reload in-memory launchd schedule. - Fixed a bug that would prevent JSS_REPOS defaults from getting set. From 3abee2713e2368bfa5b29aeb3e25f2d344e5fbbf Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Thu, 30 Jul 2015 15:07:41 -0500 Subject: [PATCH 26/59] Catch one other obscure condition that could results in duplicate listings -- Addressed condition: Add repo by name (not URL) using cli, open AutoPkgr, add full URL with git extension. -- Bonus: Only display contextual menu to update repo when repo is installed. --- AutoPkgr/Models/AutoPkg Task/LGAutoPkgRepo.h | 9 +++++++++ AutoPkgr/Models/AutoPkg Task/LGAutoPkgRepo.m | 19 ++++++++++++------- .../LGRepoTableViewController.m | 16 +++++++++------- .../Tab Views/LGRecipeReposViewController.m | 9 ++++++--- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRepo.h b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRepo.h index 9d04d3c1..d52f4314 100644 --- a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRepo.h +++ b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRepo.h @@ -46,6 +46,15 @@ typedef NS_ENUM(OSStatus, LGAutoPkgRepoStatus) { @property (assign, nonatomic, readonly) LGAutoPkgRepoStatus status; @property (copy) void (^statusChangeBlock)(LGAutoPkgRepoStatus); +/** + * Initialize a repo with a clone url + * + * @param cloneURL clone URL + * + * @return initialized AutoPkgRepo Object + */ +- (instancetype)initWithCloneURL:(NSString *)cloneURL; + /** * Check if there are updates available for the repo. * diff --git a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRepo.m b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRepo.m index a04c3546..47342da2 100644 --- a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRepo.m +++ b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgRepo.m @@ -150,13 +150,18 @@ - (void)updateRepo:(void (^)(NSError *))reply - (void)install:(void (^)(NSError *))reply { - [LGAutoPkgTask repoAdd:_cloneURL.absoluteString reply:^(NSError *error) { - _activeRepos = [LGAutoPkgTask repoList]; - if (!error) { - [self statusDidChange:kLGAutoPkgRepoUpToDate]; - } - reply(error); - }]; + _activeRepos = [LGAutoPkgTask repoList]; + if (!self.isInstalled) { + [LGAutoPkgTask repoAdd:_cloneURL.absoluteString reply:^(NSError *error) { + _activeRepos = [LGAutoPkgTask repoList]; + if (!error) { + [self statusDidChange:kLGAutoPkgRepoUpToDate]; + } + reply(error); + }]; + } else { + reply(nil); + } } - (void)remove:(void (^)(NSError *))reply diff --git a/AutoPkgr/Views-Controllers-XIB/Recipes & Repos Table Controllers/LGRepoTableViewController.m b/AutoPkgr/Views-Controllers-XIB/Recipes & Repos Table Controllers/LGRepoTableViewController.m index 6d7d7fb2..811e2d21 100644 --- a/AutoPkgr/Views-Controllers-XIB/Recipes & Repos Table Controllers/LGRepoTableViewController.m +++ b/AutoPkgr/Views-Controllers-XIB/Recipes & Repos Table Controllers/LGRepoTableViewController.m @@ -230,13 +230,15 @@ - (NSMenu *)contextualMenuForRow:(NSInteger)row NSMenu *menu = [[NSMenu alloc] init]; // Update Repo... - NSMenuItem *updateItem = [[NSMenuItem alloc] initWithTitle:@"Update This Repo Only" - action:@selector(updateRepo:) - keyEquivalent:@""]; - updateItem.target = self; - updateItem.representedObject = repo; - - [menu addItem:updateItem]; + if (repo.isInstalled) { + NSMenuItem *updateItem = [[NSMenuItem alloc] initWithTitle:@"Update This Repo Only" + action:@selector(updateRepo:) + keyEquivalent:@""]; + updateItem.target = self; + updateItem.representedObject = repo; + + [menu addItem:updateItem]; + } // Commits ... if (repo.commitsURL) { diff --git a/AutoPkgr/Views-Controllers-XIB/Tab Views/LGRecipeReposViewController.m b/AutoPkgr/Views-Controllers-XIB/Tab Views/LGRecipeReposViewController.m index 41a5bde5..48d626b8 100644 --- a/AutoPkgr/Views-Controllers-XIB/Tab Views/LGRecipeReposViewController.m +++ b/AutoPkgr/Views-Controllers-XIB/Tab Views/LGRecipeReposViewController.m @@ -22,6 +22,7 @@ #import "LGRecipeTableViewController.h" #import "LGRepoTableViewController.h" #import "LGAutoPkgTask.h" +#import "LGAutoPkgRepo.h" #import "LGAutoPkgRecipe.h" #import "LGRecipeSearch.h" #import "LGNotificationManager.h" @@ -150,14 +151,16 @@ - (IBAction)cancelAutoPkgRun:(id)sender - (IBAction)addAutoPkgRepoURL:(id)sender { - NSString *repo = [_repoURLToAdd stringValue]; - [self.progressDelegate startProgressWithMessage:[NSString stringWithFormat:@"Adding %@", repo]]; + NSString *cloneURL = [_repoURLToAdd stringValue]; + [self.progressDelegate startProgressWithMessage:[NSString stringWithFormat:@"Adding %@", cloneURL]]; - [LGAutoPkgTask repoAdd:repo reply:^(NSError *error) { + LGAutoPkgRepo *repo = [[LGAutoPkgRepo alloc] initWithCloneURL:cloneURL]; + [repo install:^(NSError *error) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [self.progressDelegate stopProgress:error]; }]; }]; + [_repoURLToAdd setStringValue:@""]; } From 6b62e6784adaaf8b52d89cc5094d6968ccd26aa8 Mon Sep 17 00:00:00 2001 From: Elliot Jordan Date: Fri, 31 Jul 2015 10:47:02 -0700 Subject: [PATCH 27/59] Updated change log. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2000fea..91403dbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file. This projec - Fixed a bug that would cause proxy settings to be displayed incorrectly. (#399) - Fixed a bug where schedule changes would not reload in-memory launchd schedule. - Fixed a bug that would prevent JSS_REPOS defaults from getting set. +- Fixed a bug that would result in repos appearing twice in the repo table. (#406) ## [1.3] - 2015-05-08 From fcb5679b83271f5172be4e43629572c29b916e87 Mon Sep 17 00:00:00 2001 From: Eldon Ahrold Date: Fri, 31 Jul 2015 14:26:28 -0500 Subject: [PATCH 28/59] Handle `autopkg search` string parsing better. Split on " " and trim. --- .../AutoPkg Task/LGAutoPkgResultHandler.m | 20 ++++++------------- .../LGAutoPkgIntegrationView.xib | 7 +++++++ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgResultHandler.m b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgResultHandler.m index e1c394da..8279b928 100644 --- a/AutoPkgr/Models/AutoPkg Task/LGAutoPkgResultHandler.m +++ b/AutoPkgr/Models/AutoPkg Task/LGAutoPkgResultHandler.m @@ -114,10 +114,7 @@ - (id)results __block NSMutableArray *searchResults; - NSMutableCharacterSet *repoCharacters = [NSMutableCharacterSet alphanumericCharacterSet]; - [repoCharacters formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; - - NSCharacterSet *skippedCharacters = [NSCharacterSet whitespaceCharacterSet]; + NSMutableCharacterSet *whiteSpace = [NSCharacterSet whitespaceAndNewlineCharacterSet]; NSPredicate *skipLinePredicate = [NSPredicate predicateWithFormat:@"SELF BEGINSWITH 'To add' \ or SELF BEGINSWITH '----' \ @@ -125,17 +122,12 @@ - (id)results [self.dataString enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) { if (![skipLinePredicate evaluateWithObject:line ]) { - NSScanner *scanner = [NSScanner scannerWithString:line]; - [scanner setCharactersToBeSkipped:skippedCharacters]; - - NSString *recipe, *repo, *path; - - [scanner scanCharactersFromSet:repoCharacters intoString:&recipe]; - [scanner scanCharactersFromSet:repoCharacters intoString:&repo]; - [scanner scanCharactersFromSet:repoCharacters intoString:&path]; - + NSArray *array = [line componentsSeparatedByString:@" "].filtered_noEmptyStrings; + if (array.count == 3) { + NSString *recipe = [array[0] stringByTrimmingCharactersInSet:whiteSpace]; + NSString *repo = [array[1] stringByTrimmingCharactersInSet:whiteSpace]; + NSString *path = [array[2] stringByTrimmingCharactersInSet:whiteSpace]; - if (recipe && repo && path) { if (!searchResults) { searchResults = [[NSMutableArray alloc] init]; } diff --git a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGAutoPkgIntegrationView.xib b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGAutoPkgIntegrationView.xib index 9bc83f34..2edc8a28 100644 --- a/AutoPkgr/Views-Controllers-XIB/Integration Views/LGAutoPkgIntegrationView.xib +++ b/AutoPkgr/Views-Controllers-XIB/Integration Views/LGAutoPkgIntegrationView.xib @@ -226,6 +226,13 @@ + + + + + + +