PageRenderTime 131ms CodeModel.GetById 12ms app.highlight 112ms RepoModel.GetById 1ms app.codeStats 0ms

/core/externals/update-engine/externals/gdata-objectivec-client/Source/OAuth2/Mac/GTMOAuth2WindowController.m

http://macfuse.googlecode.com/
Objective C | 727 lines | 513 code | 122 blank | 92 comment | 68 complexity | 29f8d021060d405d7d6f145cd993b4e2 MD5 | raw file
  1/* Copyright (c) 2011 Google Inc.
  2 *
  3 * Licensed under the Apache License, Version 2.0 (the "License");
  4 * you may not use this file except in compliance with the License.
  5 * You may obtain a copy of the License at
  6 *
  7 *     http://www.apache.org/licenses/LICENSE-2.0
  8 *
  9 * Unless required by applicable law or agreed to in writing, software
 10 * distributed under the License is distributed on an "AS IS" BASIS,
 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 * See the License for the specific language governing permissions and
 13 * limitations under the License.
 14 */
 15
 16#import <Foundation/Foundation.h>
 17
 18#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
 19
 20#if !TARGET_OS_IPHONE
 21
 22#import "GTMOAuth2WindowController.h"
 23
 24@interface GTMOAuth2WindowController ()
 25@property (nonatomic, retain) GTMOAuth2SignIn *signIn;
 26@property (nonatomic, copy) NSURLRequest *initialRequest;
 27@property (nonatomic, retain) GTMCookieStorage *cookieStorage;
 28@property (nonatomic, retain) NSWindow *sheetModalForWindow;
 29
 30- (void)signInCommonForWindow:(NSWindow *)parentWindowOrNil;
 31- (void)setupSheetTerminationHandling;
 32- (void)destroyWindow;
 33- (void)handlePrematureWindowClose;
 34- (BOOL)shouldUseKeychain;
 35- (void)signIn:(GTMOAuth2SignIn *)signIn displayRequest:(NSURLRequest *)request;
 36- (void)signIn:(GTMOAuth2SignIn *)signIn finishedWithAuth:(GTMOAuth2Authentication *)auth error:(NSError *)error;
 37- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo;
 38
 39- (void)handleCookiesForResponse:(NSURLResponse *)response;
 40- (NSURLRequest *)addCookiesToRequest:(NSURLRequest *)request;
 41@end
 42
 43const char *kKeychainAccountName = "OAuth";
 44
 45@implementation GTMOAuth2WindowController
 46
 47// IBOutlets
 48@synthesize keychainCheckbox = keychainCheckbox_,
 49            webView = webView_,
 50            webCloseButton = webCloseButton_,
 51            webBackButton = webBackButton_;
 52
 53// regular ivars
 54@synthesize signIn = signIn_,
 55            initialRequest = initialRequest_,
 56            cookieStorage = cookieStorage_,
 57            sheetModalForWindow = sheetModalForWindow_,
 58            keychainItemName = keychainItemName_,
 59            initialHTMLString = initialHTMLString_,
 60            shouldAllowApplicationTermination = shouldAllowApplicationTermination_,
 61            externalRequestSelector = externalRequestSelector_,
 62            shouldPersistUser = shouldPersistUser_,
 63            userData = userData_,
 64            properties = properties_;
 65
 66#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
 67// Create a controller for authenticating to Google services
 68+ (id)controllerWithScope:(NSString *)scope
 69                 clientID:(NSString *)clientID
 70             clientSecret:(NSString *)clientSecret
 71         keychainItemName:(NSString *)keychainItemName
 72           resourceBundle:(NSBundle *)bundle {
 73  return [[[self alloc] initWithScope:scope
 74                             clientID:clientID
 75                         clientSecret:clientSecret
 76                     keychainItemName:keychainItemName
 77                       resourceBundle:bundle] autorelease];
 78}
 79
 80- (id)initWithScope:(NSString *)scope
 81           clientID:(NSString *)clientID
 82       clientSecret:(NSString *)clientSecret
 83   keychainItemName:(NSString *)keychainItemName
 84     resourceBundle:(NSBundle *)bundle {
 85  Class signInClass = [[self class] signInClass];
 86  GTMOAuth2Authentication *auth;
 87  auth = [signInClass standardGoogleAuthenticationForScope:scope
 88                                                  clientID:clientID
 89                                              clientSecret:clientSecret];
 90  NSURL *authorizationURL = [signInClass googleAuthorizationURL];
 91  return [self initWithAuthentication:auth
 92                     authorizationURL:authorizationURL
 93                     keychainItemName:keychainItemName
 94                       resourceBundle:bundle];
 95}
 96#endif
 97
 98// Create a controller for authenticating to any service
 99+ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
