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