Revision: 27927
          http://sourceforge.net/p/bibdesk/svn/27927
Author:   hofman
Date:     2022-09-25 14:57:39 +0000 (Sun, 25 Sep 2022)
Log Message:
-----------
Don't include password in search group file or URL. Save password in keychain 
when converting mutable info, and get it from the keychain when missing.

Modified Paths:
--------------
    trunk/bibdesk/BDSKPasswordController.h
    trunk/bibdesk/BDSKPasswordController.m
    trunk/bibdesk/BDSKSearchGroup.m
    trunk/bibdesk/BDSKServerInfo+Scripting.m
    trunk/bibdesk/BDSKServerInfo.m
    trunk/bibdesk/BDSKZoomGroupServer.m

Modified: trunk/bibdesk/BDSKPasswordController.h
===================================================================
--- trunk/bibdesk/BDSKPasswordController.h      2022-09-25 06:30:18 UTC (rev 
27926)
+++ trunk/bibdesk/BDSKPasswordController.h      2022-09-25 14:57:39 UTC (rev 
27927)
@@ -53,6 +53,10 @@
 
 + (BOOL)addOrModifyPassword:(NSString *)password forKeychainService:(NSString 
*)service account:(NSString *)account name:(NSString *)name;
 
++ (NSString *)passwordForKeychainServer:(NSString *)server 
port:(NSInteger)port account:(NSString *)account;
+
++ (BOOL)addOrModifyPassword:(NSString *)password forKeychainServer:(NSString 
*)server port:(NSInteger)port account:(NSString *)account;
+
 + (NSString *)passwordFromPanelWithMessage:(NSString *)status;
 
 - (IBAction)buttonAction:(id)sender;

Modified: trunk/bibdesk/BDSKPasswordController.m
===================================================================
--- trunk/bibdesk/BDSKPasswordController.m      2022-09-25 06:30:18 UTC (rev 
27926)
+++ trunk/bibdesk/BDSKPasswordController.m      2022-09-25 14:57:39 UTC (rev 
27927)
@@ -139,6 +139,72 @@
     return (err == noErr);
 }
 
++ (NSString *)passwordForKeychainServer:(NSString *)server 
port:(NSInteger)port account:(NSString *)account {
+    // use the service name to get password from keychain and hash it with 
sha1 for comparison purposes
+    NSString *passwordString = nil;
+    NSData *passwordData = nil;
+    OSStatus err;
+    NSMutableDictionary *query = [NSMutableDictionary dictionary];
+    
+    [query setObject:(NSString *)kSecClassInternetPassword forKey:(NSString 
*)kSecClass];
+    [query setObject:(NSString *)kSecMatchLimitOne forKey:(NSString 
*)kSecMatchLimit];
+    [query setObject:[NSNumber numberWithBool:YES] forKey:(NSString 
*)kSecReturnData];
+    [query setObject:server forKey:(NSString *)kSecAttrServer];
+    [query setObject:account forKey:(NSString *)kSecAttrAccount];
+    if (port)
+        [query setObject:[NSNumber numberWithInteger:port] forKey:(NSString 
*)kSecAttrPort];
+    
+    // see if the password exists in the keychain
+    err = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef 
*)&passwordData);
+    
+    if (err == noErr) {
+        passwordString = [[[NSString alloc] initWithData:passwordData 
encoding:NSUTF8StringEncoding] autorelease];
+        [passwordData release];
+    } else {
+        logError(@"getting", err);
+    }
+    
+    return passwordString;
+}
+
++ (BOOL)addOrModifyPassword:(NSString *)password forKeychainServer:(NSString 
*)server port:(NSInteger)port account:(NSString *)account {
+    NSString *passwordString = nil;
+    NSData *passwordData = nil;
+    OSStatus err;
+    NSMutableDictionary *query = [NSMutableDictionary dictionary];
+    NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
+    
+    // first try to update an existing item
+    [query setObject:(NSString *)kSecClassInternetPassword forKey:(NSString 
*)kSecClass];
+    [query setObject:(NSString *)kSecMatchLimitOne forKey:(NSString 
*)kSecMatchLimit];
+    [query setObject:server forKey:(NSString *)kSecAttrServer];
+    [query setObject:account forKey:(NSString *)kSecAttrAccount];
+    if (port)
+        [query setObject:[NSNumber numberWithInteger:port] forKey:(NSString 
*)kSecAttrPort];
+
+    [attributes setObject:[password dataUsingEncoding:NSUTF8StringEncoding] 
forKey:(NSString *)kSecValueData];
+    
+    err = SecItemUpdate((CFDictionaryRef)query, (CFDictionaryRef)attributes);
+    
+    logError(@"updating", err);
+    
+    if (err == errSecItemNotFound) {
+        [attributes addEntriesFromDictionary:query];
+        [attributes removeObjectForKey:(NSString *)kSecMatchLimit];
+        [attributes setObject:(NSString *)kSecAttrProtocolHTTPS 
forKey:(NSString *)kSecAttrProtocol];
+        
+        if (err == errSecItemNotFound) {
+            // password not yet on keychain, so add it
+            
+            err = SecItemAdd((CFDictionaryRef)attributes, NULL);
+            
+            logError(@"adding", err);
+        }
+    }
+    
+    return (err == noErr);
+}
+
 - (NSString *)runModalWithMessage:(NSString *)status {
     NSString *password = nil;
     [self window]; // load window before seting the status

Modified: trunk/bibdesk/BDSKSearchGroup.m
===================================================================
--- trunk/bibdesk/BDSKSearchGroup.m     2022-09-25 06:30:18 UTC (rev 27926)
+++ trunk/bibdesk/BDSKSearchGroup.m     2022-09-25 14:57:39 UTC (rev 27927)
@@ -108,7 +108,8 @@
 - (id)initWithDictionary:(NSDictionary *)groupDict {
     NSString *aSearchTerm = [groupDict objectForKey:@"search term"];
     NSArray *aHistory = [groupDict objectForKey:@"history"];
-    BDSKServerInfo *serverInfo = [[BDSKServerInfo alloc] 
initWithDictionary:groupDict];
+    // use BDSKMutableServerInfo, so the password will be save in the keychain
+    BDSKServerInfo *serverInfo = [[BDSKMutableServerInfo alloc] 
initWithDictionary:groupDict];
     
     self = [self initWithServerInfo:serverInfo searchTerm:aSearchTerm];
     if (self) {
@@ -343,12 +344,8 @@
     [components setScheme:BDSKSearchGroupURLScheme];
     BDSKServerInfo *serverInfo = [self serverInfo];
     NSString *username = [serverInfo username];
-    if (username) {
+    if (username)
         [components setPercentEncodedUser:escapeUser(username)];
-        NSString *password = [serverInfo password];
-        if (password)
-            [components setPercentEncodedPassword:escapePassword(password)];
-    }
     if ([serverInfo isZoom]) {
         [components setPercentEncodedHost:escapeHost([serverInfo host])];
         [components setPort:[NSNumber numberWithInteger:[[serverInfo port] 
integerValue]]];
@@ -358,14 +355,13 @@
     [components setPercentEncodedPath:[NSString stringWithFormat:@"/%@;%@", 
escapeDatabaseOrName([serverInfo database]), escapeDatabaseOrName([serverInfo 
name])]];
     if ([serverInfo isZoom]) {
         NSMutableArray  *query = [NSMutableArray array];
-        for (NSString *key in [serverInfo options]) {
-            NSString *value = [[serverInfo options] objectForKey:key];
+        [[serverInfo options] enumerateKeysAndObjectsUsingBlock:^(NSString 
*key, NSString *value, BOOL *stop){
             if ([key isEqualToString:@"removeDiacritics"])
                 value = [serverInfo removeDiacritics] ? @"1" : @"0";
             else if (username && ([key isEqualToString:@"username"] || [key 
isEqualToString:@"password"]))
-                continue;
+                return;
             [query addObject:[NSString stringWithFormat:@"%@=%@", key, [value 
stringByAddingPercentEscapesForQueryTerm]]];
-        }
+        }];
         [components setPercentEncodedQuery:[query 
componentsJoinedByString:@"&"]];
     } else if ([serverInfo isISI] && [serverInfo isLite]) {
         [components setPercentEncodedQuery:@"lite=1"];

Modified: trunk/bibdesk/BDSKServerInfo+Scripting.m
===================================================================
--- trunk/bibdesk/BDSKServerInfo+Scripting.m    2022-09-25 06:30:18 UTC (rev 
27926)
+++ trunk/bibdesk/BDSKServerInfo+Scripting.m    2022-09-25 14:57:39 UTC (rev 
27927)
@@ -64,13 +64,13 @@
         [info setValue:[self host] forKey:@"host"];
         [info setValue:[self port] forKey:@"port"];
         [info setValue:[self username] forKey:@"username"];
-        [info setValue:[self password] forKey:@"password"];
+        //[info setValue:[self password] forKey:@"password"];
         [info setValue:[self recordSyntax] forKey:@"recordSyntax"];
         [info setValue:[self resultEncoding] forKey:@"resultEncoding"];
         [info setValue:[NSNumber numberWithBool:[self removeDiacritics]] 
forKey:@"removeDiacritics"];
     } else if ([self isISI]) {
         [info setValue:[self username] forKey:@"username"];
-        [info setValue:[self password] forKey:@"password"];
+        //[info setValue:[self password] forKey:@"password"];
         [info setValue:[NSNumber numberWithBool:[self isLite]] forKey:@"lite"];
     }
     

Modified: trunk/bibdesk/BDSKServerInfo.m
===================================================================
--- trunk/bibdesk/BDSKServerInfo.m      2022-09-25 06:30:18 UTC (rev 27926)
+++ trunk/bibdesk/BDSKServerInfo.m      2022-09-25 14:57:39 UTC (rev 27927)
@@ -40,6 +40,7 @@
 #import "BDSKSearchGroup.h"
 #import "NSString_BDSKExtensions.h"
 #import "NSError_BDSKExtensions.h"
+#import "BDSKPasswordController.h"
 
 #define TYPE_KEY             @"type"
 #define NAME_KEY             @"name" 
@@ -59,6 +60,8 @@
 #define DEFAULT_HOST     @"host.domain.com"
 #define DEFAULT_PORT     @"0"
 
+#define ISI_SERVER @"search.webofknowledge.com"
+
 // IMPORTANT WARNING:
 // When anything changes about server infos, e.g. a new type is added, this 
should be carefully considered, as it has many consequences for data integrity 
and and the editing sheet.
 // Assumptions are made in BDSKSearchGroup and BDSKSearchGroupSheetController.
@@ -65,10 +68,16 @@
 // Currently, anything other than zoom is expected to have just a type, name, 
and database.
 // Also when other validations are necessary, changing the type must make sure 
that the data validates properly for the new type, if necessary adding missing 
values.
 
+@interface BDSKServerInfo ()
+@property (nonatomic, readonly) NSDictionary *optionsWithoutPassword;
+@property (nonatomic, readonly) NSString *passwordFromKeychain;
+- (void)savePasswordInKeychain;
+@end
+
 @implementation BDSKServerInfo
 
 @synthesize type, name, database;
-@dynamic dictionaryValue, host, port, password, username, recordSyntax, 
resultEncoding, removeDiacritics, lite, options, entrez, zoom, ISI, DBLP;
+@dynamic dictionaryValue, host, port, password, username, recordSyntax, 
resultEncoding, removeDiacritics, lite, options, optionsWithoutPassword, 
entrez, zoom, ISI, DBLP, passwordFromKeychain;
 
 + (id)defaultServerInfoWithType:(NSString *)aType;
 {
@@ -162,6 +171,10 @@
     return (object1 == nil && object2 == nil) || [object1 isEqual:object2];
 }
 
+static inline BOOL isEqualOrBothEmpty(id object1, id object2) {
+    return ([object1 count] == 0 && [object2 count] == 0) || [object1 
isEqual:object2];
+}
+
 - (BOOL)isEqual:(id)other {
     BOOL isEqual = YES;
     // we don't compare the name, as that is just a label
@@ -172,9 +185,9 @@
     else if ([self isZoom])
         isEqual = isEqualOrBothNil([self host], [other host]) && 
                   isEqualOrBothNil([self port], [(BDSKServerInfo *)other 
port]) && 
-                  (isEqualOrBothNil([self options], [(BDSKServerInfo *)other 
options]) || ([[self options] count] == 0 && [[(BDSKServerInfo *)other options] 
count] == 0));
+                  isEqualOrBothEmpty([self optionsWithoutPassword], 
[(BDSKServerInfo *)other optionsWithoutPassword]);
     else if ([self isISI])
-        isEqual = (isEqualOrBothNil([self options], [(BDSKServerInfo *)other 
options]) || ([[self options] count] == 0 && [[(BDSKServerInfo *)other options] 
count] == 0));
+        isEqual = isEqualOrBothEmpty([self optionsWithoutPassword], 
[(BDSKServerInfo *)other optionsWithoutPassword]);
     return isEqual;
 }
 
@@ -184,12 +197,12 @@
     if ([self isZoom]) {
         hash = prime * hash + [[self host] hash];
         hash = prime * hash + [[self port] hash];
-        hash = prime * hash + [[self password] hash];
-        if ([options count])
-            hash = prime * hash + [[self options] hash];
-    } else if ([self isISI] && [options count] > 0) {
-        hash = prime * hash + [[self options] hash];
     }
+    if ([self isZoom] || [self isISI]) {
+        NSDictionary *opts = [self optionsWithoutPassword];
+        if ([opts count])
+            hash = prime * hash + [opts hash];
+    }
     return hash;
 }
 
@@ -201,9 +214,9 @@
     if ([self isZoom]) {
         [info setValue:[self host] forKey:HOST_KEY];
         [info setValue:[self port] forKey:PORT_KEY];
-        [info setValue:[self options] forKey:OPTIONS_KEY];
+        [info setValue:[self optionsWithoutPassword] forKey:OPTIONS_KEY];
     } else if ([self isISI] && [[self options] count] > 0) {
-        [info setValue:[self options] forKey:OPTIONS_KEY];
+        [info setValue:[self optionsWithoutPassword] forKey:OPTIONS_KEY];
     }
     return info;
 }
@@ -212,7 +225,12 @@
 
 - (NSString *)port { return [self isZoom] ? port : nil; }
 
-- (NSString *)password { return [[self options] objectForKey:PASSWORD_KEY]; }
+- (NSString *)password {
+    NSString *password = [[self options] objectForKey:PASSWORD_KEY];
+    if (password == nil && (password = [self passwordFromKeychain]))
+        [options setObject:password forKey:PASSWORD_KEY];
+    return password;
+}
 
 - (NSString *)username { return [[self options] objectForKey:USERNAME_KEY]; }
 
@@ -224,8 +242,16 @@
 
 - (BOOL)isLite { return [[[self options] objectForKey:LITE_KEY] boolValue]; }
 
-- (NSDictionary *)options { return [self isZoom] || [options count] > 0 ? 
[[options copy] autorelease] : nil; }
+- (NSDictionary *)options { return [self isZoom] || [options count] > 0 ? 
options : nil; }
 
+- (NSDictionary *)optionsWithoutPassword {
+    if ([self isZoom] == NO && [options count] == 0)
+        return nil;
+    NSMutableDictionary *opts = [options mutableCopy];
+    [opts removeObjectForKey:PASSWORD_KEY];
+    return opts;
+}
+
 - (BOOL)isEntrez { return [[self type] isEqualToString:BDSKSearchGroupEntrez]; 
}
 - (BOOL)isZoom { return [[self type] isEqualToString:BDSKSearchGroupZoom]; }
 - (BOOL)isISI { return [[self type] isEqualToString:BDSKSearchGroupISI]; }
@@ -244,6 +270,30 @@
     return BDSKServerTypeEntrez;
 }
 
+- (void)savePasswordInKeychain {
+    if ([self isZoom] == NO && [self isISI] == NO)
+        return;
+    // don't get the password from the keychain
+    NSString *password = [[self options] objectForKey:PASSWORD_KEY];
+    if (password == nil)
+        return;
+    NSString *account = [self username];
+    NSString *server = [self isISI] ? ISI_SERVER : [self host];
+    if (account == nil || server == nil)
+        return;
+    [BDSKPasswordController addOrModifyPassword:password 
forKeychainServer:server port:[[self port] integerValue] account:account];
+}
+
+- (NSString *)passwordFromKeychain {
+    if ([self isZoom] == NO && [self isISI] == NO)
+        return nil;
+    NSString *account = [self username];
+    NSString *server = [self isISI] ? ISI_SERVER : [self host];
+    if (account == nil || server == nil)
+        return nil;
+    return [BDSKPasswordController passwordForKeychainServer:server 
port:[[self port] integerValue] account:account];
+}
+
 @end
 
 
@@ -273,6 +323,16 @@
     return set;
 }
 
+- (id)copyWithZone:(NSZone *)zone {
+    [self savePasswordInKeychain];
+    return [super copyWithZone:zone];
+}
+
+- (NSDictionary *)dictionaryValue {
+    [self savePasswordInKeychain];
+    return [super dictionaryValue];
+}
+
 // When changing the type, all data must be properly updated to be valid, 
taking into account the condition implict in the validation methods
 - (void)setType:(NSString *)newType {
     if ([type isEqualToString:newType] == NO) {

Modified: trunk/bibdesk/BDSKZoomGroupServer.m
===================================================================
--- trunk/bibdesk/BDSKZoomGroupServer.m 2022-09-25 06:30:18 UTC (rev 27926)
+++ trunk/bibdesk/BDSKZoomGroupServer.m 2022-09-25 14:57:39 UTC (rev 27927)
@@ -268,10 +268,10 @@
         
         NSSet *specialKeys = [NSSet setWithObjects:@"password", @"username", 
@"recordSyntax", @"resultEncoding", @"removeDiacritics", @"queryConfig", nil];
         
-        for (NSString *key in [info options]) {
+        [[info options] enumerateKeysAndObjectsUsingBlock:^(NSString *key, 
NSString *value, BOOL *stop){
             if ([specialKeys containsObject:key] == NO)
-                [connection setOption:[[info options] objectForKey:key] 
forKey:key];
-        }
+                [connection setOption:value forKey:key];
+        }];
         
         atomic_store(&flags.needsReset, NO);
     }

This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.



_______________________________________________
Bibdesk-commit mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/bibdesk-commit

Reply via email to