Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
373 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// | ||
// RCOAuth2Engine.h | ||
// | ||
// Created by Ruiwen Chua on 5/16/12. | ||
// Copyright (c) 2012 Ruiwen Chua. MIT Licensed. | ||
// | ||
|
||
#import "MKNetworkEngine.h" | ||
#import "KeychainItemWrapper.h" | ||
|
||
typedef enum _RCOAuth2GrantType | ||
{ | ||
none, | ||
authorization_code, | ||
|
||
} RCOAuth2GrantType; | ||
|
||
typedef enum _RCOAuth2Responsetype | ||
{ | ||
token, | ||
code, | ||
|
||
} RCOAuth2ResponseType; | ||
|
||
|
||
typedef void (^RCOAuth2CompletionBlock)(NSError *error); | ||
|
||
@interface RCOAuth2Engine : MKNetworkEngine | ||
{ | ||
RCOAuth2CompletionBlock _oAuthCompletionBlock; | ||
|
||
@private | ||
KeychainItemWrapper *_keychainWrapper; | ||
NSMutableDictionary *_tokens; | ||
NSString *_hostname; | ||
NSString *_authPath; | ||
NSString *_tokenPath; | ||
NSString *_redirect; | ||
} | ||
|
||
@property (readonly) NSString *clientId; | ||
@property (readonly) NSString *clientSecret; | ||
|
||
- (RCOAuth2Engine *)initWithHostname:(NSString *)hostname | ||
customHeaderFields:(NSDictionary *)headers | ||
clientId:(NSString *)clientId | ||
clientSecret:(NSString *)clientSecret | ||
authPath:(NSString *)authPath | ||
tokenPath:(NSString *)tokenPath | ||
redirectURI:(NSString *)redirect; | ||
|
||
|
||
- (BOOL)isAuthenticated; | ||
- (void)authenticateWithCompletionBlock:(RCOAuth2CompletionBlock)completionBlock; | ||
- (void)completeOAuthWithCode:(NSString *)code; | ||
- (void)parseOAuth2Query:(NSURL *)url; | ||
- (void)resetAuth; | ||
|
||
@end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,313 @@ | ||
// | ||
// RCOAuth2Engine.m | ||
// | ||
// Created by Ruiwen Chua on 5/16/12. | ||
// Copyright (c) 2012 Ruiwen Chua. MIT Licensed | ||
// | ||
|
||
#import "RCOAuth2Engine.h" | ||
|
||
#define oAuthKeys [NSArray arrayWithObjects:@"access_token", @"expires_in", @"refresh_token", @"scope", nil] | ||
|
||
@interface RCOAuth2Engine () | ||
|
||
- (void)setAuthPath:(NSString *)authPath; | ||
- (void)setTokenPath:(NSString *)tokenPath; | ||
- (void)removeOAuthTokenFromKeychain; | ||
- (void)storeOAuthTokenInKeychain; | ||
- (void)retrieveOAuthTokenFromKeychain; | ||
|
||
@end | ||
|
||
|
||
@implementation RCOAuth2Engine | ||
|
||
- (NSString *)hostname { | ||
return (_hostname) ? _hostname : @""; | ||
} | ||
|
||
- (NSString *)authPath { | ||
return (_authPath) ? _authPath : @""; | ||
} | ||
|
||
- (NSString *)tokenPath { | ||
return (_tokenPath) ? _tokenPath : @""; | ||
} | ||
|
||
- (NSString *)redirect { | ||
return (_redirect) ? _redirect : @""; | ||
} | ||
|
||
- (NSString *)clientId { | ||
return (_tokens) ? [_tokens objectForKey:@"clientId"] : @""; | ||
} | ||
|
||
- (NSString *)clientSecret { | ||
return (_tokens) ? [_tokens objectForKey:@"clientSecret"] : @""; | ||
} | ||
|
||
- (void)setClientId:(NSString *)clientId { | ||
if (_tokens) { | ||
[_tokens setObject:clientId forKey:@"clientId"]; | ||
} | ||
} | ||
|
||
- (void)setClientSecret:(NSString *)clientSecret { | ||
if(_tokens) { | ||
[_tokens setObject:clientSecret forKey:@"clientSecret"]; | ||
} | ||
} | ||
|
||
|
||
- (void)setAuthPath:(NSString *)authPath { | ||
_authPath = authPath; | ||
} | ||
|
||
- (void)setTokenPath:(NSString *)tokenPath { | ||
_tokenPath = tokenPath; | ||
} | ||
|
||
|
||
- (RCOAuth2Engine *)initWithHostname:(NSString *)hostname customHeaderFields:(NSDictionary *)headers clientId:(NSString *)clientId clientSecret:(NSString *)clientSecret authPath:(NSString *)authPath tokenPath:(NSString *)tokenPath redirectURI:(NSString *)redirect { | ||
|
||
self = [super initWithHostName:hostname customHeaderFields:headers]; | ||
|
||
if (self) { | ||
|
||
NSLog(@"Begin init setup"); | ||
|
||
_keychainWrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"RememberApp" accessGroup:nil]; | ||
_tokens = [[NSMutableDictionary alloc] init]; | ||
|
||
|
||
[self setClientId:clientId]; | ||
[self setClientSecret:clientSecret]; | ||
|
||
_hostname = hostname; | ||
_authPath = authPath; | ||
_tokenPath = tokenPath; | ||
_redirect = redirect; | ||
|
||
// Retrieve tokens from Keychain if possible | ||
NSLog(@"Retrieving"); | ||
// NSMutableDictionary *t = [_keychainWrapper objectForKey:(__bridge id)kSecAttrAccount]; | ||
// if(t && [t isKindOfClass:[NSDictionary class]]) { | ||
// NSLog(@"Haz token: %@", t); | ||
// _tokens = t; | ||
// } | ||
[self retrieveOAuthTokenFromKeychain]; | ||
|
||
NSLog(@"Init tokens: %@", _tokens); | ||
|
||
} | ||
|
||
return self; | ||
} | ||
|
||
- (BOOL)isAuthenticated { | ||
NSLog(@"isAuthenticated"); | ||
NSLog(@"%@", _tokens); | ||
return [_tokens objectForKey:@"access_token"] != nil; | ||
} | ||
|
||
|
||
- (void)authenticateWithCompletionBlock:(RCOAuth2CompletionBlock)completionBlock { | ||
|
||
// Store the Completion Block to call after authentication | ||
_oAuthCompletionBlock = completionBlock; | ||
|
||
// Begin OAuth2 | ||
NSLog(@"Begin the OAuth!"); | ||
// Build the params | ||
NSMutableDictionary *p = [NSMutableDictionary dictionaryWithObjectsAndKeys: | ||
@"code", @"response_type", | ||
self.clientId, @"client_id", | ||
self.redirect, @"redirect_uri", | ||
@"read", @"scope", | ||
@"write", @"scope", | ||
nil]; | ||
|
||
MKNetworkOperation *op = [self operationWithPath:self.authPath | ||
params:p | ||
httpMethod:@"GET"]; | ||
|
||
[op onCompletion:^(MKNetworkOperation *completedOperation) { | ||
NSLog(@"Step one complete"); | ||
|
||
NSURL *url = [NSURL URLWithString:[completedOperation url]]; | ||
|
||
if(url && [[url host] isEqualToString:self.hostname]) { | ||
NSLog(@"%@", url); | ||
[[UIApplication sharedApplication] openURL:url]; // Open the URL in Safari | ||
} | ||
else { | ||
//NSLog(@"Headers: %@", headers); | ||
} | ||
|
||
} onError:^(NSError *error) { | ||
NSLog(@"Error"); | ||
}]; | ||
|
||
[self enqueueOperation:op]; | ||
} | ||
|
||
- (void)completeOAuthWithCode:(NSString *)code { | ||
NSLog(@"completeOAuth: %@", code); | ||
|
||
NSMutableDictionary *p = [NSMutableDictionary dictionaryWithObjectsAndKeys: | ||
@"authorization_code", @"grant_type", | ||
self.clientSecret, @"client_secret", | ||
self.clientId, @"client_id", | ||
code, @"code", | ||
self.redirect, @"redirect_uri", | ||
nil]; | ||
|
||
MKNetworkOperation *op = [self operationWithPath:self.tokenPath | ||
params:p | ||
httpMethod:@"POST"]; | ||
|
||
|
||
[op onCompletion:^(MKNetworkOperation *completedOperation) { | ||
NSLog(@"Step Two complete"); | ||
|
||
// Process the data | ||
NSLog(@"Data: %@", [completedOperation responseJSON]); | ||
NSDictionary *data =[completedOperation responseJSON]; | ||
|
||
//NSArray *keys = [NSArray arrayWithObjects:@"access_token", @"expires_in", @"refresh_token", @"scope", nil]; | ||
for (NSString *k in oAuthKeys) { | ||
[_tokens setObject:[data objectForKey:k] forKey:k]; | ||
} | ||
|
||
NSLog(@"Tokens: %@", _tokens); | ||
|
||
// Store them in the Keychain | ||
// [_keychainWrapper setObject:[self clientId] forKey:(__bridge id)kSecAttrAccount]; | ||
// [_keychainWrapper setObject:_tokens forKey:(__bridge id)kSecValueData]; | ||
[self storeOAuthTokenInKeychain]; | ||
|
||
// Complete the callback from earlier | ||
if (_oAuthCompletionBlock) { | ||
NSLog(@"Sending.."); | ||
_oAuthCompletionBlock(nil); | ||
} | ||
|
||
} onError:^(NSError *error) { | ||
NSLog(@"Step Two Error"); | ||
}]; | ||
|
||
[self enqueueOperation:op]; | ||
} | ||
|
||
- (void)parseOAuth2Query:(NSURL *)url { | ||
NSArray *query = [url.query componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"&="]]; | ||
|
||
NSLog(@"Query: %@", query); | ||
|
||
// Extract the code from the return URL | ||
NSString *code = [query objectAtIndex:[query indexOfObject:@"code"]+1]; | ||
NSLog(@"Code: %@", code); | ||
|
||
[self completeOAuthWithCode:code]; | ||
} | ||
|
||
|
||
- (void)resetAuth { | ||
// Clear the items in memory | ||
for(NSString *k in oAuthKeys) { | ||
[_tokens removeObjectForKey:k]; | ||
} | ||
|
||
// Clear the Keychain | ||
[_keychainWrapper resetKeychainItem]; | ||
} | ||
|
||
#pragma mark - OAuth Access Token store/retrieve, borrowed from https://github.com/rsieiro/RSOAuthEngine | ||
|
||
- (void)removeOAuthTokenFromKeychain | ||
{ | ||
// Build the keychain query | ||
NSMutableDictionary *keychainQuery = [NSMutableDictionary dictionaryWithObjectsAndKeys: | ||
(__bridge_transfer NSString *)kSecClassGenericPassword, (__bridge_transfer NSString *)kSecClass, | ||
[self clientId], kSecAttrService, | ||
[self clientId], kSecAttrAccount, | ||
kCFBooleanTrue, kSecReturnAttributes, | ||
nil]; | ||
|
||
// If there's a token stored for this user, delete it | ||
CFDictionaryRef query = (__bridge_retained CFDictionaryRef) keychainQuery; | ||
SecItemDelete(query); | ||
CFRelease(query); | ||
} | ||
|
||
- (void)storeOAuthTokenInKeychain | ||
{ | ||
// Build the keychain query | ||
NSMutableDictionary *keychainQuery = [NSMutableDictionary dictionaryWithObjectsAndKeys: | ||
(__bridge_transfer NSString *)kSecClassGenericPassword, (__bridge_transfer NSString *)kSecClass, | ||
[self clientId], kSecAttrService, | ||
[self clientId], kSecAttrAccount, | ||
kCFBooleanTrue, kSecReturnAttributes, | ||
nil]; | ||
|
||
CFTypeRef resData = NULL; | ||
|
||
// If there's a token stored for this user, delete it first | ||
CFDictionaryRef query = (__bridge_retained CFDictionaryRef) keychainQuery; | ||
SecItemDelete(query); | ||
CFRelease(query); | ||
|
||
// Build the token dictionary | ||
/*NSMutableDictionary *tokenDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys: | ||
self.token, @"oauth_token", | ||
self.tokenSecret, @"oauth_token_secret", | ||
//self.screenName, @"screen_name", | ||
nil]; | ||
*/ | ||
|
||
// Add the token dictionary to the query | ||
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:_tokens] | ||
forKey:(__bridge_transfer NSString *)kSecValueData]; | ||
|
||
// Add the token data to the keychain | ||
// Even if we never use resData, replacing with NULL in the call throws EXC_BAD_ACCESS | ||
query = (__bridge_retained CFDictionaryRef) keychainQuery; | ||
SecItemAdd(query, (CFTypeRef *) &resData); | ||
CFRelease(query); | ||
} | ||
|
||
- (void)retrieveOAuthTokenFromKeychain | ||
{ | ||
// Build the keychain query | ||
NSMutableDictionary *keychainQuery = [NSMutableDictionary dictionaryWithObjectsAndKeys: | ||
(__bridge_transfer NSString *)kSecClassGenericPassword, (__bridge_transfer NSString *)kSecClass, | ||
[self clientId], kSecAttrService, | ||
[self clientId], kSecAttrAccount, | ||
kCFBooleanTrue, kSecReturnData, | ||
kSecMatchLimitOne, kSecMatchLimit, | ||
nil]; | ||
|
||
// Get the token data from the keychain | ||
CFTypeRef resData = NULL; | ||
|
||
// Get the token dictionary from the keychain | ||
CFDictionaryRef query = (__bridge_retained CFDictionaryRef) keychainQuery; | ||
|
||
if (SecItemCopyMatching(query, (CFTypeRef *) &resData) == noErr) | ||
{ | ||
NSData *resultData = (__bridge_transfer NSData *)resData; | ||
|
||
if (resultData) | ||
{ | ||
NSMutableDictionary *tokenDictionary = [NSKeyedUnarchiver unarchiveObjectWithData:resultData]; | ||
|
||
if (tokenDictionary) { | ||
_tokens = tokenDictionary; | ||
} | ||
} | ||
} | ||
|
||
CFRelease(query); | ||
} | ||
|
||
@end |