aboutsummaryrefslogtreecommitdiffhomepage
path: root/example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.m
diff options
context:
space:
mode:
Diffstat (limited to 'example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.m')
-rw-r--r--example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.m727
1 files changed, 727 insertions, 0 deletions
diff --git a/example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.m b/example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.m
new file mode 100644
index 00000000..948975bf
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.m
@@ -0,0 +1,727 @@
+/* Copyright (c) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
+
+#if !TARGET_OS_IPHONE
+
+#import "GTMOAuth2WindowController.h"
+
+@interface GTMOAuth2WindowController ()
+@property (nonatomic, retain) GTMOAuth2SignIn *signIn;
+@property (nonatomic, copy) NSURLRequest *initialRequest;
+@property (nonatomic, retain) GTMCookieStorage *cookieStorage;
+@property (nonatomic, retain) NSWindow *sheetModalForWindow;
+
+- (void)signInCommonForWindow:(NSWindow *)parentWindowOrNil;
+- (void)setupSheetTerminationHandling;
+- (void)destroyWindow;
+- (void)handlePrematureWindowClose;
+- (BOOL)shouldUseKeychain;
+- (void)signIn:(GTMOAuth2SignIn *)signIn displayRequest:(NSURLRequest *)request;
+- (void)signIn:(GTMOAuth2SignIn *)signIn finishedWithAuth:(GTMOAuth2Authentication *)auth error:(NSError *)error;
+- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo;
+
+- (void)handleCookiesForResponse:(NSURLResponse *)response;
+- (NSURLRequest *)addCookiesToRequest:(NSURLRequest *)request;
+@end
+
+const char *kKeychainAccountName = "OAuth";
+
+@implementation GTMOAuth2WindowController
+
+// IBOutlets
+@synthesize keychainCheckbox = keychainCheckbox_,
+ webView = webView_,
+ webCloseButton = webCloseButton_,
+ webBackButton = webBackButton_;
+
+// regular ivars
+@synthesize signIn = signIn_,
+ initialRequest = initialRequest_,
+ cookieStorage = cookieStorage_,
+ sheetModalForWindow = sheetModalForWindow_,
+ keychainItemName = keychainItemName_,
+ initialHTMLString = initialHTMLString_,
+ shouldAllowApplicationTermination = shouldAllowApplicationTermination_,
+ externalRequestSelector = externalRequestSelector_,
+ shouldPersistUser = shouldPersistUser_,
+ userData = userData_,
+ properties = properties_;
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+// Create a controller for authenticating to Google services
++ (id)controllerWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ resourceBundle:(NSBundle *)bundle {
+ return [[[self alloc] initWithScope:scope
+ clientID:clientID
+ clientSecret:clientSecret
+ keychainItemName:keychainItemName
+ resourceBundle:bundle] autorelease];
+}
+
+- (id)initWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ resourceBundle:(NSBundle *)bundle {
+ Class signInClass = [[self class] signInClass];
+ GTMOAuth2Authentication *auth;
+ auth = [signInClass standardGoogleAuthenticationForScope:scope
+ clientID:clientID
+ clientSecret:clientSecret];
+ NSURL *authorizationURL = [signInClass googleAuthorizationURL];
+ return [self initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ keychainItemName:keychainItemName
+ resourceBundle:bundle];
+}
+#endif
+
+// Create a controller for authenticating to any service
++ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ resourceBundle:(NSBundle *)bundle {
+ return [[[self alloc] initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ keychainItemName:keychainItemName
+ resourceBundle:bundle] autorelease];
+}
+
+- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ resourceBundle:(NSBundle *)bundle {
+ if (bundle == nil) {
+ bundle = [NSBundle mainBundle];
+ }
+
+ NSString *nibName = [[self class] authNibName];
+ NSString *nibPath = [bundle pathForResource:nibName
+ ofType:@"nib"];
+ self = [super initWithWindowNibPath:nibPath
+ owner:self];
+ if (self != nil) {
+ // use the supplied auth and OAuth endpoint URLs
+ Class signInClass = [[self class] signInClass];
+ signIn_ = [[signInClass alloc] initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ delegate:self
+ webRequestSelector:@selector(signIn:displayRequest:)
+ finishedSelector:@selector(signIn:finishedWithAuth:error:)];
+ keychainItemName_ = [keychainItemName copy];
+
+ // create local, temporary storage for WebKit cookies
+ cookieStorage_ = [[GTMCookieStorage alloc] init];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [signIn_ release];
+ [initialRequest_ release];
+ [cookieStorage_ release];
+ [delegate_ release];
+#if NS_BLOCKS_AVAILABLE
+ [completionBlock_ release];
+#endif
+ [sheetModalForWindow_ release];
+ [keychainItemName_ release];
+ [initialHTMLString_ release];
+ [userData_ release];
+ [properties_ release];
+
+ [super dealloc];
+}
+
+- (void)awakeFromNib {
+ // load the requested initial sign-in page
+ [self.webView setResourceLoadDelegate:self];
+ [self.webView setPolicyDelegate:self];
+
+ // the app may prefer some html other than blank white to be displayed
+ // before the sign-in web page loads
+ NSString *html = self.initialHTMLString;
+ if ([html length] > 0) {
+ [[self.webView mainFrame] loadHTMLString:html baseURL:nil];
+ }
+
+ // hide the keychain checkbox if we're not supporting keychain
+ BOOL hideKeychainCheckbox = ![self shouldUseKeychain];
+
+ const NSTimeInterval kJanuary2011 = 1293840000;
+ BOOL isDateValid = ([[NSDate date] timeIntervalSince1970] > kJanuary2011);
+ if (isDateValid) {
+ // start the asynchronous load of the sign-in web page
+ [[self.webView mainFrame] performSelector:@selector(loadRequest:)
+ withObject:self.initialRequest
+ afterDelay:0.01
+ inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
+ } else {
+ // clock date is invalid, so signing in would fail with an unhelpful error
+ // from the server. Warn the user in an html string showing a watch icon,
+ // question mark, and the system date and time. Hopefully this will clue
+ // in brighter users, or at least let them make a useful screenshot to show
+ // to developers.
+ //
+ // Even better is for apps to check the system clock and show some more
+ // helpful, localized instructions for users; this is really a fallback.
+ NSString *const htmlTemplate = @"<html><body><div align=center><font size='7'>"
+ @"&#x231A; ?<br><i>System Clock Incorrect</i><br>%@"
+ @"</font></div></body></html>";
+ NSString *errHTML = [NSString stringWithFormat:htmlTemplate, [NSDate date]];
+
+ [[webView_ mainFrame] loadHTMLString:errHTML baseURL:nil];
+ hideKeychainCheckbox = YES;
+ }
+
+#if DEBUG
+ // Verify that Javascript is enabled
+ BOOL hasJS = [[webView_ preferences] isJavaScriptEnabled];
+ NSAssert(hasJS, @"GTMOAuth2: Javascript is required");
+#endif
+
+ [keychainCheckbox_ setHidden:hideKeychainCheckbox];
+}
+
++ (NSString *)authNibName {
+ // subclasses may override this to specify a custom nib name
+ return @"GTMOAuth2Window";
+}
+
+#pragma mark -
+
+- (void)signInSheetModalForWindow:(NSWindow *)parentWindowOrNil
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector {
+ // check the selector on debug builds
+ GTMAssertSelectorNilOrImplementedWithArgs(delegate, finishedSelector,
+ @encode(GTMOAuth2WindowController *), @encode(GTMOAuth2Authentication *),
+ @encode(NSError *), 0);
+
+ delegate_ = [delegate retain];
+ finishedSelector_ = finishedSelector;
+
+ [self signInCommonForWindow:parentWindowOrNil];
+}
+
+#if NS_BLOCKS_AVAILABLE
+- (void)signInSheetModalForWindow:(NSWindow *)parentWindowOrNil
+ completionHandler:(void (^)(GTMOAuth2Authentication *, NSError *))handler {
+ completionBlock_ = [handler copy];
+
+ [self signInCommonForWindow:parentWindowOrNil];
+}
+#endif
+
+- (void)signInCommonForWindow:(NSWindow *)parentWindowOrNil {
+ self.sheetModalForWindow = parentWindowOrNil;
+ hasDoneFinalRedirect_ = NO;
+ hasCalledFinished_ = NO;
+
+ [self.signIn startSigningIn];
+}
+
+- (void)cancelSigningIn {
+ // The user has explicitly asked us to cancel signing in
+ // (so no further callback is required)
+ hasCalledFinished_ = YES;
+
+ [delegate_ autorelease];
+ delegate_ = nil;
+
+#if NS_BLOCKS_AVAILABLE
+ [completionBlock_ autorelease];
+ completionBlock_ = nil;
+#endif
+
+ // The signIn object's cancel method will close the window
+ [self.signIn cancelSigningIn];
+ hasDoneFinalRedirect_ = YES;
+}
+
+- (IBAction)closeWindow:(id)sender {
+ // dismiss the window/sheet before we call back the client
+ [self destroyWindow];
+ [self handlePrematureWindowClose];
+}
+
+#pragma mark SignIn callbacks
+
+- (void)signIn:(GTMOAuth2SignIn *)signIn displayRequest:(NSURLRequest *)request {
+ // this is the signIn object's webRequest method, telling the controller
+ // to either display the request in the webview, or close the window
+ //
+ // All web requests and all window closing goes through this routine
+
+#if DEBUG
+ if ((isWindowShown_ && request != nil)
+ || (!isWindowShown_ && request == nil)) {
+ NSLog(@"Window state unexpected for request %@", [request URL]);
+ return;
+ }
+#endif
+
+ if (request != nil) {
+ // display the request
+ self.initialRequest = request;
+
+ NSWindow *parentWindow = self.sheetModalForWindow;
+ if (parentWindow) {
+ [self setupSheetTerminationHandling];
+
+ NSWindow *sheet = [self window];
+ [NSApp beginSheet:sheet
+ modalForWindow:parentWindow
+ modalDelegate:self
+ didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:)
+ contextInfo:nil];
+ } else {
+ // modeless
+ [self showWindow:self];
+ }
+ isWindowShown_ = YES;
+ } else {
+ // request was nil
+ [self destroyWindow];
+ }
+}
+
+- (void)setupSheetTerminationHandling {
+ NSWindow *sheet = [self window];
+
+ SEL sel = @selector(setPreventsApplicationTerminationWhenModal:);
+ if ([sheet respondsToSelector:sel]) {
+ // setPreventsApplicationTerminationWhenModal is available in NSWindow
+ // on 10.6 and later
+ BOOL boolVal = !self.shouldAllowApplicationTermination;
+ NSMethodSignature *sig = [sheet methodSignatureForSelector:sel];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
+ [invocation setSelector:sel];
+ [invocation setTarget:sheet];
+ [invocation setArgument:&boolVal atIndex:2];
+ [invocation invoke];
+ }
+}
+
+- (void)destroyWindow {
+ // no request; close the window
+
+ // Avoid more callbacks after the close happens, as the window
+ // controller may be gone.
+ [self.webView stopLoading:nil];
+
+ NSWindow *parentWindow = self.sheetModalForWindow;
+ if (parentWindow) {
+ [NSApp endSheet:[self window]];
+ } else {
+ // defer closing the window, in case we're responding to some window event
+ [[self window] performSelector:@selector(close)
+ withObject:nil
+ afterDelay:0.1
+ inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
+
+ }
+ isWindowShown_ = NO;
+}
+
+- (void)handlePrematureWindowClose {
+ if (!hasDoneFinalRedirect_) {
+ // tell the sign-in object to tell the user's finished method
+ // that we're done
+ [self.signIn windowWasClosed];
+ hasDoneFinalRedirect_ = YES;
+ }
+}
+
+- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
+ [sheet orderOut:self];
+
+ self.sheetModalForWindow = nil;
+}
+
+- (void)signIn:(GTMOAuth2SignIn *)signIn finishedWithAuth:(GTMOAuth2Authentication *)auth error:(NSError *)error {
+ if (!hasCalledFinished_) {
+ hasCalledFinished_ = YES;
+
+ if (error == nil) {
+ BOOL shouldUseKeychain = [self shouldUseKeychain];
+ if (shouldUseKeychain) {
+ BOOL canAuthorize = auth.canAuthorize;
+ BOOL isKeychainChecked = ([keychainCheckbox_ state] == NSOnState);
+
+ NSString *keychainItemName = self.keychainItemName;
+
+ if (isKeychainChecked && canAuthorize) {
+ // save the auth params in the keychain
+ [[self class] saveAuthToKeychainForName:keychainItemName
+ authentication:auth];
+ } else {
+ // remove the auth params from the keychain
+ [[self class] removeAuthFromKeychainForName:keychainItemName];
+ }
+ }
+ }
+
+ if (delegate_ && finishedSelector_) {
+ SEL sel = finishedSelector_;
+ NSMethodSignature *sig = [delegate_ methodSignatureForSelector:sel];
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
+ [invocation setSelector:sel];
+ [invocation setTarget:delegate_];
+ [invocation setArgument:&self atIndex:2];
+ [invocation setArgument:&auth atIndex:3];
+ [invocation setArgument:&error atIndex:4];
+ [invocation invoke];
+ }
+
+ [delegate_ autorelease];
+ delegate_ = nil;
+
+#if NS_BLOCKS_AVAILABLE
+ if (completionBlock_) {
+ completionBlock_(auth, error);
+
+ // release the block here to avoid a retain loop on the controller
+ [completionBlock_ autorelease];
+ completionBlock_ = nil;
+ }
+#endif
+ }
+}
+
+static Class gSignInClass = Nil;
+
++ (Class)signInClass {
+ if (gSignInClass == Nil) {
+ gSignInClass = [GTMOAuth2SignIn class];
+ }
+ return gSignInClass;
+}
+
++ (void)setSignInClass:(Class)theClass {
+ gSignInClass = theClass;
+}
+
+#pragma mark Token Revocation
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (void)revokeTokenForGoogleAuthentication:(GTMOAuth2Authentication *)auth {
+ [[self signInClass] revokeTokenForGoogleAuthentication:auth];
+}
+#endif
+
+#pragma mark WebView methods
+
+- (NSURLRequest *)webView:(WebView *)sender resource:(id)identifier willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse fromDataSource:(WebDataSource *)dataSource {
+ // override WebKit's cookie storage with our own to avoid cookie persistence
+ // across sign-ins and interaction with the Safari browser's sign-in state
+ [self handleCookiesForResponse:redirectResponse];
+ request = [self addCookiesToRequest:request];
+
+ if (!hasDoneFinalRedirect_) {
+ hasDoneFinalRedirect_ = [self.signIn requestRedirectedToRequest:request];
+ if (hasDoneFinalRedirect_) {
+ // signIn has told the window to close
+ return nil;
+ }
+ }
+ return request;
+}
+
+- (void)webView:(WebView *)sender resource:(id)identifier didReceiveResponse:(NSURLResponse *)response fromDataSource:(WebDataSource *)dataSource {
+ // override WebKit's cookie storage with our own
+ [self handleCookiesForResponse:response];
+}
+
+- (void)webView:(WebView *)sender resource:(id)identifier didFinishLoadingFromDataSource:(WebDataSource *)dataSource {
+ NSString *title = [sender stringByEvaluatingJavaScriptFromString:@"document.title"];
+ if ([title length] > 0) {
+ [self.signIn titleChanged:title];
+ }
+
+ [signIn_ cookiesChanged:(NSHTTPCookieStorage *)cookieStorage_];
+}
+
+- (void)webView:(WebView *)sender resource:(id)identifier didFailLoadingWithError:(NSError *)error fromDataSource:(WebDataSource *)dataSource {
+ [self.signIn loadFailedWithError:error];
+}
+
+- (void)windowWillClose:(NSNotification *)note {
+ if (isWindowShown_) {
+ [self handlePrematureWindowClose];
+ }
+ isWindowShown_ = NO;
+}
+
+- (void)webView:(WebView *)webView
+decidePolicyForNewWindowAction:(NSDictionary *)actionInformation
+ request:(NSURLRequest *)request
+ newFrameName:(NSString *)frameName
+decisionListener:(id<WebPolicyDecisionListener>)listener {
+ SEL sel = self.externalRequestSelector;
+ if (sel) {
+ [delegate_ performSelector:sel
+ withObject:self
+ withObject:request];
+ } else {
+ // default behavior is to open the URL in NSWorkspace's default browser
+ NSURL *url = [request URL];
+ [[NSWorkspace sharedWorkspace] openURL:url];
+ }
+ [listener ignore];
+}
+
+#pragma mark Cookie management
+
+// Rather than let the WebView use Safari's default cookie storage, we intercept
+// requests and response to segregate and later discard cookies from signing in.
+//
+// This allows the application to actually sign out by discarding the auth token
+// rather than the user being kept signed in by the cookies.
+
+- (void)handleCookiesForResponse:(NSURLResponse *)response {
+ if (self.shouldPersistUser) {
+ // we'll let WebKit handle the cookies; they'll persist across apps
+ // and across runs of this app
+ return;
+ }
+
+ if ([response respondsToSelector:@selector(allHeaderFields)]) {
+ // grab the cookies from the header as NSHTTPCookies and store them locally
+ NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
+ if (headers) {
+ NSURL *url = [response URL];
+ NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:headers
+ forURL:url];
+ if ([cookies count] > 0) {
+ [cookieStorage_ setCookies:cookies];
+ }
+ }
+ }
+}
+
+- (NSURLRequest *)addCookiesToRequest:(NSURLRequest *)request {
+ if (self.shouldPersistUser) {
+ // we'll let WebKit handle the cookies; they'll persist across apps
+ // and across runs of this app
+ return request;
+ }
+
+ // override WebKit's usual automatic storage of cookies
+ NSMutableURLRequest *mutableRequest = [[request mutableCopy] autorelease];
+ [mutableRequest setHTTPShouldHandleCookies:NO];
+
+ // add our locally-stored cookies for this URL, if any
+ NSArray *cookies = [cookieStorage_ cookiesForURL:[request URL]];
+ if ([cookies count] > 0) {
+ NSDictionary *headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
+ NSString *cookieHeader = [headers objectForKey:@"Cookie"];
+ if (cookieHeader) {
+ [mutableRequest setValue:cookieHeader forHTTPHeaderField:@"Cookie"];
+ }
+ }
+ return mutableRequest;
+}
+
+#pragma mark Keychain support
+
++ (NSString *)prefsKeyForName:(NSString *)keychainItemName {
+ NSString *result = [@"OAuth2: " stringByAppendingString:keychainItemName];
+ return result;
+}
+
++ (BOOL)saveAuthToKeychainForName:(NSString *)keychainItemName
+ authentication:(GTMOAuth2Authentication *)auth {
+
+ [self removeAuthFromKeychainForName:keychainItemName];
+
+ // don't save unless we have a token that can really authorize requests
+ if (!auth.canAuthorize) return NO;
+
+ // make a response string containing the values we want to save
+ NSString *password = [auth persistenceResponseString];
+
+ SecKeychainRef defaultKeychain = NULL;
+ SecKeychainItemRef *dontWantItemRef= NULL;
+ const char *utf8ServiceName = [keychainItemName UTF8String];
+ const char *utf8Password = [password UTF8String];
+
+ OSStatus err = SecKeychainAddGenericPassword(defaultKeychain,
+ (UInt32) strlen(utf8ServiceName), utf8ServiceName,
+ (UInt32) strlen(kKeychainAccountName), kKeychainAccountName,
+ (UInt32) strlen(utf8Password), utf8Password,
+ dontWantItemRef);
+ BOOL didSucceed = (err == noErr);
+ if (didSucceed) {
+ // write to preferences that we have a keychain item (so we know later
+ // that we can read from the keychain without raising a permissions dialog)
+ NSString *prefKey = [self prefsKeyForName:keychainItemName];
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ [defaults setBool:YES forKey:prefKey];
+ }
+
+ return didSucceed;
+}
+
++ (BOOL)removeAuthFromKeychainForName:(NSString *)keychainItemName {
+
+ SecKeychainRef defaultKeychain = NULL;
+ SecKeychainItemRef itemRef = NULL;
+ const char *utf8ServiceName = [keychainItemName UTF8String];
+
+ // we don't really care about the password here, we just want to
+ // get the SecKeychainItemRef so we can delete it.
+ OSStatus err = SecKeychainFindGenericPassword (defaultKeychain,
+ (UInt32) strlen(utf8ServiceName), utf8ServiceName,
+ (UInt32) strlen(kKeychainAccountName), kKeychainAccountName,
+ 0, NULL, // ignore password
+ &itemRef);
+ if (err != noErr) {
+ // failure to find is success
+ return YES;
+ } else {
+ // found something, so delete it
+ err = SecKeychainItemDelete(itemRef);
+ CFRelease(itemRef);
+
+ // remove our preference key
+ NSString *prefKey = [self prefsKeyForName:keychainItemName];
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ [defaults removeObjectForKey:prefKey];
+
+ return (err == noErr);
+ }
+}
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (GTMOAuth2Authentication *)authForGoogleFromKeychainForName:(NSString *)keychainItemName
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret {
+ Class signInClass = [self signInClass];
+ NSURL *tokenURL = [signInClass googleTokenURL];
+ NSString *redirectURI = [signInClass nativeClientRedirectURI];
+
+ GTMOAuth2Authentication *auth;
+ auth = [GTMOAuth2Authentication authenticationWithServiceProvider:kGTMOAuth2ServiceProviderGoogle
+ tokenURL:tokenURL
+ redirectURI:redirectURI
+ clientID:clientID
+ clientSecret:clientSecret];
+
+ [GTMOAuth2WindowController authorizeFromKeychainForName:keychainItemName
+ authentication:auth];
+ return auth;
+}
+#endif
+
++ (BOOL)authorizeFromKeychainForName:(NSString *)keychainItemName
+ authentication:(GTMOAuth2Authentication *)newAuth {
+ [newAuth setAccessToken:nil];
+
+ // before accessing the keychain, check preferences to verify that we've
+ // previously saved a token to the keychain (so we don't needlessly raise
+ // a keychain access permission dialog)
+ NSString *prefKey = [self prefsKeyForName:keychainItemName];
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ BOOL flag = [defaults boolForKey:prefKey];
+ if (!flag) {
+ return NO;
+ }
+
+ BOOL didGetTokens = NO;
+
+ SecKeychainRef defaultKeychain = NULL;
+ const char *utf8ServiceName = [keychainItemName UTF8String];
+ SecKeychainItemRef *dontWantItemRef = NULL;
+
+ void *passwordBuff = NULL;
+ UInt32 passwordBuffLength = 0;
+
+ OSStatus err = SecKeychainFindGenericPassword(defaultKeychain,
+ (UInt32) strlen(utf8ServiceName), utf8ServiceName,
+ (UInt32) strlen(kKeychainAccountName), kKeychainAccountName,
+ &passwordBuffLength, &passwordBuff,
+ dontWantItemRef);
+ if (err == noErr && passwordBuff != NULL) {
+
+ NSString *password = [[[NSString alloc] initWithBytes:passwordBuff
+ length:passwordBuffLength
+ encoding:NSUTF8StringEncoding] autorelease];
+
+ // free the password buffer that was allocated above
+ SecKeychainItemFreeContent(NULL, passwordBuff);
+
+ if (password != nil) {
+ [newAuth setKeysForResponseString:password];
+ didGetTokens = YES;
+ }
+ }
+ return didGetTokens;
+}
+
+#pragma mark User Properties
+
+- (void)setProperty:(id)obj forKey:(NSString *)key {
+ if (obj == nil) {
+ // User passed in nil, so delete the property
+ [properties_ removeObjectForKey:key];
+ } else {
+ // Be sure the property dictionary exists
+ if (properties_ == nil) {
+ [self setProperties:[NSMutableDictionary dictionary]];
+ }
+ [properties_ setObject:obj forKey:key];
+ }
+}
+
+- (id)propertyForKey:(NSString *)key {
+ id obj = [properties_ objectForKey:key];
+
+ // Be sure the returned pointer has the life of the autorelease pool,
+ // in case self is released immediately
+ return [[obj retain] autorelease];
+}
+
+#pragma mark Accessors
+
+- (GTMOAuth2Authentication *)authentication {
+ return self.signIn.authentication;
+}
+
+- (void)setNetworkLossTimeoutInterval:(NSTimeInterval)val {
+ self.signIn.networkLossTimeoutInterval = val;
+}
+
+- (NSTimeInterval)networkLossTimeoutInterval {
+ return self.signIn.networkLossTimeoutInterval;
+}
+
+- (BOOL)shouldUseKeychain {
+ NSString *name = self.keychainItemName;
+ return ([name length] > 0);
+}
+
+@end
+
+#endif // #if !TARGET_OS_IPHONE
+
+#endif // #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES