From 86b104a1da6857792c1feb7dcec0d3d654fdac5f Mon Sep 17 00:00:00 2001 From: rowanbeentje Date: Mon, 6 Jul 2009 21:01:52 +0000 Subject: - Improve handling of the new favorites interface in preferences, simplifying the code and fixing one or two edge cases that caused disassociation (desynch) of keychain passwords from the favorite --- Interfaces/English.lproj/Preferences.xib | 273 +++++++++++++++---------------- Source/SPPreferenceController.h | 14 +- Source/SPPreferenceController.m | 198 +++++++++++----------- 3 files changed, 230 insertions(+), 255 deletions(-) diff --git a/Interfaces/English.lproj/Preferences.xib b/Interfaces/English.lproj/Preferences.xib index 78dde430..b07f25e5 100644 --- a/Interfaces/English.lproj/Preferences.xib +++ b/Interfaces/English.lproj/Preferences.xib @@ -8,7 +8,7 @@ 353.00 YES - + YES @@ -3982,14 +3982,6 @@ AQAAAAA 777 - - - nameField - - - - 778 - contentArray: values.favorites @@ -4491,22 +4483,6 @@ AQAAAAA 1115 - - - sshHostField - - - - 1116 - - - - sshUserField - - - - 1117 - delegate @@ -4792,22 +4768,6 @@ AQAAAAA 1245 - - - sshSQLHostField - - - - 1296 - - - - sshSQLUserField - - - - 1297 - sshSQLPasswordField @@ -4848,14 +4808,6 @@ AQAAAAA 1302 - - - sshDatabaseField - - - - 1303 - delegate @@ -4864,14 +4816,6 @@ AQAAAAA 1304 - - - socketUserField - - - - 1305 - delegate @@ -4896,30 +4840,6 @@ AQAAAAA 1308 - - - socketDatabaseField - - - - 1309 - - - - standardSQLHostField - - - - 1310 - - - - standardUserField - - - - 1311 - standardPasswordField @@ -4929,12 +4849,12 @@ AQAAAAA 1312 - - standardDatabaseField + + favoriteTypeDidChange: - + - 1313 + 1329 @@ -4952,18 +4872,20 @@ AQAAAAA YES NSAllowsEditingMultipleValuesSelection + NSContinuouslyUpdatesValue NSNoSelectionPlaceholder YES + no selection 2 - 1314 + 1330 @@ -4981,18 +4903,20 @@ AQAAAAA YES NSAllowsEditingMultipleValuesSelection + NSContinuouslyUpdatesValue NSNoSelectionPlaceholder YES + no selection 2 - 1315 + 1331 @@ -5010,12 +4934,14 @@ AQAAAAA YES NSAllowsEditingMultipleValuesSelection + NSContinuouslyUpdatesValue NSNoSelectionPlaceholder NSNullPlaceholder YES + no selection optional @@ -5023,7 +4949,7 @@ AQAAAAA 2 - 1316 + 1332 @@ -5041,12 +4967,14 @@ AQAAAAA YES NSAllowsEditingMultipleValuesSelection + NSContinuouslyUpdatesValue NSNoSelectionPlaceholder NSNullPlaceholder YES + no selection 3306 @@ -5054,7 +4982,7 @@ AQAAAAA 2 - 1317 + 1333 @@ -5068,13 +4996,22 @@ AQAAAAA value selection.user - NSNoSelectionPlaceholder - no selection + YES + + YES + NSContinuouslyUpdatesValue + NSNoSelectionPlaceholder + + + YES + + no selection + 2 - 1318 + 1334 @@ -5091,11 +5028,13 @@ AQAAAAA YES YES + NSContinuouslyUpdatesValue NSNoSelectionPlaceholder NSNullPlaceholder YES + no selection optional @@ -5103,7 +5042,7 @@ AQAAAAA 2 - 1319 + 1335 @@ -5120,11 +5059,13 @@ AQAAAAA YES YES + NSContinuouslyUpdatesValue NSNoSelectionPlaceholder NSNullPlaceholder YES + no selection optional @@ -5132,7 +5073,38 @@ AQAAAAA 2 - 1320 + 1336 + + + + value: selection.name + + + + + + value: selection.name + value + selection.name + + YES + + YES + NSAllowsEditingMultipleValuesSelection + NSContinuouslyUpdatesValue + NSNoSelectionPlaceholder + + + YES + + + no selection + + + 2 + + + 1337 @@ -5146,13 +5118,22 @@ AQAAAAA value selection.host - NSNoSelectionPlaceholder - no selection + YES + + YES + NSContinuouslyUpdatesValue + NSNoSelectionPlaceholder + + + YES + + no selection + 2 - 1321 + 1338 @@ -5166,13 +5147,22 @@ AQAAAAA value selection.user - NSNoSelectionPlaceholder - no selection + YES + + YES + NSContinuouslyUpdatesValue + NSNoSelectionPlaceholder + + + YES + + no selection + 2 - 1322 + 1339 @@ -5189,11 +5179,13 @@ AQAAAAA YES YES + NSContinuouslyUpdatesValue NSNoSelectionPlaceholder NSNullPlaceholder YES + no selection optional @@ -5201,7 +5193,7 @@ AQAAAAA 2 - 1323 + 1340 @@ -5218,11 +5210,13 @@ AQAAAAA YES YES + NSContinuouslyUpdatesValue NSNoSelectionPlaceholder NSNullPlaceholder YES + no selection 3306 @@ -5230,7 +5224,7 @@ AQAAAAA 2 - 1324 + 1341 @@ -5244,13 +5238,22 @@ AQAAAAA value selection.sshHost - NSNoSelectionPlaceholder - no selection + YES + + YES + NSContinuouslyUpdatesValue + NSNoSelectionPlaceholder + + + YES + + no selection + 2 - 1325 + 1342 @@ -5264,13 +5267,22 @@ AQAAAAA value selection.sshUser - NSNoSelectionPlaceholder - no selection + YES + + YES + NSContinuouslyUpdatesValue + NSNoSelectionPlaceholder + + + YES + + no selection + 2 - 1326 + 1343 @@ -5287,11 +5299,13 @@ AQAAAAA YES YES + NSContinuouslyUpdatesValue NSNoSelectionPlaceholder NSNullPlaceholder YES + no selection 22 @@ -5299,44 +5313,23 @@ AQAAAAA 2 - 1327 + 1344 - - value: selection.name + + nextKeyView - - - - - value: selection.name - value - selection.name - - YES - - YES - NSAllowsEditingMultipleValuesSelection - NSNoSelectionPlaceholder - - - YES - - no selection - - - 2 - + - 1328 + 1346 - - favoriteTypeDidChange: - - + + nextKeyView + + - 1329 + 1350 @@ -8519,7 +8512,7 @@ AQAAAAA - 1329 + 1350 diff --git a/Source/SPPreferenceController.h b/Source/SPPreferenceController.h index 221508a2..f5035787 100644 --- a/Source/SPPreferenceController.h +++ b/Source/SPPreferenceController.h @@ -45,22 +45,13 @@ IBOutlet NSArrayController *favoritesController; IBOutlet NSTabView *favoritesTabView; - IBOutlet NSTextField *nameField; - IBOutlet NSTextField *standardSQLHostField; - IBOutlet NSTextField *standardUserField; IBOutlet NSSecureTextField *standardPasswordField; - IBOutlet NSTextField *standardDatabaseField; - IBOutlet NSTextField *socketUserField; IBOutlet NSSecureTextField *socketPasswordField; - IBOutlet NSTextField *socketDatabaseField; - IBOutlet NSTextField *sshSQLHostField; - IBOutlet NSTextField *sshSQLUserField; IBOutlet NSSecureTextField *sshSQLPasswordField; - IBOutlet NSTextField *sshDatabaseField; - IBOutlet NSTextField *sshHostField; - IBOutlet NSTextField *sshUserField; IBOutlet NSSecureTextField *sshPasswordField; + KeyChain *keychain; + NSDictionary *currentFavorite; IBOutlet NSTextField *editorFontName; @@ -104,5 +95,6 @@ - (void)selectFavoriteAtIndex:(unsigned int)theIndex; - (void)changeFont:(id)sender; - (IBAction)favoriteTypeDidChange:(id)sender; +- (void)updateFavoritePasswordsFromField:(NSControl *)passwordControl; @end diff --git a/Source/SPPreferenceController.m b/Source/SPPreferenceController.m index 9550e897..78d16a20 100644 --- a/Source/SPPreferenceController.m +++ b/Source/SPPreferenceController.m @@ -61,6 +61,7 @@ if (self = [super initWithWindowNibName:@"Preferences"]) { prefs = [NSUserDefaults standardUserDefaults]; [self applyRevisionChanges]; + currentFavorite = nil; } return self; @@ -597,7 +598,7 @@ [favoritesController setSelectionIndexes:[favoritesTableView selectedRowIndexes]]; } - // If no selection is present, blank the field. + // If no selection is present, blank the password fields (which can't use bindings) if ([[favoritesTableView selectedRowIndexes] count] == 0) { [standardPasswordField setStringValue:@""]; [socketPasswordField setStringValue:@""]; @@ -605,18 +606,22 @@ [sshPasswordField setStringValue:@""]; return; } + + // Keep a copy of the favorite as it currently stands + if (currentFavorite) [currentFavorite release]; + currentFavorite = [[[favoritesController selectedObjects] objectAtIndex:0] copy]; - // Otherwise retrieve and set the password. - NSString *keychainName = [keychain nameForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]]; - NSString *keychainAccount = [keychain accountForUser:[favoritesController valueForKeyPath:@"selection.user"] host:[favoritesController valueForKeyPath:@"selection.host"] database:[favoritesController valueForKeyPath:@"selection.database"]]; + // Retrieve and set the password. + NSString *keychainName = [keychain nameForFavoriteName:[currentFavorite objectForKey:@"name"] id:[currentFavorite objectForKey:@"id"]]; + NSString *keychainAccount = [keychain accountForUser:[currentFavorite objectForKey:@"user"] host:[currentFavorite objectForKey:@"host"] database:[currentFavorite objectForKey:@"database"]]; NSString *passwordValue = [keychain getPasswordForName:keychainName account:keychainAccount]; [standardPasswordField setStringValue:passwordValue]; [socketPasswordField setStringValue:passwordValue]; [sshSQLPasswordField setStringValue:passwordValue]; // Retrieve the SSH keychain password if appropriate. - NSString *keychainSSHName = [keychain nameForSSHForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]]; - NSString *keychainSSHAccount = [keychain accountForSSHUser:[favoritesController valueForKeyPath:@"selection.sshUser"] sshHost:[favoritesController valueForKeyPath:@"selection.sshHost"]]; + NSString *keychainSSHName = [keychain nameForSSHForFavoriteName:[currentFavorite objectForKey:@"name"] id:[currentFavorite objectForKey:@"id"]]; + NSString *keychainSSHAccount = [keychain accountForSSHUser:[currentFavorite objectForKey:@"sshUser"] sshHost:[currentFavorite objectForKey:@"sshHost"]]; [sshPasswordField setStringValue:[keychain getPasswordForName:keychainSSHName account:keychainSSHAccount]]; } @@ -709,130 +714,112 @@ // ------------------------------------------------------------------------------- - (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor { - NSString *nameValue, *hostValue, *userValue, *databaseValue, *passwordValue; + + // Request a password refresh to keep keychain references in synch with favorites + [self updateFavoritePasswordsFromField:control]; + + // Proceed with editing + return YES; +} + +// ------------------------------------------------------------------------------- +// favoriteTypeDidChange: +// Update the favorite host when the type changes. +// ------------------------------------------------------------------------------- +- (IBAction)favoriteTypeDidChange:(id)sender +{ + if ([sender indexOfSelectedItem] == 1) { // Socket + [favoritesController setValue:@"localhost" forKeyPath:@"selection.host"]; + } else if ([[favoritesController valueForKeyPath:@"selection.host"] isEqualToString:@"localhost"]) { + [favoritesController setValue:@"" forKeyPath:@"selection.host"]; + } + + // Request a password refresh to keep keychain references in synch with the favorites + [self updateFavoritePasswordsFromField:nil]; +} + +// ------------------------------------------------------------------------------- +// updateFavoritePasswordsFromField: +// Check all fields used in the keychain names against the old values for that +// favorite, and update the keychain names to match if necessary. +// If an (optional) recognised password field is supplied, that field is assumed +// to have changed and is used to supply the new value. +// ------------------------------------------------------------------------------- +- (void)updateFavoritePasswordsFromField:(NSControl *)passwordControl +{ + if (!currentFavorite) return; + + NSString *passwordValue; NSString *oldKeychainName, *newKeychainName; NSString *oldKeychainAccount, *newKeychainAccount; - // Only proceed for name, host, user or database changes, for standard, socket or SSH - if (control != nameField && control != standardSQLHostField && control != standardUserField - && control != standardPasswordField && control != standardDatabaseField - && control != socketUserField && control != socketPasswordField - && control != socketDatabaseField && control != sshSQLHostField - && control != sshSQLUserField && control != sshSQLPasswordField - && control != sshDatabaseField && control != sshHostField - && control != sshUserField && control != sshPasswordField) - return YES; - - // Determine the appropriate name, host, user and database details - nameValue = [nameField stringValue]; - switch ([[favoritesController valueForKeyPath:@"selection.type"] intValue]) { - case 0: // Standard - hostValue = [standardSQLHostField stringValue]; - userValue = [standardUserField stringValue]; - databaseValue = [standardDatabaseField stringValue]; - passwordValue = [standardPasswordField stringValue]; - break; + // SQL passwords are indexed by name, host, user and database. If any of these + // have changed, or a standard password field has, alter the keychain item to match. + if (![[currentFavorite objectForKey:@"name"] isEqualToString:[favoritesController valueForKeyPath:@"selection.name"]] + || ![[currentFavorite objectForKey:@"host"] isEqualToString:[favoritesController valueForKeyPath:@"selection.host"]] + || ![[currentFavorite objectForKey:@"user"] isEqualToString:[favoritesController valueForKeyPath:@"selection.user"]] + || ![[currentFavorite objectForKey:@"database"] isEqualToString:[favoritesController valueForKeyPath:@"selection.database"]] + || passwordControl == standardPasswordField || passwordControl == socketPasswordField || passwordControl == sshSQLPasswordField) + { - case 1: // Socket - hostValue = @"localhost"; - userValue = [socketUserField stringValue]; - databaseValue = [socketDatabaseField stringValue]; + // Determine the correct password field to read the password from, defaulting to standard + if (passwordControl == socketPasswordField) { passwordValue = [socketPasswordField stringValue]; - break; - - case 2: // SSH - hostValue = [sshSQLHostField stringValue]; - userValue = [sshSQLUserField stringValue]; - databaseValue = [sshDatabaseField stringValue]; + } else if (passwordControl == sshSQLPasswordField) { passwordValue = [sshSQLPasswordField stringValue]; - break; - } - - // If account/password details have changed, update the keychain to match - if (![nameValue isEqualToString:[favoritesController valueForKeyPath:@"selection.name"]] - || ![hostValue isEqualToString:[favoritesController valueForKeyPath:@"selection.host"]] - || ![userValue isEqualToString:[favoritesController valueForKeyPath:@"selection.user"]] - || ![databaseValue isEqualToString:[favoritesController valueForKeyPath:@"selection.database"]] - || control == standardPasswordField || control == socketPasswordField || control == sshSQLPasswordField) { - - // Get the current keychain name and account strings - oldKeychainName = [keychain nameForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]]; - oldKeychainAccount = [keychain accountForUser:[favoritesController valueForKeyPath:@"selection.user"] host:[favoritesController valueForKeyPath:@"selection.host"] database:[favoritesController valueForKeyPath:@"selection.database"]]; + } else { + passwordValue = [standardPasswordField stringValue]; + } - // Set up the new keychain name and account strings - newKeychainName = [keychain nameForFavoriteName:nameValue id:[favoritesController valueForKeyPath:@"selection.id"]]; - newKeychainAccount = [keychain accountForUser:userValue host:hostValue database:databaseValue]; + // Get the old keychain name and account strings + oldKeychainName = [keychain nameForFavoriteName:[currentFavorite objectForKey:@"name"] id:[favoritesController valueForKeyPath:@"selection.id"]]; + oldKeychainAccount = [keychain accountForUser:[currentFavorite objectForKey:@"user"] host:[currentFavorite objectForKey:@"host"] database:[currentFavorite objectForKey:@"database"]]; // Delete the old keychain item [keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount]; + // Set up the new keychain name and account strings + newKeychainName = [keychain nameForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]]; + newKeychainAccount = [keychain accountForUser:[favoritesController valueForKeyPath:@"selection.user"] host:[favoritesController valueForKeyPath:@"selection.host"] database:[favoritesController valueForKeyPath:@"selection.database"]]; + // Add the new keychain item if the password field has a value if ([passwordValue length]) [keychain addPassword:passwordValue forName:newKeychainName account:newKeychainAccount]; + + // Synch password changes + [standardPasswordField setStringValue:passwordValue]; + [socketPasswordField setStringValue:passwordValue]; + [sshSQLPasswordField setStringValue:passwordValue]; + + passwordValue = @""; } - - // Synch password changes - [standardPasswordField setStringValue:passwordValue]; - [socketPasswordField setStringValue:passwordValue]; - [sshSQLPasswordField setStringValue:passwordValue]; // If SSH account/password details have changed, update the keychain to match - if (![[nameField stringValue] isEqualToString:[favoritesController valueForKeyPath:@"selection.name"]] - || ![[sshHostField stringValue] isEqualToString:[favoritesController valueForKeyPath:@"selection.sshHost"]] - || ![[sshUserField stringValue] isEqualToString:[favoritesController valueForKeyPath:@"selection.sshUser"]] - || control == sshPasswordField) { - - // Get the current keychain name and account strings - oldKeychainName = [keychain nameForSSHForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]]; - oldKeychainAccount = [keychain accountForSSHUser:[favoritesController valueForKeyPath:@"selection.sshUser"] sshHost:[favoritesController valueForKeyPath:@"selection.sshHost"]]; + if (![[currentFavorite objectForKey:@"name"] isEqualToString:[favoritesController valueForKeyPath:@"selection.name"]] + || ![[currentFavorite objectForKey:@"sshHost"] isEqualToString:[favoritesController valueForKeyPath:@"selection.sshHost"]] + || ![[currentFavorite objectForKey:@"sshUser"] isEqualToString:[favoritesController valueForKeyPath:@"selection.sshUser"]] + || passwordControl == sshPasswordField) { - // Set up the new keychain name and account strings - newKeychainName = [keychain nameForSSHForFavoriteName:nameValue id:[favoritesController valueForKeyPath:@"selection.id"]]; - newKeychainAccount = [keychain accountForSSHUser:[sshUserField stringValue] sshHost:[sshHostField stringValue]]; + // Get the old keychain name and account strings + oldKeychainName = [keychain nameForSSHForFavoriteName:[currentFavorite objectForKey:@"name"] id:[favoritesController valueForKeyPath:@"selection.id"]]; + oldKeychainAccount = [keychain accountForSSHUser:[currentFavorite objectForKey:@"sshUser"] sshHost:[currentFavorite objectForKey:@"sshHost"]]; // Delete the old keychain item [keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount]; + // Set up the new keychain name and account strings + newKeychainName = [keychain nameForSSHForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]]; + newKeychainAccount = [keychain accountForSSHUser:[favoritesController valueForKeyPath:@"selection.sshUser"] sshHost:[favoritesController valueForKeyPath:@"selection.sshHost"]]; + // Add the new keychain item if the password field has a value if ([[sshPasswordField stringValue] length]) [keychain addPassword:[sshPasswordField stringValue] forName:newKeychainName account:newKeychainAccount]; } - // Proceed with editing - return YES; -} - -// ------------------------------------------------------------------------------- -// favoriteTypeDidChange: -// Update the favorite host when the type changes. -// ------------------------------------------------------------------------------- -- (IBAction)favoriteTypeDidChange:(id)sender -{ - NSString *oldKeychainName, *newKeychainName; - NSString *oldKeychainAccount, *newKeychainAccount; - - // Get the current keychain name and account strings - oldKeychainName = [keychain nameForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]]; - oldKeychainAccount = [keychain accountForUser:[favoritesController valueForKeyPath:@"selection.user"] host:[favoritesController valueForKeyPath:@"selection.host"] database:[favoritesController valueForKeyPath:@"selection.database"]]; - - // Delete the old keychain item - [keychain deletePasswordForName:oldKeychainName account:oldKeychainAccount]; - - if ([sender indexOfSelectedItem] == 1) { // Socket - [favoritesController setValue:@"localhost" forKeyPath:@"selection.host"]; - [self control:socketPasswordField textShouldEndEditing:nil]; - } else if ([[favoritesController valueForKeyPath:@"selection.host"] isEqualToString:@"localhost"]) { - [favoritesController setValue:@"" forKeyPath:@"selection.host"]; - [self control:standardPasswordField textShouldEndEditing:nil]; - } - - // Set up the new keychain name and account strings - newKeychainName = [keychain nameForFavoriteName:[favoritesController valueForKeyPath:@"selection.name"] id:[favoritesController valueForKeyPath:@"selection.id"]]; - newKeychainAccount = [keychain accountForUser:[favoritesController valueForKeyPath:@"selection.user"] host:[favoritesController valueForKeyPath:@"selection.host"] database:[favoritesController valueForKeyPath:@"selection.database"]]; - - // Add the new keychain item if the password field has a value - if ([[standardPasswordField stringValue] length]) - [keychain addPassword:[standardPasswordField stringValue] forName:newKeychainName account:newKeychainAccount]; + // Update the current favorite + if (currentFavorite) [currentFavorite release], currentFavorite = nil; + if ([[favoritesTableView selectedRowIndexes] count] > 0) + currentFavorite = [[[favoritesController selectedObjects] objectAtIndex:0] copy]; } #pragma mark - @@ -913,6 +900,7 @@ - (void)selectFavorites:(NSArray *)favorites { [favoritesController setSelectedObjects:favorites]; + [favoritesTableView scrollRowToVisible:[favoritesController selectionIndex]]; } // ------------------------------------------------------------------------------- @@ -923,6 +911,7 @@ - (void)selectFavoriteAtIndex:(unsigned int)theIndex { [favoritesController setSelectionIndex:theIndex]; + [favoritesTableView scrollRowToVisible:theIndex]; } // ------------------------------------------------------------------------------- @@ -970,7 +959,8 @@ - (void)dealloc { [keychain release], keychain = nil; - + if (currentFavorite) [currentFavorite release]; + [super dealloc]; } -- cgit v1.2.3