diff --git a/AppSales.xcodeproj/project.pbxproj b/AppSales.xcodeproj/project.pbxproj index be58317b..45cc4170 100644 --- a/AppSales.xcodeproj/project.pbxproj +++ b/AppSales.xcodeproj/project.pbxproj @@ -2201,6 +2201,11 @@ isa = PBXProject; attributes = { LastUpgradeCheck = 0430; + TargetAttributes = { + 7F709D2613BC98860008DBAD = { + DevelopmentTeam = 43KR46Q4ST; + }; + }; }; buildConfigurationList = 7F709D2113BC98860008DBAD /* Build configuration list for PBXProject "AppSales" */; compatibilityVersion = "Xcode 3.2"; @@ -2899,6 +2904,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + DEVELOPMENT_TEAM = 43KR46Q4ST; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "OtherSources/AppSales-Prefix.pch"; HEADER_SEARCH_PATHS = ""; @@ -2915,6 +2921,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + DEVELOPMENT_TEAM = 43KR46Q4ST; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "OtherSources/AppSales-Prefix.pch"; HEADER_SEARCH_PATHS = ""; diff --git a/Classes/ASAccount.h b/Classes/ASAccount.h index 362d326c..58c78c86 100644 --- a/Classes/ASAccount.h +++ b/Classes/ASAccount.h @@ -18,8 +18,8 @@ } @property (nonatomic, assign) BOOL isDownloadingReports; -@property (nonatomic, retain) NSString *password; // this property encapsulates the keychain access, - // it is not actually stored in the Core Data model +@property (nonatomic, retain) NSString *token; // this property encapsulates the keychain access, + // it is not actually stored in the Core Data model @property (nonatomic, retain) NSString *downloadStatus; @property (nonatomic, assign) float downloadProgress; @@ -36,6 +36,7 @@ @property (nonatomic, retain) NSNumber *paymentsBadge; - (void)deletePassword; +- (void)deleteToken; - (NSString *)displayName; @end diff --git a/Classes/ASAccount.m b/Classes/ASAccount.m index ab70474d..e62db6a6 100644 --- a/Classes/ASAccount.m +++ b/Classes/ASAccount.m @@ -11,19 +11,46 @@ #define kAccountKeychainServiceIdentifier @"iTunesConnect" +@interface ASAccount (PrimitiveAccessors) +- (NSString *)primitiveUsername; +@end + @implementation ASAccount @dynamic username, vendorID, title, sortIndex, dailyReports, weeklyReports, products, payments, reportsBadge, paymentsBadge; @synthesize isDownloadingReports, downloadStatus, downloadProgress; -- (NSString *)password +/* + * For backwards compatibility, allow existing usernames to continue to be used (for things like data folder names, etc). If + * no username is set, use the current token. If no token is set, use "default". + */ +- (NSString *)username +{ + [self willAccessValueForKey:@"username"]; + NSString *username = [self primitiveUsername]; + [self didAccessValueForKey:@"username"]; + if (!username || [username isEqualToString:@""]) { + if (self.token && ![self.token isEqualToString:@""]) { + return self.token; + } + return @"default"; + } + return username; +} + +- (NSString *)token { - return [SSKeychain passwordForService:kAccountKeychainServiceIdentifier account:self.username]; + return [SSKeychain passwordForService:kAccountKeychainServiceIdentifier account:kAccountKeychainServiceIdentifier]; } -- (void)setPassword:(NSString *)newPassword +- (void)setToken:(NSString *)token { - [SSKeychain setPassword:newPassword forService:kAccountKeychainServiceIdentifier account:self.username]; + [SSKeychain setPassword:token forService:kAccountKeychainServiceIdentifier account:kAccountKeychainServiceIdentifier]; +} + +- (void)deleteToken +{ + [SSKeychain deletePasswordForService:kAccountKeychainServiceIdentifier account:kAccountKeychainServiceIdentifier]; } - (void)deletePassword @@ -36,7 +63,10 @@ - (NSString *)displayName if (self.title && ![self.title isEqualToString:@""]) { return self.title; } - return self.username; + if (self.token && ![self.token isEqualToString:@""]) { + return self.token; + } + return @"Default"; } @end diff --git a/Classes/AccountsViewController.h b/Classes/AccountsViewController.h index e291d993..230e0864 100644 --- a/Classes/AccountsViewController.h +++ b/Classes/AccountsViewController.h @@ -12,6 +12,7 @@ #define kAccountUsername @"username" #define kAccountPassword @"password" +#define kAccountToken @"token" #define kAccountVendorID @"vendorID" #define ASViewSettingsDidChangeNotification @"ASViewSettingsDidChangeNotification" @@ -41,6 +42,7 @@ @property (nonatomic, retain) UIDocumentInteractionController *documentInteractionController; - (void)reloadAccounts; +- (void)deleteAllAccountPasswords; - (void)downloadReports:(id)sender; - (void)doExport; - (NSString *)folderNameForExportingReportsOfAccount:(ASAccount *)account; @@ -55,4 +57,4 @@ - (void)accountsViewController:(AccountsViewController *)viewController didSelectAccount:(ASAccount *)account; -@end \ No newline at end of file +@end diff --git a/Classes/AccountsViewController.m b/Classes/AccountsViewController.m index 71329a46..be8e4766 100644 --- a/Classes/AccountsViewController.m +++ b/Classes/AccountsViewController.m @@ -104,13 +104,21 @@ - (void)reloadAccounts [self.tableView reloadData]; } +- (void)deleteAllAccountPasswords { + [self.accounts enumerateObjectsUsingBlock:^(ASAccount * _Nonnull account, NSUInteger idx, BOOL * _Nonnull stop) { + NSLog(@"deleting %@ pass", account.username); + [account deletePassword]; + }]; + [self saveContext]; +} + - (void)downloadReports:(id)sender { for (ASAccount *account in self.accounts) { - if (account.password && account.password.length > 0) { //Only download reports for accounts with login + if (account.token && account.token.length > 0) { //Only download reports for accounts with login if (!account.vendorID || account.vendorID.length == 0) { [[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Vendor ID Missing", nil) - message:[NSString stringWithFormat:NSLocalizedString(@"You have not entered a vendor ID for the account \"%@\". Please go to the account's settings and fill in the missing information.", nil), [account displayName]] + message:[NSString stringWithFormat:NSLocalizedString(@"You have not entered a vendor ID for the account with token \"%@\". Please go to the account's settings and fill in the missing information.", nil), [account token]] delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil] autorelease] show]; @@ -267,13 +275,12 @@ - (void)addNewAccount titleField.placeholder = NSLocalizedString(@"optional", nil); FieldSectionSpecifier *titleSection = [FieldSectionSpecifier sectionWithFields:[NSArray arrayWithObject:titleField] title:nil description:nil]; - FieldSpecifier *usernameField = [FieldSpecifier emailFieldWithKey:kAccountUsername title:NSLocalizedString(@"Email", nil) defaultValue:@""]; - FieldSpecifier *passwordField = [FieldSpecifier passwordFieldWithKey:kAccountPassword title:NSLocalizedString(@"Password", nil) defaultValue:@""]; + FieldSpecifier *tokenField = [FieldSpecifier textFieldWithKey:kAccountToken title:NSLocalizedString(@"Token", nil) defaultValue:@""]; FieldSpecifier *vendorIDField = [FieldSpecifier numericFieldWithKey:kAccountVendorID title:NSLocalizedString(@"Vendor ID", nil) defaultValue:@""]; vendorIDField.placeholder = @"8XXXXXXX"; - FieldSpecifier *selectVendorIDButtonField = [FieldSpecifier buttonFieldWithKey:@"SelectVendorIDButton" title:NSLocalizedString(@"Auto-Fill Vendor ID...", nil)]; +// FieldSpecifier *selectVendorIDButtonField = [FieldSpecifier buttonFieldWithKey:@"SelectVendorIDButton" title:NSLocalizedString(@"Auto-Fill Vendor ID...", nil)]; - FieldSectionSpecifier *loginSection = [FieldSectionSpecifier sectionWithFields:[NSArray arrayWithObjects:usernameField, passwordField, vendorIDField, selectVendorIDButtonField, nil] + FieldSectionSpecifier *loginSection = [FieldSectionSpecifier sectionWithFields:[NSArray arrayWithObjects:tokenField, vendorIDField, nil] title:NSLocalizedString(@"iTunes Connect Login", nil) description:NSLocalizedString(@"You can import reports via iTunes File Sharing without entering your login.", nil)]; @@ -295,21 +302,19 @@ - (void)addNewAccount - (void)editAccount:(ASAccount *)account { self.selectedAccount = account; - NSString *username = account.username; - NSString *password = account.password; + NSString *token = account.token; NSString *title = account.title; NSString *vendorID = account.vendorID; FieldSpecifier *titleField = [FieldSpecifier textFieldWithKey:kAccountTitle title:@"Description" defaultValue:title]; titleField.placeholder = NSLocalizedString(@"optional", nil); - FieldSpecifier *usernameField = [FieldSpecifier emailFieldWithKey:kAccountUsername title:NSLocalizedString(@"Username", nil) defaultValue:username]; - FieldSpecifier *passwordField = [FieldSpecifier passwordFieldWithKey:kAccountPassword title:NSLocalizedString(@"Password", nil) defaultValue:password]; + FieldSpecifier *tokenField = [FieldSpecifier textFieldWithKey:kAccountToken title:NSLocalizedString(@"Token", nil) defaultValue:token]; FieldSpecifier *vendorIDField = [FieldSpecifier numericFieldWithKey:kAccountVendorID title:NSLocalizedString(@"Vendor ID", nil) defaultValue:vendorID]; - FieldSpecifier *selectVendorIDButtonField = [FieldSpecifier buttonFieldWithKey:@"SelectVendorIDButton" title:NSLocalizedString(@"Auto-Fill Vendor ID...", nil)]; +// FieldSpecifier *selectVendorIDButtonField = [FieldSpecifier buttonFieldWithKey:@"SelectVendorIDButton" title:NSLocalizedString(@"Auto-Fill Vendor ID...", nil)]; vendorIDField.placeholder = @"8XXXXXXX"; - FieldSectionSpecifier *loginSection = [FieldSectionSpecifier sectionWithFields:[NSArray arrayWithObjects:usernameField, passwordField, vendorIDField, selectVendorIDButtonField, nil] + FieldSectionSpecifier *loginSection = [FieldSectionSpecifier sectionWithFields:[NSArray arrayWithObjects:tokenField, vendorIDField, nil] title:NSLocalizedString(@"iTunes Connect Login", nil) description:nil]; FieldSpecifier *loginSubsectionField = [FieldSpecifier subsectionFieldWithSection:loginSection key:@"iTunesConnect"]; @@ -431,33 +436,30 @@ - (void)showSettings - (void)fieldEditor:(FieldEditorViewController *)editor didFinishEditingWithValues:(NSDictionary *)returnValues { if ([editor.editorIdentifier isEqualToString:kAddNewAccountEditorIdentifier] || [editor.editorIdentifier isEqualToString:kEditAccountEditorIdentifier]) { - NSString *username = [returnValues objectForKey:kAccountUsername]; - NSString *password = [returnValues objectForKey:kAccountPassword]; + NSString *token = [returnValues objectForKey:kAccountToken]; NSString *vendorID = [returnValues objectForKey:kAccountVendorID]; NSString *title = [returnValues objectForKey:kAccountTitle]; - if ((!username || [username isEqualToString:@""]) && (!title || [title isEqualToString:@""])) { - [[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Missing Information", nil) message:NSLocalizedString(@"You need to enter at least a username or a description.\n\nIf you want to download reports from iTunes Connect, you need to enter your username and password, otherwise you can just enter a description.", nil) delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil] autorelease] show]; + if (!title || [title isEqualToString:@""]) { + [[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Missing Information", nil) message:NSLocalizedString(@"You need to enter at least a description.\n\nIf you want to download reports from iTunes Connect, you need to enter an access token, otherwise you can just enter a description.", nil) delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil] autorelease] show]; return; } if ([editor.editorIdentifier isEqualToString:kAddNewAccountEditorIdentifier]) { - if (password && password.length > 0 && (!vendorID || vendorID.length == 0)) { + if (token && token.length > 0 && (!vendorID || vendorID.length == 0)) { [[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Missing Information", nil) message:NSLocalizedString(@"You need to enter a vendor ID. If you don't know your vendor ID, tap \"Auto-Fill Vendor ID\".", nil) delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil] autorelease] show]; return; } ASAccount *account = (ASAccount *)[NSEntityDescription insertNewObjectForEntityForName:@"Account" inManagedObjectContext:self.managedObjectContext]; account.title = title; - account.username = username; account.vendorID = vendorID; account.sortIndex = [NSNumber numberWithLong:time(NULL)]; - [account setPassword:password]; + [account setToken:token]; } else if ([editor.editorIdentifier isEqualToString:kEditAccountEditorIdentifier]) { ASAccount *account = (ASAccount *)editor.context; - [account deletePassword]; - account.username = username; + [account deleteToken]; account.title = title; account.vendorID = vendorID; - [account setPassword:password]; + [account setToken:token]; NSMutableDictionary *productsByID = [NSMutableDictionary dictionary]; for (Product *product in self.selectedAccount.products) { @@ -544,28 +546,27 @@ - (void)fieldEditor:(FieldEditorViewController *)editor pressedButtonWithKey:(NS otherButtonTitles:NSLocalizedString(@"Delete", nil), nil] autorelease]; confirmDeleteAlert.tag = kAlertTagConfirmDelete; [confirmDeleteAlert show]; - } else if ([key isEqualToString:@"SelectVendorIDButton"]) { - FieldEditorViewController *vc = nil; - if (self.presentedViewController) { - UINavigationController *nav = (UINavigationController *)self.presentedViewController; - vc = (FieldEditorViewController *)[[nav viewControllers] objectAtIndex:0]; - } else { - vc = (FieldEditorViewController *)[self.navigationController.viewControllers lastObject]; - } - NSString *username = [vc.values objectForKey:kAccountUsername]; - NSString *password = [vc.values objectForKey:kAccountPassword]; - [vc dismissKeyboard]; - - if (!username || username.length == 0 || !password || password.length == 0) { - [[[[UIAlertView alloc] initWithTitle:nil message:NSLocalizedString(@"Please enter your username and password first.", nil) delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil] autorelease] show]; - return; - } - - NSDictionary *loginInfo = [NSDictionary dictionaryWithObjectsAndKeys:password, kAccountPassword, username, kAccountUsername, nil]; - - MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:vc.navigationController.view animated:YES]; - hud.labelText = NSLocalizedString(@"Checking Vendor ID...", nil); - [hud showWhileExecuting:@selector(findVendorIDsWithLogin:) onTarget:self withObject:loginInfo animated:YES]; +// } else if ([key isEqualToString:@"SelectVendorIDButton"]) { +// FieldEditorViewController *vc = nil; +// if (self.presentedViewController) { +// UINavigationController *nav = (UINavigationController *)self.presentedViewController; +// vc = (FieldEditorViewController *)[[nav viewControllers] objectAtIndex:0]; +// } else { +// vc = (FieldEditorViewController *)[self.navigationController.viewControllers lastObject]; +// } +// NSString *token = [vc.values objectForKey:kAccountToken]; +// [vc dismissKeyboard]; +// +// if (!token || token.length == 0) { +// [[[[UIAlertView alloc] initWithTitle:nil message:NSLocalizedString(@"Please enter your iTunes Connect access token. To create an access token, login to iTunes Connect, visit the Sales and Trends module, then click 'Sales' and click 'Reports'.", nil) delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", nil) otherButtonTitles:nil] autorelease] show]; +// return; +// } +// +// NSDictionary *loginInfo = [NSDictionary dictionaryWithObjectsAndKeys:token, kAccountToken, nil]; +// +// MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:vc.navigationController.view animated:YES]; +// hud.labelText = NSLocalizedString(@"Checking Vendor ID...", nil); +// [hud showWhileExecuting:@selector(findVendorIDsWithLogin:) onTarget:self withObject:loginInfo animated:YES]; } else if ([key isEqualToString:kDownloadBoxcarButton]) { if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"boxcar://provider/965"]]) { [[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Boxcar Already Installed", nil) diff --git a/Classes/AppSalesAppDelegate.m b/Classes/AppSalesAppDelegate.m index 313c69c8..96720ab4 100644 --- a/Classes/AppSalesAppDelegate.m +++ b/Classes/AppSalesAppDelegate.m @@ -68,6 +68,9 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [self.accountsViewController reloadAccounts]; } + // since Apple uses tokens for the Reporter tool now, clean up any old passwords + [self.accountsViewController deleteAllAccountPasswords]; + [[CurrencyManager sharedManager] refreshIfNeeded]; NSString* productSortByValue = [[NSUserDefaults standardUserDefaults] objectForKey:@"ProductSortby"]; @@ -183,7 +186,6 @@ - (BOOL)migrateDataIfNeeded if (!account) { account = (ASAccount *)[NSEntityDescription insertNewObjectForEntityForName:@"Account" inManagedObjectContext:[self managedObjectContext]]; if (oldUsername) account.username = oldUsername; - if (oldPassword) account.password = oldPassword; } [self saveContext]; [[ReportDownloadCoordinator sharedReportDownloadCoordinator] importReportsIntoAccount:account fromDirectory:legacyReportDirectory deleteAfterImport:YES]; diff --git a/Classes/ReportDownloadOperation.h b/Classes/ReportDownloadOperation.h index 2b82f452..78bf4820 100644 --- a/Classes/ReportDownloadOperation.h +++ b/Classes/ReportDownloadOperation.h @@ -14,10 +14,10 @@ @class ASAccount; @interface ReportDownloadOperation : NSOperation { - + ASAccount *_account; NSString *username; - NSString *password; + NSString *token; NSPersistentStoreCoordinator *psc; NSManagedObjectID *accountObjectID; NSInteger downloadCount; diff --git a/Classes/ReportDownloadOperation.m b/Classes/ReportDownloadOperation.m index a0cc8e1e..a6bd2cd9 100644 --- a/Classes/ReportDownloadOperation.m +++ b/Classes/ReportDownloadOperation.m @@ -32,7 +32,7 @@ - (id)initWithAccount:(ASAccount *)account self = [super init]; if (self) { username = [[account username] copy]; - password = [[account password] copy]; + token = [[account token] copy]; _account = [account retain]; accountObjectID = [[account objectID] copy]; psc = [[[account managedObjectContext] persistentStoreCoordinator] retain]; @@ -157,15 +157,13 @@ - (void)main } } - NSString *escapedUsername = [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)username, NULL, CFSTR("!*'();:@&=+$,/?%#[]"), kCFStringEncodingUTF8) autorelease]; - NSString *escapedPassword = [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)password, NULL, CFSTR("!*'();:@&=+$,/?%#[]"), kCFStringEncodingUTF8) autorelease]; + NSString *escapedToken = [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)token, NULL, CFSTR("!*'();:@&=+$,/?%#[]"), kCFStringEncodingUTF8) autorelease]; NSURL *URL = [NSURL URLWithString:@"https://reportingitc-reporter.apple.com/reportservice/sales/v1"]; NSString *command = [NSString stringWithFormat:@"[p=Reporter.properties, Sales.getReport, %@,Sales,Summary,%@,%@]", vendorID, dateType, reportDateString]; NSDictionary *body = @{ - @"userid" : escapedUsername, - @"password" : escapedPassword, + @"accesstoken" : escapedToken, @"version" : @"2.0", @"mode" : @"Normal", @"queryInput" : command @@ -358,7 +356,7 @@ - (NSString *)stringFromSynchronousPostRequestWithURL:(NSURL *)URL bodyDictionar - (void)dealloc { [username release]; - [password release]; + [token release]; [accountObjectID release]; [_account release]; [psc release]; diff --git a/Classes/SalesViewController.m b/Classes/SalesViewController.m index 896dc0fb..b1a02d82 100644 --- a/Classes/SalesViewController.m +++ b/Classes/SalesViewController.m @@ -300,7 +300,7 @@ - (void)setViewMode:(DashboardViewMode)newViewMode - (void)downloadReports:(id)sender { - if (self.account.password && self.account.password.length > 0) { //Only download reports for accounts with login + if (self.account.token && self.account.token.length > 0) { //Only download reports for accounts with login if (!account.vendorID || account.vendorID.length == 0) { [[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Vendor ID Missing", nil) message:NSLocalizedString(@"You have not entered a vendor ID for this account. Please go to the account's settings and fill in the missing information.", nil)