2
0

Fixed critical issue with storing passwords & inconsistency recovery + dictation support.

[FIXED]     Fixed an issue that caused stored passwords to be saved without encryption.
[ADDED]     Logic to check for any data inconsistencies and fix them.
[ADDED]     Support for using dictation in site search box.
This commit is contained in:
Maarten Billemont 2014-04-26 14:03:44 -04:00
parent fc82790b8c
commit 9fee4a2bbe
30 changed files with 624 additions and 312 deletions

View File

@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>Crashlytics</string>
<key>CFBundleIdentifier</key>
<string>com.crashlytics.sdk.mac</string>
<string>com.crashlytics.ios</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
@ -15,16 +15,16 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.1.2</string>
<string>2.1.7</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>macosx</string>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>9</string>
<string>26</string>
<key>DTPlatformName</key>
<string>macosx</string>
<string>iphoneos</string>
<key>MinimumOSVersion</key>
<string>10.6</string>
<string>4.0</string>
</dict>
</plist>

Binary file not shown.

View File

@ -37,6 +37,8 @@
- (NSString *)shortNameOfType:(MPElementType)type;
- (NSString *)classNameOfType:(MPElementType)type;
- (Class)classOfType:(MPElementType)type;
- (NSArray *)allTypes;
- (NSArray *)allTypesStartingWith:(MPElementType)startingType;
- (MPElementType)nextType:(MPElementType)type;
- (MPElementType)previousType:(MPElementType)type;

View File

