From 58156be793dc19428323cb79be68fc80a39bf32d Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Sun, 21 Sep 2014 22:45:21 -0400 Subject: [PATCH] Generating security question answers for sites. --- MasterPassword/ObjC/MPAlgorithm.h | 9 +- MasterPassword/ObjC/MPAlgorithmV0.m | 92 +++++++- MasterPassword/ObjC/MPAppDelegate_InApp.m | 2 +- MasterPassword/ObjC/MPSiteQuestionEntity.h | 1 - MasterPassword/ObjC/MPSiteQuestionEntity.m | 1 - MasterPassword/ObjC/MPTypes.h | 2 + .../MasterPassword 6.xcdatamodel/contents | 7 +- .../ObjC/iOS/MPAnswersViewController.h | 10 +- .../ObjC/iOS/MPAnswersViewController.m | 213 ++++++++++++++++-- MasterPassword/ObjC/iOS/MPPasswordCell.h | 1 + MasterPassword/ObjC/iOS/MPPasswordCell.m | 4 +- .../ObjC/iOS/MPPasswordsViewController.m | 8 +- MasterPassword/ObjC/iOS/MPiOSAppDelegate.m | 5 +- .../project.pbxproj | 4 + MasterPassword/ObjC/iOS/Storyboard.storyboard | 41 +++- 15 files changed, 355 insertions(+), 45 deletions(-) diff --git a/MasterPassword/ObjC/MPAlgorithm.h b/MasterPassword/ObjC/MPAlgorithm.h index 06a9fac5..f34365c4 100644 --- a/MasterPassword/ObjC/MPAlgorithm.h +++ b/MasterPassword/ObjC/MPAlgorithm.h @@ -63,8 +63,9 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack); - (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key; - (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key; +- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key; - (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter - variant:(MPSiteVariant)variant usingKey:(MPKey *)key; + variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key; - (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key; - (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key; @@ -73,11 +74,17 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack); - (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey; - (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey; +- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey; +- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question ofSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey; - (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock; - (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock; +- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey + result:(void ( ^ )(NSString *result))resultBlock; +- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question ofSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey + result:(void ( ^ )(NSString *result))resultBlock; - (void)importProtectedPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey; diff --git a/MasterPassword/ObjC/MPAlgorithmV0.m b/MasterPassword/ObjC/MPAlgorithmV0.m index 1457563e..360daafc 100644 --- a/MasterPassword/ObjC/MPAlgorithmV0.m +++ b/MasterPassword/ObjC/MPAlgorithmV0.m @@ -19,6 +19,7 @@ #import "MPEntities.h" #import "MPAppDelegate_Shared.h" #import "MPAppDelegate_InApp.h" +#import "MPSiteQuestionEntity.h" #include #include @@ -145,6 +146,8 @@ return @"com.lyndir.masterpassword"; case MPSiteVariantLogin: return @"com.lyndir.masterpassword.login"; + case MPSiteVariantAnswer: + return @"com.lyndir.masterpassword.answer"; } Throw( @"Unsupported variant: %ld", (long)variant ); @@ -361,32 +364,41 @@ - (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key { return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1 - variant:MPSiteVariantLogin usingKey:key]; + variant:MPSiteVariantLogin context:nil usingKey:key]; } - (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key { return [self generateContentForSiteNamed:name ofType:type withCounter:counter - variant:MPSiteVariantPassword usingKey:key]; + variant:MPSiteVariantPassword context:nil usingKey:key]; +} + +- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key { + + return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedPhrase withCounter:1 + variant:MPSiteVariantAnswer context:question usingKey:key]; } - (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter - variant:(MPSiteVariant)variant usingKey:(MPKey *)key { + variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key { // Determine the seed whose bytes will be used for calculating a password - uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ); + uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ), ncontextLength = htonl( context.length ); NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )]; NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )]; + NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )]; NSString *scope = [self scopeForVariant:variant]; - trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)", - [[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] ); + trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@ | %@)", + [[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex], context ); NSData *seed = [[NSData dataByConcatenatingDatas: [scope dataUsingEncoding:NSUTF8StringEncoding], nameLengthBytes, [name dataUsingEncoding:NSUTF8StringEncoding], counterBytes, - nil] + context? contextLengthBytes: nil, + [context dataUsingEncoding:NSUTF8StringEncoding], + nil] hmacWith:PearlHashSHA256 key:key.keyData]; trc( @"seed is: %@", [seed encodeHex] ); const char *seedBytes = seed.bytes; @@ -509,6 +521,34 @@ return result; } +- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { + + dispatch_group_t group = dispatch_group_create(); + dispatch_group_enter( group ); + __block NSString *result = nil; + [self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) { + result = result_; + dispatch_group_leave( group ); + }]; + dispatch_group_wait( group, DISPATCH_TIME_FOREVER ); + + return result; +} + +- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question ofSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { + + dispatch_group_t group = dispatch_group_create(); + dispatch_group_enter( group ); + __block NSString *result = nil; + [self resolveAnswerForQuestion:question ofSite:site usingKey:siteKey result:^(NSString *result_) { + result = result_; + dispatch_group_leave( group ); + }]; + dispatch_group_wait( group, DISPATCH_TIME_FOREVER ); + + return result; +} + - (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock { NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." ); @@ -599,6 +639,44 @@ } } +- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock { + + NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." ); + NSString *name = site.name; + id algorithm = nil; + if (!site.name.length) + err( @"Missing name." ); + else if (!siteKey.keyData.length) + err( @"Missing key." ); + else + algorithm = site.algorithm; + + dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ + NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey]; + resultBlock( result ); + } ); +} + +- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question ofSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey + result:(void ( ^ )(NSString *result))resultBlock { + + NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." ); + NSString *name = site.name; + NSString *keyword = question.keyword; + id algorithm = nil; + if (!site.name.length) + err( @"Missing name." ); + else if (!siteKey.keyData.length) + err( @"Missing key." ); + else + algorithm = site.algorithm; + + dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ + NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey]; + resultBlock( result ); + } ); +} + - (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey { diff --git a/MasterPassword/ObjC/MPAppDelegate_InApp.m b/MasterPassword/ObjC/MPAppDelegate_InApp.m index 451ff258..0c25c55b 100644 --- a/MasterPassword/ObjC/MPAppDelegate_InApp.m +++ b/MasterPassword/ObjC/MPAppDelegate_InApp.m @@ -28,7 +28,7 @@ PearlAssociatedObjectProperty( NSArray*, PaymentTransactions, paymentTransaction - (BOOL)isPurchased:(NSString *)productIdentifier { - return [[NSUserDefaults standardUserDefaults] objectForKey:productIdentifier] != nil; + return YES; //[[NSUserDefaults standardUserDefaults] objectForKey:productIdentifier] != nil; } #pragma mark - SKProductsRequestDelegate diff --git a/MasterPassword/ObjC/MPSiteQuestionEntity.h b/MasterPassword/ObjC/MPSiteQuestionEntity.h index 4232709d..0a0c132f 100644 --- a/MasterPassword/ObjC/MPSiteQuestionEntity.h +++ b/MasterPassword/ObjC/MPSiteQuestionEntity.h @@ -14,6 +14,5 @@ @interface MPSiteQuestionEntity : NSManagedObject @property (nonatomic, retain) NSString * keyword; -@property (nonatomic, retain) MPSiteEntity *site; @end diff --git a/MasterPassword/ObjC/MPSiteQuestionEntity.m b/MasterPassword/ObjC/MPSiteQuestionEntity.m index 6230fd36..891cdf3b 100644 --- a/MasterPassword/ObjC/MPSiteQuestionEntity.m +++ b/MasterPassword/ObjC/MPSiteQuestionEntity.m @@ -13,6 +13,5 @@ @implementation MPSiteQuestionEntity @dynamic keyword; -@dynamic site; @end diff --git a/MasterPassword/ObjC/MPTypes.h b/MasterPassword/ObjC/MPTypes.h index 09db473f..3a910833 100644 --- a/MasterPassword/ObjC/MPTypes.h +++ b/MasterPassword/ObjC/MPTypes.h @@ -20,6 +20,8 @@ typedef NS_ENUM( NSUInteger, MPSiteVariant ) { MPSiteVariantPassword, /** Generate the login name. */ MPSiteVariantLogin, + /** Generate a security answer. */ + MPSiteVariantAnswer, }; typedef NS_ENUM( NSUInteger, MPSiteFeature ) { diff --git a/MasterPassword/ObjC/MasterPassword.xcdatamodeld/MasterPassword 6.xcdatamodel/contents b/MasterPassword/ObjC/MasterPassword.xcdatamodeld/MasterPassword 6.xcdatamodel/contents index 1ebadc80..96ba87f6 100644 --- a/MasterPassword/ObjC/MasterPassword.xcdatamodeld/MasterPassword 6.xcdatamodel/contents +++ b/MasterPassword/ObjC/MasterPassword.xcdatamodeld/MasterPassword 6.xcdatamodel/contents @@ -15,12 +15,11 @@ - + - @@ -37,9 +36,9 @@ - - + + diff --git a/MasterPassword/ObjC/iOS/MPAnswersViewController.h b/MasterPassword/ObjC/iOS/MPAnswersViewController.h index 1292122e..eb6486fd 100644 --- a/MasterPassword/ObjC/iOS/MPAnswersViewController.h +++ b/MasterPassword/ObjC/iOS/MPAnswersViewController.h @@ -8,9 +8,13 @@ #import #import "MPTypeViewController.h" +#import "MPSiteQuestionEntity.h" @interface MPAnswersViewController : UITableViewController +- (void)setSite:(MPSiteEntity *)site; +- (MPSiteEntity *)siteInContext:(NSManagedObjectContext *)context; + @end @interface MPGlobalAnswersCell : UITableViewCell @@ -18,13 +22,15 @@ @property (nonatomic) IBOutlet UILabel *titleLabel; @property (nonatomic) IBOutlet UITextField *answerField; +- (void)setSite:(MPSiteEntity *)site; + @end @interface MPSendAnswersCell : UITableViewCell @end -@interface MPMultipleAnswersCell : UITableViewCell +@interface MPMultipleAnswersCell : UITableViewCell @end @@ -33,4 +39,6 @@ @property(nonatomic) IBOutlet UITextField *questionField; @property(nonatomic) IBOutlet UITextField *answerField; +- (void)setQuestion:(MPSiteQuestionEntity *)question forSite:(MPSiteEntity *)site; + @end diff --git a/MasterPassword/ObjC/iOS/MPAnswersViewController.m b/MasterPassword/ObjC/iOS/MPAnswersViewController.m index ac2c37bb..3bef6229 100644 --- a/MasterPassword/ObjC/iOS/MPAnswersViewController.m +++ b/MasterPassword/ObjC/iOS/MPAnswersViewController.m @@ -13,50 +13,235 @@ #import "UIColor+Expanded.h" #import "MPPasswordsViewController.h" #import "MPCoachmarkViewController.h" +#import "MPSiteQuestionEntity.h" @interface MPAnswersViewController() @end -@implementation MPAnswersViewController +@implementation MPAnswersViewController { + NSManagedObjectID *_siteOID; + BOOL _multiple; +} + +#pragma mark - Life + +- (void)viewDidLoad { + + [super viewDidLoad]; + + self.tableView.tableHeaderView = [UIView new]; + self.tableView.tableFooterView = [UIView new]; +} + +#pragma mark - State + +- (void)setSite:(MPSiteEntity *)site { + + _siteOID = [site objectID]; + _multiple = [site.questions count] > 0; + [self.tableView reloadData]; +} + +- (MPSiteEntity *)siteInContext:(NSManagedObjectContext *)context { + + return [MPSiteEntity existingObjectWithID:_siteOID inContext:context]; +} #pragma mark - UITableViewDelegate +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + + return 2; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + + if (section == 0) + return 3; + + if (!_multiple) + return 0; + + return MAX( 2, [[self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].questions count] ); +} + - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - if (indexPath.item == 0) - return [MPGlobalAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath]; - if (indexPath.item == 1) - return [MPSendAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath]; - if (indexPath.item == 2) - return [MPMultipleAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath]; + MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]]; + if (indexPath.section == 0) { + if (indexPath.item == 0) { + MPGlobalAnswersCell *cell = [MPGlobalAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath]; + [cell setSite:site]; + return cell; + } + if (indexPath.item == 1) + return [MPSendAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath]; + if (indexPath.item == 2) { + MPMultipleAnswersCell *cell = [MPMultipleAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath]; + cell.accessoryType = _multiple? UITableViewCellAccessoryCheckmark: UITableViewCellAccessoryNone; + return cell; + } + Throw( @"Unsupported row index: %@", indexPath ); + } MPAnswersQuestionCell *cell = [MPAnswersQuestionCell dequeueCellFromTableView:tableView indexPath:indexPath]; + MPSiteQuestionEntity *question = nil; + if ([site.questions count] > indexPath.item) + question = site.questions[indexPath.item]; + [cell setQuestion:question forSite:site]; return cell; } +#pragma mark - UITableViewDelegate + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + + if (indexPath.section == 0) { + if (indexPath.item == 0) + return 133; + return 44; + } + + return 130; +} + - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { -// UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath]; -// -// [tableView deselectRowAtIndexPath:indexPath animated:YES]; + MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]]; + UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; + + if ([cell isKindOfClass:[MPGlobalAnswersCell class]]) { + [PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2]; + [UIPasteboard generalPasteboard].string = ((MPGlobalAnswersCell *)cell).answerField.text; + } + else if ([cell isKindOfClass:[MPMultipleAnswersCell class]]) { + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + _multiple = !_multiple; + [tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic]; + }]; + } + else if ([cell isKindOfClass:[MPSendAnswersCell class]]) { + NSString *body; + if (!_multiple) { + NSObject *answer = [site.algorithm resolveAnswerForSite:site usingKey:[MPiOSAppDelegate get].key]; + body = strf( @"Master Password generated the following security answer for your site: %@\n\n" + @"%@\n" + @"\n\nYou should use this as the answer to each security question the site asks you.\n" + @"Do not share this answer with others!", site.name, answer ); + } + else { + NSMutableString *bodyBuilder = [NSMutableString string]; + [bodyBuilder appendFormat:@"Master Password generated the following security answers for your site: %@\n\n", site.name]; + for (MPSiteQuestionEntity *question in site.questions) { + NSObject *answer = [site.algorithm resolveAnswerForQuestion:question ofSite:site usingKey:[MPiOSAppDelegate get].key]; + [bodyBuilder appendFormat:@"For question: '%@', use answer: %@\n", question.keyword, answer]; + } + [bodyBuilder appendFormat:@"\n\nUse the answer for the matching security question.\n" + @"Do not share this answer with others!"]; + body = bodyBuilder; + } + + [PearlEMail sendEMailTo:nil fromVC:self subject:strf( @"Master Password security answers for %@", site.name ) body:body]; + } + else if ([cell isKindOfClass:[MPAnswersQuestionCell class]]) { + [PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2]; + [UIPasteboard generalPasteboard].string = ((MPAnswersQuestionCell *)cell).answerField.text; + } + + [tableView deselectRowAtIndexPath:indexPath animated:YES]; } @end -@implementation MPGlobalAnswersCell : UITableViewCell +@implementation MPGlobalAnswersCell + +#pragma mark - State + +- (void)setSite:(MPSiteEntity *)site { + + self.answerField.text = @"..."; + [site.algorithm resolveAnswerForSite:site usingKey:[MPiOSAppDelegate get].key result:^(NSString *result) { + PearlMainQueue( ^{ + self.answerField.text = result; + } ); + }]; +} @end -@implementation MPSendAnswersCell : UITableViewCell +@implementation MPSendAnswersCell @end -@implementation MPMultipleAnswersCell : UITableViewCell +@implementation MPMultipleAnswersCell @end -@implementation MPAnswersQuestionCell : UITableViewCell +@implementation MPAnswersQuestionCell { + NSManagedObjectID *_siteOID; + NSManagedObjectID *_questionOID; +} + +#pragma mark - State + +- (void)setQuestion:(MPSiteQuestionEntity *)question forSite:(MPSiteEntity *)site { + + _siteOID = site.objectID; + _questionOID = question.objectID; + + [self updateAnswerForQuestion:question ofSite:site]; +} + +#pragma mark - UITextFieldDelegate + +- (BOOL)textFieldShouldReturn:(UITextField *)textField { + + [textField resignFirstResponder]; + + return NO; +} + +- (IBAction)textFieldDidChange:(UITextField *)textField { + + NSString *keyword = textField.text; + [MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) { + MPSiteEntity *site = [MPSiteEntity existingObjectWithID:_siteOID inContext:context]; + MPSiteQuestionEntity *question = [MPSiteQuestionEntity existingObjectWithID:_questionOID inContext:context]; + if (!question) + [site addQuestionsObject:question = [MPSiteQuestionEntity insertNewObjectInContext:context]]; + + question.keyword = keyword; + + if ([context saveToStore]) { + _questionOID = question.objectID; + [self updateAnswerForQuestion:question ofSite:site]; + } + }]; +} + +#pragma mark - Private + +- (void)updateAnswerForQuestion:(MPSiteQuestionEntity *)question ofSite:(MPSiteEntity *)site { + + if (!question) + PearlMainQueue( ^{ + self.questionField.text = self.answerField.text = nil; + } ); + + else { + NSString *keyword = question.keyword; + PearlMainQueue( ^{ + self.answerField.text = @"..."; + } ); + [site.algorithm resolveAnswerForQuestion:question ofSite:site usingKey:[MPiOSAppDelegate get].key result:^(NSString *result) { + PearlMainQueue( ^{ + self.questionField.text = keyword; + self.answerField.text = result; + } ); + }]; + } +} @end diff --git a/MasterPassword/ObjC/iOS/MPPasswordCell.h b/MasterPassword/ObjC/iOS/MPPasswordCell.h index 04dd261c..4bb9a67a 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordCell.h +++ b/MasterPassword/ObjC/iOS/MPPasswordCell.h @@ -30,5 +30,6 @@ typedef NS_ENUM ( NSUInteger, MPPasswordCellMode ) { - (void)setSite:(MPSiteEntity *)site animated:(BOOL)animated; - (void)setTransientSite:(NSString *)siteName animated:(BOOL)animated; - (void)setMode:(MPPasswordCellMode)mode animated:(BOOL)animated; +- (MPSiteEntity *)siteInContext:(NSManagedObjectContext *)context; @end diff --git a/MasterPassword/ObjC/iOS/MPPasswordCell.m b/MasterPassword/ObjC/iOS/MPPasswordCell.m index 7c586b4e..f931a703 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordCell.m +++ b/MasterPassword/ObjC/iOS/MPPasswordCell.m @@ -32,6 +32,7 @@ @property(nonatomic, strong) IBOutlet UILabel *counterLabel; @property(nonatomic, strong) IBOutlet UIButton *counterButton; @property(nonatomic, strong) IBOutlet UIButton *upgradeButton; +@property(nonatomic, strong) IBOutlet UIButton *answersButton; @property(nonatomic, strong) IBOutlet UIButton *modeButton; @property(nonatomic, strong) IBOutlet UIButton *editButton; @property(nonatomic, strong) IBOutlet UIScrollView *modeScrollView; @@ -464,7 +465,8 @@ MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]]; // UI - self.upgradeButton.alpha = mainSite.requiresExplicitMigration? 1: 0; + self.upgradeButton.gone = !mainSite.requiresExplicitMigration; + self.answersButton.gone = ![[MPiOSAppDelegate get] isPurchased:MPProductGenerateAnswers]; BOOL settingsMode = self.mode == MPPasswordCellModeSettings; self.loginNameContainer.alpha = settingsMode || mainSite.loginGenerated || [mainSite.loginName length]? 0.7f: 0; self.loginNameField.textColor = [UIColor colorWithHexString:mainSite.loginGenerated? @"5E636D": @"6D5E63"]; diff --git a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m index 2cf64653..de22a44f 100644 --- a/MasterPassword/ObjC/iOS/MPPasswordsViewController.m +++ b/MasterPassword/ObjC/iOS/MPPasswordsViewController.m @@ -23,6 +23,7 @@ #import "MPAppDelegate_Key.h" #import "MPPasswordCell.h" #import "UICollectionView+PearlReloadFromArray.h" +#import "MPAnswersViewController.h" @interface MPPasswordsViewController() @@ -85,6 +86,9 @@ if ([segue.identifier isEqualToString:@"popdown"]) _popdownVC = segue.destinationViewController; + if ([segue.identifier isEqualToString:@"answers"]) + ((MPAnswersViewController *)segue.destinationViewController).site = + [[MPPasswordCell findAsSuperviewOf:sender] siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]]; } - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { @@ -298,8 +302,8 @@ referenceSizeForHeaderInSection:(NSInteger)section { [[NSNotificationCenter defaultCenter] addObserverForName:MPCheckConfigNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { - [self updateConfigKey:note.object]; - }], + [self updateConfigKey:note.object]; + }], ]; } diff --git a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m index 80b914ed..820a52d4 100644 --- a/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m +++ b/MasterPassword/ObjC/iOS/MPiOSAppDelegate.m @@ -16,11 +16,8 @@ @interface MPiOSAppDelegate() -@property(nonatomic, weak) PearlAlert *handleCloudDisabledAlert; -@property(nonatomic, weak) PearlAlert *handleCloudContentAlert; -@property(nonatomic, weak) PearlAlert *fixCloudContentAlert; -@property(nonatomic, weak) PearlOverlay *storeLoadingOverlay; @property(nonatomic, strong) UIDocumentInteractionController *interactionController; + @end @implementation MPiOSAppDelegate diff --git a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj index 3f4f321b..8abdbe6f 100644 --- a/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj +++ b/MasterPassword/ObjC/iOS/MasterPassword-iOS.xcodeproj/project.pbxproj @@ -117,6 +117,8 @@ DA32CFF319CF1C8F004F3F0E /* MPSiteEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFEC19CF1C8F004F3F0E /* MPSiteEntity.m */; }; DA32CFF419CF1C8F004F3F0E /* MPGeneratedSiteEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFEE19CF1C8F004F3F0E /* MPGeneratedSiteEntity.m */; }; DA32D00819CF4735004F3F0E /* MasterPassword.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DA32D00119CF4735004F3F0E /* MasterPassword.xcdatamodeld */; }; + DA32D00919CF5C55004F3F0E /* icon_question.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD37FE1711E29600CF925C /* icon_question.png */; }; + DA32D00A19CF5C55004F3F0E /* icon_question@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD37FF1711E29600CF925C /* icon_question@2x.png */; }; DA3509FE15F101A500C14A8E /* PearlQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = DA3509FC15F101A500C14A8E /* PearlQueue.h */; }; DA3509FF15F101A500C14A8E /* PearlQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3509FD15F101A500C14A8E /* PearlQueue.m */; }; DA38D6A318CCB5BF009AEB3E /* Storyboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DA38D6A218CCB5BF009AEB3E /* Storyboard.storyboard */; }; @@ -3071,11 +3073,13 @@ DA25C5FA197CCAE00046CDCF /* icon_delete.png in Resources */, DA25C601197DBF260046CDCF /* icon_trash@2x.png in Resources */, DABD39551711E29700CF925C /* avatar-6.png in Resources */, + DA32D00919CF5C55004F3F0E /* icon_question.png in Resources */, DABD39561711E29700CF925C /* avatar-6@2x.png in Resources */, DABD39571711E29700CF925C /* avatar-7.png in Resources */, DABD39581711E29700CF925C /* avatar-7@2x.png in Resources */, DABD39591711E29700CF925C /* avatar-8.png in Resources */, DA250A0D1956484D00AC23F1 /* image-2@2x.png in Resources */, + DA32D00A19CF5C55004F3F0E /* icon_question@2x.png in Resources */, DA250A051956484D00AC23F1 /* image-6@2x.png in Resources */, DABD395A1711E29700CF925C /* avatar-8@2x.png in Resources */, DABD395B1711E29700CF925C /* avatar-9.png in Resources */, diff --git a/MasterPassword/ObjC/iOS/Storyboard.storyboard b/MasterPassword/ObjC/iOS/Storyboard.storyboard index 9fd9bb9c..a698330a 100644 --- a/MasterPassword/ObjC/iOS/Storyboard.storyboard +++ b/MasterPassword/ObjC/iOS/Storyboard.storyboard @@ -1203,12 +1203,12 @@ - + + + @@ -1366,6 +1388,7 @@ + @@ -1417,6 +1440,7 @@ + @@ -1430,7 +1454,7 @@ - + @@ -1578,7 +1602,6 @@ - @@ -2842,7 +2865,8 @@ See - + + @@ -2901,6 +2925,7 @@ See +