// // ASIAuthenticationDialog.m // Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest // // Created by Ben Copsey on 21/08/2009. // Copyright 2009 All-Seeing Interactive. All rights reserved. // #import "ASIAuthenticationDialog.h" #import "ASIHTTPRequest.h" #import static ASIAuthenticationDialog *sharedDialog = nil; BOOL isDismissing = NO; static NSMutableArray *requestsNeedingAuthentication = nil; static const NSUInteger kUsernameRow = 0; static const NSUInteger kUsernameSection = 0; static const NSUInteger kPasswordRow = 1; static const NSUInteger kPasswordSection = 0; static const NSUInteger kDomainRow = 0; static const NSUInteger kDomainSection = 1; @implementation ASIAutorotatingViewController - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation { return YES; } @end @interface ASIAuthenticationDialog () - (void)showTitle; - (void)show; - (NSArray *)requestsRequiringTheseCredentials; - (void)presentNextDialog; - (void)keyboardWillShow:(NSNotification *)notification; - (void)orientationChanged:(NSNotification *)notification; - (void)cancelAuthenticationFromDialog:(id)sender; - (void)loginWithCredentialsFromDialog:(id)sender; @property (retain) UITableView *tableView; @end @implementation ASIAuthenticationDialog #pragma mark init / dealloc + (void)initialize { if (self == [ASIAuthenticationDialog class]) { requestsNeedingAuthentication = [[NSMutableArray array] retain]; } } + (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)theRequest { // No need for a lock here, this will always be called on the main thread if (!sharedDialog) { sharedDialog = [[self alloc] init]; [sharedDialog setRequest:theRequest]; if ([theRequest authenticationNeeded] == ASIProxyAuthenticationNeeded) { [sharedDialog setType:ASIProxyAuthenticationType]; } else { [sharedDialog setType:ASIStandardAuthenticationType]; } [sharedDialog show]; } else { [requestsNeedingAuthentication addObject:theRequest]; } } - (id)init { if ((self = [self initWithNibName:nil bundle:nil])) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { #endif if (![UIDevice currentDevice].generatesDeviceOrientationNotifications) { [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; [self setDidEnableRotationNotifications:YES]; } [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 } #endif } return self; } - (void)dealloc { if ([self didEnableRotationNotifications]) { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; } [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; [request release]; [tableView release]; [presentingController.view removeFromSuperview]; [presentingController release]; [super dealloc]; } #pragma mark keyboard notifications - (void)keyboardWillShow:(NSNotification *)notification { #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { #endif #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_2 NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey]; #else NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardBoundsUserInfoKey]; #endif CGRect keyboardBounds; [keyboardBoundsValue getValue:&keyboardBounds]; UIEdgeInsets e = UIEdgeInsetsMake(0, 0, keyboardBounds.size.height, 0); [[self tableView] setScrollIndicatorInsets:e]; [[self tableView] setContentInset:e]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 } #endif } // Manually handles orientation changes on iPhone - (void)orientationChanged:(NSNotification *)notification { [self showTitle]; UIInterfaceOrientation o = (UIInterfaceOrientation)[[UIApplication sharedApplication] statusBarOrientation]; CGFloat angle = 0; switch (o) { case UIDeviceOrientationLandscapeLeft: angle = 90; break; case UIDeviceOrientationLandscapeRight: angle = -90; break; case UIDeviceOrientationPortraitUpsideDown: angle = 180; break; default: break; } CGRect f = [[UIScreen mainScreen] applicationFrame]; // Swap the frame height and width if necessary if (UIDeviceOrientationIsLandscape(o)) { CGFloat t; t = f.size.width; f.size.width = f.size.height; f.size.height = t; } CGAffineTransform previousTransform = self.view.layer.affineTransform; CGAffineTransform newTransform = CGAffineTransformMakeRotation((CGFloat)(angle * M_PI / 180.0)); // Reset the transform so we can set the size self.view.layer.affineTransform = CGAffineTransformIdentity; self.view.frame = (CGRect){ { 0, 0 }, f.size}; // Revert to the previous transform for correct animation self.view.layer.affineTransform = previousTransform; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration:0.3]; // Set the new transform self.view.layer.affineTransform = newTransform; // Fix the view origin self.view.frame = (CGRect){ { f.origin.x, f.origin.y },self.view.frame.size}; [UIView commitAnimations]; } #pragma mark utilities - (UIViewController *)presentingController { if (!presentingController) { presentingController = [[ASIAutorotatingViewController alloc] initWithNibName:nil bundle:nil]; // Attach to the window, but don't interfere. UIWindow *window = [[[UIApplication sharedApplication] windows] objectAtIndex:0]; [window addSubview:[presentingController view]]; [[presentingController view] setFrame:CGRectZero]; [[presentingController view] setUserInteractionEnabled:NO]; } return presentingController; } - (UITextField *)textFieldInRow:(NSUInteger)row section:(NSUInteger)section { return [[[[[self tableView] cellForRowAtIndexPath: [NSIndexPath indexPathForRow:row inSection:section]] contentView] subviews] objectAtIndex:0]; } - (UITextField *)usernameField { return [self textFieldInRow:kUsernameRow section:kUsernameSection]; } - (UITextField *)passwordField { return [self textFieldInRow:kPasswordRow section:kPasswordSection]; } - (UITextField *)domainField { return [self textFieldInRow:kDomainRow section:kDomainSection]; } #pragma mark show / dismiss + (void)dismiss { if ([sharedDialog respondsToSelector:@selector(presentingViewController)]) [[sharedDialog presentingViewController] dismissModalViewControllerAnimated:YES]; else [[sharedDialog parentViewController] dismissModalViewControllerAnimated:YES]; } - (void)viewDidDisappear:(BOOL)animated { [self retain]; [sharedDialog release]; sharedDialog = nil; [self performSelector:@selector(presentNextDialog) withObject:nil afterDelay:0]; [self release]; } - (void)dismiss { if (self == sharedDialog) { [[self class] dismiss]; } else { if ([self respondsToSelector:@selector(presentingViewController)]) [[self presentingViewController] dismissModalViewControllerAnimated:YES]; else [[self parentViewController] dismissModalViewControllerAnimated:YES]; } } - (void)showTitle { UINavigationBar *navigationBar = [[[self view] subviews] objectAtIndex:0]; UINavigationItem *navItem = [[navigationBar items] objectAtIndex:0]; if (UIInterfaceOrientationIsPortrait([[UIDevice currentDevice] orientation])) { // Setup the title if ([self type] == ASIProxyAuthenticationType) { [navItem setPrompt:@"Login to this secure proxy server."]; } else { [navItem setPrompt:@"Login to this secure server."]; } } else { [navItem setPrompt:nil]; } [navigationBar sizeToFit]; CGRect f = [[self view] bounds]; f.origin.y = [navigationBar frame].size.height; f.size.height -= f.origin.y; [[self tableView] setFrame:f]; } - (void)show { // Remove all subviews UIView *v; while ((v = [[[self view] subviews] lastObject])) { [v removeFromSuperview]; } // Setup toolbar UINavigationBar *bar = [[[UINavigationBar alloc] init] autorelease]; [bar setAutoresizingMask:UIViewAutoresizingFlexibleWidth]; UINavigationItem *navItem = [[[UINavigationItem alloc] init] autorelease]; bar.items = [NSArray arrayWithObject:navItem]; [[self view] addSubview:bar]; [self showTitle]; // Setup toolbar buttons if ([self type] == ASIProxyAuthenticationType) { [navItem setTitle:[[self request] proxyHost]]; } else { [navItem setTitle:[[[self request] url] host]]; } [navItem setLeftBarButtonItem:[[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelAuthenticationFromDialog:)] autorelease]]; [navItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@"Login" style:UIBarButtonItemStyleDone target:self action:@selector(loginWithCredentialsFromDialog:)] autorelease]]; // We show the login form in a table view, similar to Safari's authentication dialog [bar sizeToFit]; CGRect f = [[self view] bounds]; f.origin.y = [bar frame].size.height; f.size.height -= f.origin.y; [self setTableView:[[[UITableView alloc] initWithFrame:f style:UITableViewStyleGrouped] autorelease]]; [[self tableView] setDelegate:self]; [[self tableView] setDataSource:self]; [[self tableView] setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; [[self view] addSubview:[self tableView]]; // Force reload the table content, and focus the first field to show the keyboard [[self tableView] reloadData]; [[[[[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].contentView subviews] objectAtIndex:0] becomeFirstResponder]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { [self setModalPresentationStyle:UIModalPresentationFormSheet]; } #endif [[self presentingController] presentModalViewController:self animated:YES]; } #pragma mark button callbacks - (void)cancelAuthenticationFromDialog:(id)sender { for (ASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) { [theRequest cancelAuthentication]; [requestsNeedingAuthentication removeObject:theRequest]; } [self dismiss]; } - (NSArray *)requestsRequiringTheseCredentials { NSMutableArray *requestsRequiringTheseCredentials = [NSMutableArray array]; NSURL *requestURL = [[self request] url]; for (ASIHTTPRequest *otherRequest in requestsNeedingAuthentication) { NSURL *theURL = [otherRequest url]; if (([otherRequest authenticationNeeded] == [[self request] authenticationNeeded]) && [[theURL host] isEqualToString:[requestURL host]] && ([theURL port] == [requestURL port] || ([requestURL port] && [[theURL port] isEqualToNumber:[requestURL port]])) && [[theURL scheme] isEqualToString:[requestURL scheme]] && ((![otherRequest authenticationRealm] && ![[self request] authenticationRealm]) || ([otherRequest authenticationRealm] && [[self request] authenticationRealm] && [[[self request] authenticationRealm] isEqualToString:[otherRequest authenticationRealm]]))) { [requestsRequiringTheseCredentials addObject:otherRequest]; } } [requestsRequiringTheseCredentials addObject:[self request]]; return requestsRequiringTheseCredentials; } - (void)presentNextDialog { if ([requestsNeedingAuthentication count]) { ASIHTTPRequest *nextRequest = [requestsNeedingAuthentication objectAtIndex:0]; [requestsNeedingAuthentication removeObjectAtIndex:0]; [[self class] presentAuthenticationDialogForRequest:nextRequest]; } } - (void)loginWithCredentialsFromDialog:(id)sender { for (ASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) { NSString *username = [[self usernameField] text]; NSString *password = [[self passwordField] text]; if (username == nil) { username = @""; } if (password == nil) { password = @""; } if ([self type] == ASIProxyAuthenticationType) { [theRequest setProxyUsername:username]; [theRequest setProxyPassword:password]; } else { [theRequest setUsername:username]; [theRequest setPassword:password]; } // Handle NTLM domains NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme]; if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) { NSString *domain = [[self domainField] text]; if ([self type] == ASIProxyAuthenticationType) { [theRequest setProxyDomain:domain]; } else { [theRequest setDomain:domain]; } } [theRequest retryUsingSuppliedCredentials]; [requestsNeedingAuthentication removeObject:theRequest]; } [self dismiss]; } #pragma mark table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView { NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme]; if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) { return 2; } return 1; } - (CGFloat)tableView:(UITableView *)aTableView heightForFooterInSection:(NSInteger)section { if (section == [self numberOfSectionsInTableView:aTableView]-1) { return 30; } return 0; } - (CGFloat)tableView:(UITableView *)aTableView heightForHeaderInSection:(NSInteger)section { if (section == 0) { #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { return 54; } #endif return 30; } return 0; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { if (section == 0) { return [[self request] authenticationRealm]; } return nil; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_0 UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease]; #else UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,0,0) reuseIdentifier:nil] autorelease]; #endif [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; CGRect f = CGRectInset([cell bounds], 10, 10); UITextField *textField = [[[UITextField alloc] initWithFrame:f] autorelease]; [textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone]; [textField setAutocorrectionType:UITextAutocorrectionTypeNo]; NSUInteger s = [indexPath section]; NSUInteger r = [indexPath row]; if (s == kUsernameSection && r == kUsernameRow) { [textField setPlaceholder:@"User"]; } else if (s == kPasswordSection && r == kPasswordRow) { [textField setPlaceholder:@"Password"]; [textField setSecureTextEntry:YES]; } else if (s == kDomainSection && r == kDomainRow) { [textField setPlaceholder:@"Domain"]; } [cell.contentView addSubview:textField]; return cell; } - (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section { if (section == 0) { return 2; } else { return 1; } } - (NSString *)tableView:(UITableView *)aTableView titleForFooterInSection:(NSInteger)section { if (section == [self numberOfSectionsInTableView:aTableView]-1) { // If we're using Basic authentication and the connection is not using SSL, we'll show the plain text message if ([[[self request] authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic] && ![[[[self request] url] scheme] isEqualToString:@"https"]) { return @"Password will be sent in the clear."; // We are using Digest, NTLM, or any scheme over SSL } else { return @"Password will be sent securely."; } } return nil; } #pragma mark - @synthesize request; @synthesize type; @synthesize tableView; @synthesize didEnableRotationNotifications; @synthesize presentingController; @end