Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d704f451a3 | ||
|
|
2c9ab5d153 | ||
|
|
d5d33da12f | ||
|
|
cbef1a611b | ||
|
|
0a1f215a1a | ||
|
|
907d2a8ca6 |
2
platform-darwin/External/Pearl
vendored
2
platform-darwin/External/Pearl
vendored
Submodule platform-darwin/External/Pearl updated: 3ceb601ba4...8853d299b2
@@ -430,58 +430,38 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
|
||||
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter( group );
|
||||
__block NSString *result = nil;
|
||||
[self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||
result = result_;
|
||||
dispatch_group_leave( group );
|
||||
}];
|
||||
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
||||
|
||||
return result;
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
[self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||
setResult( result_ );
|
||||
}];
|
||||
} );
|
||||
}
|
||||
|
||||
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter( group );
|
||||
__block NSString *result = nil;
|
||||
[self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||
result = result_;
|
||||
dispatch_group_leave( group );
|
||||
}];
|
||||
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
||||
|
||||
return result;
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
[self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||
setResult( 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;
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
[self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) {
|
||||
setResult( result_ );
|
||||
}];
|
||||
} );
|
||||
}
|
||||
|
||||
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey {
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter( group );
|
||||
__block NSString *result = nil;
|
||||
[self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) {
|
||||
result = result_;
|
||||
dispatch_group_leave( group );
|
||||
}];
|
||||
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
||||
|
||||
return result;
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
[self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) {
|
||||
setResult( result_ );
|
||||
}];
|
||||
} );
|
||||
}
|
||||
|
||||
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
||||
@@ -498,10 +478,12 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
else
|
||||
algorithm = site.algorithm;
|
||||
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
resultBlock( loginName || !loginGenerated? loginName:
|
||||
[algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
|
||||
} );
|
||||
if (!loginGenerated || [loginName length])
|
||||
resultBlock( loginName );
|
||||
else
|
||||
PearlNotMainQueue( ^{
|
||||
resultBlock( [algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
|
||||
} );
|
||||
}
|
||||
|
||||
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
|
||||
@@ -533,9 +515,8 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
else
|
||||
algorithm = site.algorithm;
|
||||
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
NSString *result = [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey];
|
||||
resultBlock( result );
|
||||
PearlNotMainQueue( ^{
|
||||
resultBlock( [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey] );
|
||||
} );
|
||||
break;
|
||||
}
|
||||
@@ -549,9 +530,8 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
|
||||
NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject;
|
||||
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
|
||||
resultBlock( result );
|
||||
PearlNotMainQueue( ^{
|
||||
resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
|
||||
} );
|
||||
break;
|
||||
}
|
||||
@@ -563,9 +543,8 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
|
||||
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:siteQuery];
|
||||
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
|
||||
resultBlock( result );
|
||||
PearlNotMainQueue( ^{
|
||||
resultBlock( [self decryptContent:encryptedContent usingKey:siteKey] );
|
||||
} );
|
||||
break;
|
||||
}
|
||||
@@ -584,9 +563,8 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
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 );
|
||||
PearlNotMainQueue( ^{
|
||||
resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey] );
|
||||
} );
|
||||
}
|
||||
|
||||
@@ -605,9 +583,8 @@ NSOperationQueue *_mpwQueue = nil;
|
||||
else if ([[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateAnswers])
|
||||
algorithm = question.site.algorithm;
|
||||
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey];
|
||||
resultBlock( result );
|
||||
PearlNotMainQueue( ^{
|
||||
resultBlock( [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey] );
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
@@ -72,14 +72,23 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
|
||||
|
||||
MPKeyOrigin keyOrigin;
|
||||
NSDictionary *keyQuery = createKeyQuery( user, NO, &keyOrigin );
|
||||
NSData *keyData = [PearlKeyChain dataOfItemForQuery:keyQuery];
|
||||
if (!keyData) {
|
||||
id<MPAlgorithm> keyAlgorithm = user.algorithm;
|
||||
MPKey *key = [[MPKey alloc] initForFullName:user.name withKeyResolver:^NSData *(id<MPAlgorithm> algorithm) {
|
||||
return ![algorithm isEqual:keyAlgorithm]? nil:
|
||||
PearlMainQueueAwait( (id)^{
|
||||
return [PearlKeyChain dataOfItemForQuery:keyQuery];
|
||||
} );
|
||||
} keyOrigin:keyOrigin];
|
||||
|
||||
if ([key keyIDForAlgorithm:user.algorithm])
|
||||
inf( @"Found key in keychain for user: %@", user.userID );
|
||||
|
||||
else {
|
||||
inf( @"No key found in keychain for user: %@", user.userID );
|
||||
return nil;
|
||||
key = nil;
|
||||
}
|
||||
|
||||
inf( @"Found key in keychain for user: %@", user.userID );
|
||||
return [[MPKey alloc] initForFullName:user.name withKeyData:keyData forAlgorithm:user.algorithm keyOrigin:keyOrigin];
|
||||
return key;
|
||||
}
|
||||
|
||||
- (void)storeSavedKeyFor:(MPUserEntity *)user {
|
||||
@@ -230,28 +239,22 @@ static NSDictionary *createKeyQuery(MPUserEntity *user, BOOL newItem, MPKeyOrigi
|
||||
NSString *content;
|
||||
while (!(content = [site.algorithm storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
|
||||
// Failed to decrypt site with the current recoveryKey. Ask user for a new one to use.
|
||||
__block NSString *masterPassword = nil;
|
||||
NSString *masterPassword = nil;
|
||||
|
||||
#ifdef PEARL_UIKIT
|
||||
dispatch_group_t recoverPasswordGroup = dispatch_group_create();
|
||||
dispatch_group_enter( recoverPasswordGroup );
|
||||
[PearlAlert showAlertWithTitle:@"Enter Old Master Password"
|
||||
message:PearlString( @"Your old master password is required to migrate the stored password for %@",
|
||||
site.name )
|
||||
viewStyle:UIAlertViewStyleSecureTextInput
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
@try {
|
||||
masterPassword = PearlAwait( ^(void (^setResult)(id)) {
|
||||
[PearlAlert showAlertWithTitle:@"Enter Old Master Password"
|
||||
message:PearlString(
|
||||
@"Your old master password is required to migrate the stored password for %@",
|
||||
site.name )
|
||||
viewStyle:UIAlertViewStyleSecureTextInput
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
if (buttonIndex_ == [alert_ cancelButtonIndex])
|
||||
// Don't Migrate
|
||||
return;
|
||||
|
||||
masterPassword = [alert_ textFieldAtIndex:0].text;
|
||||
}
|
||||
@finally {
|
||||
dispatch_group_leave( recoverPasswordGroup );
|
||||
}
|
||||
} cancelTitle:@"Don't Migrate" otherTitles:@"Migrate", nil];
|
||||
dispatch_group_wait( recoverPasswordGroup, DISPATCH_TIME_FOREVER );
|
||||
setResult( nil );
|
||||
else
|
||||
setResult( [alert_ textFieldAtIndex:0].text );
|
||||
} cancelTitle:@"Don't Migrate" otherTitles:@"Migrate", nil];
|
||||
} );
|
||||
#endif
|
||||
if (!masterPassword)
|
||||
// Don't Migrate
|
||||
|
||||
@@ -28,12 +28,12 @@ typedef NS_ENUM( NSUInteger, MPKeyOrigin ) {
|
||||
|
||||
@interface MPKey : NSObject
|
||||
|
||||
@property(nonatomic, readonly) NSString *fullName;
|
||||
@property(nonatomic, readonly) MPKeyOrigin origin;
|
||||
@property(nonatomic, readonly, copy) NSString *fullName;
|
||||
|
||||
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword;
|
||||
- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData
|
||||
forAlgorithm:(id<MPAlgorithm>)algorithm keyOrigin:(MPKeyOrigin)origin;
|
||||
- (instancetype)initForFullName:(NSString *)fullName withKeyResolver:(NSData *( ^ )(id<MPAlgorithm>))keyResolver
|
||||
keyOrigin:(MPKeyOrigin)origin;
|
||||
|
||||
- (NSData *)keyIDForAlgorithm:(id<MPAlgorithm>)algorithm;
|
||||
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm;
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
|
||||
@interface MPKey()
|
||||
|
||||
@property(nonatomic) NSString *fullName;
|
||||
@property(nonatomic) MPKeyOrigin origin;
|
||||
@property(nonatomic) NSString *masterPassword;
|
||||
@property(nonatomic, copy) NSString *fullName;
|
||||
@property(nonatomic, copy) NSData *( ^keyResolver )(id<MPAlgorithm>);
|
||||
|
||||
@end
|
||||
|
||||
@@ -31,25 +31,22 @@
|
||||
|
||||
- (instancetype)initForFullName:(NSString *)fullName withMasterPassword:(NSString *)masterPassword {
|
||||
|
||||
return [self initForFullName:fullName withKeyResolver:^NSData *(id<MPAlgorithm> algorithm) {
|
||||
return [algorithm keyDataForFullName:self.fullName withMasterPassword:masterPassword];
|
||||
} keyOrigin:MPKeyOriginMasterPassword];
|
||||
}
|
||||
|
||||
- (instancetype)initForFullName:(NSString *)fullName withKeyResolver:(NSData *( ^ )(id<MPAlgorithm>))keyResolver
|
||||
keyOrigin:(MPKeyOrigin)origin {
|
||||
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
_keyCache = [NSCache new];
|
||||
self.fullName = fullName;
|
||||
self.origin = MPKeyOriginMasterPassword;
|
||||
self.masterPassword = masterPassword;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initForFullName:(NSString *)fullName withKeyData:(NSData *)keyData
|
||||
forAlgorithm:(id<MPAlgorithm>)algorithm keyOrigin:(MPKeyOrigin)origin {
|
||||
|
||||
if (!(self = [self initForFullName:fullName withMasterPassword:nil]))
|
||||
return nil;
|
||||
|
||||
self.origin = origin;
|
||||
[_keyCache setObject:keyData forKey:algorithm];
|
||||
self.fullName = fullName;
|
||||
self.keyResolver = keyResolver;
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -61,15 +58,17 @@
|
||||
|
||||
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm {
|
||||
|
||||
NSData *keyData = [_keyCache objectForKey:algorithm];
|
||||
if (keyData)
|
||||
@synchronized (self) {
|
||||
NSData *keyData = [_keyCache objectForKey:algorithm];
|
||||
if (keyData)
|
||||
return keyData;
|
||||
|
||||
keyData = self.keyResolver( algorithm );
|
||||
if (keyData)
|
||||
[_keyCache setObject:keyData forKey:algorithm];
|
||||
|
||||
return keyData;
|
||||
|
||||
keyData = [algorithm keyDataForFullName:self.fullName withMasterPassword:self.masterPassword];
|
||||
if (keyData)
|
||||
[_keyCache setObject:keyData forKey:algorithm];
|
||||
|
||||
return keyData;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSData *)keyDataForAlgorithm:(id<MPAlgorithm>)algorithm trimmedLength:(NSUInteger)subKeyLength {
|
||||
@@ -80,7 +79,7 @@
|
||||
|
||||
- (BOOL)isEqualToKey:(MPKey *)key {
|
||||
|
||||
return [self.fullName isEqualToString:key.fullName] && [self.masterPassword isEqualToString:self.masterPassword];
|
||||
return [[self keyIDForAlgorithm:MPAlgorithmDefault] isEqualToData:[key keyIDForAlgorithm:MPAlgorithmDefault]];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
|
||||
[super windowDidLoad];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window
|
||||
queue:nil usingBlock:^(NSNotification *note) {
|
||||
[MPMacAppDelegate get].initialWindowController = nil;
|
||||
}];
|
||||
PearlAddNotificationObserver( NSWindowWillCloseNotification, self.window, nil, ^(id host, NSNotification *note) {
|
||||
PearlRemoveNotificationObserversFrom( host );
|
||||
[MPMacAppDelegate get].initialWindowController = nil;
|
||||
} );
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
@@ -41,40 +41,38 @@
|
||||
[self replaceFonts:self.window.contentView];
|
||||
prof_rewind( @"replaceFonts" );
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
PearlAddNotificationObserver( NSWindowDidBecomeKeyNotification, self.window, [NSOperationQueue mainQueue],
|
||||
^(id host, NSNotification *note) {
|
||||
prof_new( @"didBecomeKey" );
|
||||
[self.window makeKeyAndOrderFront:nil];
|
||||
prof_rewind( @"fadeIn" );
|
||||
[self updateUser];
|
||||
prof_finish( @"updateUser" );
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
} );
|
||||
PearlAddNotificationObserver( NSWindowWillCloseNotification, self.window, [NSOperationQueue mainQueue],
|
||||
^(id host, NSNotification *note) {
|
||||
PearlRemoveNotificationObservers();
|
||||
|
||||
NSWindow *sheet = [self.window attachedSheet];
|
||||
if (sheet)
|
||||
[self.window endSheet:sheet];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillResignActiveNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
} );
|
||||
PearlAddNotificationObserver( NSApplicationWillResignActiveNotification, nil, [NSOperationQueue mainQueue],
|
||||
^(id host, NSNotification *note) {
|
||||
[self.window close];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedInNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
[self updateUser];
|
||||
}];
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||
[self updateUser];
|
||||
}];
|
||||
} );
|
||||
PearlAddNotificationObserver( MPSignedInNotification, nil, [NSOperationQueue mainQueue], ^(id host, NSNotification *note) {
|
||||
[self updateUser];
|
||||
} );
|
||||
PearlAddNotificationObserver( MPSignedOutNotification, nil, [NSOperationQueue mainQueue], ^(id host, NSNotification *note) {
|
||||
[self updateUser];
|
||||
} );
|
||||
[self observeKeyPath:@"sitesController.selection" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
|
||||
prof_new( @"sitesController.selection" );
|
||||
[_self updateSelection];
|
||||
prof_finish( @"updateSelection" );
|
||||
}];
|
||||
prof_rewind( @"observers" );
|
||||
|
||||
NSSearchFieldCell *siteFieldCell = (NSSearchFieldCell *)self.siteField.cell;
|
||||
NSSearchFieldCell *siteFieldCell = self.siteField.cell;
|
||||
siteFieldCell.searchButtonCell = nil;
|
||||
siteFieldCell.cancelButtonCell = nil;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
@property(weak, nonatomic) IBOutlet UISegmentedControl *typeControl;
|
||||
@property(weak, nonatomic) IBOutlet UILabel *counterLabel;
|
||||
@property(weak, nonatomic) IBOutlet UIActivityIndicatorView *activity;
|
||||
@property(weak, nonatomic) IBOutlet UILabel *passwordLabel;
|
||||
@property(weak, nonatomic) IBOutlet UIButton *passwordButton;
|
||||
@property(weak, nonatomic) IBOutlet UIView *tipContainer;
|
||||
|
||||
- (IBAction)controlChanged:(UIControl *)control;
|
||||
|
||||
@@ -81,21 +81,19 @@
|
||||
[self updatePassword];
|
||||
}
|
||||
|
||||
- (IBAction)copyPassword:(UITapGestureRecognizer *)recognizer {
|
||||
- (IBAction)copyPassword:(id)sender {
|
||||
|
||||
if (recognizer.state == UIGestureRecognizerStateEnded) {
|
||||
NSString *sitePassword = self.passwordLabel.text;
|
||||
if ([sitePassword length]) {
|
||||
[UIPasteboard generalPasteboard].string = sitePassword;
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.tipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
PearlMainQueueAfter( 3, ^{
|
||||
self.tipContainer.alpha = 0;
|
||||
} );
|
||||
}];
|
||||
}
|
||||
NSString *sitePassword = [self.passwordButton titleForState:UIControlStateNormal];
|
||||
if ([sitePassword length]) {
|
||||
[UIPasteboard generalPasteboard].string = sitePassword;
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
self.tipContainer.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished)
|
||||
PearlMainQueueAfter( 3, ^{
|
||||
self.tipContainer.alpha = 0;
|
||||
} );
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +104,7 @@
|
||||
NSString *fullName = self.fullNameField.text;
|
||||
NSString *masterPassword = self.masterPasswordField.text;
|
||||
|
||||
self.passwordLabel.text = nil;
|
||||
[self.passwordButton setTitle:nil forState:UIControlStateNormal];
|
||||
[self.activity startAnimating];
|
||||
[_emergencyKeyQueue cancelAllOperations];
|
||||
[_emergencyKeyQueue addOperationWithBlock:^{
|
||||
@@ -128,7 +126,7 @@
|
||||
NSUInteger siteCounter = (NSUInteger)self.counterStepper.value;
|
||||
self.counterLabel.text = strf( @"%lu", (unsigned long)siteCounter );
|
||||
|
||||
self.passwordLabel.text = nil;
|
||||
[self.passwordButton setTitle:nil forState:UIControlStateNormal];
|
||||
[self.activity startAnimating];
|
||||
[_emergencyPasswordQueue cancelAllOperations];
|
||||
[_emergencyPasswordQueue addOperationWithBlock:^{
|
||||
@@ -138,7 +136,7 @@
|
||||
|
||||
PearlMainQueue( ^{
|
||||
[self.activity stopAnimating];
|
||||
self.passwordLabel.text = sitePassword;
|
||||
[self.passwordButton setTitle:sitePassword forState:UIControlStateNormal];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
@property(nonatomic, strong) IBOutlet UITextField *passwordField;
|
||||
@property(nonatomic, strong) IBOutlet UIView *loginNameContainer;
|
||||
@property(nonatomic, strong) IBOutlet UITextField *loginNameField;
|
||||
@property(nonatomic, strong) IBOutlet UILabel *loginNameGenerated;
|
||||
@property(nonatomic, strong) IBOutlet UILabel *strengthLabel;
|
||||
@property(nonatomic, strong) IBOutlet UILabel *counterLabel;
|
||||
@property(nonatomic, strong) IBOutlet UIButton *counterButton;
|
||||
@@ -199,7 +200,7 @@
|
||||
atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
|
||||
|
||||
if (textField == self.loginNameField)
|
||||
self.loginNameButton.titleLabel.alpha = [self.loginNameField.text length] || self.loginNameField.enabled? 0: 1;
|
||||
self.loginNameButton.hidden = [self.loginNameField.attributedText length] || self.loginNameField.enabled;
|
||||
}
|
||||
|
||||
- (IBAction)textFieldDidChange:(UITextField *)textField {
|
||||
@@ -224,7 +225,7 @@
|
||||
|
||||
if (textField == self.passwordField || textField == self.loginNameField) {
|
||||
textField.enabled = NO;
|
||||
NSString *text = textField.text;
|
||||
NSString *text = [textField.attributedText string]?: textField.text;
|
||||
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPSiteEntity *site = [self siteInContext:context];
|
||||
@@ -235,10 +236,8 @@
|
||||
if ([site.algorithm savePassword:text toSite:site usingKey:[MPiOSAppDelegate get].key])
|
||||
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Updated" dismissAfter:2];
|
||||
}
|
||||
else if (textField == self.loginNameField &&
|
||||
((site.loginGenerated && ![text length]) ||
|
||||
(!site.loginGenerated && ![text isEqualToString:site.loginName]))) {
|
||||
if (site.loginGenerated || !([site.loginName isEqualToString:text] || (!text && !site.loginName))) {
|
||||
else if (textField == self.loginNameField) {
|
||||
if (![text isEqualToString:[site.algorithm resolveLoginForSite:site usingKey:[MPiOSAppDelegate get].key]]) {
|
||||
site.loginGenerated = NO;
|
||||
site.loginName = text;
|
||||
|
||||
@@ -508,7 +507,6 @@
|
||||
self.answersButton.gone = ![[MPiOSAppDelegate get] isFeatureUnlocked: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"];
|
||||
self.modeButton.alpha = self.transientSite? 0: settingsMode? 0.5f: 0.1f;
|
||||
self.counterLabel.alpha = self.counterButton.alpha = mainSite.type & MPSiteTypeClassGenerated? 0.5f: 0;
|
||||
self.modeButton.selected = settingsMode;
|
||||
@@ -520,13 +518,21 @@
|
||||
[self.passwordField resignFirstResponder];
|
||||
}
|
||||
if ([[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateLogins])
|
||||
[self.loginNameButton setTitle:@"Tap to generate username or use pencil to save one" forState:UIControlStateNormal];
|
||||
[self.loginNameButton setTitle:@"Tap here to ⚙ generate username or the pencil to type one" forState:UIControlStateNormal];
|
||||
else
|
||||
[self.loginNameButton setTitle:@"Tap the pencil to save a username" forState:UIControlStateNormal];
|
||||
[self.loginNameButton setTitle:@"Tap the pencil to type a username" forState:UIControlStateNormal];
|
||||
|
||||
// Site Name
|
||||
[self updateSiteName:mainSite];
|
||||
|
||||
// Site Counter
|
||||
if ([mainSite isKindOfClass:[MPGeneratedSiteEntity class]])
|
||||
self.counterLabel.text = strf( @"%lu", (unsigned long)((MPGeneratedSiteEntity *)mainSite).counter );
|
||||
|
||||
// Site Login Name
|
||||
self.loginNameField.enabled = self.passwordField.enabled = //
|
||||
[self.loginNameField isFirstResponder] || [self.passwordField isFirstResponder];
|
||||
|
||||
// Site Password
|
||||
self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue];
|
||||
self.passwordField.attributedPlaceholder = stra(
|
||||
@@ -534,12 +540,15 @@
|
||||
mainSite.type & MPSiteTypeClassGenerated? strl( @"..." ): @"", @{
|
||||
NSForegroundColorAttributeName: [UIColor whiteColor]
|
||||
} );
|
||||
|
||||
// Calculate Fields
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
MPSiteEntity *site = [self siteInContext:context];
|
||||
MPKey *key = [MPiOSAppDelegate get].key;
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
BOOL loginGenerated = site.loginGenerated;
|
||||
NSString *password = nil, *loginName = [site resolveLoginUsingKey:key];
|
||||
MPSiteType transientType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPSiteTypeGeneratedLong;
|
||||
if (self.transientSite && transientType & MPSiteTypeClassGenerated)
|
||||
@@ -559,13 +568,15 @@
|
||||
BOOL requiresExplicitMigration = site.requiresExplicitMigration;
|
||||
|
||||
PearlMainQueue( ^{
|
||||
self.loginNameField.text = loginName;
|
||||
self.passwordField.text = password;
|
||||
self.strengthLabel.text = timeToCrackString;
|
||||
self.loginNameButton.titleLabel.alpha = [loginName length] || self.loginNameField.enabled? 0: 1;
|
||||
self.loginNameGenerated.hidden = !loginGenerated;
|
||||
self.loginNameField.attributedText =
|
||||
strarm( stra( loginName?: @"", self.siteNameLabel.textAttributes ), NSParagraphStyleAttributeName, nil );
|
||||
self.loginNameButton.hidden = [loginName length] || self.loginNameField.enabled;
|
||||
|
||||
if (![password length]) {
|
||||
self.indicatorView.alpha = 1;
|
||||
self.indicatorView.hidden = NO;
|
||||
[self.indicatorView removeFromSuperview];
|
||||
[self.modeScrollView addSubview:self.indicatorView];
|
||||
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX
|
||||
@@ -575,7 +586,7 @@
|
||||
}];
|
||||
}
|
||||
else if (requiresExplicitMigration) {
|
||||
self.indicatorView.alpha = 1;
|
||||
self.indicatorView.hidden = NO;
|
||||
[self.indicatorView removeFromSuperview];
|
||||
[self.modeScrollView addSubview:self.indicatorView];
|
||||
[self.contentView addConstraintsWithVisualFormat:@"V:[indicator][target]" options:NSLayoutFormatAlignAllCenterX
|
||||
@@ -585,18 +596,10 @@
|
||||
}];
|
||||
}
|
||||
else
|
||||
self.indicatorView.alpha = 0;
|
||||
self.indicatorView.hidden = YES;
|
||||
} );
|
||||
}];
|
||||
|
||||
// Site Counter
|
||||
if ([mainSite isKindOfClass:[MPGeneratedSiteEntity class]])
|
||||
self.counterLabel.text = strf( @"%lu", (unsigned long)((MPGeneratedSiteEntity *)mainSite).counter );
|
||||
|
||||
// Site Login Name
|
||||
self.loginNameField.enabled = self.passwordField.enabled = //
|
||||
[self.loginNameField isFirstResponder] || [self.passwordField isFirstResponder];
|
||||
|
||||
[self.contentView layoutIfNeeded];
|
||||
}];
|
||||
}
|
||||
@@ -616,8 +619,9 @@
|
||||
range:NSMakeRange( s, [self.fuzzyGroups[f] length] )];
|
||||
}
|
||||
|
||||
[attributedSiteName appendAttributedString:stra(
|
||||
strf( @" - %@", self.transientSite? @"Tap to create": [site.algorithm shortNameOfType:site.type] ), @{} )];
|
||||
if (self.transientSite)
|
||||
[attributedSiteName appendAttributedString:stra( @" – Tap to create", @{} )];
|
||||
|
||||
self.siteNameLabel.attributedText = attributedSiteName;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
NSUInteger _transientItem;
|
||||
NSCharacterSet *_siteNameAcceptableCharactersSet;
|
||||
NSArray *_fuzzyGroups;
|
||||
NSMutableArray *_passwordCollectionViewUpdatesBatch;
|
||||
}
|
||||
|
||||
#pragma mark - Life
|
||||
@@ -62,6 +63,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
_backgroundColor = self.passwordCollectionView.backgroundColor;
|
||||
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
|
||||
_transientItem = NSNotFound;
|
||||
_passwordCollectionViewUpdatesBatch = [NSMutableArray arrayWithCapacity:4];
|
||||
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
[self.passwordCollectionView automaticallyAdjustInsetsForKeyboard];
|
||||
@@ -181,10 +183,13 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath
|
||||
forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
|
||||
|
||||
Weakify( self );
|
||||
|
||||
if (controller == _fetchedResultsController) {
|
||||
@try {
|
||||
[self.passwordCollectionView performBatchUpdates:^{
|
||||
[self fetchedItemsDidUpdate];
|
||||
@synchronized (_passwordCollectionViewUpdatesBatch) {
|
||||
[_passwordCollectionViewUpdatesBatch addObject:[^{
|
||||
Strongify( self );
|
||||
|
||||
switch (type) {
|
||||
case NSFetchedResultsChangeInsert:
|
||||
[self.passwordCollectionView insertItemsAtIndexPaths:@[ newIndexPath ]];
|
||||
@@ -193,18 +198,35 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
[self.passwordCollectionView deleteItemsAtIndexPaths:@[ indexPath ]];
|
||||
break;
|
||||
case NSFetchedResultsChangeMove:
|
||||
[self.passwordCollectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath];
|
||||
if (![indexPath isEqual:newIndexPath])
|
||||
[self.passwordCollectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath];
|
||||
break;
|
||||
case NSFetchedResultsChangeUpdate:
|
||||
[self.passwordCollectionView reloadItemsAtIndexPaths:@[ indexPath ]];
|
||||
break;
|
||||
}
|
||||
} completion:nil];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
wrn( @"While updating password cells: %@", [exception fullDescription] );
|
||||
[self.passwordCollectionView reloadData];
|
||||
} copy]];
|
||||
}
|
||||
|
||||
[controller.managedObjectContext performBlock:^{
|
||||
PearlMainQueueOperation( ^{
|
||||
@try {
|
||||
[self.passwordCollectionView performBatchUpdates:^{
|
||||
[self updateTransientItem];
|
||||
|
||||
@synchronized (_passwordCollectionViewUpdatesBatch) {
|
||||
for (VoidBlock block in _passwordCollectionViewUpdatesBatch)
|
||||
block();
|
||||
[_passwordCollectionViewUpdatesBatch removeAllObjects];
|
||||
}
|
||||
} completion:nil];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
wrn( @"While updating password cells: %@", [exception fullDescription] );
|
||||
[self.passwordCollectionView reloadData];
|
||||
}
|
||||
} );
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,25 +312,24 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)fetchedItemsDidUpdate {
|
||||
- (void)updateTransientItem {
|
||||
|
||||
NSString *query = self.query;
|
||||
_showTransientItem = [query length] > 0;
|
||||
NSUInteger objects = ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[0]).numberOfObjects;
|
||||
if (_showTransientItem && objects == 1 &&
|
||||
[[[self.fetchedResultsController.fetchedObjects firstObject] name] isEqualToString:query])
|
||||
_showTransientItem = NO;
|
||||
if ([self.passwordCollectionView numberOfSections] > 0) {
|
||||
if (!_showTransientItem && _transientItem != NSNotFound)
|
||||
[self.passwordCollectionView deleteItemsAtIndexPaths:
|
||||
@[ [NSIndexPath indexPathForItem:_transientItem inSection:0] ]];
|
||||
else if (_showTransientItem && _transientItem == NSNotFound)
|
||||
[self.passwordCollectionView insertItemsAtIndexPaths:
|
||||
@[ [NSIndexPath indexPathForItem:objects inSection:0] ]];
|
||||
else if (_transientItem != NSNotFound)
|
||||
[self.passwordCollectionView reloadItemsAtIndexPaths:
|
||||
@[ [NSIndexPath indexPathForItem:_transientItem inSection:0] ]];
|
||||
_showTransientItem = [query length] > 0 && ![[[self.fetchedResultsController.sections[0] objects] filteredArrayUsingPredicate:
|
||||
[NSPredicate predicateWithBlock:^BOOL(MPSiteEntity *site, NSDictionary<NSString *, id> *bindings) {
|
||||
return [site.name isEqualToString:query];
|
||||
}]] count];
|
||||
if (!_showTransientItem && _transientItem != NSNotFound)
|
||||
[self.passwordCollectionView deleteItemsAtIndexPaths:
|
||||
@[ [NSIndexPath indexPathForItem:_transientItem inSection:0] ]];
|
||||
else if (_showTransientItem && _transientItem == NSNotFound) {
|
||||
NSUInteger objects = [self.fetchedResultsController.sections[0] numberOfObjects];
|
||||
[self.passwordCollectionView insertItemsAtIndexPaths:
|
||||
@[ [NSIndexPath indexPathForItem:objects inSection:0] ]];
|
||||
}
|
||||
else if (_transientItem != NSNotFound)
|
||||
[self.passwordCollectionView reloadItemsAtIndexPaths:
|
||||
@[ [NSIndexPath indexPathForItem:_transientItem inSection:0] ]];
|
||||
}
|
||||
|
||||
- (void)registerObservers {
|
||||
@@ -361,6 +382,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
if (mainContext)
|
||||
PearlAddNotificationObserver( NSManagedObjectContextDidSaveNotification, mainContext, nil,
|
||||
^(MPPasswordsViewController *self, NSNotification *note) {
|
||||
// TODO: either move this into the app delegate or remove the duplicate signOutAnimated: call from the app delegate.
|
||||
if (![[MPiOSAppDelegate get] activeUserInContext:note.object])
|
||||
[[MPiOSAppDelegate get] signOutAnimated:YES];
|
||||
} );
|
||||
@@ -421,7 +443,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
|
||||
PearlMainQueue( ^{
|
||||
@try {
|
||||
[self.passwordCollectionView performBatchUpdates:^{
|
||||
[self fetchedItemsDidUpdate];
|
||||
[self updateTransientItem];
|
||||
|
||||
NSInteger fromSections = self.passwordCollectionView.numberOfSections;
|
||||
NSInteger toSections = [self numberOfSectionsInCollectionView:self.passwordCollectionView];
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
@implementation MPPopdownSegue {
|
||||
}
|
||||
|
||||
static char UnwindingObserverKey;
|
||||
|
||||
- (void)perform {
|
||||
|
||||
MPPasswordsViewController *passwordsVC;
|
||||
@@ -39,19 +37,20 @@ static char UnwindingObserverKey;
|
||||
[passwordsVC.popdownContainer addConstraintsWithVisualFormats:@[ @"H:|[popdownView]|", @"V:|[popdownView]|" ] options:0
|
||||
metrics:nil views:NSDictionaryOfVariableBindings( popdownView )];
|
||||
|
||||
[UIView animateWithDuration:0.3f animations:^{
|
||||
[[passwordsVC.popdownToTopConstraint updatePriority:1] layoutIfNeeded];
|
||||
} completion:^(BOOL finished) {
|
||||
[popdownVC didMoveToParentViewController:passwordsVC];
|
||||
[passwordsVC.popdownToTopConstraint layoutIfNeeded];
|
||||
|
||||
id<NSObject> observer = [[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil
|
||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||
^(NSNotification *note) {
|
||||
[[[MPPopdownSegue alloc] initWithIdentifier:@"unwind-popdown" source:popdownVC
|
||||
destination:passwordsVC] perform];
|
||||
}];
|
||||
objc_setAssociatedObject( popdownVC, &UnwindingObserverKey, observer, OBJC_ASSOCIATION_RETAIN );
|
||||
}];
|
||||
[UIView animateWithDuration:0.6f delay:0 usingSpringWithDamping:0.75f initialSpringVelocity:1
|
||||
options:UIViewAnimationOptionCurveEaseOut animations:^{
|
||||
[[passwordsVC.popdownToTopConstraint updatePriority:1] layoutIfNeeded];
|
||||
} completion:^(BOOL finished) {
|
||||
[popdownVC didMoveToParentViewController:passwordsVC];
|
||||
|
||||
PearlAddNotificationObserverTo( popdownVC, MPSignedOutNotification, nil, [NSOperationQueue mainQueue],
|
||||
^(id host, NSNotification *note) {
|
||||
[[[MPPopdownSegue alloc] initWithIdentifier:@"unwind-popdown" source:popdownVC destination:passwordsVC]
|
||||
perform];
|
||||
} );
|
||||
}];
|
||||
}
|
||||
else {
|
||||
popdownVC = self.sourceViewController;
|
||||
@@ -59,16 +58,16 @@ static char UnwindingObserverKey;
|
||||
passwordsVC = (id)passwordsVC.parentViewController);
|
||||
NSAssert( passwordsVC, @"Couldn't find passwords VC to pop back to." );
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:objc_getAssociatedObject( popdownVC, &UnwindingObserverKey )];
|
||||
objc_setAssociatedObject( popdownVC, &UnwindingObserverKey, nil, OBJC_ASSOCIATION_RETAIN );
|
||||
PearlRemoveNotificationObserversFrom( popdownVC );
|
||||
|
||||
[popdownVC willMoveToParentViewController:nil];
|
||||
[UIView animateWithDuration:0.3f delay:0 options:UIViewAnimationOptionOverrideInheritedDuration animations:^{
|
||||
[[passwordsVC.popdownToTopConstraint updatePriority:UILayoutPriorityDefaultHigh] layoutIfNeeded];
|
||||
} completion:^(BOOL finished) {
|
||||
[popdownVC.view removeFromSuperview];
|
||||
[popdownVC removeFromParentViewController];
|
||||
}];
|
||||
[UIView animateWithDuration:0.4f delay:0 options:UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionOverrideInheritedDuration
|
||||
animations:^{
|
||||
[[passwordsVC.popdownToTopConstraint updatePriority:UILayoutPriorityDefaultHigh] layoutIfNeeded];
|
||||
} completion:^(BOOL finished) {
|
||||
[popdownVC.view removeFromSuperview];
|
||||
[popdownVC removeFromParentViewController];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,10 @@
|
||||
@property(weak, nonatomic) IBOutlet UITableViewCell *exportCell;
|
||||
@property(weak, nonatomic) IBOutlet UITableViewCell *checkInconsistencies;
|
||||
@property(weak, nonatomic) IBOutlet UIImageView *avatarImage;
|
||||
@property(weak, nonatomic) IBOutlet UISegmentedControl *generatedTypeControl;
|
||||
@property(weak, nonatomic) IBOutlet UISegmentedControl *generated1TypeControl;
|
||||
@property(weak, nonatomic) IBOutlet UISegmentedControl *generated2TypeControl;
|
||||
@property(weak, nonatomic) IBOutlet UISegmentedControl *storedTypeControl;
|
||||
@property(weak, nonatomic) IBOutlet UILabel *passwordTypeExample;
|
||||
|
||||
- (IBAction)previousAvatar:(id)sender;
|
||||
- (IBAction)nextAvatar:(id)sender;
|
||||
|
||||
@@ -55,12 +55,24 @@
|
||||
- (void)reload {
|
||||
|
||||
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForMainThread];
|
||||
self.generatedTypeControl.selectedSegmentIndex = [self generatedSegmentIndexForType:activeUser.defaultType];
|
||||
self.storedTypeControl.selectedSegmentIndex = [self storedSegmentIndexForType:activeUser.defaultType];
|
||||
self.avatarImage.image = [UIImage imageNamed:strf( @"avatar-%lu", (unsigned long)activeUser.avatar )];
|
||||
self.savePasswordSwitch.on = activeUser.saveKey;
|
||||
self.touchIDSwitch.on = activeUser.touchID;
|
||||
self.touchIDSwitch.enabled = self.savePasswordSwitch.on && [[MPiOSAppDelegate get] isFeatureUnlocked:MPProductTouchID];
|
||||
|
||||
MPSiteType defaultType = activeUser.defaultType;
|
||||
self.generated1TypeControl.selectedSegmentIndex = [self generated1SegmentIndexForType:defaultType];
|
||||
self.generated2TypeControl.selectedSegmentIndex = [self generated2SegmentIndexForType:defaultType];
|
||||
self.storedTypeControl.selectedSegmentIndex = [self storedSegmentIndexForType:defaultType];
|
||||
PearlNotMainQueue( ^{
|
||||
NSString *examplePassword = nil;
|
||||
if (defaultType & MPSiteTypeClassGenerated)
|
||||
examplePassword = [MPAlgorithmDefault generatePasswordForSiteNamed:@"test" ofType:defaultType
|
||||
withCounter:1 usingKey:[MPiOSAppDelegate get].key];
|
||||
PearlMainQueue( ^{
|
||||
self.passwordTypeExample.text = [examplePassword length]? [NSString stringWithFormat:@"eg. %@", examplePassword]: nil;
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
#pragma mark - UITableViewDelegate
|
||||
@@ -88,14 +100,18 @@
|
||||
[self dismissPopup];
|
||||
[[MPiOSAppDelegate get] signOutAnimated:YES];
|
||||
}
|
||||
|
||||
if (cell == self.feedbackCell)
|
||||
[[MPiOSAppDelegate get] showFeedbackWithLogs:YES forVC:self];
|
||||
|
||||
if (cell == self.exportCell)
|
||||
[[MPiOSAppDelegate get] showExportForVC:self];
|
||||
|
||||
if (cell == self.showHelpCell) {
|
||||
MPPasswordsViewController *passwordsVC = [self dismissPopup];
|
||||
[passwordsVC performSegueWithIdentifier:@"guide" sender:self];
|
||||
}
|
||||
|
||||
if (cell == self.checkInconsistencies)
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
if ([[MPiOSAppDelegate get] findAndFixInconsistenciesSaveInContext:context] == MPFixableResultNoProblems)
|
||||
@@ -140,11 +156,13 @@
|
||||
} );
|
||||
}];
|
||||
|
||||
if (sender == self.generatedTypeControl || sender == self.storedTypeControl) {
|
||||
if (sender == self.generatedTypeControl)
|
||||
if (sender == self.generated1TypeControl || sender == self.generated2TypeControl || sender == self.storedTypeControl) {
|
||||
if (sender != self.generated1TypeControl)
|
||||
self.generated1TypeControl.selectedSegmentIndex = -1;
|
||||
if (sender != self.generated2TypeControl)
|
||||
self.generated2TypeControl.selectedSegmentIndex = -1;
|
||||
if (sender != self.storedTypeControl)
|
||||
self.storedTypeControl.selectedSegmentIndex = -1;
|
||||
else if (sender == self.storedTypeControl)
|
||||
self.generatedTypeControl.selectedSegmentIndex = -1;
|
||||
|
||||
MPSiteType defaultType = [self typeForSelectedSegment];
|
||||
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||
@@ -226,10 +244,17 @@
|
||||
|
||||
- (MPSiteType)typeForSelectedSegment {
|
||||
|
||||
NSInteger selectedGeneratedIndex = self.generatedTypeControl.selectedSegmentIndex;
|
||||
NSInteger selectedGenerated1Index = self.generated1TypeControl.selectedSegmentIndex;
|
||||
NSInteger selectedGenerated2Index = self.generated2TypeControl.selectedSegmentIndex;
|
||||
NSInteger selectedStoredIndex = self.storedTypeControl.selectedSegmentIndex;
|
||||
|
||||
switch (selectedGeneratedIndex) {
|
||||
switch (selectedGenerated1Index) {
|
||||
case 0:
|
||||
return MPSiteTypeGeneratedPhrase;
|
||||
case 1:
|
||||
return MPSiteTypeGeneratedName;
|
||||
default:
|
||||
switch (selectedGenerated2Index) {
|
||||
case 0:
|
||||
return MPSiteTypeGeneratedMaximum;
|
||||
case 1:
|
||||
@@ -250,13 +275,26 @@
|
||||
case 1:
|
||||
return MPSiteTypeStoredDevicePrivate;
|
||||
default:
|
||||
Throw( @"unsupported selected type index: generated=%ld, stored=%ld", (long)selectedGeneratedIndex,
|
||||
(long)selectedStoredIndex );
|
||||
Throw( @"unsupported selected type index: generated1=%ld, generated2=%ld, stored=%ld",
|
||||
(long)selectedGenerated1Index, (long)selectedGenerated2Index, (long)selectedStoredIndex );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)generatedSegmentIndexForType:(MPSiteType)type {
|
||||
- (NSInteger)generated1SegmentIndexForType:(MPSiteType)type {
|
||||
|
||||
switch (type) {
|
||||
case MPSiteTypeGeneratedPhrase:
|
||||
return 0;
|
||||
case MPSiteTypeGeneratedName:
|
||||
return 1;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)generated2SegmentIndexForType:(MPSiteType)type {
|
||||
|
||||
switch (type) {
|
||||
case MPSiteTypeGeneratedMaximum:
|
||||
|
||||
@@ -89,11 +89,11 @@
|
||||
if ([selectedSite isKindOfClass:[MPGeneratedSiteEntity class]])
|
||||
counter = ((MPGeneratedSiteEntity *)selectedSite).counter;
|
||||
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ), ^{
|
||||
PearlNotMainQueue( ^{
|
||||
NSString *typeContent = [MPAlgorithmDefault generatePasswordForSiteNamed:name ofType:cellType
|
||||
withCounter:counter usingKey:[MPiOSAppDelegate get].key];
|
||||
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
PearlMainQueue( ^{
|
||||
[(UITextField *)[[tableView cellForRowAtIndexPath:indexPath] viewWithTag:2] setText:typeContent];
|
||||
} );
|
||||
} );
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#import "MPiOSAppDelegate.h"
|
||||
#import "MPAppDelegate_Key.h"
|
||||
#import "MPAppDelegate_Store.h"
|
||||
#import "IASKSettingsReader.h"
|
||||
#import "MPStoreViewController.h"
|
||||
|
||||
@interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate>
|
||||
@@ -164,26 +163,24 @@
|
||||
return NO;
|
||||
|
||||
// Arbitrary URL to mpsites data.
|
||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||
NSError *error;
|
||||
NSURLResponse *response;
|
||||
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
|
||||
returningResponse:&response error:&error];
|
||||
if (error)
|
||||
err( @"While reading imported sites from %@: %@", url, [error fullDescription] );
|
||||
if (!importedSitesData) {
|
||||
[PearlAlert showError:strf( @"Master Password couldn't read the import sites.\n\n%@", [error localizedDescription]?: error )];
|
||||
return;
|
||||
}
|
||||
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
|
||||
^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
|
||||
if (error)
|
||||
err( @"While reading imported sites from %@: %@", url, [error fullDescription] );
|
||||
if (!importedSitesData) {
|
||||
[PearlAlert showError:strf( @"Master Password couldn't read the import sites.\n\n%@",
|
||||
[error localizedDescription]?: error )];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
|
||||
if (!importedSitesString) {
|
||||
[PearlAlert showError:@"Master Password couldn't understand the import file."];
|
||||
return;
|
||||
}
|
||||
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
|
||||
if (!importedSitesString) {
|
||||
[PearlAlert showError:@"Master Password couldn't understand the import file."];
|
||||
return;
|
||||
}
|
||||
|
||||
[self importSites:importedSitesString];
|
||||
} );
|
||||
[self importSites:importedSitesString];
|
||||
}] resume];
|
||||
|
||||
return YES;
|
||||
}
|
||||
@@ -199,57 +196,33 @@
|
||||
|
||||
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:@"Importing"];
|
||||
MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
|
||||
__block NSString *masterPassword = nil;
|
||||
|
||||
dispatch_group_t importPasswordGroup = dispatch_group_create();
|
||||
dispatch_group_enter( importPasswordGroup );
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
return PearlAwait( ^(void (^setResult)(id)) {
|
||||
[PearlAlert showAlertWithTitle:@"Import File's Master Password"
|
||||
message:strf( @"%@'s export was done using a different master password.\n"
|
||||
@"Enter that master password to unlock the exported data.", userName )
|
||||
viewStyle:UIAlertViewStyleSecureTextInput
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
@try {
|
||||
if (buttonIndex_ == [alert_ cancelButtonIndex])
|
||||
return;
|
||||
|
||||
masterPassword = [alert_ textFieldAtIndex:0].text;
|
||||
}
|
||||
@finally {
|
||||
dispatch_group_leave( importPasswordGroup );
|
||||
}
|
||||
if (buttonIndex_ == [alert_ cancelButtonIndex])
|
||||
setResult( nil );
|
||||
else
|
||||
setResult( [alert_ textFieldAtIndex:0].text );
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock Import", nil];
|
||||
} );
|
||||
dispatch_group_wait( importPasswordGroup, DISPATCH_TIME_FOREVER );
|
||||
|
||||
return masterPassword;
|
||||
} askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) {
|
||||
__block NSString *masterPassword = nil;
|
||||
|
||||
dispatch_group_t userPasswordGroup = dispatch_group_create();
|
||||
dispatch_group_enter( userPasswordGroup );
|
||||
dispatch_async( dispatch_get_main_queue(), ^{
|
||||
return PearlAwait( (id)^(void (^setResult)(id)) {
|
||||
[PearlAlert showAlertWithTitle:strf( @"Master Password for\n%@", userName )
|
||||
message:strf( @"Imports %lu sites, overwriting %lu.",
|
||||
(unsigned long)importCount, (unsigned long)deleteCount )
|
||||
viewStyle:UIAlertViewStyleSecureTextInput
|
||||
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
|
||||
@try {
|
||||
if (buttonIndex_ == [alert_ cancelButtonIndex])
|
||||
return;
|
||||
|
||||
masterPassword = [alert_ textFieldAtIndex:0].text;
|
||||
}
|
||||
@finally {
|
||||
dispatch_group_leave( userPasswordGroup );
|
||||
}
|
||||
if (buttonIndex_ == [alert_ cancelButtonIndex])
|
||||
setResult( nil );
|
||||
else
|
||||
setResult( [alert_ textFieldAtIndex:0].text );
|
||||
}
|
||||
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil];
|
||||
} );
|
||||
dispatch_group_wait( userPasswordGroup, DISPATCH_TIME_FOREVER );
|
||||
|
||||
return masterPassword;
|
||||
}];
|
||||
|
||||
switch (result) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Reference in New Issue
Block a user