Use service catalog and update version
authorMiltiadis Vasilakis <mvasilak@gmail.com>
Tue, 25 Jun 2013 08:35:55 +0000 (11:35 +0300)
committerMiltiadis Vasilakis <mvasilak@gmail.com>
Tue, 25 Jun 2013 08:35:55 +0000 (11:35 +0300)
Support tokens mechanism to retrieve service catalog,
for account creation and change of settings.
Allow fallback for older server versions.
Add option for ignoring SSL errors.
Migrate providers from older versions.
Other minor changes.
Update version.

22 files changed:
Classes/AccountDetailsViewController.h
Classes/AccountDetailsViewController.m
Classes/AccountManager.h
Classes/AccountManager.m
Classes/AccountSettingsViewController.h
Classes/AccountSettingsViewController.m
Classes/ErrorAlerter.m
Classes/FolderDetailViewController.m
Classes/GetObjectsRequest.m
Classes/NSString+Conveniences.m
Classes/OpenStackAccount.h
Classes/OpenStackAccount.m
Classes/OpenStackAppDelegate.m
Classes/OpenStackRequest.h
Classes/OpenStackRequest.m
Classes/Provider.h
Classes/Provider.m
Classes/ProvidersViewController.m
Classes/RSTextFieldCell.m
Classes/StorageObjectViewController.m
Classes/UIViewController+Conveniences.m
OpenStack-Info.plist

index 04ea7b4..6159fcf 100755 (executable)
 
 @interface AccountDetailsViewController : UITableViewController <UITextFieldDelegate> {
     Provider *provider;
-    UITextField *usernameTextField;
-    UITextField *authTokenTextField;
     UITextField *providerNameTextField;
-    UITextField *apiEndpointTextField;
+    UITextField *providerAuthURLTextField;
+    UITextField *providerPithosObjectStoreURLTextField;
+    UITextField *providerAstakosAccountURLTextField;
+    UITextField *providerAstakosWebloginURLTextField;
+    UISwitch *providerManualSwitch;
+
+    UITextField *authTokenTextField;
+    UITextField *usernameTextField;
+    
+    UISwitch *ignoreSSLErrorsSwitch;
+    
     RootViewController *rootViewController;
 
+    NSString *providerName;
+    NSString *providerAuthURLString;
+    NSString *providerPithosObjectStoreURLString;
+    NSString *providerAstakosAccountURLString;
+    NSString *providerAstakosWebloginURLString;
+    
     NSString *username;
     NSString *authToken;
+    
     BOOL customProvider;
-    NSInteger authenticationSection;
     NSInteger providerSection;
+    NSInteger authenticationSection;
     NSInteger getTokenSection;
+    NSInteger optionsSection;
     
-    OpenStackAccount *account;    
+    OpenStackAccount *account;
 }
 
-@property (nonatomic, retain) Provider *provider;
+@property (nonatomic, copy) Provider *provider;
 @property (nonatomic, retain) RootViewController *rootViewController;
 
-@property (nonatomic, retain) NSString *username;
 @property (nonatomic, retain) NSString *authToken;
+@property (nonatomic, retain) NSString *username;
 
-- (void)setUsername:(NSString *)aUsername andAuthToken:(NSString *)anAuthToken;
 - (void)saveButtonPressed:(id)sender;
 
 @end
index efcc50d..0ea04a0 100755 (executable)
 #import "OpenStackAppDelegate.h"
 #import "UIColor+MoreColors.h"
 
-#define kUsername 0
-#define kAuthToken 1
-
 #define kProviderName 0
-#define kAuthEndpoint 1
+#define kProviderManualSwitch 1
+#define kProviderAuthURLOrPithosObjectStoreURL 2
+#define kProviderAstakosAccountURL 3
+#define kProviderAstakosWebloginURL 4
+
+#define kAuthToken 0
+#define kUsername 1
+
+#define kIgnoreSSLErrorsSwitch 0
+
+@interface AccountDetailsViewController()
+@property (nonatomic, retain) UITextField *providerNameTextField;
+@property (nonatomic, retain) UITextField *providerAuthURLTextField;
+@property (nonatomic, retain) UITextField *providerPithosObjectStoreURLTextField;
+@property (nonatomic, retain) UITextField *providerAstakosAccountURLTextField;
+@property (nonatomic, retain) UITextField *providerAstakosWebloginURLTextField;
+
+@property (nonatomic, retain) UITextField *authTokenTextField;
+@property (nonatomic, retain) UITextField *usernameTextField;
+
+@property (nonatomic, retain) NSString *providerName;
+@property (nonatomic, retain) NSString *providerAuthURLString;
+@property (nonatomic, retain) NSString *providerPithosObjectStoreURLString;
+@property (nonatomic, retain) NSString *providerAstakosAccountURLString;
+@property (nonatomic, retain) NSString *providerAstakosWebloginURLString;
+@end
 
 @implementation AccountDetailsViewController
 
 @synthesize provider, rootViewController;
-@synthesize username, authToken;
+@synthesize authToken, username;
+@synthesize providerNameTextField, providerAuthURLTextField, providerPithosObjectStoreURLTextField, providerAstakosAccountURLTextField,
+            providerAstakosWebloginURLTextField;
+@synthesize authTokenTextField, usernameTextField;
+@synthesize providerName, providerAuthURLString, providerPithosObjectStoreURLString, providerAstakosAccountURLString,
+            providerAstakosWebloginURLString;
 
 #pragma mark - View lifecycle
 
 - (void)viewDidLoad {
     [super viewDidLoad];
     self.navigationItem.title = @"Authentication";
+    self.navigationItem.rightBarButtonItem.enabled = NO;
     providerSection = -1;
     authenticationSection = 0;
     getTokenSection = 1;
+    optionsSection = 2;
     [self addSaveButton];
+    
+    ignoreSSLErrorsSwitch = [[UISwitch alloc] init];
+    ignoreSSLErrorsSwitch.on = NO;
 }
 
 - (void)viewWillAppear:(BOOL)animated {
-    [super viewWillAppear:animated];
-    if (provider == nil) {
+    if (!provider) {
         customProvider = YES;
         providerSection = 0;
         authenticationSection = 1;
         getTokenSection = 2;
-        [self.tableView reloadData];
+        optionsSection = 3;
+        provider = [[Provider alloc] init];
+        
+        providerManualSwitch = [[UISwitch alloc] init];
+        [providerManualSwitch addTarget:self action:@selector(providerManualSwitchChanged:) forControlEvents:UIControlEventValueChanged];
+        providerManualSwitch.on = provider.manual;
     }
+    [super viewWillAppear:animated];
 }
 
 - (void)viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
-    [customProvider ? providerNameTextField : usernameTextField becomeFirstResponder];
+    [customProvider ? providerNameTextField : authTokenTextField becomeFirstResponder];
 }
 
 - (void)viewWillDisappear:(BOOL)animated {
     [super viewWillDisappear:animated];
+    [providerNameTextField resignFirstResponder];
+    [providerAuthURLTextField resignFirstResponder];
+    [providerPithosObjectStoreURLTextField resignFirstResponder];
+    [providerAstakosAccountURLTextField resignFirstResponder];
+    [providerAstakosWebloginURLTextField resignFirstResponder];
     [usernameTextField resignFirstResponder];
     [authTokenTextField resignFirstResponder];
-    [providerNameTextField resignFirstResponder];
-    [apiEndpointTextField resignFirstResponder];
 }
 
 #pragma mark - Memory management
 
 - (void)dealloc {
+    [account release];
     [provider release];
     [rootViewController release];
-    [username release];
+    [providerNameTextField release];
+    [providerAuthURLTextField release];
+    [providerPithosObjectStoreURLTextField release];
+    [providerAstakosAccountURLTextField release];
+    [providerAstakosWebloginURLTextField release];
+    [authTokenTextField release];
+    [usernameTextField release];
     [authToken release];
+    [username release];
+    [providerName release];
+    [providerAuthURLString release];
+    [providerPithosObjectStoreURLString release];
+    [providerAstakosAccountURLString release];
+    [providerAstakosWebloginURLString release];
+    [providerManualSwitch release];
+    [ignoreSSLErrorsSwitch release];
     [super dealloc];
 }
 
+#pragma mark - Internal
+
+- (void)providerManualSwitchChanged:(id)sender {
+    provider.manual = providerManualSwitch.on;
+    [self.tableView reloadData];
+}
+
 #pragma mark - UITableViewDataSource
 
 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
-    if (customProvider)
-        return 3;
-    else
-        return 2;
+    return (customProvider ? 4 : 3);
 }
 
 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    if (section == getTokenSection)
+    if (section == authenticationSection) {
+        return (provider.manual ? 2 : 1);
+    } else if (section == providerSection) {
+        return (provider.manual ? 5 : 3);
+    } else {
         return 1;
-    else
-        return 2;
+    }
 }
 
 - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
         } else {
             return [NSString stringWithFormat:@"%@ Login", provider.name];
         }
+    } else if (section == optionsSection) {
+        return @"Options";
     } else if (section == providerSection) {
         return @"Provider Details";
     } else {
-        return @"";
+        return nil;
     }
 }
 
-- (RSTextFieldCell *)textCell:(NSString *)labelText textField:(UITextField **)textField secure:(BOOL)secure returnKeyType:(UIReturnKeyType)returnKeyType {
-    RSTextFieldCell *cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:labelText];
-    
-    if (cell == nil) {
-        if ([labelText isEqualToString:@"API URL"])
-            cell = [[[RSTextFieldCell alloc] initCellWithFixedLabel:@"/v1" withStyle:UITableViewCellStyleValue1
-                                                    reuseIdentifier:labelText] autorelease];
-        else
-            cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1
-                                           reuseIdentifier:labelText] autorelease];
-        
-        cell.selectionStyle = UITableViewCellSelectionStyleNone;
+- (RSTextFieldCell *)textFieldCellWithReuseIdentifier:(NSString *)reuseIdentifier {
+    RSTextFieldCell *cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
+    if (!cell) {
+        cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier] autorelease];
         cell.modalPresentationStyle = UIModalPresentationFormSheet;
-        cell.textLabel.text = labelText;
-        *textField = cell.textField;
-        ((UITextField *)*textField).delegate = self;
-        ((UITextField *)*textField).secureTextEntry = secure;
-        ((UITextField *)*textField).returnKeyType = returnKeyType;
+        cell.selectionStyle = UITableViewCellSelectionStyleNone;
     }
-    
     return cell;
 }
 
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     UITableViewCell *cell = nil;
-    
     if (indexPath.section == authenticationSection) {
-        if (indexPath.row == kUsername) {
-            cell = [self textCell:@"UUID" textField:&usernameTextField secure:NO returnKeyType:UIReturnKeyNext];
-            if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
-                CGRect rect = usernameTextField.frame;
-                CGFloat offset = 19.0;
-                usernameTextField.frame = CGRectMake(rect.origin.x + offset, rect.origin.y, rect.size.width - offset, rect.size.height);
+        if (indexPath.row == kAuthToken) {
+            static NSString *CellIdentifier = @"AuthTokenCell";
+            cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
+                ((RSTextFieldCell *)cell).modalPresentationStyle = UIModalPresentationFormSheet;
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+                cell.textLabel.text = @"Token";
+                self.authTokenTextField = ((RSTextFieldCell *)cell).textField;
+                if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
+                    CGRect rect = authTokenTextField.frame;
+                    CGFloat offset = 19.0;
+                    authTokenTextField.frame = CGRectMake(rect.origin.x + offset, rect.origin.y, rect.size.width - offset, rect.size.height);
+                }
+                authTokenTextField.placeholder = @"Input or retrieve";
+                authTokenTextField.delegate = self;
             }
-            if (username)
-                usernameTextField.text = username;
-        } else if (indexPath.row == kAuthToken) {
-            cell = [self textCell:@"Token" textField:&authTokenTextField secure:NO returnKeyType:UIReturnKeyDone];
+            authTokenTextField.returnKeyType = (provider.manual ? UIReturnKeyNext : UIReturnKeyDone);
             if (authToken)
                 authTokenTextField.text = authToken;
-        }
-    } else if (indexPath.section == providerSection) {
-        if (indexPath.row == kProviderName) {
-            cell = [self textCell:@"Name" textField:&providerNameTextField secure:NO returnKeyType:UIReturnKeyNext];
-            providerNameTextField.placeholder = @"E.g.: pithos";
-        } else if (indexPath.row == kAuthEndpoint) {
-            cell = [self textCell:@"API URL" textField:&apiEndpointTextField secure:NO returnKeyType:UIReturnKeyNext];
-            apiEndpointTextField.placeholder = @"Type server's URL here";
+        } else if (indexPath.row == kUsername) {
+            static NSString *CellIdentifier = @"UsernameCell";
+            cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
+                ((RSTextFieldCell *)cell).modalPresentationStyle = UIModalPresentationFormSheet;
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+                cell.textLabel.text = @"UUID";
+                self.usernameTextField = ((RSTextFieldCell *)cell).textField;
+                usernameTextField.placeholder = @"Input or retrieve";
+                usernameTextField.returnKeyType = UIReturnKeyDone;
+                usernameTextField.delegate = self;
+            }
+            if (username)
+                usernameTextField.text = username;
         }
     } else if (indexPath.section == getTokenSection) {
         static NSString *CellIdentifier = @"Cell";
         cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
-        if (cell == nil) {
+        if (!cell) {
             cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
         }
-        cell.textLabel.text = @"Get Token";
+        cell.textLabel.textAlignment = UITextAlignmentCenter;
+        cell.textLabel.text = @"Retrieve Token";
+    } else if (indexPath.section == optionsSection) {
+        if (indexPath.row == kIgnoreSSLErrorsSwitch) {
+            static NSString *CellIdentifier = @"SwitchCell";
+            cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+            }
+            cell.textLabel.text = @"Ignore SSL Errors";
+            cell.accessoryView = ignoreSSLErrorsSwitch;
+        }
+    } else if (indexPath.section == providerSection) {
+        if (indexPath.row == kProviderName) {
+            static NSString *CellIdentifier = @"ProviderNameCell";
+            cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
+                ((RSTextFieldCell *)cell).modalPresentationStyle = UIModalPresentationFormSheet;
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+                cell.textLabel.text = @"Name";
+                self.providerNameTextField = ((RSTextFieldCell *)cell).textField;
+                providerNameTextField.placeholder = @"E.g.: pithos";
+                providerNameTextField.returnKeyType = UIReturnKeyNext;
+                providerNameTextField.delegate = self;
+            }
+            providerNameTextField.text = providerName;
+        } else if (indexPath.row == kProviderManualSwitch) {
+            static NSString *CellIdentifier = @"SwitchCell";
+            cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+            }
+            cell.textLabel.text = @"Manual";
+            cell.accessoryView = providerManualSwitch;
+        } else if (indexPath.row == kProviderAuthURLOrPithosObjectStoreURL) {
+            if (providerManualSwitch.on) {
+                static NSString *CellIdentifier = @"ProviderPithosObjectStoreURLCell";
+                cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+                if (!cell) {
+                    cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
+                    ((RSTextFieldCell *)cell).modalPresentationStyle = UIModalPresentationFormSheet;
+                    cell.selectionStyle = UITableViewCellSelectionStyleNone;
+                    cell.textLabel.text = @"Pithos URL";
+                    self.providerPithosObjectStoreURLTextField = ((RSTextFieldCell *)cell).textField;
+                    providerPithosObjectStoreURLTextField.placeholder = @"Required";
+                    providerPithosObjectStoreURLTextField.keyboardType = UIKeyboardTypeURL;
+                    providerPithosObjectStoreURLTextField.returnKeyType = UIReturnKeyNext;
+                    providerPithosObjectStoreURLTextField.delegate = self;
+                }
+                providerPithosObjectStoreURLTextField.text = providerPithosObjectStoreURLString;
+            } else {
+                static NSString *CellIdentifier = @"ProviderAuthURLCell";
+                cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+                if (!cell) {
+                    cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
+                    ((RSTextFieldCell *)cell).modalPresentationStyle = UIModalPresentationFormSheet;
+                    cell.selectionStyle = UITableViewCellSelectionStyleNone;
+                    cell.textLabel.text = @"Auth URL";
+                    self.providerAuthURLTextField = ((RSTextFieldCell *)cell).textField;
+                    providerAuthURLTextField.placeholder = @"Required";
+                    providerAuthURLTextField.keyboardType = UIKeyboardTypeURL;
+                    providerAuthURLTextField.returnKeyType = UIReturnKeyNext;
+                    providerAuthURLTextField.delegate = self;
+                }
+                providerAuthURLTextField.text = providerAuthURLString;
+            }
+        } else if (indexPath.row == kProviderAstakosAccountURL) {
+            static NSString *CellIdentifier = @"ProviderAstakosAccountURLCell";
+            cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
+                ((RSTextFieldCell *)cell).modalPresentationStyle = UIModalPresentationFormSheet;
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+                cell.textLabel.text = @"Astakos URL";
+                self.providerAstakosAccountURLTextField = ((RSTextFieldCell *)cell).textField;
+                providerAstakosAccountURLTextField.placeholder = @"Required";
+                providerAstakosAccountURLTextField.keyboardType = UIKeyboardTypeURL;
+                providerAstakosAccountURLTextField.returnKeyType = UIReturnKeyNext;
+                providerAstakosAccountURLTextField.delegate = self;
+            }
+            providerAstakosAccountURLTextField.text = providerAstakosAccountURLString;
+        } else if (indexPath.row == kProviderAstakosWebloginURL) {
+            static NSString *CellIdentifier = @"ProviderAstakosWebloginURLCell";
+            cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
+                ((RSTextFieldCell *)cell).modalPresentationStyle = UIModalPresentationFormSheet;
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+                cell.textLabel.text = @"Astakos Weblogin";
+                self.providerAstakosWebloginURLTextField = ((RSTextFieldCell *)cell).textField;
+                providerAstakosWebloginURLTextField.placeholder = @"Required";
+                providerAstakosWebloginURLTextField.keyboardType = UIKeyboardTypeURL;
+                providerAstakosWebloginURLTextField.returnKeyType = UIReturnKeyNext;
+                providerAstakosWebloginURLTextField.delegate = self;
+            }
+            providerAstakosWebloginURLTextField.text = providerAstakosWebloginURLString;
+        }
     }
-    
     return cell;
 }
 
 
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
     if (indexPath.section == getTokenSection) {
-        NSURL *loginURLPrefix = nil;
-        if (customProvider) {
-            if (!apiEndpointTextField.text || ![apiEndpointTextField.text isURL]) {
-                [self alert:@"No API URL" message:@"Please enter an API Authentication URL"];
+        if (customProvider && !provider.manual && ![providerAuthURLString isURL]) {
+            [self alert:nil message:@"Please enter an Auth URL"];
+            [providerAuthURLTextField becomeFirstResponder];
+            [tableView deselectRowAtIndexPath:indexPath animated:NO];
+        } else if (customProvider && provider.manual && ![providerAstakosWebloginURLString isURL]) {
+            [self alert:nil message:@"Please enter a Weblogin URL"];
+            [providerAstakosWebloginURLTextField becomeFirstResponder];
+            [tableView deselectRowAtIndexPath:indexPath animated:NO];
+        } else {
+            if (!account) {
+                account = [[OpenStackAccount alloc] init];
+            }
+            
+            if (customProvider) {
+                if (!provider.manual) {
+                    provider.authURL = [NSURL URLWithString:providerAuthURLString];
+                } else {
+                    provider.astakosWebloginURL = [NSURL URLWithString:providerAstakosWebloginURLString];
+                }
+            }
+            account.provider = [[provider copy] autorelease];
+            account.authToken = nil;
+            account.username = nil;
+            account.ignoreSSLErrors = ignoreSSLErrorsSwitch.on;
+
+            if (customProvider && provider.manual) {
+                NSString *protocol = [[[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"] objectAtIndex:0] objectForKey:@"CFBundleURLSchemes"] objectAtIndex:0];
+                NSString *loginURL = [NSString stringWithFormat:@"%@?next=%@://login&force=", account.provider.loginURL, protocol];
+                [[UIApplication sharedApplication] openURL:[NSURL URLWithString:loginURL]];
+                [tableView deselectRowAtIndexPath:indexPath animated:NO];
             } else {
-                loginURLPrefix = [[NSURL URLWithString:apiEndpointTextField.text] URLByAppendingPathComponent:@"login"];
+                // POST <authURL>/tokens with no body to retrieve URLs or fallback to default manual provider
+                __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Accessing service catalog..."
+                                                                                                               andAddToView:self.view];
+                [[account.manager serviceCatalog]
+                 success:^(OpenStackRequest *request) {
+                     [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                     self.providerPithosObjectStoreURLString = account.provider.pithosObjectStoreURL.description;
+                     self.providerAstakosAccountURLString = account.provider.astakosAccountURL.description;
+                     self.providerAstakosWebloginURLString = account.provider.astakosWebloginURL.description;
+                     [self.tableView reloadData];
+                     NSString *protocol = [[[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"] objectAtIndex:0] objectForKey:@"CFBundleURLSchemes"] objectAtIndex:0];
+                     NSString *loginURL = [NSString stringWithFormat:@"%@?next=%@://login&force=", account.provider.loginURL, protocol];
+                     [[UIApplication sharedApplication] openURL:[NSURL URLWithString:loginURL]];
+                     [tableView deselectRowAtIndexPath:indexPath animated:NO];
+                 }
+                 failure:^(OpenStackRequest *request) {
+                     [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                     if (request.responseStatusCode == 404) {
+                         self.providerPithosObjectStoreURLString = account.provider.pithosObjectStoreURL.description;
+                         self.providerAstakosAccountURLString = account.provider.astakosAccountURL.description;
+                         self.providerAstakosWebloginURLString = account.provider.astakosWebloginURL.description;
+                         provider.manual = YES;
+                         providerManualSwitch.on = YES;
+                         [self.tableView reloadData];
+                         NSString *protocol = [[[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"] objectAtIndex:0] objectForKey:@"CFBundleURLSchemes"] objectAtIndex:0];
+                         NSString *loginURL = [NSString stringWithFormat:@"%@?next=%@://login&force=", account.provider.loginURL, protocol];
+                         [[UIApplication sharedApplication] openURL:[NSURL URLWithString:loginURL]];
+                     } else {
+                         [self alert:nil request:request];
+                     }
+                     [tableView deselectRowAtIndexPath:indexPath animated:NO];
+                 }];
             }
-        } else {
-            loginURLPrefix = provider.loginURL;
         }
-        NSString *protocol = [[[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"] objectAtIndex:0] objectForKey:@"CFBundleURLSchemes"] objectAtIndex:0];
-        NSString *loginURL = [NSString stringWithFormat:@"%@?next=%@://login&force=", loginURLPrefix, protocol];
-        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:loginURL]];
-        [tableView deselectRowAtIndexPath:indexPath animated:NO];
     }
 }
 
 
 - (BOOL)textFieldShouldReturn:(UITextField *)textField {
     if ([textField isEqual:providerNameTextField]) {
-        [apiEndpointTextField becomeFirstResponder];
-    } else if ([textField isEqual:apiEndpointTextField]) {
-        [usernameTextField becomeFirstResponder];
-        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:kAuthToken inSection:authenticationSection] atScrollPosition:UITableViewScrollPositionNone animated:YES];
-    } else if ([textField isEqual:usernameTextField]) {
+        [providerManualSwitch.on ? providerPithosObjectStoreURLTextField : providerAuthURLTextField becomeFirstResponder];
+        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:kProviderAuthURLOrPithosObjectStoreURL inSection:providerSection] atScrollPosition:UITableViewScrollPositionNone animated:YES];
+    } else if ([textField isEqual:providerPithosObjectStoreURLTextField]) {
+        [providerAstakosAccountURLTextField becomeFirstResponder];
+        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:kProviderAstakosAccountURL inSection:providerSection] atScrollPosition:UITableViewScrollPositionNone animated:YES];
+    } else if ([textField isEqual:providerAstakosAccountURLTextField]) {
+        [providerAstakosWebloginURLTextField becomeFirstResponder];
+        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:kProviderAstakosWebloginURL inSection:providerSection] atScrollPosition:UITableViewScrollPositionNone animated:YES];
+    } else if ([textField isEqual:providerAuthURLTextField] || [textField isEqual:providerAstakosWebloginURLTextField]) {
         [authTokenTextField becomeFirstResponder];
-        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:kAuthToken inSection:authenticationSection] atScrollPosition:UITableViewScrollPositionNone animated:YES];
+        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:kAuthToken inSection:authenticationSection] atScrollPosition:UITableViewScrollPositionNone animated:YES];        
+    } else if ([textField isEqual:authTokenTextField] && (authTokenTextField.returnKeyType == UIReturnKeyNext)) {
+        [usernameTextField becomeFirstResponder];
+        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:kUsername inSection:authenticationSection] atScrollPosition:UITableViewScrollPositionNone animated:YES];
     } else {
         [textField resignFirstResponder];
         [self saveButtonPressed:self];
 
 - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
     NSString *result = [textField.text stringByReplacingCharactersInRange:range withString:string];
-    if ([textField isEqual:usernameTextField]) {
-        self.username = result;
-    } else if ([textField isEqual:authTokenTextField]) {
+    if ([textField isEqual:authTokenTextField]) {
         self.authToken = result;
+    } else if ([textField isEqual:usernameTextField]) {
+        self.username = result;
+    } else if ([textField isEqual:providerNameTextField]) {
+        self.providerName = result;
+    } else if ([textField isEqual:providerAuthURLTextField]) {
+        self.providerAuthURLString = result;
+    } else if ([textField isEqual:providerPithosObjectStoreURLTextField]) {
+        self.providerPithosObjectStoreURLString = result;
+    } else if ([textField isEqual:providerAstakosAccountURLTextField]) {
+        self.providerAstakosAccountURLString = result;
+    } else if ([textField isEqual:providerAstakosWebloginURLTextField]) {
+        self.providerAstakosWebloginURLString = result;
     }
     return YES;
 }
 
-#pragma mark - Actions
-
-- (void)setUsername:(NSString *)aUsername andAuthToken:(NSString *)anAuthToken {
-    if (aUsername)
-        self.username = aUsername;
-    if (anAuthToken)
-        self.authToken = anAuthToken;
-}
-
 #pragma mark - Button Handlers
 
 - (void)saveButtonPressed:(id)sender {
 #pragma mark - Authentication
 
 - (void)authenticate {
-    if (customProvider && (!providerNameTextField.text || [providerNameTextField.text isEqualToString:@""])) {
-        [self alert:nil message:@"Please enter a Provider Name."];
+    if (customProvider && !providerName.length) {
+        [self alert:nil message:@"Please enter a Provider Name"];
         self.navigationItem.rightBarButtonItem.enabled = YES;
         [providerNameTextField becomeFirstResponder];
-    } else if (customProvider && (!apiEndpointTextField.text || ![apiEndpointTextField.text isURL])) {
-        [self alert:nil message:@"Please enter an API Authentication URL."];
+    } else if (customProvider && !provider.manual && ![providerAuthURLString isURL]) {
+        [self alert:nil message:@"Please enter an Auth URL"];
         self.navigationItem.rightBarButtonItem.enabled = YES;
-        [apiEndpointTextField becomeFirstResponder];
-    } else if (!usernameTextField.text || [usernameTextField.text isEqualToString:@""]) {
-        [self alert:nil message:@"Please enter your UUID."];
+        [providerAuthURLTextField becomeFirstResponder];
+    } else if (customProvider && provider.manual && ![providerPithosObjectStoreURLString isURL]) {
+        [self alert:nil message:@"Please enter a Pithos URL"];
         self.navigationItem.rightBarButtonItem.enabled = YES;
-        [usernameTextField becomeFirstResponder];
+        [providerPithosObjectStoreURLTextField becomeFirstResponder];
+    } else if (customProvider && provider.manual && ![providerAstakosAccountURLString isURL]) {
+        [self alert:nil message:@"Please enter an Astakos URL"];
+        self.navigationItem.rightBarButtonItem.enabled = YES;
+        [providerAstakosAccountURLTextField becomeFirstResponder];
+    } else if (customProvider && provider.manual && ![providerAstakosWebloginURLString isURL]) {
+        [self alert:nil message:@"Please enter a Weblogin URL"];
+        self.navigationItem.rightBarButtonItem.enabled = YES;
+        [providerAstakosWebloginURLTextField becomeFirstResponder];
     } else if (!authTokenTextField.text || [authTokenTextField.text isEqualToString:@""]) {
-        [self alert:nil message:@"Please enter your Token."];
+        [self alert:nil message:@"Please enter your Token"];
         self.navigationItem.rightBarButtonItem.enabled = YES;
         [authTokenTextField becomeFirstResponder];
+    } else if (provider.manual && (!usernameTextField.text || [usernameTextField.text isEqualToString:@""])) {
+        [self alert:nil message:@"Please enter your UUID"];
+        self.navigationItem.rightBarButtonItem.enabled = YES;
+        [usernameTextField becomeFirstResponder];
     } else {
-        account = [[OpenStackAccount alloc] init];
+        if (!account) {
+            account = [[OpenStackAccount alloc] init];
+        }
+        
         if (customProvider) {
-            account.provider = [[[Provider alloc] init] autorelease];
-            account.provider.name = providerNameTextField.text;
-            account.provider.hostURL = [NSURL URLWithString:apiEndpointTextField.text];
-            account.provider.version = @"v1";
-        } else {
-            account.provider = provider;
+            if (!provider.manual) {
+                provider.authURL = [NSURL URLWithString:providerAuthURLString];
+            } else {
+                provider.pithosObjectStoreURL = [NSURL URLWithString:providerPithosObjectStoreURLString];
+                provider.astakosAccountURL = [NSURL URLWithString:providerAstakosAccountURLString];
+                provider.astakosWebloginURL = [NSURL URLWithString:providerAstakosWebloginURLString];
+            }
         }
-        account.username = usernameTextField.text;
+        account.provider = [[provider copy] autorelease];
         account.authToken = authTokenTextField.text;
-
+        account.username = usernameTextField.text;
+        account.ignoreSSLErrors = ignoreSSLErrorsSwitch.on;
+        
         __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Authenticating..."
                                                                                                        andAddToView:self.view];
-        [[account.manager authenticate]
-         success:^(OpenStackRequest *request) {
-             if ([request isSuccess]) {
-                 NSString *storageURLString = [[request responseHeaders] objectForKey:@"X-Storage-Url"];
-                 if (storageURLString) {
-                     account.filesURL = [NSURL URLWithString:storageURLString];
+        if (provider.manual) {
+            [[account.manager authenticate]
+             success:^(OpenStackRequest *request) {
+                 if ([request isSuccess]) {
+                     NSString *storageURLString = [[request responseHeaders] objectForKey:@"X-Storage-Url"];
+                     if (storageURLString) {
+                         account.filesURL = [NSURL URLWithString:storageURLString];
+                     } else {
+                         account.filesURL = [account.provider.authEndpointURL URLByAppendingPathComponent:account.username];
+                     }
+                     [account persist];
+                     [[account.manager userCatalogForDisplaynames:nil UUIDs:[NSArray arrayWithObject:account.username]]
+                      success:^(OpenStackRequest *request) {
+                          [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                          [rootViewController.tableView reloadData];
+                          [self.navigationController dismissModalViewControllerAnimated:YES];
+                      }
+                      failure:^(OpenStackRequest *request) {
+                          [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                          [rootViewController.tableView reloadData];
+                          [self.navigationController dismissModalViewControllerAnimated:YES];
+                      }];
+                } else {
+                    [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                    self.navigationItem.rightBarButtonItem.enabled = YES;
+                    [self alert:@"Authentication Failure" message:@"Please check your Token and UUID."];
+                }
+             }
+             failure:^(OpenStackRequest *request) {
+                 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                 self.navigationItem.rightBarButtonItem.enabled = YES;
+                 if ([request responseStatusCode] == 401) {
+                     [self alert:@"Authentication Failure" message:@"Please check your Token and UUID."];
                  } else {
+                     [self alert:nil request:request];
+                 }
+             }];
+        } else {
+            [[account.manager serviceCatalog]
+             success:^(OpenStackRequest *request) {
+                 if ([request isSuccess]) {
                      account.filesURL = [account.provider.authEndpointURL URLByAppendingPathComponent:account.username];
+                     [account persist];
+                     [[account.manager userCatalogForDisplaynames:nil UUIDs:[NSArray arrayWithObject:account.username]]
+                      success:^(OpenStackRequest *request) {
+                          [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                          [rootViewController.tableView reloadData];
+                          [self.navigationController dismissModalViewControllerAnimated:YES];
+                      }
+                      failure:^(OpenStackRequest *request) {
+                          [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                          [rootViewController.tableView reloadData];
+                          [self.navigationController dismissModalViewControllerAnimated:YES];
+                      }];
+                 } else {
+                     [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                     self.navigationItem.rightBarButtonItem.enabled = YES;
+                     [self alert:@"Authentication Failure" message:@"Please check your Token."];
                  }
-                 [account persist];
-                 [[account.manager userCatalogForDisplaynames:nil UUIDs:[NSArray arrayWithObject:account.username]]
-                  success:^(OpenStackRequest *request) {
-                      [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
-                      [rootViewController.tableView reloadData];
-                      [self.navigationController dismissModalViewControllerAnimated:YES];
-                  }
-                  failure:^(OpenStackRequest *request) {
-                      [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
-                      [rootViewController.tableView reloadData];
-                      [self.navigationController dismissModalViewControllerAnimated:YES];
-                  }];
-            } else {
-                [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
-                self.navigationItem.rightBarButtonItem.enabled = YES;
-                [self alert:@"Authentication Failure" message:@"Please check your UUID and Token."];
-            }
-         }
-         failure:^(OpenStackRequest *request) {
-             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
-             self.navigationItem.rightBarButtonItem.enabled = YES;
-             if ([request responseStatusCode] == 401) {
-                 [self alert:@"Authentication Failure" message:@"Please check your UUID and Token."];
-             } else {
-                 [self failOnBadConnection];
              }
-         }];
+             failure:^(OpenStackRequest *request) {
+                 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                 self.navigationItem.rightBarButtonItem.enabled = YES;
+                 if (request.responseStatusCode == 404) {
+                     self.providerPithosObjectStoreURLString = account.provider.pithosObjectStoreURL.description;
+                     self.providerAstakosAccountURLString = account.provider.astakosAccountURL.description;
+                     self.providerAstakosWebloginURLString = account.provider.astakosWebloginURL.description;
+                     provider.manual = YES;
+                     providerManualSwitch.on = YES;
+                     [self.tableView reloadData];
+                     [self alert:@"Authentication Failure" message:@"Please enter also your UUID."];
+                 } else if ([request responseStatusCode] == 401) {
+                     [self alert:@"Authentication Failure" message:@"Please check your Token."];
+                 } else {
+                     [self alert:nil request:request];
+                 }
+             }];
+        }
     }
 }
 
index 2ec4418..9c55dba 100755 (executable)
@@ -19,6 +19,8 @@
 @property (nonatomic, assign) OpenStackAccount *account;
 @property (nonatomic, retain) ASINetworkQueue *queue;
 
+- (APICallback *)serviceCatalog;
+
 - (APICallback *)userCatalogForDisplaynames:(NSArray *)displaynames UUIDs:(NSArray *)UUIDs;
 
 - (APICallback *)authenticate;
index 3b1e3d4..53047fd 100755 (executable)
 }
 
 #pragma mark - API Calls
+#pragma mark Service Catalog
+
+- (APICallback *)serviceCatalog {
+    __block OpenStackRequest *request = [OpenStackRequest serviceCatalogRequest:self.account];
+    return [self callbackWithRequest:request success:^(OpenStackRequest *request) {
+        NSURL *pithosObjectStoreURL = nil;
+        NSURL *astakosAccountURL = nil;
+        NSURL *astakosWebloginURL = nil;
+        NSArray *serviceCatalog = [request serviceCatalog];
+        for (NSDictionary *service in serviceCatalog) {
+            NSString *serviceName = [service objectForKey:@"name"];
+            if (!pithosObjectStoreURL && [serviceName isEqualToString:@"pithos_object-store"]) {
+                pithosObjectStoreURL = [NSURL URLWithString:[[[service objectForKey:@"endpoints"]
+                                                              objectAtIndex:0]
+                                                             objectForKey:@"publicURL"]];
+            } else if (!astakosAccountURL && [serviceName isEqualToString:@"astakos_account"]) {
+                astakosAccountURL = [NSURL URLWithString:[[[service objectForKey:@"endpoints"]
+                                                            objectAtIndex:0]
+                                                           objectForKey:@"publicURL"]];
+            } else if (!astakosWebloginURL && [serviceName isEqualToString:@"astakos_weblogin"]) {
+                astakosWebloginURL = [NSURL URLWithString:[[[service objectForKey:@"endpoints"]
+                                                            objectAtIndex:0]
+                                                           objectForKey:@"SNF:webloginURL"]];
+            }
+            if (pithosObjectStoreURL && astakosAccountURL && astakosWebloginURL)
+                break;
+        }
+        self.account.provider.pithosObjectStoreURL = pithosObjectStoreURL;
+        self.account.provider.astakosAccountURL = astakosAccountURL;
+        self.account.provider.astakosWebloginURL = astakosWebloginURL;
+        self.account.provider.manual = NO;
+        
+        if (self.account.authToken.length) {
+            NSDictionary *token = [request token];
+            self.account.authToken = [token objectForKey:@"id"];
+            self.account.username = [[token objectForKey:@"tenant"] objectForKey:@"id"];
+        }
+    } failure:^(OpenStackRequest *request) {
+        if (request.responseStatusCode == 404) {
+            self.account.provider.pithosObjectStoreURL = [self.account.provider.authURL URLByAppendingPathComponent:@"v1"];
+            self.account.provider.astakosAccountURL = [[self.account.provider.authURL copy] autorelease];
+            self.account.provider.astakosWebloginURL = [[self.account.provider.authURL copy] autorelease];
+            self.account.provider.manual = YES;
+        }
+    }];
+}
+
 #pragma mark User Catalog
 
 - (APICallback *)userCatalogForDisplaynames:(NSArray *)displaynames UUIDs:(NSArray *)UUIDs {
index 9c46a67..b49449e 100755 (executable)
@@ -6,27 +6,46 @@
 //  The OpenStack project is provided under the Apache 2.0 license.
 //
 
-@class OpenStackAccount;
+@class OpenStackAccount, Provider;
 
 @interface AccountSettingsViewController : UITableViewController <UITextFieldDelegate> {
-    UITextField *usernameTextField;
+    UITextField *providerAuthURLTextField;
+    UITextField *providerPithosObjectStoreURLTextField;
+    UITextField *providerAstakosAccountURLTextField;
+    UITextField *providerAstakosWebloginURLTextField;
+    UISwitch *providerManualSwitch;
+    
     UITextField *authTokenTextField;
+    UITextField *usernameTextField;
+    UITextField *displaynameTextField;
+    
+    UISwitch *ignoreSSLErrorsSwitch;
     
-    NSInteger userDetailsSection;
+    NSInteger providerSection;
+    NSInteger authenticationSection;
     NSInteger getTokenSection;
+    NSInteger optionsSection;
+    
+    OpenStackAccount *account;
+    Provider *provider;
+    
+    NSString *providerAuthURLString;
+    NSString *providerPithosObjectStoreURLString;
+    NSString *providerAstakosAccountURLString;
+    NSString *providerAstakosWebloginURLString;
     
     NSString *username;
     NSString *authToken;
     NSString *displayname;
+
+    UIColor *editColor;
 }
 
 @property (nonatomic, retain) OpenStackAccount *account;
 
 @property (nonatomic, retain) NSString *username;
 @property (nonatomic, retain) NSString *authToken;
-@property (nonatomic, retain) NSString *displayname;
 
-- (void)setUsername:(NSString *)aUsername andAuthToken:(NSString *)anAuthToken;
 - (void)saveButtonPressed:(id)sender;
 
 @end
index f6121db..c2c2a1c 100755 (executable)
 #import "OpenStackAppDelegate.h"
 #import "UIViewController+Conveniences.h"
 #import "OpenStackRequest.h"
+#import "NSString+Conveniences.h"
 
-#define kUsername 0
-#define kAuthToken 1
+#define kProviderManualSwitch 0
+#define kProviderPithosObjectStoreURL 1
+#define kProviderAstakosAccountURL 2
+#define kProviderAstakosWebloginURL 3
+#define kProviderAuthURL 4
+
+#define kAuthToken 0
+#define kUsername 1
 #define kDisplayname 2
 
+#define kIgnoreSSLErrorsSwitch 0
+
+@interface AccountSettingsViewController()
+@property (nonatomic, retain) Provider *provider;
+@property (nonatomic, retain) UITextField *providerAuthURLTextField;
+@property (nonatomic, retain) UITextField *providerPithosObjectStoreURLTextField;
+@property (nonatomic, retain) UITextField *providerAstakosAccountURLTextField;
+@property (nonatomic, retain) UITextField *providerAstakosWebloginURLTextField;
+
+@property (nonatomic, retain) UITextField *authTokenTextField;
+@property (nonatomic, retain) UITextField *usernameTextField;
+@property (nonatomic, retain) UITextField *displaynameTextField;
+
+@property (nonatomic, retain) NSString *providerAuthURLString;
+@property (nonatomic, retain) NSString *providerPithosObjectStoreURLString;
+@property (nonatomic, retain) NSString *providerAstakosAccountURLString;
+@property (nonatomic, retain) NSString *providerAstakosWebloginURLString;
+
+@property (nonatomic, retain) NSString *displayname;
+
+@property (nonatomic, retain) UIColor *editColor;
+@end
+
 @implementation AccountSettingsViewController
 
-@synthesize account, username, authToken, displayname;
+@synthesize account, username, authToken;
+@synthesize provider, providerAuthURLTextField, providerPithosObjectStoreURLTextField, providerAstakosAccountURLTextField,
+            providerAstakosWebloginURLTextField;
+@synthesize authTokenTextField, usernameTextField, displaynameTextField;
+@synthesize providerAuthURLString, providerPithosObjectStoreURLString, providerAstakosAccountURLString, providerAstakosWebloginURLString;
+@synthesize displayname, editColor;
 
 #pragma mark - View lifecycle
 
 - (void)viewDidLoad {
     [super viewDidLoad];
     self.navigationItem.title = @"Authentication";
-    userDetailsSection = 0;
-    getTokenSection = 1;
+    providerSection = 0;
+    authenticationSection = 1;
+    getTokenSection = 2;
+    optionsSection = 3;
     displayname = [[self.account displaynameForUUID:self.account.username safe:NO] retain];
     [self addSaveButton];
-    self.navigationItem.rightBarButtonItem.enabled = NO;
+    
+    providerManualSwitch = [[UISwitch alloc] init];
+    [providerManualSwitch addTarget:self action:@selector(providerManualSwitchChanged:) forControlEvents:UIControlEventValueChanged];
+    
+    ignoreSSLErrorsSwitch = [[UISwitch alloc] init];
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+    self.provider = [[account.provider copy] autorelease];
+    self.providerAuthURLString = provider.authURL.description;
+    self.providerPithosObjectStoreURLString = provider.pithosObjectStoreURL.description;
+    self.providerAstakosAccountURLString = provider.astakosAccountURL.description;
+    self.providerAstakosWebloginURLString = provider.astakosWebloginURL.description;
+    providerManualSwitch.on = provider.manual;
+    self.authToken = account.authToken;
+    self.username = account.username;
+    ignoreSSLErrorsSwitch.on = account.ignoreSSLErrors;
+    [super viewWillAppear:animated];
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+    [providerAuthURLTextField resignFirstResponder];
+    [providerPithosObjectStoreURLTextField resignFirstResponder];
+    [providerAstakosAccountURLTextField resignFirstResponder];
+    [providerAstakosWebloginURLTextField resignFirstResponder];
+    [usernameTextField resignFirstResponder];
+    [authTokenTextField resignFirstResponder];
 }
 
 #pragma mark - Memory management
 
 - (void)dealloc {
-    [username release];
+    [account release];
+    [provider release];
+    [providerAuthURLTextField release];
+    [providerPithosObjectStoreURLTextField release];
+    [providerAstakosAccountURLTextField release];
+    [providerAstakosWebloginURLTextField release];
+    [providerManualSwitch release];
+    [authTokenTextField release];
+    [usernameTextField release];
+    [ignoreSSLErrorsSwitch release];
+    [providerAuthURLString release];
+    [providerPithosObjectStoreURLString release];
+    [providerAstakosAccountURLString release];
+    [providerAstakosWebloginURLString release];
     [authToken release];
+    [username release];
     [displayname release];
-    [account release];
+    [editColor release];
     [super dealloc];
 }
 
 #pragma mark - Internal
 
-- (void) updateSaveButtonForUsername:(NSString *)checkUsername andAuthToken:(NSString *)checkAuthToken {
-    self.navigationItem.rightBarButtonItem.enabled = (checkUsername && checkUsername.length &&
-                                                      checkAuthToken && checkAuthToken.length &&
-                                                      (![checkUsername isEqualToString:account.username] ||
-                                                       ![checkAuthToken isEqualToString:account.authToken]));
+- (void)providerManualSwitchChanged:(id)sender {
+    provider.manual = providerManualSwitch.on;
+    [self.tableView reloadData];
 }
 
 #pragma mark - Properties
     [username release];
     username = [aUsername retain];
     self.displayname = [self.account displaynameForUUID:username safe:NO];
-    [self updateSaveButtonForUsername:username andAuthToken:authTokenTextField.text];
-}
-
-- (void)setAuthToken:(NSString *)anAuthToken {
-    [authToken release];
-    authToken = [anAuthToken retain];
-    [self updateSaveButtonForUsername:usernameTextField.text andAuthToken:authToken];
 }
 
 - (void)setDisplayname:(NSString *)aDisplayname {
     [displayname release];
     displayname = [aDisplayname retain];
     if (reloadDisplayname) {
-        [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:kDisplayname inSection:userDetailsSection]]
+        [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:kDisplayname inSection:authenticationSection]]
                               withRowAnimation:UITableViewRowAnimationNone];
     }
 }
 
-#pragma mark - Actions
-
-- (void)setUsername:(NSString *)aUsername andAuthToken:(NSString *)anAuthToken {
-    if (aUsername) {
-        [username release];
-        username = [aUsername retain];
-        self.displayname = [self.account displaynameForUUID:username safe:NO];
-    }
-    if (anAuthToken) {
-        [authToken release];
-        authToken = [anAuthToken retain];
-    }
-    [self updateSaveButtonForUsername:username andAuthToken:authToken];
-}
-
 #pragma mark - UITableViewDataSource
 
 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
-    return 2;
+    return 4;
 }
 
 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    if (section == userDetailsSection)
+    if (section == providerSection) {
+        return (provider.manual ? 4 : 5);
+    } else if (section == authenticationSection) {
         return 3;
-    else
+    } else {
         return 1;
+    }
 }
 
 - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
-    if (section == userDetailsSection)
-        return [NSString stringWithFormat:@"%@ Login", self.account.provider.name];
-    else
-        return nil;
-}
-
-- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
-    if (section == userDetailsSection)
-        return @"API Version 1.0";
-    else
+    if (section == providerSection) {
+        return [NSString stringWithFormat:@"%@ Provider Details", self.account.provider.name];
+    } else if (section == authenticationSection) {
+        return @"Login";
+    } else if (section == optionsSection) {
+        return @"Options";
+    } else {
         return nil;
+    }
 }
 
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
-    static NSString *CellIdentifier = @"Cell";
-    RSTextFieldCell *cell = nil;
-    if (indexPath.section == userDetailsSection) {
-        cell = (RSTextFieldCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
-        if (cell == nil) {
-            cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
-            cell.selectionStyle = UITableViewCellSelectionStyleNone;
-        }
-        
-        if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
-            cell.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.8];
+    UITableViewCell *cell = nil;
+    if (indexPath.section == providerSection) {
+        if (indexPath.row == kProviderManualSwitch) {
+            static NSString *CellIdentifier = @"SwitchCell";
+            cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+            }
+            cell.textLabel.text = @"Manual";
+            cell.accessoryView = providerManualSwitch;
+        } else if (indexPath.row == kProviderPithosObjectStoreURL) {
+            static NSString *CellIdentifier = @"ProviderPithosObjectStoreURLCell";
+            cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+                cell.textLabel.text = @"Pithos URL";
+                self.providerPithosObjectStoreURLTextField = ((RSTextFieldCell *)cell).textField;
+                providerPithosObjectStoreURLTextField.keyboardType = UIKeyboardTypeURL;
+                providerPithosObjectStoreURLTextField.returnKeyType = UIReturnKeyNext;
+                if (!editColor)
+                    self.editColor = providerPithosObjectStoreURLTextField.textColor;
+            }
+            if (provider.manual) {
+                cell.userInteractionEnabled = YES;
+                providerPithosObjectStoreURLTextField.textColor = editColor;
+                providerPithosObjectStoreURLTextField.placeholder = @"Required";
+                providerPithosObjectStoreURLTextField.delegate = self;
+            } else {
+                cell.userInteractionEnabled = NO;
+                providerPithosObjectStoreURLTextField.textColor = [UIColor grayColor];
+                providerPithosObjectStoreURLTextField.placeholder = @"Fetched on save";
+                providerPithosObjectStoreURLTextField.delegate = nil;
+            }
+            providerPithosObjectStoreURLTextField.text = providerPithosObjectStoreURLString;
+        } else if (indexPath.row == kProviderAstakosAccountURL) {
+            static NSString *CellIdentifier = @"ProviderAstakosAccountURLCell";
+            cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+                cell.textLabel.text = @"Astakos URL";
+                self.providerAstakosAccountURLTextField = ((RSTextFieldCell *)cell).textField;
+                providerAstakosAccountURLTextField.keyboardType = UIKeyboardTypeURL;
+                providerAstakosAccountURLTextField.returnKeyType = UIReturnKeyNext;
+            }
+            if (provider.manual) {
+                cell.userInteractionEnabled = YES;
+                providerAstakosAccountURLTextField.textColor = editColor;
+                providerAstakosAccountURLTextField.placeholder = @"Required";
+                providerAstakosAccountURLTextField.delegate = self;
+            } else {
+                cell.userInteractionEnabled = NO;
+                providerAstakosAccountURLTextField.textColor = [UIColor grayColor];
+                providerAstakosAccountURLTextField.placeholder = @"Fetched on save";
+                providerAstakosAccountURLTextField.delegate = nil;
+            }
+            providerAstakosAccountURLTextField.text = providerAstakosAccountURLString;
+        } else if (indexPath.row == kProviderAstakosWebloginURL) {
+            static NSString *CellIdentifier = @"ProviderAstakosWebloginURLCell";
+            cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+                cell.textLabel.text = @"Weblogin URL";
+                self.providerAstakosWebloginURLTextField = ((RSTextFieldCell *)cell).textField;
+                providerAstakosWebloginURLTextField.keyboardType = UIKeyboardTypeURL;
+                providerAstakosWebloginURLTextField.returnKeyType = UIReturnKeyNext;
+            }
+            if (provider.manual) {
+                cell.userInteractionEnabled = YES;
+                providerAstakosWebloginURLTextField.textColor = editColor;
+                providerAstakosWebloginURLTextField.placeholder = @"Required";
+                providerAstakosWebloginURLTextField.delegate = self;
+            } else {
+                cell.userInteractionEnabled = NO;
+                providerAstakosWebloginURLTextField.textColor = [UIColor grayColor];
+                providerAstakosWebloginURLTextField.placeholder = @"Fetched on save";
+                providerAstakosWebloginURLTextField.delegate = nil;
+            }
+            providerAstakosWebloginURLTextField.text = providerAstakosWebloginURLString;
+        } else if (indexPath.row == kProviderAuthURL) {
+            static NSString *CellIdentifier = @"ProviderAuthURLCell";
+            cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+                cell.textLabel.text = @"Auth URL";
+                self.providerAuthURLTextField = ((RSTextFieldCell *)cell).textField;
+                providerAuthURLTextField.placeholder = @"Required";
+                providerAuthURLTextField.keyboardType = UIKeyboardTypeURL;
+                providerAuthURLTextField.returnKeyType = UIReturnKeyNext;
+                providerAstakosWebloginURLTextField.delegate = self;
+            }
+            providerAuthURLTextField.text = providerAuthURLString;
         }
-        
-        [cell.textLabel setBackgroundColor:[UIColor clearColor]];
-        
-        if (indexPath.row == kUsername) {
-            cell.textLabel.text = @"UUID";
-            cell.userInteractionEnabled = YES;
-            cell.textField.delegate = self;
-            cell.textField.returnKeyType = UIReturnKeyNext;
-            cell.textField.text = (username ? username : self.account.username);
-            usernameTextField = cell.textField;
-        } else if (indexPath.row == kAuthToken) {
-            cell.textLabel.text = @"Token";
-            cell.userInteractionEnabled = YES;
-            cell.textField.delegate = self;
-            cell.textField.returnKeyType = UIReturnKeyDone;
-            cell.textField.text = (authToken ? authToken : self.account.authToken);
-            authTokenTextField = cell.textField;
+    } else if (indexPath.section == authenticationSection) {
+        if (indexPath.row == kAuthToken) {
+            static NSString *CellIdentifier = @"AuthTokenCell";
+            cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+                cell.textLabel.text = @"Token";
+                self.authTokenTextField = ((RSTextFieldCell *)cell).textField;
+                authTokenTextField.placeholder = @"Input or retrieve";
+                authTokenTextField.delegate = self;
+            }
+            authTokenTextField.returnKeyType = (provider.manual ? UIReturnKeyNext : UIReturnKeyDone);
+            authTokenTextField.text = authToken;
+        } else if (indexPath.row == kUsername) {
+            static NSString *CellIdentifier = @"UsernameCell";
+            cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+                cell.textLabel.text = @"UUID";
+                self.usernameTextField = ((RSTextFieldCell *)cell).textField;
+                usernameTextField.returnKeyType = UIReturnKeyDone;
+            }
+            if (provider.manual) {
+                cell.userInteractionEnabled = YES;
+                usernameTextField.textColor = editColor;
+                usernameTextField.placeholder = @"Input or retrieve";
+                usernameTextField.delegate = self;
+            } else {
+                cell.userInteractionEnabled = NO;
+                usernameTextField.textColor = [UIColor grayColor];
+                usernameTextField.placeholder = @"Fetched on save";
+                usernameTextField.delegate = nil;
+            }
+            usernameTextField.text = username;
         } else if (indexPath.row == kDisplayname) {
-            cell.textLabel.text = @"User";
-            cell.userInteractionEnabled = NO;
-            cell.textField.textColor = [UIColor grayColor];
-            cell.textField.delegate = nil;
-            cell.textField.text = displayname;
+            static NSString *CellIdentifier = @"DisplaynameCell";
+            cell = (RSTextFieldCell *)[self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+                cell.textLabel.text = @"User";
+                cell.userInteractionEnabled = NO;
+                self.displaynameTextField = ((RSTextFieldCell *)cell).textField;
+                displaynameTextField.textColor = [UIColor grayColor];
+            }
+            displaynameTextField.text = displayname;
         }
-    } else {
-        cell = (RSTextFieldCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
-        if (cell == nil) {
-            cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
+    } else if (indexPath.section == getTokenSection) {
+        static NSString *CellIdentifier = @"Cell";
+        cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+        if (!cell) {
+            cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
+        }
+        cell.textLabel.textAlignment = UITextAlignmentCenter;
+        cell.textLabel.text = @"Retrieve Token";
+    } else if (indexPath.section == optionsSection) {
+        if (indexPath.row == kIgnoreSSLErrorsSwitch) {
+            static NSString *CellIdentifier = @"SwitchCell";
+            cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+            if (!cell) {
+                cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
+                cell.selectionStyle = UITableViewCellSelectionStyleNone;
+            }
+            cell.textLabel.text = @"Ignore SSL Errors";
+            cell.accessoryView = ignoreSSLErrorsSwitch;
         }
-        
-        cell.textLabel.text = @"Get Token";
     }
     return cell;
 }
 
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
     if (indexPath.section == getTokenSection) {
-        NSString *protocol = [[[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"] objectAtIndex:0] objectForKey:@"CFBundleURLSchemes"] objectAtIndex:0];
-        NSString *loginURL = [NSString stringWithFormat:@"%@?next=%@://login&force=", account.provider.loginURL, protocol];
-        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:loginURL]];
-        [tableView deselectRowAtIndexPath:indexPath animated:NO];
+        if (!provider.manual && ![providerAuthURLString isURL]) {
+            [self alert:nil message:@"Please enter an Auth URL"];
+            [providerAuthURLTextField becomeFirstResponder];
+            [tableView deselectRowAtIndexPath:indexPath animated:NO];
+        } else if (provider.manual && ![providerAstakosWebloginURLString isURL]) {
+            [self alert:nil message:@"Please enter a Weblogin URL"];
+            [providerAstakosWebloginURLTextField becomeFirstResponder];
+            [tableView deselectRowAtIndexPath:indexPath animated:NO];
+        } else {
+            if (!provider.manual) {
+                provider.authURL = [NSURL URLWithString:providerAuthURLString];
+            } else {
+                provider.astakosWebloginURL = [NSURL URLWithString:providerAstakosWebloginURLString];
+            }
+            OpenStackAccount *temporaryAccount = [[[OpenStackAccount alloc] init] autorelease];
+            temporaryAccount.provider = [[provider copy] autorelease];
+            temporaryAccount.username = nil;
+            temporaryAccount.authToken = nil;
+            temporaryAccount.ignoreSSLErrors = ignoreSSLErrorsSwitch.on;
+        
+            if (provider.manual) {
+                NSString *protocol = [[[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"] objectAtIndex:0] objectForKey:@"CFBundleURLSchemes"] objectAtIndex:0];
+                NSString *loginURL = [NSString stringWithFormat:@"%@?next=%@://login&force=", temporaryAccount.provider.loginURL, protocol];
+                [[UIApplication sharedApplication] openURL:[NSURL URLWithString:loginURL]];
+                [tableView deselectRowAtIndexPath:indexPath animated:NO];
+            } else {
+                // POST <authURL>/tokens with no body to retrieve URLs or fallback to default manual provider
+                __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Accessing service catalog..."
+                                                                                                               andAddToView:self.view];
+                [[temporaryAccount.manager serviceCatalog]
+                 success:^(OpenStackRequest *request) {
+                     [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                     self.providerPithosObjectStoreURLString = request.account.provider.pithosObjectStoreURL.description;
+                     self.providerAstakosAccountURLString = request.account.provider.astakosAccountURL.description;
+                     self.providerAstakosWebloginURLString = request.account.provider.astakosWebloginURL.description;
+                     [self.tableView reloadData];
+                     NSString *protocol = [[[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"] objectAtIndex:0] objectForKey:@"CFBundleURLSchemes"] objectAtIndex:0];
+                     NSString *loginURL = [NSString stringWithFormat:@"%@?next=%@://login&force=", request.account.provider.loginURL, protocol];
+                     [[UIApplication sharedApplication] openURL:[NSURL URLWithString:loginURL]];
+                     [tableView deselectRowAtIndexPath:indexPath animated:NO];
+                 }
+                 failure:^(OpenStackRequest *request) {
+                     [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                     if (request.responseStatusCode == 404) {
+                         self.providerPithosObjectStoreURLString = request.account.provider.pithosObjectStoreURL.description;
+                         self.providerAstakosAccountURLString = request.account.provider.astakosAccountURL.description;
+                         self.providerAstakosWebloginURLString = request.account.provider.astakosWebloginURL.description;
+                         provider.manual = YES;
+                         providerManualSwitch.on = YES;
+                         [self.tableView reloadData];
+                         NSString *protocol = [[[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"] objectAtIndex:0] objectForKey:@"CFBundleURLSchemes"] objectAtIndex:0];
+                         NSString *loginURL = [NSString stringWithFormat:@"%@?next=%@://login&force=", request.account.provider.loginURL, protocol];
+                         [[UIApplication sharedApplication] openURL:[NSURL URLWithString:loginURL]];
+                     } else {
+                         [self alert:nil request:request];
+                     }
+                     [tableView deselectRowAtIndexPath:indexPath animated:NO];
+                 }];
+            }
+        }
     }
 }
 
 #pragma mark - UITextFieldDelegate
 
 - (BOOL)textFieldShouldReturn:(UITextField *)textField {
-    [textField resignFirstResponder];
-    if ([textField isEqual:usernameTextField]) {
+    if ([textField isEqual:providerPithosObjectStoreURLTextField]) {
+        [providerAstakosAccountURLTextField becomeFirstResponder];
+        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:kProviderAstakosAccountURL inSection:providerSection] atScrollPosition:UITableViewScrollPositionNone animated:YES];
+    } else if ([textField isEqual:providerAstakosAccountURLTextField]) {
+        [providerAstakosWebloginURLTextField becomeFirstResponder];
+        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:kProviderAstakosWebloginURL inSection:providerSection] atScrollPosition:UITableViewScrollPositionNone animated:YES];
+    } else if ([textField isEqual:providerAuthURLTextField] || [textField isEqual:providerAstakosWebloginURLTextField]) {
         [authTokenTextField becomeFirstResponder];
+        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:kAuthToken inSection:authenticationSection] atScrollPosition:UITableViewScrollPositionNone animated:YES];
+    } else if ([textField isEqual:authTokenTextField] && (authTokenTextField.returnKeyType == UIReturnKeyNext)) {
+        [usernameTextField becomeFirstResponder];
+        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:kUsername inSection:authenticationSection] atScrollPosition:UITableViewScrollPositionNone animated:YES];
+    } else {
+        [textField resignFirstResponder];
     }
     return NO;
 }
 
 - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
     NSString *result = [textField.text stringByReplacingCharactersInRange:range withString:string];
-    if ([textField isEqual:usernameTextField]) {
-        self.username = result;
-    } else if ([textField isEqual:authTokenTextField]) {
+    if ([textField isEqual:authTokenTextField]) {
         self.authToken = result;
+    } else if ([textField isEqual:usernameTextField]) {
+        self.username = result;
+    } else if ([textField isEqual:providerAuthURLTextField]) {
+        self.providerAuthURLString = result;
+    } else if ([textField isEqual:providerPithosObjectStoreURLTextField]) {
+        self.providerPithosObjectStoreURLString = result;
+    } else if ([textField isEqual:providerAstakosAccountURLTextField]) {
+        self.providerAstakosAccountURLString = result;
+    } else if ([textField isEqual:providerAstakosWebloginURLTextField]) {
+        self.providerAstakosWebloginURLString = result;
     }
     return YES;
 }
 #pragma mark - Authentication
 
 - (void)authenticate {
-    if (!usernameTextField.text || [usernameTextField.text isEqualToString:@""]) {
-        [self alert:nil message:@"Please enter your UUID."];
+    if (!provider.manual && ![providerAuthURLString isURL]) {
+        [self alert:nil message:@"Please enter an Auth URL"];
         self.navigationItem.rightBarButtonItem.enabled = YES;
-        [usernameTextField becomeFirstResponder];
+        [providerAuthURLTextField becomeFirstResponder];
+    } else if (provider.manual && ![providerPithosObjectStoreURLString isURL]) {
+        [self alert:nil message:@"Please enter a Pithos URL"];
+        self.navigationItem.rightBarButtonItem.enabled = YES;
+        [providerPithosObjectStoreURLTextField becomeFirstResponder];
+    } else if (provider.manual && ![providerAstakosAccountURLString isURL]) {
+        [self alert:nil message:@"Please enter an Astakos URL"];
+        self.navigationItem.rightBarButtonItem.enabled = YES;
+        [providerAstakosAccountURLTextField becomeFirstResponder];
+    } else if (provider.manual && ![providerAstakosWebloginURLString isURL]) {
+        [self alert:nil message:@"Please enter a Weblogin URL"];
+        self.navigationItem.rightBarButtonItem.enabled = YES;
+        [providerAstakosWebloginURLTextField becomeFirstResponder];
     } else if (!authTokenTextField.text || [authTokenTextField.text isEqualToString:@""]) {
-        [self alert:nil message:@"Please enter your Token."];
+        [self alert:nil message:@"Please enter your Token"];
         self.navigationItem.rightBarButtonItem.enabled = YES;
         [authTokenTextField becomeFirstResponder];
+    } else if (provider.manual && (!usernameTextField.text || [usernameTextField.text isEqualToString:@""])) {
+        [self alert:nil message:@"Please enter your UUID"];
+        self.navigationItem.rightBarButtonItem.enabled = YES;
+        [usernameTextField becomeFirstResponder];
     } else {
+        if (!provider.manual) {
+            provider.authURL = [NSURL URLWithString:providerAuthURLString];
+        } else {
+            provider.pithosObjectStoreURL = [NSURL URLWithString:providerPithosObjectStoreURLString];
+            provider.astakosAccountURL = [NSURL URLWithString:providerAstakosAccountURLString];
+            provider.astakosWebloginURL = [NSURL URLWithString:providerAstakosWebloginURLString];
+        }
         OpenStackAccount *temporaryAccount = [[[OpenStackAccount alloc] init] autorelease];
-        temporaryAccount.provider = account.provider;
-        temporaryAccount.username = usernameTextField.text;
+        temporaryAccount.provider = [[provider copy] autorelease];
         temporaryAccount.authToken = authTokenTextField.text;
+        temporaryAccount.username = usernameTextField.text;
+        temporaryAccount.ignoreSSLErrors = ignoreSSLErrorsSwitch.on;
         
         __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Authenticating..."
                                                                                                        andAddToView:self.view];
-        [[temporaryAccount.manager authenticate]
-         success:^(OpenStackRequest *request) {
-             if ([request isSuccess]) {
-                 account.username = request.account.username;
-                 account.authToken = request.account.authToken;
-                 NSString *storageURLString = [[request responseHeaders] objectForKey:@"X-Storage-Url"];
-                 if (storageURLString) {
-                     account.filesURL = [NSURL URLWithString:storageURLString];
+        if (provider.manual) {
+            [[temporaryAccount.manager authenticate]
+             success:^(OpenStackRequest *request) {
+                 if ([request isSuccess]) {
+                     account.provider = request.account.provider;
+                     self.provider = [[account.provider copy] autorelease];
+                     account.authToken = request.account.authToken;
+                     account.username = request.account.username;
+                     account.ignoreSSLErrors = request.account.ignoreSSLErrors;
+                     NSString *storageURLString = [[request responseHeaders] objectForKey:@"X-Storage-Url"];
+                     if (storageURLString) {
+                         account.filesURL = [NSURL URLWithString:storageURLString];
+                     } else {
+                         account.filesURL = [account.provider.authEndpointURL URLByAppendingPathComponent:account.username];
+                     }
+                     [account persist];
+                     [[account.manager userCatalogForDisplaynames:nil
+                                                            UUIDs:[NSArray arrayWithObject:account.username]]
+                      success:^(OpenStackRequest *request) {
+                          [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                          self.navigationItem.rightBarButtonItem.enabled = YES;
+                          self.displayname = [account displaynameForUUID:account.username safe:NO];
+                          [self.tableView reloadData];
+                      }
+                      failure:^(OpenStackRequest *request) {
+                          [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                          self.navigationItem.rightBarButtonItem.enabled = YES;
+                          [self.tableView reloadData];
+                      }];
                  } else {
-                     account.filesURL = [account.provider.authEndpointURL URLByAppendingPathComponent:account.username];
+                     [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                     self.navigationItem.rightBarButtonItem.enabled = YES;
+                     [self alert:@"Authentication Failure" message:@"Please check your Token and UUID."];
                  }
-                 [account persist];
-                 [[account.manager userCatalogForDisplaynames:nil
-                                                        UUIDs:[NSArray arrayWithObject:account.username]]
-                  success:^(OpenStackRequest *request) {
-                      [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
-                      self.displayname = [account displaynameForUUID:account.username safe:NO];
-                  }
-                  failure:^(OpenStackRequest *request) {
-                      [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
-                  }];
-             } else {
+             }
+             failure:^(OpenStackRequest *request) {
                  [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
                  self.navigationItem.rightBarButtonItem.enabled = YES;
-                 [self alert:@"Authentication Failure" message:@"Please check your UUID and Token."];
-             }
-         }
-         failure:^(OpenStackRequest *request) {
-             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
-             self.navigationItem.rightBarButtonItem.enabled = YES;
-             if ([request responseStatusCode] == 401) {
-                 [self alert:@"Authentication Failure" message:@"Please check your UUID and Token."];
-             } else {
-                 [self failOnBadConnection];
+                 if ([request responseStatusCode] == 401) {
+                     [self alert:@"Authentication Failure" message:@"Please check your Token and UUID."];
+                 } else {
+                     [self alert:nil request:request];
+                 }
+             }];
+        } else {
+            [[temporaryAccount.manager serviceCatalog]
+             success:^(OpenStackRequest *request) {
+                 if ([request isSuccess]) {
+                     account.provider = request.account.provider;
+                     account.authToken = request.account.authToken;
+                     account.username = request.account.username;
+                     account.ignoreSSLErrors = request.account.ignoreSSLErrors;
+                     account.filesURL = [account.provider.authEndpointURL URLByAppendingPathComponent:account.username];
+                     [account persist];
+                     [[account.manager userCatalogForDisplaynames:nil UUIDs:[NSArray arrayWithObject:account.username]]
+                      success:^(OpenStackRequest *request) {
+                          [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                          self.navigationItem.rightBarButtonItem.enabled = YES;
+                          self.username = account.username;
+                          [self.tableView reloadData];
+                      }
+                      failure:^(OpenStackRequest *request) {
+                          [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                          self.navigationItem.rightBarButtonItem.enabled = YES;
+                          self.username = account.username;
+                          [self.tableView reloadData];
+                      }];
+                 } else {
+                     [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                     self.navigationItem.rightBarButtonItem.enabled = YES;
+                     [self alert:@"Authentication Failure" message:@"Please check your Token."];
+                 }
              }
-         }];
+             failure:^(OpenStackRequest *request) {
+                 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                 self.navigationItem.rightBarButtonItem.enabled = YES;
+                 if (request.responseStatusCode == 404) {
+                     self.providerPithosObjectStoreURLString = request.account.provider.pithosObjectStoreURL.description;
+                     self.providerAstakosAccountURLString = request.account.provider.astakosAccountURL.description;
+                     self.providerAstakosWebloginURLString = request.account.provider.astakosWebloginURL.description;
+                     provider.manual = YES;
+                     providerManualSwitch.on = YES;
+                     [self.tableView reloadData];
+                     [self alert:@"Authentication Failure" message:@"Please enter also your UUID."];
+                 } else if ([request responseStatusCode] == 401) {
+                     [self alert:@"Authentication Failure" message:@"Please check your Token."];
+                 } else {
+                     [self alert:nil request:request];
+                 }
+             }];
+        }
     }
 }
 
index 2fcd2df..603a6ba 100755 (executable)
@@ -58,7 +58,7 @@
     NSString *title = @"Error";
     if (request.responseStatusCode == 0) {
         title = @"Connection Error";
-        message = @"Please check your connection or API URL and try again.";
+        message = [NSString stringWithFormat:@"Please check your connection or API URL and try again. Error: %@", request.error];
     }
 
     self.logEntryModalViewController = [[[LogEntryModalViewController alloc] initWithNibName:@"LogEntryModalViewController" bundle:nil] autorelease];
index ce0be16..1945df9 100644 (file)
         cell.detailTextLabel.textAlignment = UITextAlignmentRight;
     }
     
-    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
-        cell.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.8];
-    }
-    
     if (indexPath.section == kOverview) {
         cell.accessoryType = UITableViewCellAccessoryNone;
         cell.selectionStyle = UITableViewCellSelectionStyleNone;
index 585dbf3..94670e3 100755 (executable)
@@ -30,6 +30,7 @@
     [request addRequestHeader:@"Content-Type" value:@"application/json"];
     request.timeOutSeconds = 60;
     request.numberOfTimesToRetryOnTimeout = 5;
+    request.validatesSecureCertificate = !account.ignoreSSLErrors;
        return request;
 }
 
index 4792a11..6e631f0 100755 (executable)
 @implementation NSString (Conveniences)
 
 - (BOOL)isURL {
-//    return [self hasPrefix:@"http://"] || [self hasPrefix:@"https://"];
-    return [[NSPredicate predicateWithFormat:@"SELF MATCHES %@",
-             @"(http|https)://((\\w)*|([0-9]*)|([-|_])*)+([\\.|/]((\\w)*|([0-9]*)|([-|_])*))+"]
-            evaluateWithObject:self];
+    return [self hasPrefix:@"http://"] || [self hasPrefix:@"https://"];
+//    return [[NSPredicate predicateWithFormat:@"SELF MATCHES %@",
+//             @"(http|https)://((\\w)*|([0-9]*)|([-|_])*)+([\\.|/]((\\w)*|([0-9]*)|([-|_])*))+"]
+//            evaluateWithObject:self];
+//    return [[NSPredicate predicateWithFormat:@"SELF MATCHES %@",
+//             @"http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w-\\+ ./?%&amp;=]*)?"]
+//            evaluateWithObject:self];
 }
 
 - (NSString *)replace:(NSString *)s with:(NSString *)r {
index ff124c7..08e0698 100755 (executable)
@@ -27,6 +27,7 @@
 @property (nonatomic, assign) BOOL shared;
 @property (nonatomic, retain) NSString *sharingAccount;
 @property (nonatomic, retain) NSMutableDictionary *userCatalog;
+@property (nonatomic, assign) BOOL ignoreSSLErrors;
 
 + (NSArray *)accounts;
 + (void)persist:(NSArray *)accountArray;
index a188bfc..1435436 100755 (executable)
@@ -21,7 +21,7 @@ static NSArray *accounts = nil;
 
 @synthesize uuid, provider, username, filesURL, manager,
             bytesUsed, policyQuota, containers, hasBeenRefreshed, flaggedForDelete,
-            shared, sharingAccount, userCatalog;
+            shared, sharingAccount, userCatalog, ignoreSSLErrors;
 
 + (void)initialize {
     accounts = [[Archiver retrieve:@"accounts"] retain];
@@ -62,6 +62,8 @@ static NSArray *accounts = nil;
             self.userCatalog = [NSMutableDictionary dictionary];
         }
         
+        self.ignoreSSLErrors = [coder decodeBoolForKey:@"ignoreSSLErrors"];
+        
         manager = [[AccountManager alloc] init];
         manager.account = self;
     }
@@ -72,18 +74,20 @@ static NSArray *accounts = nil;
 
 - (id)copyWithZone:(NSZone *)zone {
     OpenStackAccount *copy = [[[self class] allocWithZone:zone] init];
-    copy.uuid = self.uuid;
-    copy.provider = self.provider;
-    copy.username = self.username;
-    copy.apiKey = self.apiKey;
-    copy.authToken = self.authToken;
+    copy.uuid = [[self.uuid copy] autorelease];
+    copy.provider = [[self.provider copy] autorelease];
+    copy.username = [[self.username copy] autorelease];
+    copy.apiKey = [[self.apiKey copy] autorelease];
+    copy.authToken = [[self.authToken copy] autorelease];
     
-    copy.filesURL = self.filesURL;
-    copy.bytesUsed = self.bytesUsed;
-    copy.policyQuota = self.policyQuota;
+    copy.filesURL = [[self.filesURL copy] autorelease];
+    copy.bytesUsed = [[self.bytesUsed copy] autorelease];
+    copy.policyQuota = [[self.policyQuota copy] autorelease];
     
     copy.userCatalog = [[self.userCatalog copy] autorelease];
     
+    copy.ignoreSSLErrors = self.ignoreSSLErrors;
+    
     copy.manager = [[[AccountManager alloc] init] autorelease];
     copy.manager.account = copy;
     return copy;
@@ -101,6 +105,8 @@ static NSArray *accounts = nil;
 //    [coder encodeObject:containers forKey:@"containers"];
     
     [coder encodeObject:userCatalog forKey:@"userCatalog"];
+    
+    [coder encodeBool:ignoreSSLErrors forKey:@"ignoreSSLErrors"];
 }
 
 - (id)decode:(NSCoder *)coder key:(NSString *)key {
index 0d1a4a5..4980dcd 100755 (executable)
@@ -60,6 +60,9 @@
     
     objectDownloadRequests = [[NSMutableDictionary alloc] init];
     
+    // Load and persist accounts once to allow any migration checks to take effect.
+    [OpenStackAccount persist:[OpenStackAccount accounts]];
+    
     [userDefaults synchronize];
 }
 
 
     if ([host hasPrefix:@"login"] && query) {
         // user=
-        NSString *authUser;
+        // optional
+        NSString *authUser = nil;
         NSRange userRange = [query rangeOfString:@"user=" options:NSCaseInsensitiveSearch];
-        if (userRange.length == 0)
-            // XXX maybe show an error message?
-            return NO;
-        NSUInteger authUserStartLocation = userRange.location + userRange.length;
-        NSRange userEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch 
-                                              range:NSMakeRange(authUserStartLocation, [query length] - authUserStartLocation)];
-        if (userEndRange.length) {
-            authUser = [[query substringWithRange:NSMakeRange(authUserStartLocation, userEndRange.location - authUserStartLocation)]
-                        stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
-        } else {
-            authUser = [[query substringFromIndex:authUserStartLocation]
-                        stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+        if (userRange.length) {
+            NSUInteger authUserStartLocation = userRange.location + userRange.length;
+            NSRange userEndRange = [query rangeOfString:@"&" options:NSCaseInsensitiveSearch 
+                                                  range:NSMakeRange(authUserStartLocation, [query length] - authUserStartLocation)];
+            if (userEndRange.length) {
+                authUser = [[query substringWithRange:NSMakeRange(authUserStartLocation, userEndRange.location - authUserStartLocation)]
+                            stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+            } else {
+                authUser = [[query substringFromIndex:authUserStartLocation]
+                            stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+            }
         }
         // token=
-        NSString *authToken;
+        // required
+        NSString *authToken = nil;
         NSRange tokenRange = [query rangeOfString:@"token=" options:NSCaseInsensitiveSearch];
         if (tokenRange.length == 0)
             // XXX maybe show an error message?
             if (vc) {
                 gotToken = YES;
                 if ([vc class] == [AccountDetailsViewController class]) {
-                    [((AccountDetailsViewController *)vc) setUsername:authUser andAuthToken:authToken];
+                    ((AccountDetailsViewController *)vc).authToken = authToken;
+                    ((AccountDetailsViewController *)vc).username = authUser;
                     [((AccountDetailsViewController *)vc).tableView reloadData];
                 } else if ([vc class] == [AccountSettingsViewController class]) {
-                    [((AccountSettingsViewController *)vc) setUsername:authUser andAuthToken:authToken];
+                    ((AccountSettingsViewController *)vc).authToken = authToken;
+                    ((AccountSettingsViewController *)vc).username = authUser;
                     [((AccountSettingsViewController *)vc).tableView reloadData];
                 }
             }
index 4bcc0bb..b47ce1f 100755 (executable)
 @property (nonatomic, retain) APICallback *callback;
 @property (nonatomic, retain) ErrorAlerter *errorAlerter;
 
++ (id)requestWithoutToken:(OpenStackAccount *)account method:(NSString *)method url:(NSURL *)url;
 + (id)request:(OpenStackAccount *)account method:(NSString *)method url:(NSURL *)url;
 + (id)filesRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path;
 
++ (id)serviceCatalogRequest:(OpenStackAccount *)account;
+- (NSDictionary *)access;
+- (NSDictionary *)token;
+- (NSArray *)serviceCatalog;
+- (NSDictionary *)user;
+
 + (id)userCatalogRequest:(OpenStackAccount *)account displaynames:(NSArray *)displaynames UUIDs:(NSArray *)UUIDs;
 - (NSDictionary *)catalogs;
 - (NSDictionary *)displaynameCatalog;
index 692c4fd..311961b 100755 (executable)
 #pragma mark - Constructors
 #pragma mark Generic
 
-+ (id)request:(OpenStackAccount *)account method:(NSString *)method url:(NSURL *)url {
++ (id)requestWithoutToken:(OpenStackAccount *)account method:(NSString *)method url:(NSURL *)url {
        OpenStackRequest *request = [[[self alloc] initWithURL:url] autorelease];
     request.account = account;
        request.requestMethod = method;
-       [request addRequestHeader:@"X-Auth-Token" value:account.authToken];
     [request addRequestHeader:@"Content-Type" value:@"application/json"];
     request.timeOutSeconds = 60;
     request.numberOfTimesToRetryOnTimeout = 5;
+    request.validatesSecureCertificate = !account.ignoreSSLErrors;
+       return request;
+}
+
++ (id)request:(OpenStackAccount *)account method:(NSString *)method url:(NSURL *)url {
+       OpenStackRequest *request = [self requestWithoutToken:account method:method url:url];
+       [request addRequestHeader:@"X-Auth-Token" value:account.authToken];
        return request;
 }
 
     return [self request:account method:method url:url];
 }
 
+#pragma mark Service Catalog
++ (id)serviceCatalogRequest:(OpenStackAccount *)account {
+    OpenStackRequest *request = [self requestWithoutToken:account method:@"POST" url:account.provider.tokensURL];
+    if (account.authToken.length)
+        [request appendPostData:[[NSString stringWithFormat:@"{\"auth\":{\"token\":{\"id\":\"%@\"}}}", account.authToken]
+                                 dataUsingEncoding:NSUTF8StringEncoding]];
+    return request;
+}
+
+- (NSDictionary *)access {
+    SBJSON *parser = [[[SBJSON alloc] init] autorelease];
+    NSDictionary *access = [[parser objectWithString:[self responseString]] objectForKey:@"access"];
+    return access;
+}
+
+- (NSDictionary *)token {
+    return [[self access] objectForKey:@"token"];
+}
+
+- (NSArray *)serviceCatalog {
+    return [[self access] objectForKey:@"serviceCatalog"];
+}
+
+- (NSArray *)user {
+    return [[self access] objectForKey:@"user"];
+}
+
 #pragma mark User Catalog
 + (id)userCatalogRequest:(OpenStackAccount *)account displaynames:(NSArray *)displaynames UUIDs:(NSArray *)UUIDs {
     OpenStackRequest *request = [self request:account method:@"POST" url:account.provider.userCatalogURL];
     request.account = account;
     request.requestMethod = @"GET";
     [request addRequestHeader:@"X-Auth-User" value:account.username];
-    [request addRequestHeader:@"X-Auth-Token" value:(account.authToken ? account.authToken : @"")];
+    [request addRequestHeader:@"X-Auth-Token" value:account.authToken];
     request.timeOutSeconds = 60;
     request.numberOfTimesToRetryOnTimeout = 5;
+    request.validatesSecureCertificate = !account.ignoreSSLErrors;
        return request;
 }
 
     if (responseStatusCode == 503) {
         NSNotification *notification = [NSNotification notificationWithName:@"serviceUnavailable" object:nil userInfo:nil];
         [[NSNotificationCenter defaultCenter] postNotification:notification];
-    } else if (responseStatusCode == 0) {
-        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
-        if (![defaults boolForKey:@"already_failed_on_connection"]) {
-            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Connection Error" message:@"Please check your connection or API URL and try again." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
-            [alert show];
-            [alert release];
-        }
-        [defaults setBool:YES forKey:@"already_failed_on_connection"];
-        [defaults synchronize];
+        // This crashes the app, check if it can be removed completely.
+//    } else if (responseStatusCode == 0) {
+//        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+//        if (![defaults boolForKey:@"already_failed_on_connection"]) {
+//            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Connection Error" message:@"Please check your connection or API URL and try again." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
+//            [alert show];
+//            [alert release];
+//        }
+//        [defaults setBool:YES forKey:@"already_failed_on_connection"];
+//        [defaults synchronize];
     }
     
     [super failWithError:theError];
index f7fbea7..a74fb6c 100755 (executable)
@@ -6,18 +6,25 @@
 //  The OpenStack project is provided under the Apache 2.0 license.
 //
 
-@interface Provider : NSObject <NSCoding> {
+@interface Provider : NSObject <NSCoding, NSCopying> {
     NSString *name;
-    NSURL *hostURL;
-    NSString *version;
+    NSURL *authURL;
+    NSURL *pithosObjectStoreURL;
+    NSURL *astakosAccountURL;
+    NSURL *astakosWebloginURL;
+    BOOL manual;
 }
 
 + (NSArray *)providers;
 
 @property (nonatomic, retain) NSString *name;
-@property (nonatomic, retain) NSURL *hostURL;
-@property (nonatomic, retain) NSString *version;
+@property (nonatomic, retain) NSURL *authURL;
+@property (nonatomic, retain) NSURL *pithosObjectStoreURL;
+@property (nonatomic, retain) NSURL *astakosAccountURL;
+@property (nonatomic, retain) NSURL *astakosWebloginURL;
+@property (nonatomic) BOOL manual;
 
+@property (nonatomic, readonly) NSURL *tokensURL;
 @property (nonatomic, readonly) NSURL *authEndpointURL;
 @property (nonatomic, readonly) NSURL *loginURL;
 @property (nonatomic, readonly) NSURL *userCatalogURL;
index a0fc530..590856b 100755 (executable)
@@ -9,17 +9,19 @@
 #import "Provider.h"
 
 static NSArray *providers = nil;
+static NSString *defaultAuthURLString = @"https://accounts.okeanos.grnet.gr/astakos/identity/v2.0";
+static NSString *defaultManualURLString = @"https://pithos.okeanos.grnet.gr";
 
 @implementation Provider
 
-@synthesize name, hostURL, version, authEndpointURL, loginURL, userCatalogURL, publicLinkURLPrefix;
+@synthesize name, authURL, pithosObjectStoreURL, astakosAccountURL, astakosWebloginURL, manual;
+@synthesize tokensURL, authEndpointURL, loginURL, userCatalogURL, publicLinkURLPrefix;
 
 + (NSArray *)providers {
     if (providers == nil) {
         Provider *provider = [[Provider alloc] init];
         provider.name = @"okeanos";
-        provider.hostURL = [NSURL URLWithString:@"https://pithos.okeanos.grnet.gr"];
-        provider.version = @"v1";
+        provider.authURL = [NSURL URLWithString:defaultAuthURLString];
         providers = [[NSArray alloc] initWithObjects:provider, nil];
         [provider release];
     }
@@ -29,30 +31,54 @@ static NSArray *providers = nil;
 
 #pragma mark - Serialization
 
+- (id)copyWithZone:(NSZone *)zone {
+    Provider *copy = [[[self class] allocWithZone:zone] init];
+    copy.name = self.name;
+    copy.authURL = self.authURL;
+    copy.pithosObjectStoreURL = self.pithosObjectStoreURL;
+    copy.astakosAccountURL = self.astakosAccountURL;
+    copy.astakosWebloginURL = self.astakosWebloginURL;
+    copy.manual = self.manual;
+    return copy;
+}
+
 - (void)encodeWithCoder:(NSCoder *)coder {
     [coder encodeObject:name forKey:@"name"];
-    [coder encodeObject:hostURL forKey:@"hostURL"];
-    [coder encodeObject:version forKey:@"version"];
+    [coder encodeObject:authURL forKey:@"authURL"];
+    [coder encodeObject:pithosObjectStoreURL forKey:@"pithosObjectStoreURL"];
+    [coder encodeObject:astakosAccountURL forKey:@"astakosAccountURL"];
+    [coder encodeObject:astakosWebloginURL forKey:@"astakosWebloginURL"];
+    [coder encodeBool:manual forKey:@"manual"];
+    
 }
 
 - (id)initWithCoder:(NSCoder *)coder {
     if ((self = [super init])) {
         self.name = [coder decodeObjectForKey:@"name"];
-        self.hostURL = [coder decodeObjectForKey:@"hostURL"];
-        self.version = [coder decodeObjectForKey:@"version"];
+        self.authURL = [coder decodeObjectForKey:@"authURL"];
+        self.pithosObjectStoreURL = [coder decodeObjectForKey:@"pithosObjectStoreURL"];
+        self.astakosAccountURL = [coder decodeObjectForKey:@"astakosAccountURL"];
+        self.astakosWebloginURL = [coder decodeObjectForKey:@"astakosWebloginURL"];
+        self.manual = [coder decodeBoolForKey:@"manual"];
         
         // Support for older versions.
-        if (!hostURL) {
-            NSURL *tmpURL = [coder decodeObjectForKey:@"authEndpointURL"];
-            if (tmpURL) {
-                self.hostURL = [tmpURL URLByDeletingLastPathComponent];
+        if (!authURL && !pithosObjectStoreURL && !astakosAccountURL && !astakosWebloginURL) {
+            NSURL *tmpURL = [coder decodeObjectForKey:@"hostURL"];
+            if (!tmpURL)
+                tmpURL = [[coder decodeObjectForKey:@"authEndpointURL"] URLByDeletingLastPathComponent];
+            if (!tmpURL)
+                tmpURL = [NSURL URLWithString:defaultManualURLString];
+            if ([[tmpURL URLByAppendingPathComponent:@""] isEqual:[[NSURL URLWithString:defaultManualURLString] URLByAppendingPathComponent:@""]] &&
+                [name isEqualToString:@"okeanos"]) {
+                self.authURL = [NSURL URLWithString:defaultAuthURLString];
+                self.manual = NO;
             } else {
-                self.hostURL = [NSURL URLWithString:@"https://pithos.okeanos.grnet.gr"];
+                self.pithosObjectStoreURL = [tmpURL URLByAppendingPathComponent:@"v1"];
+                self.astakosAccountURL = [[tmpURL copy] autorelease];
+                self.astakosWebloginURL = [[tmpURL copy] autorelease];
+                self.manual = YES;
             }
         }
-        if (!version) {
-            self.version = @"v1";
-        }
     }
     return self;
 }
@@ -61,27 +87,33 @@ static NSArray *providers = nil;
 
 - (void)dealloc {
     [name release];
-    [hostURL release];
-    [version release];
+    [authURL release];
+    [pithosObjectStoreURL release];
+    [astakosAccountURL release];
+    [astakosWebloginURL release];
     [super dealloc];
 }
 
 #pragma mark - Properties
 
+- (NSURL *)tokensURL {
+    return [authURL URLByAppendingPathComponent:@"tokens"];
+}
+
 - (NSURL *)authEndpointURL {
-    return [hostURL URLByAppendingPathComponent:version];
+    return pithosObjectStoreURL;
 }
 
 - (NSURL *)loginURL {
-    return [hostURL URLByAppendingPathComponent:@"login"];
+    return [astakosWebloginURL URLByAppendingPathComponent:@"login"];
 }
 
 - (NSURL *)userCatalogURL {
-    return [hostURL URLByAppendingPathComponent:@"user_catalogs"];
+    return [astakosAccountURL URLByAppendingPathComponent:@"user_catalogs"];
 }
 
 - (NSURL *)publicLinkURLPrefix {
-    return hostURL;
+    return pithosObjectStoreURL;
 }
 
 @end
index f298f34..392c5d5 100755 (executable)
@@ -77,7 +77,7 @@
     } else {
         Provider *provider = [[Provider providers] objectAtIndex:indexPath.row];
         cell.textLabel.text = provider.name;
-        cell.detailTextLabel.text = [provider.authEndpointURL absoluteString];
+        cell.detailTextLabel.text = [provider.authURL absoluteString];
         cell.imageView.image = [UIImage imageNamed:@"pithos-solo-smallest.png"];
     }
     return cell;
index 75b9d99..8722708 100755 (executable)
 
 @synthesize textField, modalPresentationStyle, fixedTextLabel;
 
-- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
-    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
-    if (self) {
-        [self initializeSubviews:style withFixedTextLabel:nil];
-    }
-    return self;
-}
-
 - (id)initCellWithFixedLabel:(NSString *)fixedLabelText withStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
-    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
-    if (self) {
+    if ((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) {
         [self initializeSubviews:style withFixedTextLabel:fixedLabelText];
     }
     return self;
 }
+
+- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
+    return [self initCellWithFixedLabel:nil withStyle:style reuseIdentifier:reuseIdentifier];
+}
     
 - (void)initializeSubviews:(UITableViewCellStyle)style withFixedTextLabel:(NSString *)fixedLabelText {
     self.selectionStyle = UITableViewCellSelectionStyleNone;
index 7153eed..98c353d 100755 (executable)
@@ -505,9 +505,6 @@ Delete Object
     }
     cell.textLabel.textColor = [UIColor blackColor];
     cell.detailTextLabel.numberOfLines = 0;
-    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
-        cell.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.8];
-    }
     cell.userInteractionEnabled = YES;
     cell.detailTextLabel.textAlignment = UITextAlignmentRight;
     cell.accessoryView = nil;
index 1398313..86073c6 100755 (executable)
 }
 
 - (void)alert:(NSString *)message request:(OpenStackRequest *)request addAccountSettingsButton:(BOOL)addAccountSettingsButton {
-   request.errorAlerter = [[[ErrorAlerter alloc] init] autorelease];
-   if ((request.responseStatusCode == 401) || request.responseStatusCode == 403) {
-        message = [message stringByAppendingString:@" Authorization failed. Please check your Username and Token."];
+    request.errorAlerter = [[[ErrorAlerter alloc] init] autorelease];
+    if ((request.responseStatusCode == 401) || request.responseStatusCode == 403) {
+       message = [message stringByAppendingString:@" Authorization failed. Please check your Token."];
     } else if (request.responseStatusCode == 503) {
         message = [message stringByAppendingString:@" Service is currently unavailable. Please try again later."];
     }
index 7429450..e7c0e63 100755 (executable)
@@ -41,7 +41,7 @@
        <key>CFBundlePackageType</key>
        <string>APPL</string>
        <key>CFBundleShortVersionString</key>
-       <string>1.6.4</string>
+       <string>1.7.1</string>
        <key>CFBundleSignature</key>
        <string>????</string>
        <key>CFBundleURLTypes</key>
@@ -56,7 +56,7 @@
                </dict>
        </array>
        <key>CFBundleVersion</key>
-       <string>20130502.0</string>
+       <string>20130625.0</string>
        <key>LSRequiresIPhoneOS</key>
        <true/>
        <key>NSMainNibFile</key>