New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cannot update an existing record in the keychain #103
Conversation
…o with which object was storing, which would have been insanely annoying. Happily that's not the case.
❤️❤️❤️ love a bug report with a good test case—thanks! These are the failures I'm getting here—let me know if you're seeing something different I might be misinterpreting here, but I think the library is behaving as intended in this case (though maybe how it works should change...). When The keychain just looks uses the values of the It's possible to extend CreateableSecureStorable with an update method (or you can put this on your extension CreateableSecureStorable where Self : DeleteableSecureStorable {
func replaceData() throws {
do { try self.deleteFromSecureStore() } catch {} // Ignore any errors
try self.createInSecureStore()
}
} Let me know if I've misunderstood/misinterpreted and I can take another look :) Side note (would love to hear your thoughts on this, but feel free to skip if you don't have time to answer): You are definitely not the first person who has expected that the
I'm super interested in the second point because you're a new Swift dev and I want to try to make Swift stuff as accessible to everyone as possible—what have you found confusing about Swift, Locksmith and its documentation in general? (Also worth mentioning if you weren't aware that Locksmith probably isn't the easiest library to work because it's "protocol-oriented", which is a really new (and cool) idea that I wanted to share. It's certainly not the be all and end all of keychain libraries. Maybe I should add a note on that to the docs too?) Thanks so much for taking the time to drop in the detailed test case and issue report! |
Very quickly: yes, those are the same test failures I'm getting. Will now write a longer answer about the rest. |
The protocol-oriented bit didn't bother me, actually - if anything, it makes me happy about using a library that's probably over-engineered for me at the moment because I suspect I'll be able to carry on using it for more complicated things in the future without getting fed up at limitations. But then again, at $WORK we use Moose Roles a lot, which are basically the same thing (although Perl being a dynamic language means you can do funkier things with OO). |
I mentioned CRUD (Create, Read, Update, Delete), which is a standard idiom these days, and having gone back to the documentation one thing that stands out is that it only mentions three things:
// CreateableSecureStorable lets us create the account in the keychain
try account.createInSecureStore()
// ReadableSecureStorable lets us read the account from the keychain
let result = account.readFromSecureStore()
// DeleteableSecureStorable lets us delete the account from the keychain
try account.deleteFromSecureStore() I think the documentation should mention how to update an existing record, even if by saying that it's a Keychain-ism that you can't update a record in place, you have to delete it and create it anew instead. Also, as I think I mentioned in another issue, it's really confusing that the example code talks about creating an account in the keychain, then reading it from the object that we just created. If nothing else, in the sample code above it should use a different variable name from account, e.g. let accountAgain = TwitterAccount()
let result = accountAgain.readFromSecureStore() |
Finally, I think some sort of explicit method would be useful, if nothing else because I suspect that there could be annoying race conditions involving threading if something happens between the delete and the create. This is the sort of really annoying intermittent problem that, if it ever happens, is best dealt with at the library level, rather than everyone having to find the problem for themselves. Also, because people used to the CRUD metaphor expect an explicit update method. I'm happy to roll my own for now, though. |
There is a //try to set a value
if (![SSKeychain setPassword:pwd forService:svc account:@"test" error:&error] || error)
{
[SSKeychain updatePassword:@"" forService:svc account:@"test"error:&error];
[SSKeychain updatePassword:pwd forService:svc account:@"test"error:&error];
} I made a patch to SSKeychain.m: + (BOOL)updatePassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account error:(NSError **)error
{
NSData *data = [password dataUsingEncoding:NSUTF8StringEncoding];
return [self updatePasswordData:data forService:serviceName account:account error:error];
}
+ (BOOL)updatePasswordData:(NSData *)password forService:(NSString *)serviceName account:(NSString *)account error:(NSError **)error
{
OSStatus status = SSKeychainErrorBadArguments;
if (password && serviceName && account) {
NSMutableDictionary *query = [self _queryForService:serviceName account:account];
[query setObject:password forKey:(__bridge id)kSecValueData];
#if __IPHONE_4_0 && TARGET_OS_IPHONE
NSDictionary *update = @{(__bridge id)kSecValueData:password};
if (SSKeychainAccessibilityType) {
[query setObject:(id)[self accessibilityType] forKey:(__bridge id)kSecAttrAccessible];
update = @{(__bridge id)kSecValueData:password,
(__bridge id)kSecAttrAccessible:(id)[self accessibilityType]};
}
#else
NSDictionary *update = @{(__bridge id)kSecValueData:password};
#endif
status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update);
}
if (status != noErr && error != NULL) {
*error = [NSError errorWithDomain:kSSKeychainErrorDomain code:status userInfo:nil];
}
return (status == noErr);
} You, probably, could use the same technique. Couldn't post a PR to SSKeychain with this fix, but it works for me in my objective-с projects, now I would like to Locksmith to have this behavior too =) |
Closing because I think this is fixed by @Sega-Zero in #106 :D |
This isn't actually a pull request, as (a) there's a lot of code duplication in the tests, and (b) the tests currently fail! But it was the easiest way that occurred to me of bundling up the code I have currently that's failing.
It naïvely appears to be a recurrence of the very first issue in this project, but I have absolutely no experience in debugging Swift so I couldn't say.
For a Secret Work Project, we need an app that logs into our web service, and we need to remember those login details. So GenericPasswordSecureStorable looked like the easiest way of implementing this, albeit with a constant
account
value as we'd only ever store one set of user details.As you'll hopefully see when running the tests, the C, R and D part of CRUD work fine; but when I try to update the details, I get an exception. And I get that whether I update the original object or I try to store a new one (which is good - if it had varied between which object was trying to store stuff that would have been the stuff of nightmares).
I'm new to Swift (normally I'm a Perl programmer), so some of the code might be unidiomatic, and hopefully the fix is to say "why on Earth are you doing that‽", update the documentation, and tell me not to be such a fool. But in case it actually is an issue in the guts of something horrible, you have a test method to run now.