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