aboutsummaryrefslogtreecommitdiffhomepage
path: root/example/common/gtm-oauth2/Source/Mac
diff options
context:
space:
mode:
Diffstat (limited to 'example/common/gtm-oauth2/Source/Mac')
-rw-r--r--example/common/gtm-oauth2/Source/Mac/GTMOAuth2Window.xib109
-rw-r--r--example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.h332
-rw-r--r--example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.m727
3 files changed, 1168 insertions, 0 deletions
diff --git a/example/common/gtm-oauth2/Source/Mac/GTMOAuth2Window.xib b/example/common/gtm-oauth2/Source/Mac/GTMOAuth2Window.xib
new file mode 100644
index 00000000..befc2123
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/Mac/GTMOAuth2Window.xib
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="4457.6" systemVersion="12E55" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
+ <dependencies>
+ <deployment version="1050" defaultVersion="1080" identifier="macosx"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="4457.6"/>
+ <plugIn identifier="com.apple.WebKitIBPlugin" version="3330"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="GTMOAuth2WindowController">
+ <connections>
+ <action selector="closeWindow:" destination="17" id="42"/>
+ <outlet property="keychainCheckbox" destination="43" id="46"/>
+ <outlet property="webBackButton" destination="19" id="47"/>
+ <outlet property="webCloseButton" destination="17" id="48"/>
+ <outlet property="webView" destination="5" id="49"/>
+ <outlet property="window" destination="3" id="8"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application"/>
+ <window title="Sign In" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" wantsToBeColor="NO" visibleAtLaunch="NO" animationBehavior="default" id="3">
+ <windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES"/>
+ <windowPositionMask key="initialPositionMask" leftStrut="YES" bottomStrut="YES"/>
+ <rect key="contentRect" x="74" y="707" width="515" height="419"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1920" height="1178"/>
+ <value key="minSize" type="size" width="475" height="290"/>
+ <view key="contentView" id="4">
+ <rect key="frame" x="0.0" y="0.0" width="515" height="419"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <webView id="5">
+ <rect key="frame" x="0.0" y="20" width="515" height="399"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <webPreferences key="preferences" defaultFontSize="12" defaultFixedFontSize="12"/>
+ <connections>
+ <action selector="goBack:" destination="19" id="28"/>
+ <action selector="goForward:" destination="26" id="29"/>
+ </connections>
+ </webView>
+ <button id="17">
+ <rect key="frame" x="479" y="0.0" width="16" height="19"/>
+ <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
+ <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="NSStopProgressTemplate" imagePosition="overlaps" alignment="center" imageScaling="proportionallyDown" inset="2" id="18">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ <string key="keyEquivalent" base64-UTF8="YES">
+Gw
+</string>
+ </buttonCell>
+ </button>
+ <button id="19">
+ <rect key="frame" x="437" y="0.0" width="16" height="19"/>
+ <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
+ <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="NSGoLeftTemplate" imagePosition="overlaps" alignment="center" imageScaling="proportionallyDown" inset="2" id="20">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ <connections>
+ <binding destination="-2" name="enabled" keyPath="webView.canGoBack" id="31"/>
+ </connections>
+ </button>
+ <button id="26">
+ <rect key="frame" x="456" y="0.0" width="16" height="19"/>
+ <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
+ <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="NSGoRightTemplate" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="27">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ <connections>
+ <binding destination="-2" name="enabled" keyPath="webView.canGoForward" id="35"/>
+ </connections>
+ </button>
+ <button id="43">
+ <rect key="frame" x="2" y="1" width="429" height="18"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <buttonCell key="cell" type="check" title="Stay signed in to this account" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" state="on" inset="2" id="44">
+ <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
+ <font key="font" metaFont="smallSystem"/>
+ </buttonCell>
+ </button>
+ </subviews>
+ </view>
+ <connections>
+ <outlet property="delegate" destination="-2" id="7"/>
+ </connections>
+ </window>
+ <userDefaultsController id="32"/>
+ </objects>
+ <resources>
+ <image name="NSGoLeftTemplate" width="9" height="9"/>
+ <image name="NSGoRightTemplate" width="9" height="9"/>
+ <image name="NSStopProgressTemplate" width="11" height="11"/>
+ </resources>
+ <classes>
+ <class className="GTMOAuth2WindowController" superclassName="NSWindowController">
+ <source key="sourceIdentifier" type="project" relativePath="./Classes/GTMOAuth2WindowController.h"/>
+ <relationships>
+ <relationship kind="action" name="closeWindow:"/>
+ <relationship kind="outlet" name="completionPlaceholder_"/>
+ <relationship kind="outlet" name="delegate_"/>
+ <relationship kind="outlet" name="keychainCheckbox" candidateClass="NSButton"/>
+ <relationship kind="outlet" name="userData_"/>
+ <relationship kind="outlet" name="webBackButton" candidateClass="NSButton"/>
+ <relationship kind="outlet" name="webCloseButton" candidateClass="NSButton"/>
+ <relationship kind="outlet" name="webView" candidateClass="WebView"/>
+ </relationships>
+ </class>
+ </classes>
+</document> \ No newline at end of file
diff --git a/example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.h b/example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.h
new file mode 100644
index 00000000..9ff89b70
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/Mac/GTMOAuth2WindowController.h
@@ -0,0 +1,332 @@
+/* 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.
+ */
+
+// GTMOAuth2WindowController
+//
+// This window controller for Mac handles sign-in via OAuth2 to Google or
+// other services.
+//
+// This controller is not reusable; create a new instance of this controller
+// every time the user will sign in.
+//
+// Sample usage for signing in to a Google service:
+//
+// static NSString *const kKeychainItemName = @”My App: Google Plus”;
+// NSString *scope = @"https://www.googleapis.com/auth/plus.me";
+//
+//
+// GTMOAuth2WindowController *windowController;
+// windowController = [[[GTMOAuth2WindowController alloc] initWithScope:scope
+// clientID:clientID
+// clientSecret:clientSecret
+// keychainItemName:kKeychainItemName
+// resourceBundle:nil] autorelease];
+//
+// [windowController signInSheetModalForWindow:mMainWindow
+// delegate:self
+// finishedSelector:@selector(windowController:finishedWithAuth:error:)];
+//
+// The finished selector should have a signature matching this:
+//
+// - (void)windowController:(GTMOAuth2WindowController *)windowController
+// finishedWithAuth:(GTMOAuth2Authentication *)auth
+// error:(NSError *)error {
+// if (error != nil) {
+// // sign in failed
+// } else {
+// // sign in succeeded
+// //
+// // with the GTL library, pass the authentication to the service object,
+// // like
+// // [[self contactService] setAuthorizer:auth];
+// //
+// // or use it to sign a request directly, like
+// // BOOL isAuthorizing = [self authorizeRequest:request
+// // delegate:self
+// // didFinishSelector:@selector(auth:finishedWithError:)];
+// }
+// }
+//
+// To sign in to services other than Google, use the longer init method,
+// as shown in the sample application
+//
+// If the network connection is lost for more than 30 seconds while the sign-in
+// html is displayed, the notification kGTLOAuthNetworkLost will be sent.
+
+#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
+
+#include <Foundation/Foundation.h>
+
+#if !TARGET_OS_IPHONE
+
+#import <Cocoa/Cocoa.h>
+#import <WebKit/WebKit.h>
+
+// GTMHTTPFetcher.h brings in GTLDefines/GDataDefines
+#import "GTMHTTPFetcher.h"
+
+#import "GTMOAuth2SignIn.h"
+#import "GTMOAuth2Authentication.h"
+#import "GTMHTTPFetchHistory.h" // for GTMCookieStorage
+
+@class GTMOAuth2SignIn;
+
+@interface GTMOAuth2WindowController : NSWindowController {
+ @private
+ // IBOutlets
+ NSButton *keychainCheckbox_;
+ WebView *webView_;
+ NSButton *webCloseButton_;
+ NSButton *webBackButton_;
+
+ // the object responsible for the sign-in networking sequence; it holds
+ // onto the authentication object as well
+ GTMOAuth2SignIn *signIn_;
+
+ // the page request to load when awakeFromNib occurs
+ NSURLRequest *initialRequest_;
+
+ // local storage for WebKit cookies so they're not shared with Safari
+ GTMCookieStorage *cookieStorage_;
+
+ // the user we're calling back
+ //
+ // the delegate is retained only until the callback is invoked
+ // or the sign-in is canceled
+ id delegate_;
+ SEL finishedSelector_;
+
+#if NS_BLOCKS_AVAILABLE
+ void (^completionBlock_)(GTMOAuth2Authentication *, NSError *);
+#elif !__LP64__
+ // placeholders: for 32-bit builds, keep the size of the object's ivar section
+ // the same with and without blocks
+#ifndef __clang_analyzer__
+ id completionPlaceholder_;
+#endif
+#endif
+
+ // flag allowing application to quit during display of sign-in sheet on 10.6
+ // and later
+ BOOL shouldAllowApplicationTermination_;
+
+ // delegate method for handling URLs to be opened in external windows
+ SEL externalRequestSelector_;
+
+ BOOL isWindowShown_;
+
+ // paranoid flag to ensure we only close once during the sign-in sequence
+ BOOL hasDoneFinalRedirect_;
+
+ // paranoid flag to ensure we only call the user back once
+ BOOL hasCalledFinished_;
+
+ // if non-nil, we display as a sheet on the specified window
+ NSWindow *sheetModalForWindow_;
+
+ // if non-empty, the name of the application and service used for the
+ // keychain item
+ NSString *keychainItemName_;
+
+ // if non-nil, the html string to be displayed immediately upon opening
+ // of the web view
+ NSString *initialHTMLString_;
+
+ // if true, we allow default WebView handling of cookies, so the
+ // same user remains signed in each time the dialog is displayed
+ BOOL shouldPersistUser_;
+
+ // user-defined data
+ id userData_;
+ NSMutableDictionary *properties_;
+}
+
+// User interface elements
+@property (nonatomic, assign) IBOutlet NSButton *keychainCheckbox;
+@property (nonatomic, assign) IBOutlet WebView *webView;
+@property (nonatomic, assign) IBOutlet NSButton *webCloseButton;
+@property (nonatomic, assign) IBOutlet NSButton *webBackButton;
+
+// The application and service name to use for saving the auth tokens
+// to the keychain
+@property (nonatomic, copy) NSString *keychainItemName;
+
+// If true, the sign-in will remember which user was last signed in
+//
+// Defaults to false, so showing the sign-in window will always ask for
+// the username and password, rather than skip to the grant authorization
+// page. During development, it may be convenient to set this to true
+// to speed up signing in.
+@property (nonatomic, assign) BOOL shouldPersistUser;
+
+// Optional html string displayed immediately upon opening the web view
+//
+// This string is visible just until the sign-in web page loads, and
+// may be used for a "Loading..." type of message
+@property (nonatomic, copy) NSString *initialHTMLString;
+
+// The default timeout for an unreachable network during display of the
+// sign-in page is 30 seconds, after which the notification
+// kGTLOAuthNetworkLost is sent; set this to 0 to have no timeout
+@property (nonatomic, assign) NSTimeInterval networkLossTimeoutInterval;
+
+// On 10.6 and later, the sheet can allow application termination by calling
+// NSWindow's setPreventsApplicationTerminationWhenModal:
+@property (nonatomic, assign) BOOL shouldAllowApplicationTermination;
+
+// Selector for a delegate method to handle requests sent to an external
+// browser.
+//
+// Selector should have a signature matching
+// - (void)windowController:(GTMOAuth2WindowController *)controller
+// opensRequest:(NSURLRequest *)request;
+//
+// The controller's default behavior is to use NSWorkspace's openURL:
+@property (nonatomic, assign) SEL externalRequestSelector;
+
+// The underlying object to hold authentication tokens and authorize http
+// requests
+@property (nonatomic, retain, readonly) GTMOAuth2Authentication *authentication;
+
+// The underlying object which performs the sign-in networking sequence
+@property (nonatomic, retain, readonly) GTMOAuth2SignIn *signIn;
+
+// Any arbitrary data object the user would like the controller to retain
+@property (nonatomic, retain) id userData;
+
+// Stored property values are retained for the convenience of the caller
+- (void)setProperty:(id)obj forKey:(NSString *)key;
+- (id)propertyForKey:(NSString *)key;
+
+@property (nonatomic, retain) NSDictionary *properties;
+
+- (IBAction)closeWindow:(id)sender;
+
+// Create a controller for authenticating to Google services
+//
+// scope is the requested scope of authorization
+// (like "http://www.google.com/m8/feeds")
+//
+// keychainItemName is used for storing the token on the keychain,
+// and is required for the "remember for later" checkbox to be shown;
+// keychainItemName should be like "My Application: Google Contacts"
+// (or set to nil if no persistent keychain storage is desired)
+//
+// resourceBundle may be nil if the window is in the main bundle's nib
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (id)controllerWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName // may be nil
+ resourceBundle:(NSBundle *)bundle; // may be nil
+
+- (id)initWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ resourceBundle:(NSBundle *)bundle;
+#endif
+
+// Create a controller for authenticating to non-Google services, taking
+// explicit endpoint URLs and an authentication object
++ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName // may be nil
+ resourceBundle:(NSBundle *)bundle; // may be nil
+
+// This is the designated initializer
+- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ resourceBundle:(NSBundle *)bundle;
+
+// Entry point to begin displaying the sign-in window
+//
+// the finished selector should have a signature matching
+// - (void)windowController:(GTMOAuth2WindowController *)windowController
+// finishedWithAuth:(GTMOAuth2Authentication *)auth
+// error:(NSError *)error {
+//
+// Once the finished method has been invoked with no error, the auth object
+// may be used to authorize requests (refreshing the access token, if necessary,
+// and adding the auth header) like:
+//
+// [authorizer authorizeRequest:myNSMutableURLRequest]
+// delegate:self
+// didFinishSelector:@selector(auth:finishedWithError:)];
+//
+// or can be stored in a GTL service object like
+// GTLServiceGoogleContact *service = [self contactService];
+// [service setAuthorizer:auth];
+//
+// The delegate is retained only until the finished selector is invoked or
+// the sign-in is canceled
+- (void)signInSheetModalForWindow:(NSWindow *)parentWindowOrNil
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector;
+
+#if NS_BLOCKS_AVAILABLE
+- (void)signInSheetModalForWindow:(NSWindow *)parentWindowOrNil
+ completionHandler:(void (^)(GTMOAuth2Authentication *auth, NSError *error))handler;
+#endif
+
+- (void)cancelSigningIn;
+
+// Subclasses may override authNibName to specify a custom name
++ (NSString *)authNibName;
+
+// apps may replace the sign-in class with their own subclass of it
++ (Class)signInClass;
++ (void)setSignInClass:(Class)theClass;
+
+// Revocation of an authorized token from Google
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (void)revokeTokenForGoogleAuthentication:(GTMOAuth2Authentication *)auth;
+#endif
+
+// Keychain
+//
+// The keychain checkbox is shown if the keychain application service
+// name (typically set in the initWithScope: method) is non-empty
+//
+
+// Create an authentication object for Google services from the access
+// token and secret stored in the keychain; if no token is available, return
+// an unauthorized auth object
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (GTMOAuth2Authentication *)authForGoogleFromKeychainForName:(NSString *)keychainItemName
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret;
+#endif
+
+// Add tokens from the keychain, if available, to the authentication object
+//
+// returns YES if the authentication object was authorized from the keychain
++ (BOOL)authorizeFromKeychainForName:(NSString *)keychainItemName
+ authentication:(GTMOAuth2Authentication *)auth;
+
+// Method for deleting the stored access token and secret, useful for "signing
+// out"
++ (BOOL)removeAuthFromKeychainForName:(NSString *)keychainItemName;
+
+// Method for saving the stored access token and secret; typically, this method
+// is used only by the window controller
++ (BOOL)saveAuthToKeychainForName:(NSString *)keychainItemName
+ authentication:(GTMOAuth2Authentication *)auth;
+@end
+
+#endif // #if !TARGET_OS_IPHONE
+
+#endif // #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
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