@ -96,7 +96,8 @@
nil] N:MP_N r:MP_r p:MP_p];
MPKey *key = [self keyFromKeyData:keyData];
trc(@"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", userName, password, [key.keyID encodeHex], -[start timeIntervalSinceNow]);
trc( @"User: %@, password: %@ derives to key ID: %@ (took %0.2fs)", userName, password, [key.keyID encodeHex],
-[start timeIntervalSinceNow] );
return key;
}
@ -218,10 +219,23 @@
Throw( @"Type not supported: %lu", (long)type );
}
- (MPElementType)nextType:(MPElementType)type {
- (NSArray *)allTypes {
if (!type)
Throw(@"No type given.");
return [self allTypesStartingWith:MPElementTypeGeneratedMaximum];
}
- (NSArray *)allTypesStartingWith:(MPElementType)startingType {
NSMutableArray *allTypes = [[NSMutableArray alloc] initWithCapacity:8];
MPElementType currentType = startingType;
do {
[allTypes addObject:@(currentType)];
} while ((currentType = [self nextType:currentType]) != startingType);
return allTypes;
}
- (MPElementType)nextType:(MPElementType)type {
switch (type) {
case MPElementTypeGeneratedMaximum:
@ -240,9 +254,9 @@
return MPElementTypeGeneratedPIN;
case MPElementTypeStoredDevicePrivate:
return MPElementTypeStoredPersonal;
default:
return MPElementTypeGeneratedLong;
}
Throw(@"Type not supported: %lu", (long)type);
}
- (MPElementType)previousType:(MPElementType)type {
@ -321,7 +335,8 @@
case MPElementTypeStoredPersonal: {
NSAssert( [element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type,
[element class] );
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
@ -330,7 +345,8 @@
}
case MPElementTypeStoredDevicePrivate: {
NSAssert( [element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type,
[element class] );
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
@ -375,7 +391,8 @@
case MPElementTypeGeneratedShort:
case MPElementTypeGeneratedPIN: {
NSAssert( [element isKindOfClass:[MPElementGeneratedEntity class]],
@"Element with generated type %lu is not an MPElementGeneratedEntity, but a %@.", (long)element.type, [element class]);
@"Element with generated type %lu is not an MPElementGeneratedEntity, but a %@.", (long)element.type,
[element class] );
NSString *name = element.name;
MPElementType type = element.type;
@ -397,7 +414,8 @@
case MPElementTypeStoredPersonal: {
NSAssert( [element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type,
[element class] );
NSData *encryptedContent = ((MPElementStoredEntity *)element).contentObject;
@ -409,7 +427,8 @@
}
case MPElementTypeStoredDevicePrivate: {
NSAssert( [element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type,
[element class] );
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:elementQuery];
@ -438,7 +457,8 @@
case MPElementTypeStoredPersonal: {
NSAssert( [element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type,
[element class] );
if ([importKey.keyID isEqualToData:elementKey.keyID])
((MPElementStoredEntity *)element).contentObject = [protectedContent decodeBase64];
@ -496,7 +516,8 @@
case MPElementTypeStoredPersonal: {
NSAssert( [element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type,
[element class] );
result = [((MPElementStoredEntity *)element).contentObject encodeBase64];
break;
}

View File

@ -154,6 +154,12 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
[moc saveToStore];
self.activeUser = user;
// Perform a data sanity check now that we're logged in as the user to allow fixes that require the user's key.
if ([[MPConfig get].checkInconsistency boolValue])
[MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
[self findAndFixInconsistenciesSaveInContext:context];
}];
[[NSNotificationCenter defaultCenter] postNotificationName:MPSignedInNotification object:self];
MPCheckpoint( MPCheckpointSignedIn, nil );
@ -168,7 +174,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
MPKey *recoverKey = newKey;
#ifdef PEARL_UIKIT
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:PearlString( @"Migrating %ld sites...", (long)[user.elements count] )];
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:PearlString( @"Migrating %ld sites...",
(long)[user.elements count] )];
#endif
for (MPElementEntity *element in user.elements) {

View File

@ -9,6 +9,7 @@
#import "MPAppDelegate_Shared.h"
#import "UbiquityStoreManager.h"
#import "MPFixable.h"
typedef enum {
MPImportResultSuccess,
@ -27,6 +28,7 @@ typedef enum {
+ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock;
- (UbiquityStoreManager *)storeManager;
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
/** @param completion The block to execute after adding the element, executed from the main thread with the new element in the main MOC. */
- (void)addElementNamed:(NSString *)siteName completion:(void (^)(MPElementEntity *element))completion;

View File

@ -32,10 +32,12 @@ typedef NS_ENUM(NSInteger, MPMigrationLevelCloudStore) {
};
@implementation MPAppDelegate_Shared(Store)
PearlAssociatedObjectProperty(id, SaveObserver, saveObserver);
PearlAssociatedObjectProperty(NSManagedObjectContext*, PrivateManagedObjectContext, privateManagedObjectContext);
PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext, mainManagedObjectContext);
PearlAssociatedObjectProperty( id, SaveObserver, saveObserver );
PearlAssociatedObjectProperty( NSManagedObjectContext*, PrivateManagedObjectContext, privateManagedObjectContext );
PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext, mainManagedObjectContext );
#pragma mark - Core Data setup
@ -151,6 +153,41 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
return storeManager;
}
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context {
NSError *error = nil;
NSFetchRequest *fetchRequest = [NSFetchRequest new];
fetchRequest.fetchBatchSize = 50;
MPFixableResult result = MPFixableResultNoProblems;
for (NSEntityDescription *entity in [context.persistentStoreCoordinator.managedObjectModel entities])
if (class_conformsToProtocol( NSClassFromString( entity.managedObjectClassName ), @protocol(MPFixable) )) {
fetchRequest.entity = entity;
NSArray *objects = [context executeFetchRequest:fetchRequest error:&error];
if (!objects) {
err( @"Failed to fetch %@ objects: %@", entity, error );
continue;
}
for (NSManagedObject<MPFixable> *object in objects)
result = MPApplyFix( result, ^MPFixableResult {
return [object findAndFixInconsistenciesInContext:context];
} );
}
if (result == MPFixableResultNoProblems)
inf( @"Sanity check found no problems in store." );
else {
[context saveToStore];
[[NSNotificationCenter defaultCenter] postNotificationName:MPFoundInconsistenciesNotification object:nil userInfo:@{
MPInconsistenciesFixResultUserKey : @(result)
}];
}
return result;
}
- (void)migrateStoreForManager:(UbiquityStoreManager *)manager isCloud:(BOOL)isCloudStore {
[self migrateLocalStore];
@ -377,6 +414,12 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
self.privateManagedObjectContext = privateManagedObjectContext;
self.mainManagedObjectContext = mainManagedObjectContext;
// Perform a data sanity check on the newly loaded store to find and fix any issues.
if ([[MPConfig get].checkInconsistency boolValue])
[MPAppDelegate_Shared managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
[self findAndFixInconsistenciesSaveInContext:context];
}];
}
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreErrorCause)cause
@ -621,7 +664,8 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
}
// Ask for confirmation to import these sites and the master password of the user.
inf(@"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteElements count], (unsigned long)[elementsToDelete count], [MPUserEntity idFor:importUserName]);
inf( @"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteElements count],
(unsigned long)[elementsToDelete count], [MPUserEntity idFor:importUserName] );
NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteElements count],
[elementsToDelete count] );
if (!userMasterPassword) {

View File

@ -14,5 +14,6 @@
@property(nonatomic, retain) NSNumber *rememberLogin;
@property(nonatomic, retain) NSNumber *iCloudDecided;
@property(nonatomic, retain) NSNumber *checkInconsistency;
@end

View File

@ -6,12 +6,11 @@
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPConfig.h"
#import "MPAppDelegate_Shared.h"
@implementation MPConfig
@dynamic sendInfo, rememberLogin, iCloudDecided;
@dynamic sendInfo, rememberLogin, iCloudDecided, checkInconsistency;
- (id)init {
@ -23,7 +22,8 @@
NSStringFromSelector( @selector( sendInfo ) ) : @NO,
NSStringFromSelector( @selector( rememberLogin ) ) : @NO,
NSStringFromSelector( @selector(iCloudDecided) ) : @NO
NSStringFromSelector( @selector( iCloudDecided ) ) : @NO,
NSStringFromSelector( @selector( checkInconsistency ) ) : @NO
}];
self.delegate = [MPAppDelegate_Shared get];

View File

@ -8,10 +8,11 @@
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "MPFixable.h"
@class MPUserEntity;
@interface MPElementEntity : NSManagedObject
@interface MPElementEntity : NSManagedObject <MPFixable>
@property(nonatomic, retain) NSDate *lastUsed;
@property(nonatomic, retain) NSString *loginName;

View File

@ -19,4 +19,9 @@
@dynamic version_;
@dynamic user;
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
return MPFixableResultNoProblems;
}
@end

View File

@ -7,9 +7,49 @@
//
#import "MPElementGeneratedEntity.h"
#import "MPAppDelegate_Shared.h"
@implementation MPElementGeneratedEntity
@dynamic counter_;
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
MPFixableResult result = [super findAndFixInconsistenciesInContext:context];
if (!self.type || self.type == (MPElementType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
// Invalid self.type
result = MPApplyFix( result, ^MPFixableResult {
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
self.name, self.user.name, (long)self.type, (long)self.user.defaultType );
self.type = self.user.defaultType;
return MPFixableResultProblemsFixed;
} );
if (!self.type || self.type == (MPElementType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
// Invalid self.user.defaultType
result = MPApplyFix( result, ^MPFixableResult {
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
self.name, self.user.name, (long)self.type, (long)MPElementTypeGeneratedLong );
self.type = MPElementTypeGeneratedLong;
return MPFixableResultProblemsFixed;
} );
if (![self isKindOfClass:[self.algorithm classOfType:self.type]])
// Mismatch between self.type and self.class
result = MPApplyFix( result, ^MPFixableResult {
for (MPElementType newType = self.type; self.type != (newType = [self.algorithm nextType:newType]);)
if ([self isKindOfClass:[self.algorithm classOfType:newType]]) {
wrn( @"Mismatching type for: %@ of %@, type: %lu, class: %@. Will use %ld instead.",
self.name, self.user.name, (long)self.type, self.class, (long)newType );
self.type = newType;
return MPFixableResultProblemsFixed;
}
err( @"Mismatching type for: %@ of %@, type: %lu, class: %@. Couldn't find a type to fix problem with.",
self.name, self.user.name, (long)self.type, self.class );
return MPFixableResultProblemsNotFixed;
} );
return result;
}
@end

View File

@ -12,6 +12,6 @@
@interface MPElementStoredEntity : MPElementEntity
@property(nonatomic, retain) id contentObject;
@property(nonatomic, retain) NSData *contentObject;
@end

View File

@ -7,9 +7,31 @@
//
#import "MPElementStoredEntity.h"
#import "MPEntities.h"
#import "MPAppDelegate_Shared.h"
@implementation MPElementStoredEntity
@dynamic contentObject;
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
MPFixableResult result = [super findAndFixInconsistenciesInContext:context];
if (self.contentObject && ![self.contentObject isKindOfClass:[NSData class]])
result = MPApplyFix( result, ^MPFixableResult {
MPKey *key = [MPAppDelegate_Shared get].key;
if (key && [[MPAppDelegate_Shared get] activeUserInContext:context] == self.user) {
wrn( @"Content object not encrypted for: %@ of %@. Will re-encrypt.", self.name, self.user.name );
[self.algorithm saveContent:[self.contentObject description] toElement:self usingKey:key];
return MPFixableResultProblemsFixed;
}
err( @"Content object not encrypted for: %@ of %@. Couldn't fix, please sign in.", self.name, self.user.name );
return MPFixableResultProblemsNotFixed;
} );
return result;
}
@end

View File

@ -38,34 +38,11 @@
- (MPElementType)type {
// Some people got elements with type == 0.
MPElementType type = (MPElementType)[self.type_ unsignedIntegerValue];
if (!type || type == (MPElementType)NSNotFound)
type = [self.user defaultType];
if (!type || type == (MPElementType)NSNotFound)
type = MPElementTypeGeneratedLong;
if (![self isKindOfClass:[self.algorithm classOfType:type]]) {
// NSAssert(NO, @"This object's class does not support the type: %lu", (long)type);
for (MPElementType aType = type; type != (aType = [self.algorithm nextType:aType]);)
if ([self isKindOfClass:[self.algorithm classOfType:aType]]) {
err(@"Invalid type for: %@, type: %lu. Will use %lu instead.", self.name, (long)type, (long)aType);
return aType;
}
}
return type;
return (MPElementType)[self.type_ unsignedIntegerValue];
}
- (void)setType:(MPElementType)aType {
// Make sure we don't poison our model data with invalid values.
if (!aType || aType == (MPElementType)NSNotFound)
aType = [self.user defaultType];
if (!aType || aType == (MPElementType)NSNotFound)
aType = MPElementTypeGeneratedLong;
if (![self isKindOfClass:[self.algorithm classOfType:aType]])
Throw(@"This object's class does not support the type: %lu", (long)aType);
self.type_ = @(aType);
}
@ -132,12 +109,12 @@
- (NSString *)description {
return PearlString( @"%@:%@", [self class], [self name] );
return strf( @"%@:%@", [self class], [self name] );
}
- (NSString *)debugDescription {
return PearlString( @"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}",
return strf( @"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}",
NSStringFromClass( [self class] ), self.name, self.user.name, (long)self.type, (long)self.uses, self.lastUsed, (long)self.version,
self.loginName, self.requiresExplicitMigration );
}

View File

@ -0,0 +1,33 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPFixable.h
// MPFixable
//
// Created by lhunath on 2014-04-26.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef NS_ENUM( NSUInteger, MPFixableResult ) {
MPFixableResultNoProblems,
MPFixableResultProblemsFixed,
MPFixableResultProblemsNotFixed,
};
MPFixableResult MPApplyFix(MPFixableResult previousResult, MPFixableResult(^fixBlock)(void));
@protocol MPFixable<NSObject>
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context;
@end

View File

@ -0,0 +1,40 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPFixable.m
// MPFixable
//
// Created by lhunath on 2014-04-26.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPFixable.h"
MPFixableResult MPApplyFix(MPFixableResult previousResult, MPFixableResult(^fixBlock)(void)) {
MPFixableResult additionalResult = fixBlock();
switch (previousResult) {
case MPFixableResultNoProblems:
return additionalResult;
case MPFixableResultProblemsFixed:
switch (additionalResult) {
case MPFixableResultNoProblems:
case MPFixableResultProblemsFixed:
return previousResult;
case MPFixableResultProblemsNotFixed:
return additionalResult;
}
case MPFixableResultProblemsNotFixed:
return additionalResult;
}
Throw( @"Unexpected previous=%ld or additional=%ld result.", (long)previousResult, (long)additionalResult );
}

View File

@ -77,8 +77,10 @@ typedef NS_ENUM(NSUInteger, MPElementType) {
#define MPElementUpdatedNotification @"MPElementUpdatedNotification"
#define MPCheckConfigNotification @"MPCheckConfigNotification"
#define MPSitesImportedNotification @"MPSitesImportedNotification"
#define MPFoundInconsistenciesNotification @"MPFoundInconsistenciesNotification"
#define MPSitesImportedNotificationUserKey @"MPSitesImportedNotificationUserKey"
#define MPInconsistenciesFixResultUserKey @"MPInconsistenciesFixResultUserKey"
static void MPCheckpoint(NSString *checkpoint, NSDictionary *attributes) {

View File

@ -243,8 +243,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)oldValue {
[[NSNotificationCenter defaultCenter]
postNotificationName:MPCheckConfigNotification object:NSStringFromSelector( configKey ) userInfo:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:NSStringFromSelector( configKey )];
}
#pragma mark - NSApplicationDelegate

View File

@ -44,10 +44,8 @@
element = [super saveContentTypeWithElement:element saveInContext:context];
MPElementStoredEntity *storedElement = [self storedElement:element];
if (storedElement) {
storedElement.contentObject = self.contentField.text;
[storedElement.algorithm saveContent:self.contentField.text toElement:storedElement usingKey:[MPiOSAppDelegate get].key];
[context saveToStore];
}
return element;
}
@ -78,7 +76,7 @@
switch (self.contentFieldMode) {
case MPContentFieldModePassword: {
storedElement.contentObject = newContent;
[storedElement.algorithm saveContent:newContent toElement:storedElement usingKey:[MPiOSAppDelegate get].key];
[context saveToStore];
PearlMainQueue( ^{

View File

@ -68,6 +68,7 @@
[self registerObservers];
[self observeStore];
[self updateFromConfig];
[self updatePasswords];
}
@ -464,6 +465,11 @@
self.passwordSelectionContainer.alpha = 1;
}];
}],
[[NSNotificationCenter defaultCenter]
addObserverForName:MPCheckConfigNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self updateFromConfig];
}],
];
}
@ -504,6 +510,11 @@
[[NSNotificationCenter defaultCenter] removeObserver:_storeObserver];
}
- (void)updateFromConfig {
self.passwordsSearchBar.keyboardType = [[MPiOSConfig get].dictationSearch boolValue]? UIKeyboardTypeDefault: UIKeyboardTypeURL;
}
- (void)updatePasswords {
NSString *query = self.query;

View File

@ -15,6 +15,7 @@
@property(weak, nonatomic) IBOutlet UITableViewCell *feedbackCell;
@property(weak, nonatomic) IBOutlet UITableViewCell *coachmarksCell;
@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 *storedTypeControl;

View File

@ -70,6 +70,14 @@
[vc performSegueWithIdentifier:@"coachmarks" sender:self];
}
}
if (cell == self.checkInconsistencies)
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
if ([[MPiOSAppDelegate get] findAndFixInconsistenciesSaveInContext:context] == MPFixableResultNoProblems)
[PearlAlert showAlertWithTitle:@"No Inconsistencies" message:
@"No inconsistencies were detected in your sites."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
}];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

View File

@ -116,12 +116,15 @@
@try {
[[NSNotificationCenter defaultCenter] addObserverForName:MPCheckConfigNotification object:nil queue:nil usingBlock:
^(NSNotification *note) {
[self checkConfig];
[self updateFromConfig];
}];
[[NSNotificationCenter defaultCenter]
addObserverForName:kIASKAppSettingChanged object:nil queue:nil usingBlock:^(NSNotification *note) {
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification
object:note userInfo:nil];
[[NSNotificationCenter defaultCenter] addObserverForName:kIASKAppSettingChanged object:nil queue:nil usingBlock:
^(NSNotification *note) {
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note userInfo:nil];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification object:nil queue:nil usingBlock:
^(NSNotification *note) {
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note userInfo:nil];
}];
#ifdef ADHOC
@ -148,7 +151,30 @@
@try {
inf( @"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier] );
dispatch_async( dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] addObserverForName:MPFoundInconsistenciesNotification object:nil queue:nil usingBlock:
^(NSNotification *note) {
switch ((MPFixableResult)[note.userInfo[MPInconsistenciesFixResultUserKey] unsignedIntegerValue]) {
case MPFixableResultNoProblems:
break;
case MPFixableResultProblemsFixed:
[PearlAlert showAlertWithTitle:@"Inconsistencies Fixed" message:
@"Some inconsistencies were detected in your sites.\n"
@"All issues were fixed."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
break;
case MPFixableResultProblemsNotFixed:
[PearlAlert showAlertWithTitle:@"Inconsistencies Found" message:
@"Some inconsistencies were detected in your sites.\n"
@"Not all issues could be fixed. Try signing in to each user or checking the logs."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
break;
}
}];
PearlMainQueue( ^{
if ([[MPiOSConfig get].showSetup boolValue])
[self.navigationController performSegueWithIdentifier:@"setup" sender:self];
} );
@ -322,8 +348,7 @@
- (void)applicationDidBecomeActive:(UIApplication *)application {
inf( @"Re-activated" );
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification
object:application userInfo:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:application];
#ifdef LOCALYTICS
[[LocalyticsSession sharedLocalyticsSession] resume];
@ -497,16 +522,14 @@
otherTitles:[PearlStrings get].commonButtonContinue, nil];
}
#pragma mark - PearlConfigDelegate
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value {
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification
object:NSStringFromSelector( configKey ) userInfo:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:NSStringFromSelector( configKey )];
}
- (void)checkConfig {
- (void)updateFromConfig {
// iCloud enabled / disabled
BOOL iCloudEnabled = [[MPiOSConfig get].iCloudEnabled boolValue];
@ -638,7 +661,6 @@
}
}
#pragma mark - UbiquityStoreManager
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager willLoadStoreIsCloud:(BOOL)isCloudStore {
@ -733,7 +755,6 @@
} cancelTitle:nil otherTitles:@"Fix Now", @"Turn Off", nil];
}
#pragma mark - TestFlight
- (NSDictionary *)testFlightInfo {
@ -755,7 +776,6 @@
return testFlightToken;
}
#pragma mark - Crashlytics
- (NSDictionary *)crashlyticsInfo {
@ -777,7 +797,6 @@
return crashlyticsAPIKey;
}
#pragma mark - Localytics
- (NSDictionary *)localyticsInfo {

View File

@ -18,5 +18,6 @@
@property(nonatomic, retain) NSNumber *loginNameTipShown;
@property(nonatomic, retain) NSNumber *traceMode;
@property(nonatomic, retain) NSNumber *iCloudEnabled;
@property(nonatomic, retain) NSNumber *dictationSearch;
@end

View File

@ -8,7 +8,7 @@
@implementation MPiOSConfig
@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, iCloudEnabled;
@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, iCloudEnabled, dictationSearch;
- (id)init {
@ -24,7 +24,8 @@
NSStringFromSelector( @selector(typeTipShown) ) : @(!self.firstRun),
NSStringFromSelector( @selector(loginNameTipShown) ) : @NO,
NSStringFromSelector( @selector(traceMode) ) : @NO,
NSStringFromSelector( @selector(iCloudEnabled) ) : @NO
NSStringFromSelector( @selector(iCloudEnabled) ) : @NO,
NSStringFromSelector( @selector( dictationSearch) ) : @NO
}];
return self;

View File

@ -47,6 +47,7 @@
93D39CB5E2EC1078E898F46A /* MPPasswordLargeCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3937863061C3916AF7AD2 /* MPPasswordLargeCell.m */; };
93D39D596A2E376D6F6F5DA1 /* MPCombinedViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D393310223DDB35218467A /* MPCombinedViewController.m */; };
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
93D39EAA4D064193074D3021 /* MPFixable.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A813CA9D7E192261ED2 /* MPFixable.m */; };
93D39EDD960C381D64E4DCDD /* MPPasswordSmallCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3952CC60991B97D69F26A /* MPPasswordSmallCell.m */; };
93D39F8A9254177891F38705 /* MPSetupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A28369954D147E239BA /* MPSetupViewController.m */; };
93D39FA97F4C3F69A75D5A03 /* MPPasswordLargeGeneratedCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3993422E207BF0B21D089 /* MPPasswordLargeGeneratedCell.m */; };
@ -556,10 +557,12 @@
93D39975CE5AEC99E3F086C7 /* MPPasswordCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordCell.h; sourceTree = "<group>"; };
93D3999693660C89A7465F4E /* MPCoachmarkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCoachmarkViewController.h; sourceTree = "<group>"; };
93D399E571F61E50A9BF8FAF /* MPUsersViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUsersViewController.m; sourceTree = "<group>"; };
93D399F244BB522A317811BB /* MPFixable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPFixable.h; sourceTree = "<group>"; };
93D39A1DDFA09AE2E14D26DC /* UIResponder+PearlFirstResponder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIResponder+PearlFirstResponder.m"; sourceTree = "<group>"; };
93D39A28369954D147E239BA /* MPSetupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSetupViewController.m; sourceTree = "<group>"; };
93D39A3CC4D8330831FC8CB4 /* LLToggleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LLToggleViewController.h; sourceTree = "<group>"; };
93D39A41340CF778E00D0E6D /* MPEmergencySegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEmergencySegue.h; sourceTree = "<group>"; };
93D39A813CA9D7E192261ED2 /* MPFixable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPFixable.m; sourceTree = "<group>"; };
93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = "<group>"; };
93D39ACBA9F4878B6A1CC33B /* MPEmergencyViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEmergencyViewController.m; sourceTree = "<group>"; };
93D39B050DD5F55E9794EFD4 /* MPPopdownSegue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPopdownSegue.m; sourceTree = "<group>"; };
@ -2496,6 +2499,8 @@
DABD3BB91711E2DC00CF925C /* MPUserEntity.h */,
DABD3BBA1711E2DC00CF925C /* MPUserEntity.m */,
DABD3BD01711E2DC00CF925C /* MasterPassword.xcdatamodeld */,
93D399F244BB522A317811BB /* MPFixable.h */,
93D39A813CA9D7E192261ED2 /* MPFixable.m */,
);
name = ObjC;
path = ..;
@ -3840,6 +3845,7 @@
93D39BA1EA3CAAC8A220B4A6 /* MPAppSettingsViewController.m in Sources */,
93D396D8B67DA6522CDBA142 /* MPCoachmarkViewController.m in Sources */,
93D391C07818F4C2DC1B6956 /* MPPasswordsCoachmarkViewController.m in Sources */,
93D39EAA4D064193074D3021 /* MPFixable.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -70,6 +70,24 @@
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
</dict>
<dict>
<key>FooterText</key>
<string>Enabling support for dictation in the site search box will enable the dictation button next to the space bar at the bottom of the keyboard. Press this button and speak the name of your site to look it up. Enabling dictation will change your keyboard which might make it slightly more difficult to enter a site name manually.</string>
<key>Title</key>
<string></string>
<key>Type</key>
<string>PSGroupSpecifier</string>
</dict>
<dict>
<key>DefaultValue</key>
<false/>
<key>Key</key>
<string>dictationSearch</string>
<key>Title</key>
<string>Dictation Search</string>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
</dict>
<dict>
<key>Type</key>
<string>PSGroupSpecifier</string>
@ -88,6 +106,24 @@
<key>DefaultValue</key>
<false/>
</dict>
<dict>
<key>Type</key>
<string>PSGroupSpecifier</string>
<key>Title</key>
<string></string>
<key>FooterText</key>
<string>If the app tends to crash on login, enable this to check if there are any inconsistencies in your site data. It may slow down login a bit, so keep it off when no issues are reported on login.</string>
</dict>
<dict>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<key>Title</key>
<string>Check For Inconsistencies</string>
<key>Key</key>
<string>checkInconsistency</string>
<key>DefaultValue</key>
<false/>
</dict>
</array>
<key>StringsTable</key>
<string>Root</string>

View File

@ -714,8 +714,42 @@
</tableViewCellContentView>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" rowHeight="110" id="UdB-BV-AHA" userLabel="Check Inconsistencies">
<rect key="frame" x="0.0" y="860" width="320" height="110"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="UdB-BV-AHA" id="V2Y-nu-jhZ">
<rect key="frame" x="0.0" y="0.0" width="287" height="109"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Check For Inconsistencies" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WXh-sg-l2h">
<rect key="frame" x="20" y="20" width="247" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" text="Perform a check to see if there are any inconsistencies in your site data that might cause issues." lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" preferredMaxLayoutWidth="247" translatesAutoresizingMaskIntoConstraints="NO" id="gTs-JA-zmL">
<rect key="frame" x="20" y="49" width="247" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Exo2.0-Thin" family="Exo 2.0" pointSize="11"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="gTs-JA-zmL" secondAttribute="trailing" constant="20" symbolic="YES" id="E6T-PX-DeO"/>
<constraint firstItem="gTs-JA-zmL" firstAttribute="leading" secondItem="V2Y-nu-jhZ" secondAttribute="leading" constant="20" symbolic="YES" id="KNx-Ww-XeE"/>
<constraint firstAttribute="trailing" secondItem="WXh-sg-l2h" secondAttribute="trailing" constant="20" symbolic="YES" id="MTI-sn-fnK"/>
<constraint firstItem="WXh-sg-l2h" firstAttribute="leading" secondItem="V2Y-nu-jhZ" secondAttribute="leading" constant="20" symbolic="YES" id="cXs-lo-6cy"/>
<constraint firstItem="gTs-JA-zmL" firstAttribute="top" secondItem="WXh-sg-l2h" secondAttribute="bottom" constant="8" symbolic="YES" id="jA7-sy-qAs"/>
<constraint firstItem="WXh-sg-l2h" firstAttribute="top" secondItem="V2Y-nu-jhZ" secondAttribute="top" constant="20" symbolic="YES" id="tOD-LM-bbp"/>
<constraint firstAttribute="bottom" secondItem="gTs-JA-zmL" secondAttribute="bottom" constant="20" symbolic="YES" id="trB-mg-HbI"/>
</constraints>
</tableViewCellContentView>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" rowHeight="125" id="IVT-Rs-nTu" userLabel="Export">
<rect key="frame" x="0.0" y="860" width="320" height="125"/>
<rect key="frame" x="0.0" y="970" width="320" height="125"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="IVT-Rs-nTu" id="Q5J-2f-mmz">
<rect key="frame" x="0.0" y="0.0" width="287" height="124"/>
@ -762,6 +796,7 @@
<size key="freeformSize" width="320" height="568"/>
<connections>
<outlet property="avatarImage" destination="tWi-sc-DGp" id="ifT-Ct-WL6"/>
<outlet property="checkInconsistencies" destination="UdB-BV-AHA" id="Cm2-Om-UzP"/>
<outlet property="coachmarksCell" destination="eth-Dc-JYn" id="0Tq-I3-SwK"/>
<outlet property="exportCell" destination="IVT-Rs-nTu" id="RU0-qr-Bdi"/>
<outlet property="feedbackCell" destination="9QG-lM-ymM" id="18X-Ph-0ac"/>