Skip to content

Commit

Permalink
Improved the authentication process: It should no longer be necessary…
Browse files Browse the repository at this point in the history
… to log in every time Spotify is relaunched (I was storing the temporary authentication token instead of the permanent session key in the keychain - oops)
  • Loading branch information
georgebrock committed Mar 31, 2009
1 parent 94b87ed commit 94af59a
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 165 deletions.
4 changes: 2 additions & 2 deletions LastifyController.m
Expand Up @@ -126,7 +126,7 @@ - (IBAction)auth:(id)sender
defaultButton:@"Continue"
alternateButton:@"Cancel"
otherButton:nil
informativeTextWithFormat:@"If you click OK the Last.fm website will open so you can authorise Lastify"];
informativeTextWithFormat:@"If you click 'Continue' the Last.fm website will open so you can authorise Lastify"];

if([authAlert runModal] == NSAlertAlternateReturn)
return;
Expand All @@ -145,7 +145,7 @@ - (IBAction)auth:(id)sender
if([authCompleteAlert runModal] == NSAlertAlternateReturn)
return;

[lastfm startNewSession:FALSE];
[lastfm startNewSession];
}
}

Expand Down
2 changes: 1 addition & 1 deletion LastifyLastfmClient.h
Expand Up @@ -35,7 +35,7 @@
- (void)authenticate;
- (void)authenticateQuietly;

- (void)startNewSession:(BOOL)quietly;
- (void)startNewSession;

- (BOOL)loveTrack:(NSString*)trackName byArtist:(NSString*)artistName;
- (BOOL)banTrack:(NSString*)trackName byArtist:(NSString*)artistName;
Expand Down
265 changes: 105 additions & 160 deletions LastifyLastfmClient.m
Expand Up @@ -13,9 +13,8 @@
@interface LastifyLastfmClient (Private)
- (NSString*)requestAuthToken;
- (NSString*)callMethod:(NSString*)methodName withParams:(NSDictionary*)params usingPost:(BOOL)post error:(NSError**)error;
- (NSString*)loadAuthTokenFromKeychain;
- (void)storeAuthTokenInKeychain:(NSString*)newAuthToken;
- (void)removeAuthTokenFromKeychain;
- (void)loadSessionKey;
- (void)storeSessionKey;
@end

@implementation LastifyLastfmClient
Expand Down Expand Up @@ -54,152 +53,43 @@ - (void)dealloc
[super dealloc];
}

- (void)authenticateQuietly
- (void)loadSessionKey
{
NSLog(@"LASTIFY Authenticate quiety");

NSString *loadedAuthToken = [self loadAuthTokenFromKeychain];
if(!loadedAuthToken)
return;

NSLog(@"LASTIFY Loaded an auth token: %@", loadedAuthToken);

self.authToken = loadedAuthToken;
[self startNewSession:TRUE];
}

- (void)authenticate
{
NSString *loadedAuthToken = [self loadAuthTokenFromKeychain];

if(loadedAuthToken)
{
self.authToken = loadedAuthToken;
[self startNewSession:FALSE];
return;
}

// Get a new token
NSString *newAuthToken = [self requestAuthToken];

if(!newAuthToken)
{
//TODO: Handle this
return;
}

// Store the auth token
self.authToken = newAuthToken;
[self storeAuthTokenInKeychain:newAuthToken];

NSLog(@"LASTIFY About to send the user to the Last.fm site to log in");

// Get the user to authorise the token
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://www.last.fm/api/auth/?api_key=%@&token=%@", self.APIKey, newAuthToken ]]];
self.waitingForUserAuth = TRUE;

// The authentication will resume with completeUserAuth when the user has logged in ...
return;
}

- (void)startNewSession:(BOOL)quietly
{
NSError *err = nil;
NSString *response = [self callMethod:@"auth.getSession" withParams:[NSDictionary dictionaryWithObjectsAndKeys:self.authToken, @"token", nil] usingPost:FALSE error:&err];

if(err || !response)
{
switch([err code])
{
case 15: // This token has expired
case 4: // Invalid authentication token supplied
case 14: // This token has not been authorized

[self removeAuthTokenFromKeychain];
if(!quietly)
[self authenticate];

break;

case 2: // Invalid service -This service does not exist
case 3: // Invalid Method - No method with that name in this package
case 5: // Invalid format - This service doesn't exist in that format
case 6: // Invalid parameters - Your request is missing a required parameter
case 7: // Invalid resource specified
case 9: // Invalid session key - Please re-authenticate
case 10: // Invalid API key - You must be granted a valid key by last.fm
case 11: // Service Offline - This service is temporarily offline. Try again later.
case 12: // Subscribers Only - This service is only available to paid last.fm subscribers
default:
break;
}

return;
}

// Extract the username and the session key
NSString *newSessionKey;
NSString *newUsername;
NSScanner *scanner = [NSScanner scannerWithString:response];

[scanner scanUpToString:@"<name>" intoString:NULL];
[scanner scanString:@"<name>" intoString:NULL];
[scanner scanUpToString:@"</name>" intoString:&newUsername];

[scanner scanUpToString:@"<key>" intoString:NULL];
[scanner scanString:@"<key>" intoString:NULL];
[scanner scanUpToString:@"</key>" intoString:&newSessionKey];

if(!newSessionKey)
{
//TODO: Handle this (must be an unexpected error if not caught sooner)
return;
}

self.sessionKey = newSessionKey;
self.username = newUsername;
self.waitingForUserAuth = FALSE;
self.sessionReady = TRUE;
}

- (NSString*)loadAuthTokenFromKeychain
{
NSString *loadedAuthToken = nil;

SecKeychainSearchRef search;
SecKeychainItemRef item;
SecKeychainAttributeList list;
SecKeychainAttribute attributes[3];
OSErr result;

attributes[0].tag = kSecAccountItemAttr;
attributes[0].data = (void*)[self.APIKey UTF8String];
attributes[0].length = [self.APIKey length];

NSString *itemDescription = @"Lastify Last.fm access token";
NSString *itemDescription = @"Lastify Last.fm session information";
attributes[1].tag = kSecDescriptionItemAttr;
attributes[1].data = (void*)[itemDescription UTF8String];
attributes[1].length = [itemDescription length];

NSString *itemLabel = @"Lastify Last.fm access token";
NSString *itemLabel = @"Lastify Last.fm session information";
attributes[2].tag = kSecLabelItemAttr;
attributes[2].data = (void*)[itemLabel UTF8String];
attributes[2].length = [itemLabel length];

list.count = 3;
list.attr = (SecKeychainAttribute*)&attributes;

result = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &list, &search);

if(result != noErr)
return nil;
return;

if(SecKeychainSearchCopyNext(search, &item) == noErr)
{
UInt32 length;
char *password;
OSStatus status;

status = SecKeychainItemCopyContent(item, NULL, NULL, &length, (void **)&password);

if(status == noErr)
Expand All @@ -210,21 +100,30 @@ - (NSString*)loadAuthTokenFromKeychain
strncpy(passwordBuffer, password, length);
passwordBuffer[length] = '\0';

loadedAuthToken = [NSString stringWithUTF8String:passwordBuffer];
NSString *rawLoadedPassword = [NSString stringWithUTF8String:passwordBuffer];
NSArray *parts = [rawLoadedPassword componentsSeparatedByString:@" / "];

if(parts && [parts count] == 2)
{
self.sessionKey = [parts objectAtIndex:0];
self.username = [parts objectAtIndex:1];
self.waitingForUserAuth = FALSE;
self.sessionReady = TRUE;
}
}

SecKeychainItemFreeContent(NULL, password);
}

CFRelease(item);
CFRelease (search);
}

return loadedAuthToken;
}

- (void)storeAuthTokenInKeychain:(NSString*)newAuthToken
- (void)storeSessionKey
{
NSLog(@"In storeSessionKey");

// Create attributes array
SecKeychainAttribute attributes[3];

Expand All @@ -234,64 +133,110 @@ - (void)storeAuthTokenInKeychain:(NSString*)newAuthToken
attributes[0].length = [self.APIKey length];

// Set the description
NSString *itemDescription = @"Lastify Last.fm access token";
NSString *itemDescription = @"Lastify Last.fm session information";
attributes[1].tag = kSecDescriptionItemAttr;
attributes[1].data = (void*)[itemDescription UTF8String];
attributes[1].length = [itemDescription length];

// Label the item
NSString *itemLabel = @"Lastify Last.fm access token";
NSString *itemLabel = @"Lastify Last.fm session information";
attributes[2].tag = kSecLabelItemAttr;
attributes[2].data = (void*)[itemLabel UTF8String];
attributes[2].length = [itemLabel length];

// Create list from attributes array
SecKeychainAttributeList list;
list.count = 3;
list.attr = attributes;

// Store the password
SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &list, [self.authToken length], [self.authToken UTF8String], NULL,NULL,NULL);
NSString *password = [NSString stringWithFormat:@"%@ / %@", self.sessionKey, self.username];
SecKeychainItemCreateFromContent(kSecGenericPasswordItemClass, &list, [password length], [password UTF8String], NULL,NULL,NULL);
}

- (void)removeAuthTokenFromKeychain
- (void)authenticateQuietly
{
SecKeychainSearchRef search;
SecKeychainItemRef item;
SecKeychainAttributeList list;
SecKeychainAttribute attributes[3];
OSErr result;
[self loadSessionKey];
}

attributes[0].tag = kSecAccountItemAttr;
attributes[0].data = (void*)[self.APIKey UTF8String];
attributes[0].length = [self.APIKey length];

NSString *itemDescription = @"Lastify Last.fm access token";
attributes[1].tag = kSecDescriptionItemAttr;
attributes[1].data = (void*)[itemDescription UTF8String];
attributes[1].length = [itemDescription length];
- (void)authenticate
{
// Attempt to authenticate without user interaction
[self authenticateQuietly];
if(self.sessionKey)
return;

NSString *itemLabel = @"Lastify Last.fm access token";
attributes[2].tag = kSecLabelItemAttr;
attributes[2].data = (void*)[itemLabel UTF8String];
attributes[2].length = [itemLabel length];

list.count = 3;
list.attr = (SecKeychainAttribute*)&attributes;
// We need the user to authenticate
NSString *newAuthToken = [self requestAuthToken];

if(!newAuthToken)
{
//TODO: Present an error to the user
return;
}

self.authToken = newAuthToken;

// Get the user to authorise the token
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://www.last.fm/api/auth/?api_key=%@&token=%@", self.APIKey, newAuthToken]]];
self.waitingForUserAuth = TRUE;

// The authentication will resume with startNewSession when the user has logged in ...
}

result = SecKeychainSearchCreateFromAttributes(NULL, kSecGenericPasswordItemClass, &list, &search);
- (void)startNewSession
{
NSError *err = nil;
NSString *response = [self callMethod:@"auth.getSession" withParams:[NSDictionary dictionaryWithObjectsAndKeys:self.authToken, @"token", nil] usingPost:FALSE error:&err];

if(result != noErr)
if(err || !response)
{
switch([err code])
{
case 15: // This token has expired
case 4: // Invalid authentication token supplied
case 14: // This token has not been authorized
case 2: // Invalid service -This service does not exist
case 3: // Invalid Method - No method with that name in this package
case 5: // Invalid format - This service doesn't exist in that format
case 6: // Invalid parameters - Your request is missing a required parameter
case 7: // Invalid resource specified
case 9: // Invalid session key - Please re-authenticate
case 10: // Invalid API key - You must be granted a valid key by last.fm
case 11: // Service Offline - This service is temporarily offline. Try again later.
case 12: // Subscribers Only - This service is only available to paid last.fm subscribers
default:
break;
}

return;
}

// Extract the username and the session key
NSString *newSessionKey;
NSString *newUsername;
NSScanner *scanner = [NSScanner scannerWithString:response];

if(SecKeychainSearchCopyNext(search, &item) == noErr)
[scanner scanUpToString:@"<name>" intoString:NULL];
[scanner scanString:@"<name>" intoString:NULL];
[scanner scanUpToString:@"</name>" intoString:&newUsername];

[scanner scanUpToString:@"<key>" intoString:NULL];
[scanner scanString:@"<key>" intoString:NULL];
[scanner scanUpToString:@"</key>" intoString:&newSessionKey];

if(!newSessionKey)
{
SecKeychainItemDelete(item);
CFRelease(item);
//TODO: Handle this (must be an unexpected error if not caught sooner)
return;
}

CFRelease(search);

self.sessionKey = newSessionKey;
self.username = newUsername;
self.waitingForUserAuth = FALSE;
self.sessionReady = TRUE;

[self storeSessionKey];
}

- (NSString*)requestAuthToken
Expand Down
4 changes: 2 additions & 2 deletions build/Release/Lastify.bundle.dSYM/Contents/Info.plist
Expand Up @@ -19,9 +19,9 @@
<key>dSYM_UUID</key>
<dict>
<key>ppc</key>
<string>43692AFC-D363-3F52-939D-CD466FCDE35A</string>
<string>6FF53252-B1CD-E29E-A754-FD448F768E77</string>
<key>i386</key>
<string>0D43B2D7-EDB1-E937-C103-AB228663323E</string>
<string>FAA52792-F30B-0B85-033C-2A7AF85ACC37</string>
</dict>
</dict>
</plist>
Binary file not shown.
Binary file modified build/Release/Lastify.bundle/Contents/MacOS/Lastify
Binary file not shown.

0 comments on commit 94af59a

Please sign in to comment.