This repository has been archived by the owner on Jun 17, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathXAuthTwitterEngine.m
381 lines (304 loc) · 13.7 KB
/
XAuthTwitterEngine.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
//
// XAuthTwitterEngine.h
//
// Created by Aral Balkan on 28/02/2010.
// Copyright 2010 Naklab. All rights reserved.
//
// Based on SA_OAuthTwitterEngine Ben Gottlieb.
//
// Some code and concepts taken from examples provided by
// Matt Gemmell, Chris Kimpton, and Isaiah Carew
// See ReadMe for further attributions, copyrights and license info.
//
#import "MGTwitterHTTPURLConnection.h"
#import "OAConsumer.h"
#import "OAMutableURLRequest.h"
#import "OADataFetcher.h"
#import "OAAsynchronousDataFetcher.h"
#import "OAToken.h"
#import "XAuthTwitterEngine.h"
#import "ExchangeCredentialsOperation.h"
@interface XAuthTwitterEngine (private)
- (void) requestURL:(NSURL *) url token:(OAToken *)token onSuccess:(SEL)success onFail:(SEL)fail;
- (void) setRequestToken: (OAServiceTicket *) ticket withData: (NSData *) data;
- (void) setAccessToken: (OAServiceTicket *) ticket withData: (NSData *) data;
- (NSString *) extractUsernameFromHTTPBody:(NSString *)body;
// MGTwitterEngine impliments this
// include it here just so that we
// can use this private method
- (NSString *)_queryStringWithBase:(NSString *)base parameters:(NSDictionary *)params prefixed:(BOOL)prefixed;
@end
@implementation XAuthTwitterEngine
@synthesize accessTokenURL = _accessTokenURL;
@synthesize consumerSecret = _consumerSecret, consumerKey = _consumerKey;
@synthesize accessToken = _accessToken;
@synthesize operationQueue = _operationQueue;
- (void) dealloc {
self.accessTokenURL = nil;
[self.operationQueue cancelAllOperations];
[self.operationQueue release];
[_accessToken release];
[_consumer release];
[super dealloc];
}
+ (XAuthTwitterEngine *) XAuthTwitterEngineWithDelegate: (NSObject *) delegate {
return [[[XAuthTwitterEngine alloc] initXAuthWithDelegate: delegate] autorelease];
}
- (XAuthTwitterEngine *) initXAuthWithDelegate: (NSObject *) delegate {
if (self = (id) [super initWithDelegate: delegate]) {
self.accessTokenURL = [NSURL URLWithString: @"http://twitter.com/oauth/access_token"];
self.operationQueue = [[NSOperationQueue alloc] init];
}
return self;
}
//=============================================================================================================================
#pragma mark OAuth Code
- (BOOL) OAuthSetup {
return _consumer != nil;
}
- (OAConsumer *) consumer {
if (_consumer) return _consumer;
NSAssert(self.consumerKey.length > 0 && self.consumerSecret.length > 0, @"You must first set your Consumer Key and Consumer Secret properties. Visit http://twitter.com/oauth_clients/new to obtain these.");
_consumer = [[OAConsumer alloc] initWithKey: self.consumerKey secret: self.consumerSecret];
return _consumer;
}
- (BOOL) isAuthorized {
if (_accessToken.key && _accessToken.secret) return YES;
//first, check for cached creds
NSString *accessTokenString = [_delegate respondsToSelector: @selector(cachedTwitterXAuthAccessTokenStringForUsername:)] ? [(id) _delegate cachedTwitterXAuthAccessTokenStringForUsername: self.username] : @"";
if (accessTokenString.length) {
[_accessToken release];
_accessToken = [[OAToken alloc] initWithHTTPResponseBody: accessTokenString];
[self setUsername: [self extractUsernameFromHTTPBody: accessTokenString] password: nil];
if (_accessToken.key && _accessToken.secret) return YES;
}
[_accessToken release]; // no access token found. create a new empty one
_accessToken = [[OAToken alloc] initWithKey: nil secret: nil];
return NO;
}
/*
//this is what we eventually want
- (void) requestAccessToken {
[self requestURL: self.accessTokenURL token: _requestToken onSuccess: @selector(setAccessToken:withData:) onFail: @selector(outhTicketFailed:data:)];
}
*/
- (void) clearAccessToken {
if ([_delegate respondsToSelector: @selector(storeCachedTwitterXAuthAccessTokenString:forUsername:)]) [(id) _delegate storeCachedTwitterXAuthAccessTokenString: @"" forUsername: self.username];
[_accessToken release];
_accessToken = nil;
[_consumer release];
_consumer = nil;
}
/*
- (void) setPin: (NSString *) pin {
[_pin autorelease];
_pin = [pin retain];
_accessToken.pin = pin;
_requestToken.pin = pin;
}
*/
#pragma mark -
#pragma mark Public xAuth methods
//
// Attempts to retrieve an xAuthAccessToken for the passed username and password
//
-(void)exchangeAccessTokenForUsername:(NSString *)username password:(NSString *)password
{
//
// Doing this as an operation that uses the synchronous OADataFetcher instead of using
// the asynchronous OAAsynchronousDataFetcher since the latter doesn't fire the
// fail handler when the credential exchange fails with Twitter for some reason.
// Other (non-twitter errors) fire, so I guess something is messed up when the class
// is used with Twitter's xAuth implementation. Oh well...
//
ExchangeCredentialsOperation *exchangeCredentialsOperation = [[ExchangeCredentialsOperation alloc] init];
exchangeCredentialsOperation.username = username;
exchangeCredentialsOperation.password = password;
exchangeCredentialsOperation.delegate = self;
exchangeCredentialsOperation.consumer = self.consumer;
[self.operationQueue addOperation:exchangeCredentialsOperation];
[exchangeCredentialsOperation release];
}
//
// Cancels the asynchronous xAuth token exchange process.
//
-(void)cancelAccessTokenExchange
{
[self.operationQueue cancelAllOperations];
}
//
// Sets the xAuth token directly from a token string.
//
- (void)setAccessTokenFromTokenString:(NSString *)tokenString
{
NSString *username = [self extractUsernameFromHTTPBody:tokenString];
if (username.length > 0) {
[self setUsername: username password: nil];
if ([_delegate respondsToSelector: @selector(storeCachedTwitterXAuthAccessTokenString:forUsername:)]) [(id) _delegate storeCachedTwitterXAuthAccessTokenString: tokenString forUsername: username];
}
[_accessToken release];
_accessToken = [[OAToken alloc] initWithHTTPResponseBody:tokenString];
}
#pragma mark -
#pragma mark Private XAuth methods
//
// Access token fetch failed.
//
- (void) accessTokenTicket:(OAServiceTicket *)ticket didFailWithError:(NSError *) error {
if ([_delegate respondsToSelector: @selector(twitterXAuthConnectionDidFailWithError:)]) [(id) _delegate twitterXAuthConnectionDidFailWithError: error];
}
//
// access token callback
// when twitter sends us an access token this callback will fire
// we store it in our ivar as well as writing it to the keychain
//
/*
- (void) setAccessToken: (OAServiceTicket *) ticket withData: (NSData *) data {
if (!ticket.didSucceed || !data) return;
NSString *dataString = [[[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding] autorelease];
if (!dataString) return;
[self setAccessTokenFromTokenString:dataString];
}
*/
- (NSString *) extractUsernameFromHTTPBody: (NSString *) body {
if (!body) return nil;
NSArray *tuples = [body componentsSeparatedByString: @"&"];
if (tuples.count < 1) return nil;
for (NSString *tuple in tuples) {
NSArray *keyValueArray = [tuple componentsSeparatedByString: @"="];
if (keyValueArray.count == 2) {
NSString *key = [keyValueArray objectAtIndex: 0];
NSString *value = [keyValueArray objectAtIndex: 1];
if ([key isEqualToString:@"screen_name"]) return value;
}
}
return nil;
}
//=============================================================================================================================
#pragma mark MGTwitterEngine Changes
//These are all verbatim from Isaiah Carew and Chris Kimpton's code
// --------------------------------------------------------------------------------
//
// these method overrides were created from the work that Chris Kimpton
// did. i've chosen to subclass instead of directly modifying the
// MGTwitterEngine as it makes integrating MGTwitterEngine changes a bit
// easier.
//
// the code here is largely unchanged from chris's implimentation.
// i've tried to highlight the areas that differ from
// the base class implimentation.
//
// --------------------------------------------------------------------------------
#define SET_AUTHORIZATION_IN_HEADER 1
- (NSString *)_sendRequestWithMethod:(NSString *)method
path:(NSString *)path
queryParameters:(NSDictionary *)params
body:(NSString *)body
requestType:(MGTwitterRequestType)requestType
responseType:(MGTwitterResponseType)responseType
{
NSString *fullPath = path;
// --------------------------------------------------------------------------------
// modificaiton from the base clase
// the base class appends parameters here
// --------------------------------------------------------------------------------
// if (params) {
// fullPath = [self _queryStringWithBase:fullPath parameters:params prefixed:YES];
// }
// --------------------------------------------------------------------------------
NSString *urlString = [NSString stringWithFormat:@"%@://%@/%@",
(_secureConnection) ? @"https" : @"http",
_APIDomain, fullPath];
NSURL *finalURL = [NSURL URLWithString:urlString];
if (!finalURL) {
return nil;
}
//
// Check if we're authorized. If not don't carry out the request.
// Note: This will result in the delegate callback for the cached request token
// ===== to be called if it doesn't already exist.
//
if (![self isAuthorized])
{
return nil;
}
// --------------------------------------------------------------------------------
// modificaiton from the base clase
// the base class creates a regular url request
// we're going to create an oauth url request
// --------------------------------------------------------------------------------
// NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:finalURL
// cachePolicy:NSURLRequestReloadIgnoringCacheData
// timeoutInterval:URL_REQUEST_TIMEOUT];
// --------------------------------------------------------------------------------
NSLog(@"About to carry out request with access token: %@", _accessToken);
OAMutableURLRequest *theRequest = [[[OAMutableURLRequest alloc] initWithURL:finalURL
consumer:self.consumer
token:_accessToken
realm: nil
signatureProvider:nil] autorelease];
if (method) {
[theRequest setHTTPMethod:method];
}
[theRequest setHTTPShouldHandleCookies:NO];
// Set headers for client information, for tracking purposes at Twitter.
[theRequest setValue:_clientName forHTTPHeaderField:@"X-Twitter-Client"];
[theRequest setValue:_clientVersion forHTTPHeaderField:@"X-Twitter-Client-Version"];
[theRequest setValue:_clientURL forHTTPHeaderField:@"X-Twitter-Client-URL"];
// Set the request body if this is a POST request.
BOOL isPOST = (method && [method isEqualToString:@"POST"]);
if (isPOST) {
// Set request body, if specified (hopefully so), with 'source' parameter if appropriate.
NSString *finalBody = @"";
if (body) {
finalBody = [finalBody stringByAppendingString:body];
}
if (_clientSourceToken) {
finalBody = [finalBody stringByAppendingString:[NSString stringWithFormat:@"%@source=%@",
(body) ? @"&" : @"?" ,
_clientSourceToken]];
}
if (finalBody) {
[theRequest setHTTPBody:[finalBody dataUsingEncoding:NSUTF8StringEncoding]];
}
}
// --------------------------------------------------------------------------------
// modificaiton from the base clase
// our version "prepares" the oauth url request
// --------------------------------------------------------------------------------
[theRequest prepare];
// Create a connection using this request, with the default timeout and caching policy,
// and appropriate Twitter request and response types for parsing and error reporting.
MGTwitterHTTPURLConnection *connection;
connection = [[MGTwitterHTTPURLConnection alloc] initWithRequest:theRequest
delegate:self
requestType:requestType
responseType:responseType];
if (!connection) {
return nil;
} else {
[_connections setObject:connection forKey:[connection identifier]];
[connection release];
}
return [connection identifier];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
// --------------------------------------------------------------------------------
// modificaiton from the base clase
// instead of answering the authentication challenge, we just ignore it.
// seems a bit odd to me, but this is what Chris Kimpton did and it seems to work,
// so i'm rolling with it.
// --------------------------------------------------------------------------------
// if ([challenge previousFailureCount] == 0 && ![challenge proposedCredential]) {
// NSURLCredential *credential = [NSURLCredential credentialWithUser:_username password:_password
// persistence:NSURLCredentialPersistenceForSession];
// [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
// } else {
// [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
// }
// --------------------------------------------------------------------------------
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
return;
}
@end