aboutsummaryrefslogtreecommitdiffhomepage
path: root/example/common/gtm-oauth2/Source/Touch
diff options
context:
space:
mode:
Diffstat (limited to 'example/common/gtm-oauth2/Source/Touch')
-rw-r--r--example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.h376
-rw-r--r--example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.m1070
-rw-r--r--example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewTouch.xib494
3 files changed, 1940 insertions, 0 deletions
diff --git a/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.h b/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.h
new file mode 100644
index 00000000..33adbf71
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.h
@@ -0,0 +1,376 @@
+/* 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.
+ */
+
+//
+// GTMOAuth2ViewControllerTouch.h
+//
+// This view controller for iPhone handles sign-in via OAuth to Google or
+// other services.
+//
+// This controller is not reusable; create a new instance of this controller
+// every time the user will sign in.
+//
+
+#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
+
+#import <Foundation/Foundation.h>
+
+#if TARGET_OS_IPHONE
+
+#import <UIKit/UIKit.h>
+
+#import "GTMOAuth2Authentication.h"
+
+#undef _EXTERN
+#undef _INITIALIZE_AS
+#ifdef GTMOAUTH2VIEWCONTROLLERTOUCH_DEFINE_GLOBALS
+#define _EXTERN
+#define _INITIALIZE_AS(x) =x
+#else
+#define _EXTERN extern
+#define _INITIALIZE_AS(x)
+#endif
+
+_EXTERN NSString* const kGTMOAuth2KeychainErrorDomain _INITIALIZE_AS(@"com.google.GTMOAuthKeychain");
+
+@class GTMOAuth2SignIn;
+@class GTMOAuth2ViewControllerTouch;
+
+typedef void (^GTMOAuth2ViewControllerCompletionHandler)(GTMOAuth2ViewControllerTouch *viewController, GTMOAuth2Authentication *auth, NSError *error);
+
+@interface GTMOAuth2ViewControllerTouch : UIViewController<UINavigationControllerDelegate, UIWebViewDelegate> {
+ @private
+ UIButton *backButton_;
+ UIButton *forwardButton_;
+ UIActivityIndicatorView *initialActivityIndicator_;
+ UIView *navButtonsView_;
+ UIBarButtonItem *rightBarButtonItem_;
+ UIWebView *webView_;
+
+ // 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 *request_;
+
+ // 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
+ GTMOAuth2ViewControllerCompletionHandler completionBlock_;
+
+ void (^popViewBlock_)(void);
+#endif
+
+ NSString *keychainItemName_;
+ CFTypeRef keychainItemAccessibility_;
+
+ // if non-nil, the html string to be displayed immediately upon opening
+ // of the web view
+ NSString *initialHTMLString_;
+
+ // set to 1 or -1 if the user sets the showsInitialActivityIndicator
+ // property
+ int mustShowActivityIndicator_;
+
+ // if non-nil, the URL for which cookies will be deleted when the
+ // browser view is dismissed
+ NSURL *browserCookiesURL_;
+
+ id userData_;
+ NSMutableDictionary *properties_;
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < 60000
+ // We delegate the decision to our owning NavigationController (if any).
+ // But, the NavigationController will call us back, and ask us.
+ // BOOL keeps us from infinite looping.
+ BOOL isInsideShouldAutorotateToInterfaceOrientation_;
+#endif
+
+ // YES, when view first shown in this signIn session.
+ BOOL isViewShown_;
+
+ // YES, after the view has fully transitioned in.
+ BOOL didViewAppear_;
+
+ // YES between sends of start and stop notifications
+ BOOL hasNotifiedWebViewStartedLoading_;
+
+ // To prevent us from calling our delegate's selector more than once.
+ BOOL hasCalledFinished_;
+
+ // Set in a webView callback.
+ BOOL hasDoneFinalRedirect_;
+
+ // Set during the pop initiated by the sign-in object; otherwise,
+ // viewWillDisappear indicates that some external change of the view
+ // has stopped the sign-in.
+ BOOL didDismissSelf_;
+}
+
+// the application and service name to use for saving the auth tokens
+// to the keychain
+@property (nonatomic, copy) NSString *keychainItemName;
+
+// the keychain item accessibility is a system constant for use
+// with kSecAttrAccessible.
+//
+// Since it's a system constant, we do not need to retain it.
+@property (nonatomic, assign) CFTypeRef keychainItemAccessibility;
+
+// 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 or to set the
+// initial view color
+@property (nonatomic, copy) NSString *initialHTMLString;
+
+// an activity indicator shows during initial webview load when no initial HTML
+// string is specified, but the activity indicator can be forced to be shown
+// with this property
+@property (nonatomic, assign) BOOL showsInitialActivityIndicator;
+
+// 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;
+
+// user interface elements
+@property (nonatomic, retain) IBOutlet UIButton *backButton;
+@property (nonatomic, retain) IBOutlet UIButton *forwardButton;
+@property (nonatomic, retain) IBOutlet UIActivityIndicatorView *initialActivityIndicator;
+@property (nonatomic, retain) IBOutlet UIView *navButtonsView;
+@property (nonatomic, retain) IBOutlet UIBarButtonItem *rightBarButtonItem;
+@property (nonatomic, retain) IBOutlet UIWebView *webView;
+
+#if NS_BLOCKS_AVAILABLE
+// An optional block to be called when the view should be popped. If not set,
+// the view controller will use its navigation controller to pop the view.
+@property (nonatomic, copy) void (^popViewBlock)(void);
+#endif
+
+// the default timeout for an unreachable network during display of the
+// sign-in page is 30 seconds; set this to 0 to have no timeout
+@property (nonatomic, assign) NSTimeInterval networkLossTimeoutInterval;
+
+// if set, cookies are deleted for this URL when the view is hidden
+//
+// For Google sign-ins, this is set by default to https://google.com/accounts
+// but it may be explicitly set to nil to disable clearing of browser cookies
+@property (nonatomic, retain) NSURL *browserCookiesURL;
+
+// userData is retained for the convenience of the caller
+@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;
+
+// Method for creating a controller to authenticate to Google services
+//
+// scope is the requested scope of authorization
+// (like "http://www.google.com/m8/feeds")
+//
+// keychain item name is used for storing the token on the keychain,
+// keychainItemName should be like "My Application: Google Latitude"
+// (or set to nil if no persistent keychain storage is desired)
+//
+// the delegate is retained only until the finished selector is invoked
+// or the sign-in is canceled
+//
+// If you don't like the default nibName and bundle, you can change them
+// using the UIViewController properties once you've made one of these.
+//
+// finishedSelector is called after authentication completes. It should follow
+// this signature.
+//
+// - (void)viewController:(GTMOAuth2ViewControllerTouch *)viewController
+// finishedWithAuth:(GTMOAuth2Authentication *)auth
+// error:(NSError *)error;
+//
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (id)controllerWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector;
+
+- (id)initWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector;
+
+#if NS_BLOCKS_AVAILABLE
++ (id)controllerWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler;
+
+- (id)initWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler;
+#endif
+#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
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector;
+
+// This is the designated initializer
+- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector;
+
+#if NS_BLOCKS_AVAILABLE
++ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName // may be nil
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler;
+
+- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler;
+#endif
+
+// subclasses may override authNibName to specify a custom name
++ (NSString *)authNibName;
+
+// subclasses may override authNibBundle to specify a custom bundle
++ (NSBundle *)authNibBundle;
+
+// subclasses may override setUpNavigation to provide their own navigation
+// controls
+- (void)setUpNavigation;
+
+// apps may replace the sign-in class with their own subclass of it
++ (Class)signInClass;
++ (void)setSignInClass:(Class)theClass;
+
+- (void)cancelSigningIn;
+
+// revocation of an authorized token from Google
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (void)revokeTokenForGoogleAuthentication:(GTMOAuth2Authentication *)auth;
+#endif
+
+//
+// Keychain
+//
+
+// 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. OK to pass NULL for the error parameter.
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (GTMOAuth2Authentication *)authForGoogleFromKeychainForName:(NSString *)keychainItemName
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ error:(NSError **)error;
+// Equivalent to calling the method above with a NULL error parameter.
++ (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
+ error:(NSError **)error;
+
+// 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
+//
+// returns YES if the save was successful. OK to pass NULL for the error
+// parameter.
++ (BOOL)saveParamsToKeychainForName:(NSString *)keychainItemName
+ accessibility:(CFTypeRef)accessibility
+ authentication:(GTMOAuth2Authentication *)auth
+ error:(NSError **)error;
+
+// older version, defaults to kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
++ (BOOL)saveParamsToKeychainForName:(NSString *)keychainItemName
+ authentication:(GTMOAuth2Authentication *)auth;
+
+@end
+
+// To function, GTMOAuth2ViewControllerTouch needs a certain amount of access
+// to the iPhone's keychain. To keep things simple, its keychain access is
+// broken out into a helper class. We declare it here in case you'd like to use
+// it too, to store passwords.
+
+enum {
+ kGTMOAuth2KeychainErrorBadArguments = -1301,
+ kGTMOAuth2KeychainErrorNoPassword = -1302
+};
+
+
+@interface GTMOAuth2Keychain : NSObject
+
++ (GTMOAuth2Keychain *)defaultKeychain;
+
+// OK to pass nil for the error parameter.
+- (NSString *)passwordForService:(NSString *)service
+ account:(NSString *)account
+ error:(NSError **)error;
+
+// OK to pass nil for the error parameter.
+- (BOOL)removePasswordForService:(NSString *)service
+ account:(NSString *)account
+ error:(NSError **)error;
+
+// OK to pass nil for the error parameter.
+//
+// accessibility should be one of the constants for kSecAttrAccessible
+// such as kSecAttrAccessibleWhenUnlocked
+- (BOOL)setPassword:(NSString *)password
+ forService:(NSString *)service
+ accessibility:(CFTypeRef)accessibility
+ account:(NSString *)account
+ error:(NSError **)error;
+
+// For unit tests: allow setting a mock object
++ (void)setDefaultKeychain:(GTMOAuth2Keychain *)keychain;
+
+@end
+
+#endif // TARGET_OS_IPHONE
+
+#endif // #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
diff --git a/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.m b/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.m
new file mode 100644
index 00000000..3a18104d
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewControllerTouch.m
@@ -0,0 +1,1070 @@
+/* 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.
+ */
+
+//
+// GTMOAuth2ViewControllerTouch.m
+//
+
+#import <Foundation/Foundation.h>
+#import <Security/Security.h>
+
+#if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
+
+#if TARGET_OS_IPHONE
+
+#define GTMOAUTH2VIEWCONTROLLERTOUCH_DEFINE_GLOBALS 1
+#import "GTMOAuth2ViewControllerTouch.h"
+
+#import "GTMOAuth2SignIn.h"
+#import "GTMOAuth2Authentication.h"
+
+static NSString * const kGTMOAuth2AccountName = @"OAuth";
+static GTMOAuth2Keychain* sDefaultKeychain = nil;
+
+@interface GTMOAuth2ViewControllerTouch()
+
+@property (nonatomic, copy) NSURLRequest *request;
+
+- (void)signIn:(GTMOAuth2SignIn *)signIn displayRequest:(NSURLRequest *)request;
+- (void)signIn:(GTMOAuth2SignIn *)signIn
+finishedWithAuth:(GTMOAuth2Authentication *)auth
+ error:(NSError *)error;
+- (BOOL)isNavigationBarTranslucent;
+- (void)moveWebViewFromUnderNavigationBar;
+- (void)popView;
+- (void)clearBrowserCookies;
+@end
+
+@implementation GTMOAuth2ViewControllerTouch
+
+// IBOutlets
+@synthesize request = request_,
+ backButton = backButton_,
+ forwardButton = forwardButton_,
+ navButtonsView = navButtonsView_,
+ rightBarButtonItem = rightBarButtonItem_,
+ webView = webView_,
+ initialActivityIndicator = initialActivityIndicator_;
+
+@synthesize keychainItemName = keychainItemName_,
+ keychainItemAccessibility = keychainItemAccessibility_,
+ initialHTMLString = initialHTMLString_,
+ browserCookiesURL = browserCookiesURL_,
+ signIn = signIn_,
+ userData = userData_,
+ properties = properties_;
+
+#if NS_BLOCKS_AVAILABLE
+@synthesize popViewBlock = popViewBlock_;
+#endif
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (id)controllerWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector {
+ return [[[self alloc] initWithScope:scope
+ clientID:clientID
+ clientSecret:clientSecret
+ keychainItemName:keychainItemName
+ delegate:delegate
+ finishedSelector:finishedSelector] autorelease];
+}
+
+- (id)initWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector {
+ // convenient entry point for Google authentication
+
+ 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
+ delegate:delegate
+ finishedSelector:finishedSelector];
+}
+
+#if NS_BLOCKS_AVAILABLE
+
++ (id)controllerWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler {
+ return [[[self alloc] initWithScope:scope
+ clientID:clientID
+ clientSecret:clientSecret
+ keychainItemName:keychainItemName
+ completionHandler:handler] autorelease];
+}
+
+- (id)initWithScope:(NSString *)scope
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ keychainItemName:(NSString *)keychainItemName
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler {
+ // convenient entry point for Google authentication
+
+ Class signInClass = [[self class] signInClass];
+
+ GTMOAuth2Authentication *auth;
+ auth = [signInClass standardGoogleAuthenticationForScope:scope
+ clientID:clientID
+ clientSecret:clientSecret];
+ NSURL *authorizationURL = [signInClass googleAuthorizationURL];
+ self = [self initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ keychainItemName:keychainItemName
+ delegate:nil
+ finishedSelector:NULL];
+ if (self) {
+ completionBlock_ = [handler copy];
+ }
+ return self;
+}
+
+#endif // NS_BLOCKS_AVAILABLE
+#endif // !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
+
++ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector {
+ return [[[self alloc] initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ keychainItemName:keychainItemName
+ delegate:delegate
+ finishedSelector:finishedSelector] autorelease];
+}
+
+- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ delegate:(id)delegate
+ finishedSelector:(SEL)finishedSelector {
+
+ NSString *nibName = [[self class] authNibName];
+ NSBundle *nibBundle = [[self class] authNibBundle];
+
+ self = [super initWithNibName:nibName bundle:nibBundle];
+ if (self != nil) {
+ delegate_ = [delegate retain];
+ finishedSelector_ = finishedSelector;
+
+ Class signInClass = [[self class] signInClass];
+
+ // use the supplied auth and OAuth endpoint URLs
+ signIn_ = [[signInClass alloc] initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ delegate:self
+ webRequestSelector:@selector(signIn:displayRequest:)
+ finishedSelector:@selector(signIn:finishedWithAuth:error:)];
+
+ // if the user is signing in to a Google service, we'll delete the
+ // Google authentication browser cookies upon completion
+ //
+ // for other service domains, or to disable clearing of the cookies,
+ // set the browserCookiesURL property explicitly
+ NSString *authorizationHost = [signIn_.authorizationURL host];
+ if ([authorizationHost hasSuffix:@".google.com"]) {
+ NSString *urlStr = [NSString stringWithFormat:@"https://%@/",
+ authorizationHost];
+ NSURL *cookiesURL = [NSURL URLWithString:urlStr];
+ [self setBrowserCookiesURL:cookiesURL];
+ }
+
+ [self setKeychainItemName:keychainItemName];
+ }
+ return self;
+}
+
+#if NS_BLOCKS_AVAILABLE
++ (id)controllerWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler {
+ return [[[self alloc] initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ keychainItemName:keychainItemName
+ completionHandler:handler] autorelease];
+}
+
+- (id)initWithAuthentication:(GTMOAuth2Authentication *)auth
+ authorizationURL:(NSURL *)authorizationURL
+ keychainItemName:(NSString *)keychainItemName
+ completionHandler:(GTMOAuth2ViewControllerCompletionHandler)handler {
+ // fall back to the non-blocks init
+ self = [self initWithAuthentication:auth
+ authorizationURL:authorizationURL
+ keychainItemName:keychainItemName
+ delegate:nil
+ finishedSelector:NULL];
+ if (self) {
+ completionBlock_ = [handler copy];
+ }
+ return self;
+}
+#endif
+
+- (void)dealloc {
+ [webView_ setDelegate:nil];
+
+ [backButton_ release];
+ [forwardButton_ release];
+ [initialActivityIndicator_ release];
+ [navButtonsView_ release];
+ [rightBarButtonItem_ release];
+ [webView_ release];
+ [signIn_ release];
+ [request_ release];
+ [delegate_ release];
+#if NS_BLOCKS_AVAILABLE
+ [completionBlock_ release];
+ [popViewBlock_ release];
+#endif
+ [keychainItemName_ release];
+ [initialHTMLString_ release];
+ [browserCookiesURL_ release];
+ [userData_ release];
+ [properties_ release];
+
+ [super dealloc];
+}
+
++ (NSString *)authNibName {
+ // subclasses may override this to specify a custom nib name
+ return @"GTMOAuth2ViewTouch";
+}
+
++ (NSBundle *)authNibBundle {
+ // subclasses may override this to specify a custom nib bundle
+ return nil;
+}
+
+#if !GTM_OAUTH2_SKIP_GOOGLE_SUPPORT
++ (GTMOAuth2Authentication *)authForGoogleFromKeychainForName:(NSString *)keychainItemName
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret {
+ return [self authForGoogleFromKeychainForName:keychainItemName
+ clientID:clientID
+ clientSecret:clientSecret
+ error:NULL];
+}
+
++ (GTMOAuth2Authentication *)authForGoogleFromKeychainForName:(NSString *)keychainItemName
+ clientID:(NSString *)clientID
+ clientSecret:(NSString *)clientSecret
+ error:(NSError **)error {
+ 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];
+ [[self class] authorizeFromKeychainForName:keychainItemName
+ authentication:auth
+ error:error];
+ return auth;
+}
+
+#endif
+
++ (BOOL)authorizeFromKeychainForName:(NSString *)keychainItemName
+ authentication:(GTMOAuth2Authentication *)newAuth
+ error:(NSError **)error {
+ newAuth.accessToken = nil;
+
+ BOOL didGetTokens = NO;
+ GTMOAuth2Keychain *keychain = [GTMOAuth2Keychain defaultKeychain];
+ NSString *password = [keychain passwordForService:keychainItemName
+ account:kGTMOAuth2AccountName
+ error:error];
+ if (password != nil) {
+ [newAuth setKeysForResponseString:password];
+ didGetTokens = YES;
+ }
+ return didGetTokens;
+}
+
++ (BOOL)removeAuthFromKeychainForName:(NSString *)keychainItemName {
+ GTMOAuth2Keychain *keychain = [GTMOAuth2Keychain defaultKeychain];
+ return [keychain removePasswordForService:keychainItemName
+ account:kGTMOAuth2AccountName
+ error:nil];
+}
+
++ (BOOL)saveParamsToKeychainForName:(NSString *)keychainItemName
+ authentication:(GTMOAuth2Authentication *)auth {
+ return [self saveParamsToKeychainForName:keychainItemName
+ accessibility:NULL
+ authentication:auth
+ error:NULL];
+}
+
++ (BOOL)saveParamsToKeychainForName:(NSString *)keychainItemName
+ accessibility:(CFTypeRef)accessibility
+ authentication:(GTMOAuth2Authentication *)auth
+ error:(NSError **)error {
+ [self removeAuthFromKeychainForName:keychainItemName];
+ // don't save unless we have a token that can really authorize requests
+ if (![auth canAuthorize]) {
+ if (error) {
+ *error = [NSError errorWithDomain:kGTMOAuth2ErrorDomain
+ code:kGTMOAuth2ErrorTokenUnavailable
+ userInfo:nil];
+ }
+ return NO;
+ }
+
+ if (accessibility == NULL
+ && &kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly != NULL) {
+ accessibility = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
+ }
+
+ // make a response string containing the values we want to save
+ NSString *password = [auth persistenceResponseString];
+ GTMOAuth2Keychain *keychain = [GTMOAuth2Keychain defaultKeychain];
+ return [keychain setPassword:password
+ forService:keychainItemName
+ accessibility:accessibility
+ account:kGTMOAuth2AccountName
+ error:error];
+}
+
+- (void)loadView {
+ NSString *nibPath = nil;
+ NSBundle *nibBundle = [self nibBundle];
+ if (nibBundle == nil) {
+ nibBundle = [NSBundle mainBundle];
+ }
+ NSString *nibName = self.nibName;
+ if (nibName != nil) {
+ nibPath = [nibBundle pathForResource:nibName ofType:@"nib"];
+ }
+ if (nibPath != nil && [[NSFileManager defaultManager] fileExistsAtPath:nibPath]) {
+ [super loadView];
+ } else {
+ // One of the requirements of loadView is that a valid view object is set to
+ // self.view upon completion. Otherwise, subclasses that attempt to
+ // access self.view after calling [super loadView] will enter an infinite
+ // loop due to the fact that UIViewController's -view accessor calls
+ // loadView when self.view is nil.
+ self.view = [[[UIView alloc] init] autorelease];
+
+#if DEBUG
+ NSLog(@"missing %@.nib", nibName);
+#endif
+ }
+}
+
+
+- (void)viewDidLoad {
+ [self setUpNavigation];
+}
+
+- (void)setUpNavigation {
+ rightBarButtonItem_.customView = navButtonsView_;
+ self.navigationItem.rightBarButtonItem = rightBarButtonItem_;
+}
+
+- (void)popView {
+#if NS_BLOCKS_AVAILABLE
+ void (^popViewBlock)() = self.popViewBlock;
+#else
+ id popViewBlock = nil;
+#endif
+
+ if (popViewBlock || self.navigationController.topViewController == self) {
+ if (!self.view.hidden) {
+ // Set the flag to our viewWillDisappear method so it knows
+ // this is a disappearance initiated by the sign-in object,
+ // not the user cancelling via the navigation controller
+ didDismissSelf_ = YES;
+
+ if (popViewBlock) {
+#if NS_BLOCKS_AVAILABLE
+ popViewBlock();
+ self.popViewBlock = nil;
+#endif
+ } else {
+ [self.navigationController popViewControllerAnimated:YES];
+ }
+ self.view.hidden = YES;
+ }
+ }
+}
+
+- (void)notifyWithName:(NSString *)name
+ webView:(UIWebView *)webView
+ kind:(NSString *)kind {
+ BOOL isStarting = [name isEqual:kGTMOAuth2WebViewStartedLoading];
+ if (hasNotifiedWebViewStartedLoading_ == isStarting) {
+ // Duplicate notification
+ //
+ // UIWebView's delegate methods are so unbalanced that there's little
+ // point trying to keep a count, as it could easily end up stuck greater
+ // than zero.
+ //
+ // We don't really have a way to track the starts and stops of
+ // subframe loads, too, as the webView in the notification is always
+ // for the topmost request.
+ return;
+ }
+ hasNotifiedWebViewStartedLoading_ = isStarting;
+
+ // Notification for webview load starting and stopping
+ NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
+ webView, kGTMOAuth2WebViewKey,
+ kind, kGTMOAuth2WebViewStopKindKey, // kind may be nil
+ nil];
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc postNotificationName:name
+ object:self
+ userInfo:dict];
+}
+
+- (void)cancelSigningIn {
+ // The application 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 sign-in object's cancel method will close the window
+ [signIn_ cancelSigningIn];
+ hasDoneFinalRedirect_ = YES;
+}
+
+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 Browser Cookies
+
+- (GTMOAuth2Authentication *)authentication {
+ return self.signIn.authentication;
+}
+
+- (void)clearBrowserCookies {
+ // if browserCookiesURL is non-nil, then get cookies for that URL
+ // and delete them from the common application cookie storage
+ NSURL *cookiesURL = [self browserCookiesURL];
+ if (cookiesURL) {
+ NSHTTPCookieStorage *cookieStorage;
+
+ cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ NSArray *cookies = [cookieStorage cookiesForURL:cookiesURL];
+
+ for (NSHTTPCookie *cookie in cookies) {
+ [cookieStorage deleteCookie:cookie];
+ }
+ }
+}
+
+#pragma mark Accessors
+
+- (void)setNetworkLossTimeoutInterval:(NSTimeInterval)val {
+ signIn_.networkLossTimeoutInterval = val;
+}
+
+- (NSTimeInterval)networkLossTimeoutInterval {
+ return signIn_.networkLossTimeoutInterval;
+}
+
+- (BOOL)shouldUseKeychain {
+ NSString *name = self.keychainItemName;
+ return ([name length] > 0);
+}
+
+- (BOOL)showsInitialActivityIndicator {
+ return (mustShowActivityIndicator_ == 1 || initialHTMLString_ == nil);
+}
+
+- (void)setShowsInitialActivityIndicator:(BOOL)flag {
+ mustShowActivityIndicator_ = (flag ? 1 : -1);
+}
+
+#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 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 if the request is nil,
+ // to close the window.
+ //
+ // All web requests and all window closing goes through this routine
+
+#if DEBUG
+ if (self.navigationController) {
+ if (self.navigationController.topViewController != self && request != nil) {
+ NSLog(@"Unexpected: Request to show, when already on top. request %@", [request URL]);
+ } else if(self.navigationController.topViewController != self && request == nil) {
+ NSLog(@"Unexpected: Request to pop, when not on top. request nil");
+ }
+ }
+#endif
+
+ if (request != nil) {
+ const NSTimeInterval kJanuary2011 = 1293840000;
+ BOOL isDateValid = ([[NSDate date] timeIntervalSince1970] > kJanuary2011);
+ if (isDateValid) {
+ // Display the request.
+ self.request = request;
+ // The app may prefer some html other than blank white to be displayed
+ // before the sign-in web page loads.
+ // The first fetch might be slow, so the client programmer may want
+ // to show a local "loading" message.
+ // On iOS 5+, UIWebView will ignore loadHTMLString: if it's followed by
+ // a loadRequest: call, so if there is a "loading" message we defer
+ // the loadRequest: until after after we've drawn the "loading" message.
+ //
+ // If there is no initial html string, we show the activity indicator
+ // unless the user set showsInitialActivityIndicator to NO; if there
+ // is an initial html string, we hide the indicator unless the user set
+ // showsInitialActivityIndicator to YES.
+ NSString *html = self.initialHTMLString;
+ if ([html length] > 0) {
+ [initialActivityIndicator_ setHidden:(mustShowActivityIndicator_ < 1)];
+ [self.webView loadHTMLString:html baseURL:nil];
+ } else {
+ [initialActivityIndicator_ setHidden:(mustShowActivityIndicator_ < 0)];
+ [self.webView loadRequest:request];
+ }
+ } 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 give them a clue when they report the
+ // problem 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 html = @"<html><body><div align=center><font size='7'>"
+ @"&#x231A; ?<br><i>System Clock Incorrect</i><br>%@"
+ @"</font></div></body></html>";
+ NSString *errHTML = [NSString stringWithFormat:html, [NSDate date]];
+
+ [[self webView] loadHTMLString:errHTML baseURL:nil];
+ }
+ } else {
+ // request was nil.
+ [self popView];
+ }
+}
+
+- (void)signIn:(GTMOAuth2SignIn *)signIn
+ finishedWithAuth:(GTMOAuth2Authentication *)auth
+ error:(NSError *)error {
+ if (!hasCalledFinished_) {
+ hasCalledFinished_ = YES;
+
+ if (error == nil) {
+ if (self.shouldUseKeychain) {
+ NSString *keychainItemName = self.keychainItemName;
+ if (auth.canAuthorize) {
+ // save the auth params in the keychain
+ CFTypeRef accessibility = self.keychainItemAccessibility;
+ [[self class] saveParamsToKeychainForName:keychainItemName
+ accessibility:accessibility
+ authentication:auth
+ error:NULL];
+ } 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_(self, auth, error);
+
+ // release the block here to avoid a retain loop on the controller
+ [completionBlock_ autorelease];
+ completionBlock_ = nil;
+ }
+#endif
+ }
+}
+
+- (void)moveWebViewFromUnderNavigationBar {
+ CGRect dontCare;
+ CGRect webFrame = self.view.bounds;
+ UINavigationBar *navigationBar = self.navigationController.navigationBar;
+ CGRectDivide(webFrame, &dontCare, &webFrame,
+ navigationBar.frame.size.height, CGRectMinYEdge);
+ [self.webView setFrame:webFrame];
+}
+
+// isTranslucent is defined in iPhoneOS 3.0 on.
+- (BOOL)isNavigationBarTranslucent {
+ UINavigationBar *navigationBar = [[self navigationController] navigationBar];
+ BOOL isTranslucent =
+ ([navigationBar respondsToSelector:@selector(isTranslucent)] &&
+ [navigationBar isTranslucent]);
+ return isTranslucent;
+}
+
+#pragma mark -
+#pragma mark Protocol implementations
+
+- (void)viewWillAppear:(BOOL)animated {
+ // See the comment on clearBrowserCookies in viewDidDisappear.
+ [self clearBrowserCookies];
+
+ if (!isViewShown_) {
+ isViewShown_ = YES;
+ if ([self isNavigationBarTranslucent]) {
+ [self moveWebViewFromUnderNavigationBar];
+ }
+ if (![signIn_ startSigningIn]) {
+ // Can't start signing in. We must pop our view.
+ // UIWebview needs time to stabilize. Animations need time to complete.
+ // We remove ourself from the view stack after that.
+ [self performSelector:@selector(popView)
+ withObject:nil
+ afterDelay:0.5
+ inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
+ }
+ }
+ [super viewWillAppear:animated];
+}
+
+- (void)viewDidAppear:(BOOL)animated {
+ didViewAppear_ = YES;
+ [super viewDidAppear:animated];
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+ if (!didDismissSelf_) {
+ // We won't receive further webview delegate messages, so be sure the
+ // started loading notification is balanced, if necessary
+ [self notifyWithName:kGTMOAuth2WebViewStoppedLoading
+ webView:self.webView
+ kind:kGTMOAuth2WebViewCancelled];
+
+ // We are not popping ourselves, so presumably we are being popped by the
+ // navigation controller; tell the sign-in object to close up shop
+ //
+ // this will indirectly call our signIn:finishedWithAuth:error: method
+ // for us
+ [signIn_ windowWasClosed];
+
+#if NS_BLOCKS_AVAILABLE
+ self.popViewBlock = nil;
+#endif
+ }
+
+ [super viewWillDisappear:animated];
+}
+
+- (void)viewDidDisappear:(BOOL)animated {
+ [super viewDidDisappear:animated];
+
+ // prevent the next sign-in from showing in the WebView that the user is
+ // already signed in. It's possible for the WebView to set the cookies even
+ // after this, so we also clear them when the view first appears.
+ [self clearBrowserCookies];
+}
+
+- (void)viewDidLayoutSubviews {
+ // We don't call super's version of this method because
+ // -[UIViewController viewDidLayoutSubviews] is documented as a no-op, that
+ // didn't exist before iOS 5.
+ [initialActivityIndicator_ setCenter:[webView_ center]];
+}
+
+- (BOOL)webView:(UIWebView *)webView
+ shouldStartLoadWithRequest:(NSURLRequest *)request
+ navigationType:(UIWebViewNavigationType)navigationType {
+
+ if (!hasDoneFinalRedirect_) {
+ hasDoneFinalRedirect_ = [signIn_ requestRedirectedToRequest:request];
+ if (hasDoneFinalRedirect_) {
+ // signIn has told the view to close
+ return NO;
+ }
+ }
+ return YES;
+}
+
+- (void)updateUI {
+ [backButton_ setEnabled:[[self webView] canGoBack]];
+ [forwardButton_ setEnabled:[[self webView] canGoForward]];
+}
+
+- (void)webViewDidStartLoad:(UIWebView *)webView {
+ [self notifyWithName:kGTMOAuth2WebViewStartedLoading
+ webView:webView
+ kind:nil];
+ [self updateUI];
+}
+
+- (void)webViewDidFinishLoad:(UIWebView *)webView {
+ [self notifyWithName:kGTMOAuth2WebViewStoppedLoading
+ webView:webView
+ kind:kGTMOAuth2WebViewFinished];
+
+ NSString *title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
+ if ([title length] > 0) {
+ [signIn_ titleChanged:title];
+ } else {
+#if DEBUG
+ // Verify that Javascript is enabled
+ NSString *result = [webView stringByEvaluatingJavaScriptFromString:@"1+1"];
+ NSAssert([result integerValue] == 2, @"GTMOAuth2: Javascript is required");
+#endif
+ }
+
+ if (self.request && [self.initialHTMLString length] > 0) {
+ // The request was pending.
+ [self setInitialHTMLString:nil];
+ [self.webView loadRequest:self.request];
+ } else {
+ [initialActivityIndicator_ setHidden:YES];
+ [signIn_ cookiesChanged:[NSHTTPCookieStorage sharedHTTPCookieStorage]];
+
+ [self updateUI];
+ }
+}
+
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
+ [self notifyWithName:kGTMOAuth2WebViewStoppedLoading
+ webView:webView
+ kind:kGTMOAuth2WebViewFailed];
+
+ // Tell the sign-in object that a load failed; if it was the authorization
+ // URL, it will pop the view and return an error to the delegate.
+ if (didViewAppear_) {
+ BOOL isUserInterruption = ([error code] == NSURLErrorCancelled
+ && [[error domain] isEqual:NSURLErrorDomain]);
+ if (isUserInterruption) {
+ // Ignore this error:
+ // Users report that this error occurs when clicking too quickly on the
+ // accept button, before the page has completely loaded. Ignoring
+ // this error seems to provide a better experience than does immediately
+ // cancelling sign-in.
+ //
+ // This error also occurs whenever UIWebView is sent the stopLoading
+ // message, so if we ever send that message intentionally, we need to
+ // revisit this bypass.
+ return;
+ }
+
+ [signIn_ loadFailedWithError:error];
+ } else {
+ // UIWebview needs time to stabilize. Animations need time to complete.
+ [signIn_ performSelector:@selector(loadFailedWithError:)
+ withObject:error
+ afterDelay:0.5
+ inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
+ }
+}
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < 60000
+// When running on a device with an OS version < 6, this gets called.
+//
+// Since it is never called in iOS 6 or greater, if your min deployment
+// target is iOS6 or greater, then you don't need to have this method compiled
+// into your app.
+//
+// When running on a device with an OS version 6 or greater, this code is
+// not called. - (NSUInteger)supportedInterfaceOrientations; would be called,
+// if it existed. Since it is absent,
+// Allow the default orientations: All for iPad, all but upside down for iPhone.
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
+ BOOL value = YES;
+ if (!isInsideShouldAutorotateToInterfaceOrientation_) {
+ isInsideShouldAutorotateToInterfaceOrientation_ = YES;
+ UIViewController *navigationController = [self navigationController];
+ if (navigationController != nil) {
+ value = [navigationController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
+ } else {
+ value = [super shouldAutorotateToInterfaceOrientation:interfaceOrientation];
+ }
+ isInsideShouldAutorotateToInterfaceOrientation_ = NO;
+ }
+ return value;
+}
+#endif
+
+
+@end
+
+
+#pragma mark Common Code
+
+@implementation GTMOAuth2Keychain
+
++ (GTMOAuth2Keychain *)defaultKeychain {
+ if (sDefaultKeychain == nil) {
+ sDefaultKeychain = [[self alloc] init];
+ }
+ return sDefaultKeychain;
+}
+
+
+// For unit tests: allow setting a mock object
++ (void)setDefaultKeychain:(GTMOAuth2Keychain *)keychain {
+ if (sDefaultKeychain != keychain) {
+ [sDefaultKeychain release];
+ sDefaultKeychain = [keychain retain];
+ }
+}
+
+- (NSString *)keyForService:(NSString *)service account:(NSString *)account {
+ return [NSString stringWithFormat:@"com.google.GTMOAuth.%@%@", service, account];
+}
+
+// The Keychain API isn't available on the iPhone simulator in SDKs before 3.0,
+// so, on early simulators we use a fake API, that just writes, unencrypted, to
+// NSUserDefaults.
+#if TARGET_IPHONE_SIMULATOR && __IPHONE_OS_VERSION_MAX_ALLOWED < 30000
+#pragma mark Simulator
+
+// Simulator - just simulated, not secure.
+- (NSString *)passwordForService:(NSString *)service account:(NSString *)account error:(NSError **)error {
+ NSString *result = nil;
+ if (0 < [service length] && 0 < [account length]) {
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ NSString *key = [self keyForService:service account:account];
+ result = [defaults stringForKey:key];
+ if (result == nil && error != NULL) {
+ *error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
+ code:kGTMOAuth2KeychainErrorNoPassword
+ userInfo:nil];
+ }
+ } else if (error != NULL) {
+ *error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
+ code:kGTMOAuth2KeychainErrorBadArguments
+ userInfo:nil];
+ }
+ return result;
+
+}
+
+
+// Simulator - just simulated, not secure.
+- (BOOL)removePasswordForService:(NSString *)service account:(NSString *)account error:(NSError **)error {
+ BOOL didSucceed = NO;
+ if (0 < [service length] && 0 < [account length]) {
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ NSString *key = [self keyForService:service account:account];
+ [defaults removeObjectForKey:key];
+ [defaults synchronize];
+ } else if (error != NULL) {
+ *error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
+ code:kGTMOAuth2KeychainErrorBadArguments
+ userInfo:nil];
+ }
+ return didSucceed;
+}
+
+// Simulator - just simulated, not secure.
+- (BOOL)setPassword:(NSString *)password
+ forService:(NSString *)service
+ accessibility:(CFTypeRef)accessibility
+ account:(NSString *)account
+ error:(NSError **)error {
+ BOOL didSucceed = NO;
+ if (0 < [password length] && 0 < [service length] && 0 < [account length]) {
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ NSString *key = [self keyForService:service account:account];
+ [defaults setObject:password forKey:key];
+ [defaults synchronize];
+ didSucceed = YES;
+ } else if (error != NULL) {
+ *error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
+ code:kGTMOAuth2KeychainErrorBadArguments
+ userInfo:nil];
+ }
+ return didSucceed;
+}
+
+#else // ! TARGET_IPHONE_SIMULATOR
+#pragma mark Device
+
++ (NSMutableDictionary *)keychainQueryForService:(NSString *)service account:(NSString *)account {
+ NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ (id)kSecClassGenericPassword, (id)kSecClass,
+ @"OAuth", (id)kSecAttrGeneric,
+ account, (id)kSecAttrAccount,
+ service, (id)kSecAttrService,
+ nil];
+ return query;
+}
+
+- (NSMutableDictionary *)keychainQueryForService:(NSString *)service account:(NSString *)account {
+ return [[self class] keychainQueryForService:service account:account];
+}
+
+
+
+// iPhone
+- (NSString *)passwordForService:(NSString *)service account:(NSString *)account error:(NSError **)error {
+ OSStatus status = kGTMOAuth2KeychainErrorBadArguments;
+ NSString *result = nil;
+ if (0 < [service length] && 0 < [account length]) {
+ CFDataRef passwordData = NULL;
+ NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
+ [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
+ [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
+
+ status = SecItemCopyMatching((CFDictionaryRef)keychainQuery,
+ (CFTypeRef *)&passwordData);
+ if (status == noErr && 0 < [(NSData *)passwordData length]) {
+ result = [[[NSString alloc] initWithData:(NSData *)passwordData
+ encoding:NSUTF8StringEncoding] autorelease];
+ }
+ if (passwordData != NULL) {
+ CFRelease(passwordData);
+ }
+ }
+ if (status != noErr && error != NULL) {
+ *error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
+ code:status
+ userInfo:nil];
+ }
+ return result;
+}
+
+
+// iPhone
+- (BOOL)removePasswordForService:(NSString *)service account:(NSString *)account error:(NSError **)error {
+ OSStatus status = kGTMOAuth2KeychainErrorBadArguments;
+ if (0 < [service length] && 0 < [account length]) {
+ NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
+ status = SecItemDelete((CFDictionaryRef)keychainQuery);
+ }
+ if (status != noErr && error != NULL) {
+ *error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
+ code:status
+ userInfo:nil];
+ }
+ return status == noErr;
+}
+
+// iPhone
+- (BOOL)setPassword:(NSString *)password
+ forService:(NSString *)service
+ accessibility:(CFTypeRef)accessibility
+ account:(NSString *)account
+ error:(NSError **)error {
+ OSStatus status = kGTMOAuth2KeychainErrorBadArguments;
+ if (0 < [service length] && 0 < [account length]) {
+ [self removePasswordForService:service account:account error:nil];
+ if (0 < [password length]) {
+ NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
+ NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
+ [keychainQuery setObject:passwordData forKey:(id)kSecValueData];
+
+ if (accessibility != NULL && &kSecAttrAccessible != NULL) {
+ [keychainQuery setObject:(id)accessibility
+ forKey:(id)kSecAttrAccessible];
+ }
+ status = SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
+ }
+ }
+ if (status != noErr && error != NULL) {
+ *error = [NSError errorWithDomain:kGTMOAuth2KeychainErrorDomain
+ code:status
+ userInfo:nil];
+ }
+ return status == noErr;
+}
+
+#endif // ! TARGET_IPHONE_SIMULATOR
+
+@end
+
+#endif // TARGET_OS_IPHONE
+
+#endif // #if GTM_INCLUDE_OAUTH2 || !GDATA_REQUIRE_SERVICE_INCLUDES
diff --git a/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewTouch.xib b/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewTouch.xib
new file mode 100644
index 00000000..4f91fa4a
--- /dev/null
+++ b/example/common/gtm-oauth2/Source/Touch/GTMOAuth2ViewTouch.xib
@@ -0,0 +1,494 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="7.10">
+ <data>
+ <int key="IBDocument.SystemTarget">1024</int>
+ <string key="IBDocument.SystemVersion">12C60</string>
+ <string key="IBDocument.InterfaceBuilderVersion">2840</string>
+ <string key="IBDocument.AppKitVersion">1187.34</string>
+ <string key="IBDocument.HIToolboxVersion">625.00</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string key="NS.object.0">1926</string>
+ </object>
+ <object class="NSArray" key="IBDocument.IntegratedClassDependencies">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>IBProxyObject</string>
+ <string>IBUIActivityIndicatorView</string>
+ <string>IBUIBarButtonItem</string>
+ <string>IBUIButton</string>
+ <string>IBUINavigationItem</string>
+ <string>IBUIView</string>
+ <string>IBUIWebView</string>
+ </object>
+ <object class="NSArray" key="IBDocument.PluginDependencies">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.Metadata">
+ <string key="NS.key.0">PluginDependencyRecalculationVersion</string>
+ <integer value="1" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBProxyObject" id="372490531">
+ <string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBProxyObject" id="975951072">
+ <string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBUINavigationItem" id="1047805472">
+ <string key="IBUITitle">OAuth</string>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBUIBarButtonItem" id="961671599">
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <int key="IBUIStyle">1</int>
+ </object>
+ <object class="IBUIView" id="808907889">
+ <reference key="NSNextResponder"/>
+ <int key="NSvFlags">292</int>
+ <object class="NSMutableArray" key="NSSubviews">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBUIButton" id="453250804">
+ <reference key="NSNextResponder" ref="808907889"/>
+ <int key="NSvFlags">292</int>
+ <string key="NSFrameSize">{30, 30}</string>
+ <reference key="NSSuperview" ref="808907889"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="981703116"/>
+ <bool key="IBUIOpaque">NO</bool>
+ <bool key="IBUIClearsContextBeforeDrawing">NO</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <int key="IBUIContentHorizontalAlignment">0</int>
+ <int key="IBUIContentVerticalAlignment">0</int>
+ <string key="IBUITitleShadowOffset">{0, -2}</string>
+ <string key="IBUINormalTitle">◀</string>
+ <object class="NSColor" key="IBUIHighlightedTitleColor" id="193465259">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MQA</bytes>
+ </object>
+ <object class="NSColor" key="IBUIDisabledTitleColor">
+ <int key="NSColorSpace">2</int>
+ <bytes key="NSRGB">MC41OTYwNzg0NiAwLjY4NjI3NDUzIDAuOTUyOTQxMjQgMC42MDAwMDAwMgA</bytes>
+ </object>
+ <reference key="IBUINormalTitleColor" ref="193465259"/>
+ <object class="NSColor" key="IBUINormalTitleShadowColor" id="999379443">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MC41AA</bytes>
+ </object>
+ <object class="IBUIFontDescription" key="IBUIFontDescription" id="621440819">
+ <string key="name">Helvetica-Bold</string>
+ <string key="family">Helvetica</string>
+ <int key="traits">2</int>
+ <double key="pointSize">24</double>
+ </object>
+ <object class="NSFont" key="IBUIFont" id="530402572">
+ <string key="NSName">Helvetica-Bold</string>
+ <double key="NSSize">24</double>
+ <int key="NSfFlags">16</int>
+ </object>
+ </object>
+ <object class="IBUIButton" id="981703116">
+ <reference key="NSNextResponder" ref="808907889"/>
+ <int key="NSvFlags">292</int>
+ <string key="NSFrame">{{30, 0}, {30, 30}}</string>
+ <reference key="NSSuperview" ref="808907889"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView"/>
+ <bool key="IBUIOpaque">NO</bool>
+ <bool key="IBUIClearsContextBeforeDrawing">NO</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <int key="IBUIContentHorizontalAlignment">0</int>
+ <int key="IBUIContentVerticalAlignment">0</int>
+ <string key="IBUITitleShadowOffset">{0, -2}</string>
+ <string key="IBUINormalTitle">▶</string>
+ <reference key="IBUIHighlightedTitleColor" ref="193465259"/>
+ <object class="NSColor" key="IBUIDisabledTitleColor">
+ <int key="NSColorSpace">2</int>
+ <bytes key="NSRGB">MC41ODQzMTM3NSAwLjY3NDUwOTgyIDAuOTUyOTQxMjQgMC42MDAwMDAwMgA</bytes>
+ </object>
+ <reference key="IBUINormalTitleColor" ref="193465259"/>
+ <reference key="IBUINormalTitleShadowColor" ref="999379443"/>
+ <reference key="IBUIFontDescription" ref="621440819"/>
+ <reference key="IBUIFont" ref="530402572"/>
+ </object>
+ </object>
+ <string key="NSFrameSize">{60, 30}</string>
+ <reference key="NSSuperview"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="453250804"/>
+ <object class="NSColor" key="IBUIBackgroundColor">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MSAwAA</bytes>
+ </object>
+ <bool key="IBUIOpaque">NO</bool>
+ <bool key="IBUIClearsContextBeforeDrawing">NO</bool>
+ <object class="IBUISimulatedOrientationMetrics" key="IBUISimulatedOrientationMetrics">
+ <int key="IBUIInterfaceOrientation">3</int>
+ <int key="interfaceOrientation">3</int>
+ </object>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBUIView" id="426018584">
+ <reference key="NSNextResponder"/>
+ <int key="NSvFlags">274</int>
+ <object class="NSMutableArray" key="NSSubviews">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBUIWebView" id="663477729">
+ <reference key="NSNextResponder" ref="426018584"/>
+ <int key="NSvFlags">274</int>
+ <string key="NSFrameSize">{320, 460}</string>
+ <reference key="NSSuperview" ref="426018584"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="268967673"/>
+ <object class="NSColor" key="IBUIBackgroundColor">
+ <int key="NSColorSpace">1</int>
+ <bytes key="NSRGB">MSAxIDEAA</bytes>
+ </object>
+ <bool key="IBUIClipsSubviews">YES</bool>
+ <bool key="IBUIMultipleTouchEnabled">YES</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <int key="IBUIDataDetectorTypes">1</int>
+ <bool key="IBUIDetectsPhoneNumbers">YES</bool>
+ </object>
+ <object class="IBUIActivityIndicatorView" id="268967673">
+ <reference key="NSNextResponder" ref="426018584"/>
+ <int key="NSvFlags">301</int>
+ <string key="NSFrame">{{150, 115}, {20, 20}}</string>
+ <reference key="NSSuperview" ref="426018584"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView"/>
+ <string key="NSReuseIdentifierKey">_NS:9</string>
+ <bool key="IBUIOpaque">NO</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <bool key="IBUIHidesWhenStopped">NO</bool>
+ <bool key="IBUIAnimating">YES</bool>
+ <int key="IBUIStyle">2</int>
+ </object>
+ </object>
+ <string key="NSFrameSize">{320, 460}</string>
+ <reference key="NSSuperview"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="663477729"/>
+ <object class="NSColor" key="IBUIBackgroundColor">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MQA</bytes>
+ <object class="NSColorSpace" key="NSCustomColorSpace">
+ <int key="NSID">2</int>
+ </object>
+ </object>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ </object>
+ <object class="IBObjectContainer" key="IBDocument.Objects">
+ <object class="NSMutableArray" key="connectionRecords">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">rightBarButtonItem</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="961671599"/>
+ </object>
+ <int key="connectionID">20</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">navButtonsView</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="808907889"/>
+ </object>
+ <int key="connectionID">22</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">backButton</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="453250804"/>
+ </object>
+ <int key="connectionID">25</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">forwardButton</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="981703116"/>
+ </object>
+ <int key="connectionID">26</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">view</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="426018584"/>
+ </object>
+ <int key="connectionID">28</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">webView</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="663477729"/>
+ </object>
+ <int key="connectionID">29</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">initialActivityIndicator</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="268967673"/>
+ </object>
+ <int key="connectionID">33</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">delegate</string>
+ <reference key="source" ref="663477729"/>
+ <reference key="destination" ref="372490531"/>
+ </object>
+ <int key="connectionID">9</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">rightBarButtonItem</string>
+ <reference key="source" ref="1047805472"/>
+ <reference key="destination" ref="961671599"/>
+ </object>
+ <int key="connectionID">14</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchEventConnection" key="connection">
+ <string key="label">goBack</string>
+ <reference key="source" ref="453250804"/>
+ <reference key="destination" ref="663477729"/>
+ <int key="IBEventType">7</int>
+ </object>
+ <int key="connectionID">18</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchEventConnection" key="connection">
+ <string key="label">goForward</string>
+ <reference key="source" ref="981703116"/>
+ <reference key="destination" ref="663477729"/>
+ <int key="IBEventType">7</int>
+ </object>
+ <int key="connectionID">19</int>
+ </object>
+ </object>
+ <object class="IBMutableOrderedSet" key="objectRecords">
+ <object class="NSArray" key="orderedObjects">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBObjectRecord">
+ <int key="objectID">0</int>
+ <object class="NSArray" key="object" id="0">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ <reference key="children" ref="1000"/>
+ <nil key="parent"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-1</int>
+ <reference key="object" ref="372490531"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">File's Owner</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-2</int>
+ <reference key="object" ref="975951072"/>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">6</int>
+ <reference key="object" ref="1047805472"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">10</int>
+ <reference key="object" ref="961671599"/>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">15</int>
+ <reference key="object" ref="808907889"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="453250804"/>
+ <reference ref="981703116"/>
+ </object>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">16</int>
+ <reference key="object" ref="453250804"/>
+ <reference key="parent" ref="808907889"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">17</int>
+ <reference key="object" ref="981703116"/>
+ <reference key="parent" ref="808907889"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">27</int>
+ <reference key="object" ref="426018584"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="663477729"/>
+ <reference ref="268967673"/>
+ </object>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">4</int>
+ <reference key="object" ref="663477729"/>
+ <reference key="parent" ref="426018584"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">31</int>
+ <reference key="object" ref="268967673"/>
+ <reference key="parent" ref="426018584"/>
+ </object>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="flattenedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>-1.CustomClassName</string>
+ <string>-1.IBPluginDependency</string>
+ <string>-2.CustomClassName</string>
+ <string>-2.IBPluginDependency</string>
+ <string>10.IBPluginDependency</string>
+ <string>15.IBPluginDependency</string>
+ <string>16.IBPluginDependency</string>
+ <string>17.IBPluginDependency</string>
+ <string>27.IBPluginDependency</string>
+ <string>31.IBPluginDependency</string>
+ <string>4.IBPluginDependency</string>
+ <string>6.IBPluginDependency</string>
+ </object>
+ <object class="NSArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>GTMOAuth2ViewControllerTouch</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>UIResponder</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="unlocalizedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <reference key="dict.values" ref="0"/>
+ </object>
+ <nil key="activeLocalization"/>
+ <object class="NSMutableDictionary" key="localizations">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <reference key="dict.values" ref="0"/>
+ </object>
+ <nil key="sourceID"/>
+ <int key="maxID">33</int>
+ </object>
+ <object class="IBClassDescriber" key="IBDocument.Classes">
+ <object class="NSMutableArray" key="referencedPartialClassDescriptions">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBPartialClassDescription">
+ <string key="className">GTMOAuth2ViewControllerTouch</string>
+ <string key="superclassName">UIViewController</string>
+ <object class="NSMutableDictionary" key="outlets">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>backButton</string>
+ <string>forwardButton</string>
+ <string>initialActivityIndicator</string>
+ <string>navButtonsView</string>
+ <string>rightBarButtonItem</string>
+ <string>webView</string>
+ </object>
+ <object class="NSArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>UIButton</string>
+ <string>UIButton</string>
+ <string>UIActivityIndicatorView</string>
+ <string>UIView</string>
+ <string>UIBarButtonItem</string>
+ <string>UIWebView</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="toOneOutletInfosByName">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>backButton</string>
+ <string>forwardButton</string>
+ <string>initialActivityIndicator</string>
+ <string>navButtonsView</string>
+ <string>rightBarButtonItem</string>
+ <string>webView</string>
+ </object>
+ <object class="NSArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBToOneOutletInfo">
+ <string key="name">backButton</string>
+ <string key="candidateClassName">UIButton</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">forwardButton</string>
+ <string key="candidateClassName">UIButton</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">initialActivityIndicator</string>
+ <string key="candidateClassName">UIActivityIndicatorView</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">navButtonsView</string>
+ <string key="candidateClassName">UIView</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">rightBarButtonItem</string>
+ <string key="candidateClassName">UIBarButtonItem</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">webView</string>
+ <string key="candidateClassName">UIWebView</string>
+ </object>
+ </object>
+ </object>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">./Classes/GTMOAuth2ViewControllerTouch.h</string>
+ </object>
+ </object>
+ </object>
+ </object>
+ <int key="IBDocument.localizationMode">0</int>
+ <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
+ <real value="1024" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
+ <real value="1536" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3</string>
+ <integer value="3000" key="NS.object.0"/>
+ </object>
+ <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
+ <int key="IBDocument.defaultPropertyAccessControl">3</int>
+ <string key="IBCocoaTouchPluginVersion">1926</string>
+ </data>
+</archive>