Jasonette/JASONETTE-iOS

View on GitHub
app/Jasonette/JasonOauthAction.m

Summary

Maintainability
Test Coverage
//
//  JasonOauthAction.m
//  Jasonette
//
//  Copyright © 2016 gliechtenstein. All rights reserved.
//
#import "JasonOauthAction.h"
#import <TDOAuth/TDOAuth.h>
#import <Accounts/Accounts.h>
#import <Social/Social.h>

@implementation JasonOauthAction
- (void)performSocialFrameworkRequestFor: (ACAccount *)account{
    
    NSString *path = self.options[@"path"];
    NSString *host = self.options[@"host"];
    NSString *scheme = self.options[@"scheme"];
    NSString *method = self.options[@"method"];
    if(!method) method = @"get";
    
    NSString *accname;
    if([host containsString:@"twitter"]){
            accname = @"twitter";
    } else if([host containsString:@"facebook"]){
            accname = @"facebook";
        
    } else if([host containsString:@"sina"]){
            accname = @"sina";
        
    } else if([host containsString:@"qq"]){
            accname = @"tencent";
    }
    
    NSDictionary *serviceMapping = @{@"twitter": SLServiceTypeTwitter, @"facebook": SLServiceTypeFacebook, @"sina": SLServiceTypeSinaWeibo, @"tencent": SLServiceTypeTencentWeibo};
    NSDictionary *methodMapping = @{@"get": @(SLRequestMethodGET), @"post": @(SLRequestMethodPOST), @"put": @(SLRequestMethodPUT), @"delete": @(SLRequestMethodDELETE)};
    NSDictionary *params= self.options[@"data"];
   
    // preprocess to make sure host and path merge regardless of whether theres's a slash or not
    if([host hasSuffix:@"/"]){
        if([path hasPrefix:@"/"]){
            path = [path substringFromIndex:1];
        } else {
            path = path;
        }
    } else {
        if([path hasPrefix:@"/"]){
            path = path;
        } else {
            path = [NSString stringWithFormat:@"/%@", path];
        }
    }
    
     NSString *urlString = [NSString stringWithFormat:@"%@://%@%@", scheme, host, path];
     NSURL *requestURL = [NSURL URLWithString: urlString];
     
     SLRequest *request = [SLRequest requestForServiceType:serviceMapping[accname] requestMethod:[methodMapping[method] integerValue] URL:requestURL parameters:params];
     
     request.account = account;
     [request performRequestWithHandler: ^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
        // Ignore if the url is different
        if(![[[request.preparedURLRequest URL] absoluteString] isEqualToString:urlResponse.URL.absoluteString]) return;
         if(error){
             [[Jason client] error];
         } else {
             NSDictionary *result = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableLeaves error:&error];
             [[Jason client] success: result];
         }
      }];


}
- (void)request{
    NSString *host = self.options[@"host"];
    NSString *client_id = self.options[@"client_id"];
    if(self.options[@"version"] && [self.options[@"version"] isEqualToString:@"0"]){
        ACAccountStore *account = [[ACAccountStore alloc] init];
        NSDictionary *accountMapping = @{@"twitter": ACAccountTypeIdentifierTwitter, @"facebook": ACAccountTypeIdentifierFacebook, @"sina": ACAccountTypeIdentifierSinaWeibo, @"tencent": ACAccountTypeIdentifierTencentWeibo};
        NSString *accname;
        if([host containsString:@"twitter"]){
                accname = @"twitter";
        } else if([host containsString:@"facebook"]){
                accname = @"facebook";
            
        } else if([host containsString:@"sina"]){
                accname = @"sina";
            
        } else if([host containsString:@"qq"]){
                accname = @"tencent";
        }
        
        
        if([host containsString:@"twitter"] || [host containsString:@"facebook"] || [host containsString:@"sina"] || [host containsString:@"qq"]){
            
            
            NSString *method = self.options[@"method"];
            if(!method) method = @"get";
            
            ACAccountType *accountType = [account accountTypeWithAccountTypeIdentifier:accountMapping[accname]];
            NSDictionary *options = nil;
            
            
            if([host containsString:@"facebook"]){
                options = @{
                    ACFacebookAppIdKey: client_id,
                    //ACFacebookPermissionsKey: @[@"user_friends", @"email"],
                    ACFacebookAudienceKey: ACFacebookAudienceFriends
                };
            }
            
            // For socialframework with twitter, use the host as client_id
            if([host containsString:@"twitter"]){
                client_id = host;
            }
            
            
            // IF already signed in, just use the already stored account from keychain
            UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:@"$socialframework"];
            NSString *account_identifier = [keychain stringForKey:client_id];
            if(account_identifier && account_identifier.length > 0){
                ACAccount *a = [account accountWithIdentifier:account_identifier];
                 [self performSocialFrameworkRequestFor: a];
            } else {
                
                [account requestAccessToAccountsWithType:accountType options:options completion:^(BOOL granted, NSError *error) {
                     if (granted == YES) {
                         NSArray *arrayOfAccounts = [account accountsWithAccountType:accountType];
                         if ([arrayOfAccounts count] > 0) {
                            dispatch_async(dispatch_get_main_queue(), ^{
                                AHKActionSheet *actionSheet = [[AHKActionSheet alloc] initWithTitle:nil];
                                for(int i = 0 ; i < arrayOfAccounts.count ; i++){
                                    ACAccount *a = arrayOfAccounts[i];
                                    [actionSheet addButtonWithTitle:a.username type:AHKActionSheetButtonTypeDefault handler:^(AHKActionSheet *as) {
                                        dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
                                            UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:@"$socialframework"];
                                            [keychain setString:a.identifier forKey:client_id];
                                             [self performSocialFrameworkRequestFor: a];
                                        });
                                    }];
                                }
                                actionSheet.title = @"Select an account";
                                actionSheet.blurTintColor = [UIColor colorWithWhite:1.0f alpha:0.75f];
                                actionSheet.blurRadius = 8.0f;
                                actionSheet.buttonHeight = 45.0f;

                                actionSheet.animationDuration = 0.2f;
                                UIFont *defaultFont = [UIFont fontWithName:@"HelveticaNeue" size:16.0f];
                                actionSheet.buttonTextAttributes = @{ NSFontAttributeName : defaultFont,
                                                                      NSForegroundColorAttributeName : [UIColor blackColor] };
                                actionSheet.disabledButtonTextAttributes = @{ NSFontAttributeName : defaultFont,
                                                                              NSForegroundColorAttributeName : [UIColor grayColor] };
                                actionSheet.destructiveButtonTextAttributes = @{ NSFontAttributeName : defaultFont,
                                                                                 NSForegroundColorAttributeName : [UIColor redColor] };
                                actionSheet.cancelButtonTextAttributes = @{ NSFontAttributeName : defaultFont,
                                                                            NSForegroundColorAttributeName : [UIColor blackColor] };
                                [actionSheet show];
                                
                            });
                         } else {
                             [[Jason client] error];
                         }
                     } else {
                         [[Jason client] error];
                     }
                 }];
            }
        } else {
            [[Jason client] error];
        }
        
    } else if(self.options[@"version"] && [self.options[@"version"] isEqualToString:@"1"]){
        
        /*********************************************************
         * OAuth 1 flow
         *********************************************************/
        
        
        NSString *APP_NAME = [[NSBundle mainBundle] bundleIdentifier];
        
        UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:[NSString stringWithFormat:@"%@", APP_NAME]];
        NSString *token = [keychain stringForKey:[NSString stringWithFormat:@"%@#token", client_id]];
        if(token){
            dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
                [self _process];
            });
        } else {
            [[Jason client] error];
        }
    } else {
        /*********************************************************
         * OAuth 2 flow
         *********************************************************/
        AFOAuthCredential *credential = [AFOAuthCredential retrieveCredentialWithIdentifier:client_id];
        
        if(credential){
            [self _process];
        } else {
            [[Jason client] error];
        }
    }
}
- (void)reset{
    /* 
     Signing out

     EXAMPLE:
     
    "action": {
        "type": "$oauth.reset",
        "options": {
            "version": "1",
            "client_id": "{{$keys.key}}"
        },
        "success": {
            "type": "$reload"
        }
    }
    */
    if(self.options){
        NSString *client_id = self.options[@"client_id"];
        if(self.options[@"version"] && [self.options[@"version"] isEqualToString:@"0"]){
            // For social framework, set the client_id: "#{host}"
            client_id = self.options[@"host"];
            UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:@"$socialframework"];
            [keychain removeItemForKey:client_id];
            [[Jason client] success];
        } else if(self.options[@"version"] && [self.options[@"version"] isEqualToString:@"1"]){
            
            NSString *APP_NAME = [[NSBundle mainBundle] bundleIdentifier];
            
            UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:[NSString stringWithFormat:@"%@", APP_NAME]];
            [keychain removeItemForKey:[NSString stringWithFormat:@"%@#token", client_id]];
            [keychain removeItemForKey:[NSString stringWithFormat:@"%@#secret", client_id]];
            [[Jason client] success];
        } else if(self.options[@"version"] && [self.options[@"version"] isEqualToString:@"2"]){
            
            [AFOAuthCredential deleteCredentialWithIdentifier:client_id];
            [[Jason client] success];
        } else {
            [AFOAuthCredential deleteCredentialWithIdentifier:client_id];
            [[Jason client] success];
            
        }
        return;
    }
    [[Jason client] error];
}
- (void)_process{
    NSString *client_id = self.options[@"client_id"];
    NSString *client_secret = self.options[@"client_secret"];
    NSString *path = self.options[@"path"];
    NSString *host = self.options[@"host"];
    NSString *scheme = self.options[@"scheme"];
    if(!scheme) scheme = @"https";
    NSString *method = self.options[@"method"];
    if(!method) method = @"get";
    NSDictionary *params= self.options[@"data"];
    NSDictionary *header = self.options[@"header"];
    if(self.options[@"version"] && [self.options[@"version"] isEqualToString:@"1"]){
        /*********************************************************
         * OAuth 1 flow
         *********************************************************/
        
        
        NSString *APP_NAME = [[NSBundle mainBundle] bundleIdentifier];
        
        UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:[NSString stringWithFormat:@"%@", APP_NAME]];
        NSString *access_token = [keychain stringForKey:[NSString stringWithFormat:@"%@#token", client_id]];
        NSString *access_secret = [keychain stringForKey:[NSString stringWithFormat:@"%@#secret", client_id]];
        
        NSMutableURLRequest *request ;
        request = [[TDOAuth URLRequestForPath:path
                               parameters:params
                                     host:host
                              consumerKey:client_id
                           consumerSecret:client_secret
                              accessToken:access_token
                              tokenSecret:access_secret
                                   scheme:scheme
                            requestMethod:method.uppercaseString
                             dataEncoding:TDOAuthContentTypeUrlEncodedForm
                             headerValues:nil
                          signatureMethod:TDOAuthSignatureMethodHmacSha1] mutableCopy];
        
        if(request){
            
            AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
            
            AFJSONResponseSerializer *jsonResponseSerializer = [AFJSONResponseSerializer serializer];
            NSMutableSet *jsonAcceptableContentTypes = [NSMutableSet setWithSet:jsonResponseSerializer.acceptableContentTypes];
            [jsonAcceptableContentTypes addObject:@"text/plain"];
            jsonResponseSerializer.acceptableContentTypes = jsonAcceptableContentTypes;
            [manager setResponseSerializer:jsonResponseSerializer];
            
            for(NSString *key in header){
                [request setValue:header[key] forHTTPHeaderField:key];
            }
            [[manager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                if(!error){
                    [[Jason client] success:responseObject];
                } else {
                    [[NSNotificationCenter defaultCenter] removeObserver:self];
                    [[Jason client] error];
                }
            }] resume];
            
        }
    } else {
        /*********************************************************
         * OAuth 2 flow
         *********************************************************/
        
        NSURL *baseURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@", scheme, host]];
        AFOAuthCredential *credential = [AFOAuthCredential retrieveCredentialWithIdentifier:client_id];

        AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL];
        
        manager.responseSerializer = [AFJSONResponseSerializer serializer];
        AFJSONResponseSerializer *jsonResponseSerializer = [AFJSONResponseSerializer serializer];
        NSMutableSet *jsonAcceptableContentTypes = [NSMutableSet setWithSet:jsonResponseSerializer.acceptableContentTypes];
        [jsonAcceptableContentTypes addObject:@"text/plain"];
        jsonResponseSerializer.acceptableContentTypes = jsonAcceptableContentTypes;
        manager.responseSerializer = jsonResponseSerializer;
        
        
        if(!params){
            params = @{};
        }
        NSMutableDictionary *parameters = [params mutableCopy];
        // whether to send the credentials over header or as a param(body)

        
        if([[parameters allValues] containsObject:@"{{$oauth.token}}"]){
            // auth token contained in body
            for(NSString *key in [parameters allKeys]){
                if([parameters[key] isEqualToString:@"{{$oauth.token}}"]){
                    parameters[key] = [credential accessToken];
                }
            }
        } else {
            // auth token sent as header
            [manager.requestSerializer setAuthorizationHeaderFieldWithCredential:credential];
            for(NSString *key in header){
                [manager.requestSerializer setValue:header[key] forHTTPHeaderField:key];
            }
        }
        
        
        if([method isEqualToString:@"get"]){
            [manager GET:path parameters:parameters progress:^(NSProgress * _Nonnull downloadProgress) {
                // Nothing
            } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                // Ignore if the url is different
                NSString *originalRequestPath = task.originalRequest.URL.path;
                
                // Make sure the request from the returning task is the same as the original request
                BOOL same = YES;
                if([originalRequestPath isEqualToString:path]){
                    if(task.originalRequest.URL.query && task.originalRequest.URL.query.length > 0){
                        NSArray *originalRequestQueries = [task.originalRequest.URL.query componentsSeparatedByString:@"&"];
                        for (NSString *kv in originalRequestQueries) {
                            NSArray *pairComponents = [kv componentsSeparatedByString:@"="];
                            NSString *key = [[pairComponents firstObject] stringByRemovingPercentEncoding];
                            NSString *value = [[pairComponents lastObject] stringByRemovingPercentEncoding];
                            
                            // check if the value is the same as the original
                            if(![parameters[key] isEqualToString:value]){
                                same = NO;
                                break;
                            }
                        }
                    }
                } else {
                    same = NO;
                }
                
                if(same){
                    [[Jason client] success: responseObject];
                }
                
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                   NSString* ErrorResponse = [[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding];
                   NSLog(@"#E = %@",ErrorResponse);
                 [[Jason client] error];
            }];
        } else if([method isEqualToString:@"post"]){
            [manager POST:path parameters:parameters progress:^(NSProgress * _Nonnull downloadProgress) {
                // Nothing
            } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                // Ignore if the url is different
                NSString *originalRequestPath = task.originalRequest.URL.path;
                NSString *originalRequestQuery = nil;
                if(task.originalRequest.URL.query && task.originalRequest.URL.query.length > 0){
                    originalRequestQuery = [task.originalRequest.URL.query stringByRemovingPercentEncoding];
                }
                NSString *originalRequestFullPath;
                if(originalRequestQuery){
                    originalRequestFullPath = [NSString stringWithFormat:@"%@?%@", originalRequestPath, originalRequestQuery];
                } else {
                    originalRequestFullPath = originalRequestPath;
                }
                if(![originalRequestFullPath isEqualToString:path]) return;
                
                 [[Jason client] success: responseObject];
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                 [[Jason client] error];
            }];
        } else if([method isEqualToString:@"put"]){
            [manager PUT:path parameters:parameters success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                // Ignore if the url is different
                NSString *originalRequestPath = task.originalRequest.URL.path;
                NSString *originalRequestQuery = nil;
                if(task.originalRequest.URL.query && task.originalRequest.URL.query.length > 0){
                    originalRequestQuery = [task.originalRequest.URL.query stringByRemovingPercentEncoding];
                }
                NSString *originalRequestFullPath;
                if(originalRequestQuery){
                    originalRequestFullPath = [NSString stringWithFormat:@"%@?%@", originalRequestPath, originalRequestQuery];
                } else {
                    originalRequestFullPath = originalRequestPath;
                }
                if(![originalRequestFullPath isEqualToString:path]) return;
                
                [[Jason client] success: responseObject];
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                 [[Jason client] error];
            }];
        } else if([method isEqualToString:@"head"]){
            [manager HEAD:path parameters:parameters success:^(NSURLSessionDataTask * _Nonnull task) {
                // Ignore if the url is different
                NSString *originalRequestPath = task.originalRequest.URL.path;
                NSString *originalRequestQuery = nil;
                if(task.originalRequest.URL.query && task.originalRequest.URL.query.length > 0){
                    originalRequestQuery = [task.originalRequest.URL.query stringByRemovingPercentEncoding];
                }
                NSString *originalRequestFullPath;
                if(originalRequestQuery){
                    originalRequestFullPath = [NSString stringWithFormat:@"%@?%@", originalRequestPath, originalRequestQuery];
                } else {
                    originalRequestFullPath = originalRequestPath;
                }
                if(![originalRequestFullPath isEqualToString:path]) return;
                
                [[Jason client] success];
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                 [[Jason client] error];
            }];
        } else if([method isEqualToString:@"delete"]){
            [manager DELETE:path parameters:parameters success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                // Ignore if the url is different
                NSString *originalRequestPath = task.originalRequest.URL.path;
                NSString *originalRequestQuery = nil;
                if(task.originalRequest.URL.query && task.originalRequest.URL.query.length > 0){
                    originalRequestQuery = [task.originalRequest.URL.query stringByRemovingPercentEncoding];
                }
                NSString *originalRequestFullPath;
                if(originalRequestQuery){
                    originalRequestFullPath = [NSString stringWithFormat:@"%@?%@", originalRequestPath, originalRequestQuery];
                } else {
                    originalRequestFullPath = originalRequestPath;
                }
                if(![originalRequestFullPath isEqualToString:path]) return;
                
                [[Jason client] success: responseObject];
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                 [[Jason client] error];
            }];
        } else {
            [[Jason client] error];
        }

    }
}

- (void)access_token{
    NSString *host = self.options[@"host"];
    if(self.options[@"version"] && [self.options[@"version"] isEqualToString:@"0"]){
        NSString *client_id = self.options[@"client_id"];
        ACAccountStore *accountStore = [[ACAccountStore alloc] init];
        
        ACAccountType *accountTypeFacebook = [accountStore accountTypeWithAccountTypeIdentifier: ACAccountTypeIdentifierFacebook];

        NSDictionary *options;
        if([host containsString:@"facebook"]){
            options = @{
                ACFacebookAppIdKey: client_id,
                //ACFacebookPermissionsKey: @[@"user_friends", @"email"],
                ACFacebookAudienceKey: ACFacebookAudienceFriends
            };
            [accountStore requestAccessToAccountsWithType:accountTypeFacebook options:options completion:^(BOOL granted, NSError *error) {
               if(granted) {
                    NSArray *accounts = [accountStore accountsWithAccountType:accountTypeFacebook];
                    ACAccount *account = [accounts lastObject];
                    [[Jason client] success: @{@"token": account.credential.oauthToken}];
               }
            }];
        }
    } else if(self.options[@"version"] && [self.options[@"version"] isEqualToString:@"1"]){
        /*********************************************************
         * OAuth 1 flow
         *********************************************************/
        NSString *client_id = self.options[@"access"][@"client_id"];
        
        NSString *APP_NAME = [[NSBundle mainBundle] bundleIdentifier];
        
        UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:[NSString stringWithFormat:@"%@", APP_NAME]];
        NSString *access_token = [keychain stringForKey:[NSString stringWithFormat:@"%@#token", client_id]];
        [[Jason client] success: @{@"token": access_token}];
    } else {
        /*********************************************************
         * OAuth 2 flow
         *********************************************************/
        NSString *client_id = self.options[@"access"][@"client_id"];
        AFOAuthCredential *credential = [AFOAuthCredential retrieveCredentialWithIdentifier:client_id];
        [[Jason client] success:@{@"token": [credential accessToken]}];
    }
}
- (void)refresh_token:(NSString*)provider{
    NSString *client_id = self.options[@"access"][@"client_id"];
    NSString *client_secret = self.options[@"access"][@"client_secret"];
     NSLog(@"Failed. Refreshing Token...");
    AFOAuthCredential *credential = [AFOAuthCredential retrieveCredentialWithIdentifier:provider];
    if(credential && credential.isExpired){
        NSDictionary *access_options = self.options[@"access"];
        NSString *urlString = [NSString stringWithFormat:@"%@://%@", access_options[@"scheme"],access_options[@"host"]];
        NSURL *baseURL = [NSURL URLWithString:urlString];
        AFOAuth2Manager *OAuth2Manager = [[AFOAuth2Manager alloc] initWithBaseURL:baseURL
                                        clientID:client_id
                                          secret:client_secret];
        [OAuth2Manager authenticateUsingOAuthWithURLString:access_options[@"path"] refreshToken:credential.refreshToken success:^(AFOAuthCredential *credential) {
            NSLog(@"Success! your new credential is %@", credential);
            [AFOAuthCredential storeCredential:credential withIdentifier:client_id];
            [[Jason client] success];
        } failure:^(NSError *error) {
           [[Jason client] error];
        }];
    }
}
- (void)auth{
    [Jason client].oauth_in_process = YES;
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(oauth_callback:) name:@"oauth_callback" object:nil];
    
    // Use access.oauth_consumer_key(oauth1)/access.client_id(oauth2) as current_provider
    
    if(self.options[@"version"] && [self.options[@"version"] isEqualToString:@"1"]){
        /*********************************************************
         * OAuth 1 flow
         *********************************************************/
       // Example options
        /*
         
         {
             "login": {
                 "type": "$oauth.auth",
                 "options": {
                     "version": "1",
                     "request": {
                         "client_id": "{{$keys.tumblr_key}}",
                         "client_secret": "{{$keys.tumblr_secret}}",
                         "scheme": "https",
                         "host": "www.tumblr.com",
                         "path": "/oauth/request_token",
                         "data": {
                             "oauth_callback": "jasonette://oauth"
                         }
                     },
                     "authorize": {
                         "scheme": "https",
                         "host": "www.tumblr.com",
                         "path": "/oauth/authorize"
                     },
                     "access": {
                         "scheme": "https",
                         "host": "www.tumblr.com",
                         "path": "/oauth/access_token"
                     }
                 },
                 "success": {
                     "trigger": "reload"
                 }
             },
         }
         */
        NSString *client_id = self.options[@"request"][@"client_id"];
        NSString *client_secret = self.options[@"request"][@"client_secret"];
        
        NSDictionary *request_options = self.options[@"request"];
        NSDictionary *authorize_options = self.options[@"authorize"];
        if(!request_options || request_options.count == 0){
            [[Jason client] error];
        } else {
            if(!request_options[@"scheme"] || [request_options[@"scheme"] length] == 0
               || !request_options[@"host"] || [request_options[@"host"] length] == 0
               || !request_options[@"path"] || [request_options[@"path"] length] == 0){
                [[Jason client] error];
            } else {
                NSDictionary *request_params = request_options[@"data"];
                if(!request_params || request_params.count == 0){
                    [[Jason client] error];
                } else {
                    NSMutableURLRequest *request = [[TDOAuth URLRequestForPath:request_options[@"path"]
                                POSTParameters:request_params
                                          host:request_options[@"host"]
                                   consumerKey:client_id
                                consumerSecret:client_secret
                                   accessToken:nil
                                   tokenSecret:nil] mutableCopy];
                    
                    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
                    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
                    manager.requestSerializer = [AFJSONRequestSerializer serializer];
                    
                    NSURLSessionDataTask *task = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                        // Ignore if the url is different
                        if(![request.URL.absoluteString isEqualToString:response.URL.absoluteString]) return;
                        
                        if (!error) {
                            dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
                                NSString* s= [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];

                                NSString *urlToParse = [NSString stringWithFormat:@"http://localhost?%@", s];
                                NSArray *queryItems = [self extractQueryParams:urlToParse];
                                NSString *oauth_token = [self valueForKey:@"oauth_token" fromQueryItems:queryItems];
                                NSString *oauth_token_secret = [self valueForKey:@"oauth_token_secret" fromQueryItems:queryItems];
                                NSString *authorize_url;
                                if(oauth_token_secret){
                                    self.cache = [@{@"oauth1_three_legged_secret": oauth_token_secret} mutableCopy];
                                    authorize_url = [NSString stringWithFormat:@"%@://%@%@?oauth_token=%@&oauth_token_secret=%@", authorize_options[@"scheme"], authorize_options[@"host"], authorize_options[@"path"], oauth_token, oauth_token_secret];
                                } else {
                                    self.cache = nil;
                                    authorize_url = [NSString stringWithFormat:@"%@://%@%@?oauth_token=%@", authorize_options[@"scheme"], authorize_options[@"host"], authorize_options[@"path"], oauth_token];
                                }
                                dispatch_async(dispatch_get_main_queue(), ^{
                                    NSURL *URL = [NSURL URLWithString:authorize_url];
                                    NSString *view = authorize_options[@"view"];
                                    if(view && [view isEqualToString:@"app"]){
                                        // Launch external safari for oauth
                                        [[UIApplication sharedApplication] openURL:URL];
                                    } else {
                                        // By default use SFSafariViewController
                                        SFSafariViewController *vc = [[SFSafariViewController alloc] initWithURL:URL];
                                        vc.delegate = self;
                                        [self.VC presentViewController:vc animated:NO completion:^{ }];
                                    }
                                });
                            });
                        } else {
                            [[NSNotificationCenter defaultCenter] removeObserver:self];
                            [[Jason client] error];
                        }
                    }];
                    [task resume];

                }
            }
        }

        
        
        
    } else {
        /*********************************************************
         * OAuth 2 flow
         *********************************************************/
        // Example options
        /*
         "type": "$oauth.auth",
         "options": {
             "version": "2",
             "authorize": {
                 "client_id": "{{$keys.reddit_client_id}}",
                 "scheme": "https",
                 "host": "i.reddit.com",
                 "path": "/api/v1/authorize",
                 "data": {
                     "response_type": "code",
                     "redirect_uri": "jasonette://oauth",
                     "scope": "read,mysubreddits",
                     "state": "abcdefg",
                     "duration": "permanent"
                 }
             },
             "access": { ... }
         },...
         */
        
        
        
        NSString *view = self.options[@"authorize"][@"view"];
        
        // Check for grant_type : is it a password type or code type?
        
        NSDictionary *access_options = self.options[@"access"];
        if([access_options[@"data"][@"grant_type"] isEqualToString:@"password"]){
            /********************************************************************************
            *
            * Case 1: Password type
            *
            ********************************************************************************/
            NSString *client_id = self.options[@"access"][@"client_id"];
            NSString *client_secret = self.options[@"access"][@"client_secret"];
            
            NSDictionary *access_options = self.options[@"access"];
            if(!access_options || access_options.count == 0){
                [[Jason client] error];
            } else {
                if(!access_options[@"scheme"] || [access_options[@"scheme"] length] == 0
                   || !access_options[@"host"] || [access_options[@"host"] length] == 0
                   || !access_options[@"path"] || [access_options[@"path"] length] == 0){
                    [[Jason client] error];
                } else {
                    // Setup access params
                    NSDictionary *access_data;
                    
                    if(access_options[@"data"]){
                        access_data = access_options[@"data"];
                    }
                    NSString *baseUrl = [NSString stringWithFormat:@"%@://%@", access_options[@"scheme"], access_options[@"host"]];

                    AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:baseUrl]];
                    
                    manager.responseSerializer = [AFJSONResponseSerializer serializer];
                    AFJSONResponseSerializer *jsonResponseSerializer = [AFJSONResponseSerializer serializer];
                    manager.responseSerializer = jsonResponseSerializer;

                    NSMutableDictionary *parameters = [access_data mutableCopy];
                    parameters[@"client_id"] = client_id;
                    if(client_secret){
                        parameters[@"client_secret"] = client_secret;
                    }
                    
                    [manager POST:access_options[@"path"] parameters:parameters progress:^(NSProgress * _Nonnull downloadProgress) {
                        // Nothing
                    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                        // Ignore if the url is different
                        NSString *access_token = responseObject[@"access_token"];
                        NSString *refresh_token = responseObject[@"refresh_token"];
                        if(access_token){
                            NSString *token_type = responseObject[@"token_type"];
                            AFOAuthCredential *credential = [AFOAuthCredential credentialWithOAuthToken:access_token tokenType:token_type];
                            credential.refreshToken = refresh_token;
                            [AFOAuthCredential storeCredential:credential withIdentifier:client_id];
                            [[Jason client] success];
                            
                        } else {
                            [[Jason client] error];
                        }
                    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                        [[Jason client] error];
                    }];
                }
            }
        } else {
            /********************************************************************************
            *
            * Case 2: Assumes the rest will be "code" type. (May need to add more implementations?)
            *
            ********************************************************************************/
            NSString *client_id = self.options[@"authorize"][@"client_id"];
            NSString *client_secret = self.options[@"authorize"][@"client_secret"];
            
            NSDictionary *authorize_options = self.options[@"authorize"];
            if(!authorize_options || authorize_options.count == 0){
                [[Jason client] error];
            } else {
                if(!authorize_options[@"scheme"] || [authorize_options[@"scheme"] length] == 0
                   || !authorize_options[@"host"] || [authorize_options[@"host"] length] == 0
                   || !authorize_options[@"path"] || [authorize_options[@"path"] length] == 0){
                    [[Jason client] error];
                } else {
                    // First see if I can refresh the token (if it already exists but expired)
                    AFOAuthCredential *credential = [AFOAuthCredential retrieveCredentialWithIdentifier:client_id];
                    if(credential && credential.isExpired && credential.refreshToken){
                        [self refresh_token:client_id];
                    } else {
                        // Generate the final url (U) from scheme/host/path/data
                        NSString *url = [NSString stringWithFormat:@"%@://%@%@", authorize_options[@"scheme"], authorize_options[@"host"], authorize_options[@"path"]];
                        NSMutableDictionary *parameters = [authorize_options[@"data"] mutableCopy];
                        NSURLComponents *components = [NSURLComponents componentsWithString:url];
                        NSMutableArray *queryItems = [NSMutableArray array];
                        if(authorize_options[@"client_id"]){
                            parameters[@"client_id"] = authorize_options[@"client_id"];
                        }
                        if(authorize_options[@"client_secret"]){
                            parameters[@"client_secret"] = authorize_options[@"client_secret"];
                        }
                        for (NSString *key in parameters) {
                            [queryItems addObject:[NSURLQueryItem queryItemWithName:key value:parameters[key]]];
                        }
                        components.queryItems = queryItems;
                        NSURL *U = components.URL;

                        
                        if(view && [view isEqualToString:@"app"]){
                            // Launch external safari for oauth
                            [[UIApplication sharedApplication] openURL:U];
                        } else {
                            // By default use SFSafariViewController
                            SFSafariViewController *vc = [[SFSafariViewController alloc] initWithURL:U];
                            vc.delegate = self;
                            [self.VC presentViewController:vc animated:NO completion:^{ }];
                        }
                    }
                }
            }
            
        }
        
    }
    
}
- (void)oauth_callback: (NSNotification*)notification{
    if(self.options[@"version"] && [self.options[@"version"] isEqualToString:@"1"]){
        /*********************************************************
         * OAuth 1 flow
         *********************************************************/
       // Example options
        /*
         
         {
             "login": {
                 "type": "$oauth.auth",
                 "options": {
                     "version": "1",
                     "request": {
                         "scheme": "https",
                         "host": "www.tumblr.com",
                         "path": "/oauth/request_token",
                         "data": {
                             "oauth_callback": "jasonette://oauth"
                         }
                     },
                     "authorize": {
                         "scheme": "https",
                         "host": "www.tumblr.com",
                         "path": "/oauth/authorize"
                     },
                     "access": {
                         "scheme": "https",
                         "host": "www.tumblr.com",
                         "path": "/oauth/access_token"
                     }
                 },
                 "success": {
                     "trigger": "reload"
                 }
             },
         }
         */
        NSString *client_id = self.options[@"access"][@"client_id"];
        NSString *client_secret = self.options[@"access"][@"client_secret"];
        
        [self.VC.navigationController dismissViewControllerAnimated:YES completion:nil];
        NSDictionary *access_options = self.options[@"access"];
        if(!access_options || access_options.count == 0){
            [[Jason client] error];
        } else {
            if(!access_options[@"scheme"] || [access_options[@"scheme"] length] == 0
               || !access_options[@"host"] || [access_options[@"host"] length] == 0
               || !access_options[@"path"] || [access_options[@"path"] length] == 0){
                [[Jason client] error];
            } else {
                
                NSURL *url = notification.userInfo[@"url"];
                NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
                NSArray *queryItems = [components queryItems];
                NSString *oauth_token = [self valueForKey:@"oauth_token" fromQueryItems:queryItems];
                NSString *oauth_verifier = [self valueForKey:@"oauth_verifier" fromQueryItems:queryItems];
                [[NSNotificationCenter defaultCenter] removeObserver:self];
                NSDictionary *parameters = @{ @"oauth_verifier" : oauth_verifier};
                NSString *oauth1_three_legged_secret;
                if(self.cache && self.cache[@"oauth1_three_legged_secret"]){
                    oauth1_three_legged_secret = self.cache[@"oauth1_three_legged_secret"];
                }
                NSURLRequest *request = [TDOAuth URLRequestForPath:access_options[@"path"]
                            POSTParameters:parameters
                                      host:access_options[@"host"]
                               consumerKey:client_id
                            consumerSecret:client_secret
                               accessToken:oauth_token
                               tokenSecret:oauth1_three_legged_secret];
                AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
                manager.responseSerializer = [AFHTTPResponseSerializer serializer];
                manager.requestSerializer = [AFJSONRequestSerializer serializer];
                
                NSURLSessionDataTask *task = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                    // Ignore if the url is different
                    if(![request.URL.absoluteString isEqualToString:response.URL.absoluteString]) return;
                    
                    if (!error) {
                        // Temp url just to take advantage of componentsWithURL
                        NSString* s= [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
                        NSString *urlToParse = [NSString stringWithFormat:@"http://localhost?%@",s];
                        NSURLComponents *components = [NSURLComponents componentsWithURL:[NSURL URLWithString:urlToParse] resolvingAgainstBaseURL:NO];
                        NSArray *queryItems = [components queryItems];
                        NSString *oauth_token = [self valueForKey:@"oauth_token" fromQueryItems:queryItems];
                        NSString *oauth_token_secret = [self valueForKey:@"oauth_token_secret" fromQueryItems:queryItems];
                        
                        
                        NSString *APP_NAME = [[NSBundle mainBundle] bundleIdentifier];
                        
                        UICKeyChainStore *keychain = [UICKeyChainStore keyChainStoreWithService:[NSString stringWithFormat:@"%@", APP_NAME]];
                        [keychain setString:oauth_token forKey:[NSString stringWithFormat:@"%@#token", client_id]];
                        [keychain setString:oauth_token_secret forKey:[NSString stringWithFormat:@"%@#secret", client_id]];
                        [[Jason client] success];
                    } else {
                        [[NSNotificationCenter defaultCenter] removeObserver:self];
                        [[Jason client] error];
                    }
                }];
                [task resume];
            }
        }
    } else {
        /*********************************************************
         * OAuth 2 flow
         *********************************************************/
        // Example options
        /*
         
        // Send access params as header
         "type": "$oauth.auth",
         "options": {
             "version": "2",
             "authorize": { ... },
             "access": {
                 "client_id": "{{$keys.reddit_client_id}}",
                 "client_secret": "{{$keys.reddit_client_secret}}",
                 "scheme": "https",
                 "host": "i.reddit.com",
                 "path": "/api/v1/access_token",
                 "data": {
                     "grant_type": "authorization_code",
                     "redirect_uri": "jasonette://oauth",
                 }
             }
         },...
         
        // Send access params as body
         "type": "$oauth.auth",
         "options": {
             "version": "2",
             "token_in_body": "true",
             "authorize": { ... },
             "access": {
                 "client_id": "{{$keys.reddit_client_id}}",
                 "client_secret": "{{$keys.reddit_client_secret}}",
                 "scheme": "https",
                 "host": "i.reddit.com",
                 "path": "/api/v1/access_token",
                 "data": {
                     "grant_type": "authorization_code",
                     "redirect_uri": "jasonette://oauth"
                 }
             }
         },...
         */
        
        [self.VC.navigationController dismissViewControllerAnimated:YES completion:nil];
        
        NSURL *url = notification.userInfo[@"url"];
        // extract parameters
        NSArray *returnValues = [self extractQueryParams:url.absoluteString];
        
        // Exception case (Dropbox) where authorize returns access_token directly, in which case we don't need to go forward with the next step of requesting access_token again
        NSString *access_token = [self valueForKey:@"access_token" fromQueryItems:returnValues];

        if(access_token){
            NSString *client_id = self.options[@"authorize"][@"client_id"];
            
            NSString *token_type = [self valueForKey:@"token_type" fromQueryItems:returnValues];
            AFOAuthCredential *credential = [AFOAuthCredential credentialWithOAuthToken:access_token tokenType:token_type];
            [AFOAuthCredential storeCredential:credential withIdentifier:client_id];
            [[Jason client] success];
        } else {
            NSDictionary *access_options = self.options[@"access"];
            
            // Setup access params
            NSMutableDictionary *access_data;
            
            if(access_options[@"data"]){
                access_data = [access_options[@"data"] mutableCopy];
            }
            
            NSString *client_id = self.options[@"access"][@"client_id"];
            NSString *client_secret = self.options[@"access"][@"client_secret"];
            if(!client_secret) client_secret = @"";
            
            NSString *code = [self valueForKey:@"code" fromQueryItems:returnValues];
            
            if(!access_options || access_options.count == 0
               || !access_options[@"scheme"] || [access_options[@"scheme"] length] == 0
               || !access_options[@"host"] || [access_options[@"host"] length] == 0
               || !access_options[@"path"] || [access_options[@"path"] length] == 0){
                [[Jason client] error];
            } else {
                NSString *urlString = [NSString stringWithFormat:@"%@://%@", access_options[@"scheme"], access_options[@"host"]];
                NSURL *baseURL = [NSURL URLWithString:urlString];
                    
                
                AFOAuth2Manager *OAuth2Manager = [[AFOAuth2Manager alloc] initWithBaseURL:baseURL
                                                    clientID:client_id
                                                   secret:client_secret];
                    
                // In most cases oauth endpoints don't use basic auth (although it's more secure to use basic auth)
                // So the default is 'non basic auth'
                    
                if(access_options[@"basic"]){
                    [OAuth2Manager setUseHTTPBasicAuthentication:YES];
                } else {
                    [OAuth2Manager setUseHTTPBasicAuthentication:NO];
                }
                access_data[@"code"] = code;
                
                [OAuth2Manager authenticateUsingOAuthWithURLString:access_options[@"path"]
                                                        parameters:access_data success:^(AFOAuthCredential *credential) {
                                                            [AFOAuthCredential storeCredential:credential withIdentifier:client_id];
                                                            
                                                            [[Jason client] success];
                                                        }
                                                        failure:^(NSError *error) {
                                                            NSLog(@"Error: %@", error);
                                                            NSString* ErrorResponse = [[NSString alloc] initWithData:(NSData *)error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] encoding:NSUTF8StringEncoding];
                                                            NSLog(@"#ncoded = %@",ErrorResponse);

                                                            [[Jason client] error];
                                                        }];
            }
        }
    }
    
}
- (NSArray *)extractQueryParams:(NSString *)str{
    NSString *urlToParse = str;
    NSMutableArray *ret = [[NSMutableArray alloc]init];
    NSURLComponents *components = [NSURLComponents componentsWithURL:[NSURL URLWithString:urlToParse] resolvingAgainstBaseURL:NO];
    NSArray *items = [components queryItems];
    [ret addObjectsFromArray:items];
    if([components fragment]){
        NSString *fragmentUrlToParse = [NSString stringWithFormat:@"http://localhost?%@", [components fragment]];
        NSURLComponents *fragmentComponents = [NSURLComponents componentsWithURL:[NSURL URLWithString:fragmentUrlToParse] resolvingAgainstBaseURL:NO];
        NSArray *fragmentQueryItems = [fragmentComponents queryItems];
        [ret addObjectsFromArray:fragmentQueryItems];
    }
    return ret;
    
}
- (NSString *)valueForKey:(NSString *)key
           fromQueryItems:(NSArray *)queryItems
{
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@", key];
    NSURLQueryItem *queryItem = [[queryItems
                                  filteredArrayUsingPredicate:predicate]
                                 firstObject];
    return queryItem.value;
}

@end