diff options
Diffstat (limited to 'example/common/gtm-oauth2/Source/Touch')
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'>" + @"⌚ ?<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> |