100                  authorizationURL:(NSURL *)authorizationURL
101                  keychainItemName:(NSString *)keychainItemName
102                    resourceBundle:(NSBundle *)bundle {
103 return [[[self alloc] initWithAuthentication:auth
104                             authorizationURL:authorizationURL
105                             keychainItemName:keychainItemName
106                               resourceBundle:bundle] autorelease];
107}
108
109- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
110            authorizationURL:(NSURL *)authorizationURL
111            keychainItemName:(NSString *)keychainItemName
112              resourceBundle:(NSBundle *)bundle {
113  if (bundle == nil) {
114    bundle = [NSBundle mainBundle];
115  }
116
117  NSString *nibName = [[self class] authNibName];
118  NSString *nibPath = [bundle pathForResource:nibName
119                                       ofType:@"nib"];
120  self = [super initWithWindowNibPath:nibPath
121                                owner:self];
122  if (self != nil) {
123    // use the supplied auth and OAuth endpoint URLs
124    Class signInClass = [[self class] signInClass];
125    signIn_ = [[signInClass alloc] initWithAuthentication:auth
126                                         authorizationURL:authorizationURL
127                                                 delegate:self
128                                       webRequestSelector:@selector(signIn:displayRequest:)
129                                         finishedSelector:@selector(signIn:finishedWithAuth:error:)];
130    keychainItemName_ = [keychainItemName copy];
131
132    // create local, temporary storage for WebKit cookies
133    cookieStorage_ = [[GTMCookieStorage alloc] init];
134  }
135  return self;
136}
137
138- (void)dealloc {
139  [signIn_ release];
140  [initialRequest_ release];
141  [cookieStorage_ release];
142  [delegate_ release];
143#if NS_BLOCKS_AVAILABLE
144  [completionBlock_ release];
145#endif
146  [sheetModalForWindow_ release];
147  [keychainItemName_ release];
148  [initialHTMLString_ release];
149  [userData_ release];
150  [properties_ release];
151
152  [super dealloc];
153}
154
155- (void)awakeFromNib {
156  // load the requested initial sign-in page
157  [self.webView setResourceLoadDelegate:self];
158  [self.webView setPolicyDelegate:self];
159
160  // the app may prefer some html other than blank white to be displayed
161  // before the sign-in web page loads
162  NSString *html = self.initialHTMLString;
163  if ([html length] > 0) {
164    [[self.webView mainFrame] loadHTMLString:html baseURL:nil];
165  }
166
167  // hide the keychain checkbox if we're not supporting keychain
168  BOOL hideKeychainCheckbox = ![self shouldUseKeychain];
169
170  const NSTimeInterval kJanuary2011 = 1293840000;
171  BOOL isDateValid = ([[NSDate date] timeIntervalSince1970] > kJanuary2011);
172  if (isDateValid) {
173    // start the asynchronous load of the sign-in web page
174    [[self.webView mainFrame] performSelector:@selector(loadRequest:)
175                                   withObject:self.initialRequest
176                                   afterDelay:0.01
177                                      inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
178  } else {
179    // clock date is invalid, so signing in would fail with an unhelpful error
180    // from the server. Warn the user in an html string showing a watch icon,
181    // question mark, and the system date and time. Hopefully this will clue
182    // in brighter users, or at least let them make a useful screenshot to show
183    // to developers.
184    //
185    // Even better is for apps to check the system clock and show some more
186    // helpful, localized instructions for users; this is really a fallback.
187    NSString *const htmlTemplate = @"<html><body><div align=center><font size='7'>"
188      @"&#x231A; ?<br><i>System Clock Incorrect</i><br>%@"
189      @"</font></div></body></html>";
190    NSString *errHTML = [NSString stringWithFormat:htmlTemplate, [NSDate date]];
191
192    [[webView_ mainFrame] loadHTMLString:errHTML baseURL:nil];
193    hideKeychainCheckbox = YES;
194  }
195
196#if DEBUG
197  // Verify that Javascript is enabled
198  BOOL hasJS = [[webView_ preferences] isJavaScriptEnabled];
199  NSAssert(hasJS, @"GTMOAuth2: Javascript is required");
200#endif
201
202  [keychainCheckbox_ setHidden:hideKeychainCheckbox];
203}
204
205+ (NSString *)authNibName {
206  // subclasses may override this to specify a custom nib name
207  return @"GTMOAuth2Window";
208}
209
210#pragma mark -
211
212- (void)signInSheetModalForWindow:(NSWindow *)parentWindowOrNil
213                         delegate:(id)delegate
214                 finishedSelector:(SEL)finishedSelector {
215  // check the selector on debug builds
216  GTMAssertSelectorNilOrImplementedWithArgs(delegate, finishedSelector,
217    @encode(GTMOAuth2WindowController *), @encode(GTMOAuth2Authentication *),
218    @encode(NSError *), 0);
219
220  delegate_ = [delegate retain];
221  finishedSelector_ = finishedSelector;
222
223  [self signInCommonForWindow:parentWindowOrNil];
224}
225
226#if NS_BLOCKS_AVAILABLE
227- (void)signInSheetModalForWindow:(NSWindow *)parentWindowOrNil
228                completionHandler:(void (^)(GTMOAuth2Authentication *, NSError *))handler {
229  completionBlock_ = [handler copy];
230
231  [self signInCommonForWindow:parentWindowOrNil];
232}
233#endif
234
235- (void)signInCommonForWindow:(NSWindow *)parentWindowOrNil {
236  self.sheetModalForWindow = parentWindowOrNil;
237  hasDoneFinalRedirect_ = NO;
238  hasCalledFinished_ = NO;
239  
240  [self.signIn startSigningIn];
241}
242
243- (void)cancelSigningIn {
244  // The user has explicitly asked us to cancel signing in
245  // (so no further callback is required)
246  hasCalledFinished_ = YES;
247
248  [delegate_ autorelease];
249  delegate_ = nil;
250
251#if NS_BLOCKS_AVAILABLE
252  [completionBlock_ autorelease];
253  completionBlock_ = nil;
254#endif
255
256  // The signIn object's cancel method will close the window
257  [self.signIn cancelSigningIn];
258  hasDoneFinalRedirect_ = YES;
259}
260
261- (IBAction)closeWindow:(id)sender {
262  // dismiss the window/sheet before we call back the client
263  [self destroyWindow];
264  [self handlePrematureWindowClose];
265}
266
267#pragma mark SignIn callbacks
268
269- (void)signIn:(GTMOAuth2SignIn *)signIn displayRequest:(NSURLRequest *)request {
270  // this is the signIn object's webRequest method, telling the controller
271  // to either display the request in the webview, or close the window
272  //
273  // All web requests and all window closing goes through this routine
274
275#if DEBUG
276  if ((isWindowShown_ && request != nil)
277      || (!isWindowShown_ && request == nil)) {
278    NSLog(@"Window state unexpected for request %@", [request URL]);
279    return;
280  }
281#endif
282
283  if (request != nil) {
284    // display the request
285    self.initialRequest = request;
286
287    NSWindow *parentWindow = self.sheetModalForWindow;
288    if (parentWindow) {
289      [self setupSheetTerminationHandling];
290
291      NSWindow *sheet = [self window];
292      [NSApp beginSheet:sheet
293         modalForWindow:parentWindow
294          modalDelegate:self
295         didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
296            contextInfo:nil];
297    } else {
298      // modeless
299      [self showWindow:self];
300    }
301    isWindowShown_ = YES;
302  } else {
303    // request was nil
304    [self destroyWindow];
305  }
306}
307
308- (void)setupSheetTerminationHandling {
309  NSWindow *sheet = [self window];
310
311  SEL sel = @selector(setPreventsApplicationTerminationWhenModal:);
312  if ([sheet respondsToSelector:sel]) {
313    // setPreventsApplicationTerminationWhenModal is available in NSWindow
314    // on 10.6 and later
315    BOOL boolVal = !self.shouldAllowApplicationTermination;
316    NSMethodSignature *sig = [sheet methodSignatureForSelector:sel];
317    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
318    [invocation setSelector:sel];
319    [invocation setTarget:sheet];
320    [invocation setArgument:&boolVal atIndex:2];
321    [invocation invoke];
322  }
323}
324
325- (void)destroyWindow {
326  // no request; close the window
327
328  // Avoid more callbacks after the close happens, as the window
329  // controller may be gone.
330  [self.webView stopLoading:nil];
331
332  NSWindow *parentWindow = self.sheetModalForWindow;
333  if (parentWindow) {
334    [NSApp endSheet:[self window]];
335  } else {
336    // defer closing the window, in case we're responding to some window event
337    [[self window] performSelector:@selector(close)
338                        withObject:nil
339                        afterDelay:0.1
340                           inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
341
342  }
343  isWindowShown_ = NO;
344}
345
346- (void)handlePrematureWindowClose {
347  if (!hasDoneFinalRedirect_) {
348    // tell the sign-in object to tell the user's finished method
349    // that we're done
350    [self.signIn windowWasClosed];
351    hasDoneFinalRedirect_ = YES;
352  }
353}
354
355- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
356  [sheet orderOut:self];
357
358  self.sheetModalForWindow = nil;
359}
360
361- (void)signIn:(GTMOAuth2SignIn *)signIn finishedWithAuth:(GTMOAuth2Authentication *)auth error:(NSError *)error {
362  if (!hasCalledFinished_) {
363    hasCalledFinished_ = YES;
364
365    if (error == nil) {
366      BOOL shouldUseKeychain = [self shouldUseKeychain];
367      if (shouldUseKeychain) {
368        BOOL canAuthorize = auth.canAuthorize;
369        BOOL isKeychainChecked = ([keychainCheckbox_ state] == NSOnState);
370
371        NSString *keychainItemName = self.keychainItemName;
372
373        if (isKeychainChecked && canAuthorize) {
374          // save the auth params in the keychain
375          [[self class] saveAuthToKeychainForName:keychainItemName
376                                            authentication:auth];
377        } else {
378          // remove the auth params from the keychain
379          [[self class] removeAuthFromKeychainForName:keychainItemName];
380        }
381      }
382    }
383
384    if (delegate_ && finishedSelector_) {
385      SEL sel = finishedSelector_;
386      NSMethodSignature *sig = [delegate_ methodSignatureForSelector:sel];
387      NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
388      [invocation setSelector:sel];
389      [invocation setTarget:delegate_];
390      [invocation setArgument:&self atIndex:2];
391      [invocation setArgument:&auth atIndex:3];
392      [invocation setArgument:&error atIndex:4];
393      [invocation invoke];
394    }
395
396    [delegate_ autorelease];
397    delegate_ = nil;
398
399#if NS_BLOCKS_AVAILABLE
400    if (completionBlock_) {
401      completionBlock_(auth, error);
402
403      // release the block here to avoid a retain loop on the controller
404      [completionBlock_ autorelease];
405      completionBlock_ = nil;
406    }
407#endif
408  }
409}
410
411static Class gSignInClass = Nil;
412
413+ (Class)signInClass {
414  if (gSignInClass == Nil) {
415    gSignInClass = [GTMOAuth2SignIn class];
416  }
417  return gSignInClass;
418}
419
420+ (void)setSignInClass:(Class)theClass {
421  gSignInClass = theClass;
422}
423
424#pragma mark Token Revocation
425
426#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
427+ (void)revokeTokenForGoogleAuthentication:(GTMOAuth2Authentication *)auth {
428  [[self signInClass] revokeTokenForGoogleAuthentication:auth];
429}
430#endif
431
432#pragma mark WebView methods
433
434- (NSURLRequest *)webView:(WebView *)sender resource:(id)identifier willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse fromDataSource:(WebDataSource *)dataSource {
435  // override WebKit's cookie storage with our own to avoid cookie persistence
436  // across sign-ins and interaction with the Safari browser's sign-in state
437  [self handleCookiesForResponse:redirectResponse];
438  request = [self addCookiesToRequest:request];
439
440  if (!hasDoneFinalRedirect_) {
441    hasDoneFinalRedirect_ = [self.signIn requestRedirectedToRequest:request];
442    if (hasDoneFinalRedirect_) {
443      // signIn has told the window to close
444      return nil;
445    }
446  }
447  return request;
448}
449
450- (void)webView:(WebView *)sender resource:(id)identifier didReceiveResponse:(NSURLResponse *)response fromDataSource:(WebDataSource *)dataSource {
451  // override WebKit's cookie storage with our own
452  [self handleCookiesForResponse:response];
453}
454
455- (void)webView:(WebView *)sender resource:(id)identifier didFinishLoadingFromDataSource:(WebDataSource *)dataSource {
456  NSString *title = [sender stringByEvaluatingJavaScriptFromString:@"document.title"];
457  if ([title length] > 0) {
458    [self.signIn titleChanged:title];
459  }
460
461  [signIn_ cookiesChanged:(NSHTTPCookieStorage *)cookieStorage_];
462}
463
464- (void)webView:(WebView *)sender resource:(id)identifier didFailLoadingWithError:(NSError *)error fromDataSource:(WebDataSource *)dataSource {
465  [self.signIn loadFailedWithError:error];
466}
467
468- (void)windowWillClose:(NSNotification *)note {
469  if (isWindowShown_) {
470    [self handlePrematureWindowClose];
471  }
472  isWindowShown_ = NO;
473}
474
475- (void)webView:(WebView *)webView
476decidePolicyForNewWindowAction:(NSDictionary *)actionInformation
477        request:(NSURLRequest *)request
478   newFrameName:(NSString *)frameName
479decisionListener:(id<WebPolicyDecisionListener>)listener {
480  SEL sel = self.externalRequestSelector;
481  if (sel) {
482    [delegate_ performSelector:sel
483                    withObject:self
484                    withObject:request];
485  } else {
486    // default behavior is to open the URL in NSWorkspace's default browser
487    NSURL *url = [request URL];
488    [[NSWorkspace sharedWorkspace] openURL:url];
489  }
490  [listener ignore];
491}
492
493#pragma mark Cookie management
494
495// Rather than let the WebView use Safari's default cookie storage, we intercept
496// requests and response to segregate and later discard cookies from signing in.
497//
498// This allows the application to actually sign out by discarding the auth token
499// rather than the user being kept signed in by the cookies.
500
501- (void)handleCookiesForResponse:(NSURLResponse *)response {
502  if (self.shouldPersistUser) {
503    // we'll let WebKit handle the cookies; they'll persist across apps
504    // and across runs of this app
505    return;
506  }
507
508  if ([response respondsToSelector:@selector(allHeaderFields)]) {
509    // grab the cookies from the header as NSHTTPCookies and store them locally
510    NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
511    if (headers) {
512      NSURL *url = [response URL];
513      NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:headers
514                                                                forURL:url];
515      if ([cookies count] > 0) {
516        [cookieStorage_ setCookies:cookies];
517      }
518    }
519  }
520}
521
522- (NSURLRequest *)addCookiesToRequest:(NSURLRequest *)request {
523  if (self.shouldPersistUser) {
524    // we'll let WebKit handle the cookies; they'll persist across apps
525    // and across runs of this app
526    return request;
527  }
528
529  // override WebKit's usual automatic storage of cookies
530  NSMutableURLRequest *mutableRequest = [[request mutableCopy] autorelease];
531  [mutableRequest setHTTPShouldHandleCookies:NO];
532
533  // add our locally-stored cookies for this URL, if any
534  NSArray *cookies = [cookieStorage_ cookiesForURL:[request URL]];
535  if ([cookies count] > 0) {
536    NSDictionary *headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
537    NSString *cookieHeader = [headers objectForKey:@"Cookie"];
538    if (cookieHeader) {
539      [mutableRequest setValue:cookieHeader forHTTPHeaderField:@"Cookie"];
540    }
541  }
542  return mutableRequest;
543}
544
545#pragma mark Keychain support
546
547+ (NSString *)prefsKeyForName:(NSString *)keychainItemName {
548  NSString *result = [@"OAuth2: " stringByAppendingString:keychainItemName];
549  return result;
550}
551
552+ (BOOL)saveAuthToKeychainForName:(NSString *)keychainItemName
553                            authentication:(GTMOAuth2Authentication *)auth {
554
555  [self removeAuthFromKeychainForName:keychainItemName];
556
557  // don't save unless we have a token that can really authorize requests
558  if (!auth.canAuthorize) return NO;
559
560  // make a response string containing the values we want to save
561  NSString *password = [auth persistenceResponseString];
562
563  SecKeychainRef defaultKeychain = NULL;
564  SecKeychainItemRef *dontWantItemRef= NULL;
565  const char *utf8ServiceName = [keychainItemName UTF8String];
566  const char *utf8Password = [password UTF8String];
567
568  OSStatus err = SecKeychainAddGenericPassword(defaultKeychain,
569                             (UInt32) strlen(utf8ServiceName), utf8ServiceName,
570                             (UInt32) strlen(kKeychainAccountName), kKeychainAccountName,
571                             (UInt32) strlen(utf8Password), utf8Password,
572                             dontWantItemRef);
573  BOOL didSucceed = (err == noErr);
574  if (didSucceed) {
575    // write to preferences that we have a keychain item (so we know later
576    // that we can read from the keychain without raising a permissions dialog)
577    NSString *prefKey = [self prefsKeyForName:keychainItemName];
578    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
579    [defaults setBool:YES forKey:prefKey];
580  }
581
582  return didSucceed;
583}
584
585+ (BOOL)removeAuthFromKeychainForName:(NSString *)keychainItemName {
586
587  SecKeychainRef defaultKeychain = NULL;
588  SecKeychainItemRef itemRef = NULL;
589  const char *utf8ServiceName = [keychainItemName UTF8String];
590
591  // we don't really care about the password here, we just want to
592  // get the SecKeychainItemRef so we can delete it.
593  OSStatus err = SecKeychainFindGenericPassword (defaultKeychain,
594                                   (UInt32) strlen(utf8ServiceName), utf8ServiceName,
595                                   (UInt32) strlen(kKeychainAccountName), kKeychainAccountName,
596                                   0, NULL, // ignore password
597                                   &itemRef);
598  if (err != noErr) {
599    // failure to find is success
600    return YES;
601  } else {
602    // found something, so delete it
603    err = SecKeychainItemDelete(itemRef);
604    CFRelease(itemRef);
605
606    // remove our preference key
607    NSString *prefKey = [self prefsKeyForName:keychainItemName];
608    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
609    [defaults removeObjectForKey:prefKey];
610
611    return (err == noErr);
612  }
613}
614
615#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
616+ (GTMOAuth2Authentication *)authForGoogleFromKeychainForName:(NSString *)keychainItemName
617                                                     clientID:(NSString *)clientID
618                                                 clientSecret:(NSString *)clientSecret {
619  Class signInClass = [self signInClass];
620  NSURL *tokenURL = [signInClass googleTokenURL];
621  NSString *redirectURI = [signInClass nativeClientRedirectURI];
622
623  GTMOAuth2Authentication *auth;
624  auth = [GTMOAuth2Authentication authenticationWithServiceProvider:kGTMOAuth2ServiceProviderGoogle
625                                                           tokenURL:tokenURL
626                                                        redirectURI:redirectURI
627                                                           clientID:clientID
628                                                       clientSecret:clientSecret];
629  
630  [GTMOAuth2WindowController authorizeFromKeychainForName:keychainItemName
631                                           authentication:auth];
632  return auth;
633}
634#endif
635
636+ (BOOL)authorizeFromKeychainForName:(NSString *)keychainItemName
637                      authentication:(GTMOAuth2Authentication *)newAuth {
638  [newAuth setAccessToken:nil];
639
640  // before accessing the keychain, check preferences to verify that we've
641  // previously saved a token to the keychain (so we don't needlessly raise
642  // a keychain access permission dialog)
643  NSString *prefKey = [self prefsKeyForName:keychainItemName];
644  NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
645  BOOL flag = [defaults boolForKey:prefKey];
646  if (!flag) {
647    return NO;
648  }
649
650  BOOL didGetTokens = NO;
651
652  SecKeychainRef defaultKeychain = NULL;
653  const char *utf8ServiceName = [keychainItemName UTF8String];
654  SecKeychainItemRef *dontWantItemRef = NULL;
655
656  void *passwordBuff = NULL;
657  UInt32 passwordBuffLength = 0;
658
659  OSStatus err = SecKeychainFindGenericPassword(defaultKeychain,
660                                  (UInt32) strlen(utf8ServiceName), utf8ServiceName,
661                                  (UInt32) strlen(kKeychainAccountName), kKeychainAccountName,
662                                  &passwordBuffLength, &passwordBuff,
663                                  dontWantItemRef);
664  if (err == noErr && passwordBuff != NULL) {
665
666    NSString *password = [[[NSString alloc] initWithBytes:passwordBuff
667                                                   length:passwordBuffLength
668                                                 encoding:NSUTF8StringEncoding] autorelease];
669
670    // free the password buffer that was allocated above
671    SecKeychainItemFreeContent(NULL, passwordBuff);
672
673    if (password != nil) {
674      [newAuth setKeysForResponseString:password];
675      didGetTokens = YES;
676    }
677  }
678  return didGetTokens;
679}
680
681#pragma mark User Properties
682
683- (void)setProperty:(id)obj forKey:(NSString *)key {
684  if (obj == nil) {
685    // User passed in nil, so delete the property
686    [properties_ removeObjectForKey:key];
687  } else {
688    // Be sure the property dictionary exists
689    if (properties_ == nil) {
690      [self setProperties:[NSMutableDictionary dictionary]];
691    }
692    [properties_ setObject:obj forKey:key];
693  }
694}
695
696- (id)propertyForKey:(NSString *)key {
697  id obj = [properties_ objectForKey:key];
698
699  // Be sure the returned pointer has the life of the autorelease pool,
700  // in case self is released immediately
701  return [[obj retain] autorelease];
702}
703
704#pragma mark Accessors
705
706- (GTMOAuth2Authentication *)authentication {
707  return self.signIn.authentication; 
708}
709
710- (void)setNetworkLossTimeoutInterval:(NSTimeInterval)val {
711  self.signIn.networkLossTimeoutInterval = val;
712}
713
714- (NSTimeInterval)networkLossTimeoutInterval {
715  return self.signIn.networkLossTimeoutInterval;
716}
717
718- (BOOL)shouldUseKeychain {
719  NSString *name = self.keychainItemName;
720  return ([name length] > 0);
721}
722
723@end
724
725#endif // #if !TARGET_OS_IPHONE
726
727#endif // #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES