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