2
0

Import improvements and core data fixes.

[FIXED]     Import fixes:
                - Wait for MOC to become available.
                - Progress UI while working.
                - Import files exported with a different master password.
                - Core Data crashes.
[RENAMED]   Site's User name -> Login name.
[FIXED]     Core Data crashes related to using entities from old MOCs.
This commit is contained in:
Maarten Billemont 2012-08-19 09:34:49 +02:00
parent 479d357bd1
commit 75ef15bb4d
29 changed files with 343 additions and 221 deletions

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit cc97c1be4c0f2ee0080af417940244ac8f3e5f47
Subproject commit 5a35cdb73c57c65f22959accab2f6ddbeeee0b33

View File

@ -17,10 +17,6 @@
DA0A1D1515690AF40092735D /* Icon-72@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1315690AF30092735D /* Icon-72@2x.png */; };
DA0A1D1615690AF40092735D /* Icon-Small-50@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */; };
DA0E07961577FE490008A67E /* MPEntities.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0E07951577FE490008A67E /* MPEntities.m */; };
DA0F9F3315B55397007ED9BC /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3215B55397007ED9BC /* MPUserEntity.m */; };
DA0F9F3615B55397007ED9BC /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */; };
DA0F9F3915B55397007ED9BC /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */; };
DA0F9F3C15B55397007ED9BC /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */; };
DA30E9CE15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */; };
DA30E9CF15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */; };
DA30E9D015722ECA00A68B4C /* Pearl.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9CD15722ECA00A68B4C /* Pearl.m */; };
@ -95,6 +91,10 @@
DA95D5F614DF0B9F008D1B94 /* IASKPSTextFieldSpecifierViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA95D5CC14DF0691008D1B94 /* IASKPSTextFieldSpecifierViewCell.xib */; };
DA95D5F714DF0B9F008D1B94 /* IASKPSToggleSwitchSpecifierViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA95D5CD14DF0691008D1B94 /* IASKPSToggleSwitchSpecifierViewCell.xib */; };
DA95D5F814DF0B9F008D1B94 /* IASKSpecifierValuesView.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA95D5CE14DF0691008D1B94 /* IASKSpecifierValuesView.xib */; };
DAA096FC15E0C59B00912D63 /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA096FB15E0C59B00912D63 /* MPElementEntity.m */; };
DAA096FF15E0C59B00912D63 /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA096FE15E0C59B00912D63 /* MPElementGeneratedEntity.m */; };
DAA0970215E0C59B00912D63 /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA0970115E0C59B00912D63 /* MPElementStoredEntity.m */; };
DAA0970515E0C59B00912D63 /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA0970415E0C59B00912D63 /* MPUserEntity.m */; };
DAB8D45D15036BCF00CED3BC /* MasterPassword.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */; };
DAB8D45E15036BCF00CED3BC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DAB8D43F15036BCF00CED3BC /* InfoPlist.strings */; };
DAB8D45F15036BCF00CED3BC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D44115036BCF00CED3BC /* main.m */; };
@ -912,14 +912,6 @@
DA0A1D1415690AF40092735D /* Icon-Small-50@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-50@2x.png"; sourceTree = "<group>"; };
DA0E07941577FE490008A67E /* MPEntities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEntities.h; sourceTree = "<group>"; };
DA0E07951577FE490008A67E /* MPEntities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEntities.m; sourceTree = "<group>"; };
DA0F9F3115B55397007ED9BC /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
DA0F9F3215B55397007ED9BC /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
DA0F9F3415B55397007ED9BC /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
DA0F9F3715B55397007ED9BC /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
DA0F9F3A15B55397007ED9BC /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = "<group>"; };
DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+PearlMutableInfo.m"; sourceTree = "<group>"; };
DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = "<group>"; };
@ -998,6 +990,14 @@
DA95D5CD14DF0691008D1B94 /* IASKPSToggleSwitchSpecifierViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKPSToggleSwitchSpecifierViewCell.xib; sourceTree = "<group>"; };
DA95D5CE14DF0691008D1B94 /* IASKSpecifierValuesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKSpecifierValuesView.xib; sourceTree = "<group>"; };
DA95D5F014DF0B1E008D1B94 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; };
DAA096FA15E0C59B00912D63 /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
DAA096FB15E0C59B00912D63 /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
DAA096FD15E0C59B00912D63 /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
DAA096FE15E0C59B00912D63 /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
DAA0970015E0C59B00912D63 /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
DAA0970115E0C59B00912D63 /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
DAA0970315E0C59B00912D63 /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
DAA0970415E0C59B00912D63 /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
DAAC35DD156BD77D00C5FD93 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; };
DAB8D43D15036BCF00CED3BC /* MasterPassword 1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 1.xcdatamodel"; sourceTree = "<group>"; };
DAB8D44015036BCF00CED3BC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@ -1737,6 +1737,7 @@
DAE4C967157E63BE00EFE047 /* avatar-18.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-18.png"; sourceTree = "<group>"; };
DAE4C968157E63BE00EFE047 /* avatar-18@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-18@2x.png"; sourceTree = "<group>"; };
DAEBC45214F6364500987BF6 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
DAF83D6B15E02E04009C8D49 /* MasterPassword 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 3.xcdatamodel"; sourceTree = "<group>"; };
DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+PearlExport.h"; sourceTree = "<group>"; };
DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+PearlExport.m"; sourceTree = "<group>"; };
DAFE45DA15039823003ABA7C /* NSString+PearlNSArrayFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+PearlNSArrayFormat.h"; sourceTree = "<group>"; };
@ -2022,14 +2023,14 @@
DA0E07941577FE490008A67E /* MPEntities.h */,
DA0E07951577FE490008A67E /* MPEntities.m */,
DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */,
DA0F9F3115B55397007ED9BC /* MPUserEntity.h */,
DA0F9F3215B55397007ED9BC /* MPUserEntity.m */,
DA0F9F3415B55397007ED9BC /* MPElementGeneratedEntity.h */,
DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */,
DA0F9F3715B55397007ED9BC /* MPElementStoredEntity.h */,
DA0F9F3A15B55397007ED9BC /* MPElementEntity.h */,
DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */,
DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */,
DAA096FA15E0C59B00912D63 /* MPElementEntity.h */,
DAA096FB15E0C59B00912D63 /* MPElementEntity.m */,
DAA096FD15E0C59B00912D63 /* MPElementGeneratedEntity.h */,
DAA096FE15E0C59B00912D63 /* MPElementGeneratedEntity.m */,
DAA0970015E0C59B00912D63 /* MPElementStoredEntity.h */,
DAA0970115E0C59B00912D63 /* MPElementStoredEntity.m */,
DAA0970315E0C59B00912D63 /* MPUserEntity.h */,
DAA0970415E0C59B00912D63 /* MPUserEntity.m */,
DA600C2415054F3A008E9AB6 /* MPAppDelegate_Key.h */,
DA600C2315054F3A008E9AB6 /* MPAppDelegate_Key.m */,
DA4426041557C1990052177D /* MPAppDelegate_Shared.h */,
@ -4269,10 +4270,10 @@
93D390BC6AE7A1C9B91A3668 /* MPKey.m in Sources */,
93D394744B5485303B326ECB /* MPAlgorithm.m in Sources */,
93D39DC7A7282137B08C8D82 /* MPAlgorithmV1.m in Sources */,
DA0F9F3315B55397007ED9BC /* MPUserEntity.m in Sources */,
DA0F9F3615B55397007ED9BC /* MPElementGeneratedEntity.m in Sources */,
DA0F9F3915B55397007ED9BC /* MPElementStoredEntity.m in Sources */,
DA0F9F3C15B55397007ED9BC /* MPElementEntity.m in Sources */,
DAA096FC15E0C59B00912D63 /* MPElementEntity.m in Sources */,
DAA096FF15E0C59B00912D63 /* MPElementGeneratedEntity.m in Sources */,
DAA0970215E0C59B00912D63 /* MPElementStoredEntity.m in Sources */,
DAA0970515E0C59B00912D63 /* MPUserEntity.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -5092,10 +5093,11 @@
DAB8D43C15036BCF00CED3BC /* MasterPassword.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
DAF83D6B15E02E04009C8D49 /* MasterPassword 3.xcdatamodel */,
DA46826C15AB48F100FB09E7 /* MasterPassword 2.xcdatamodel */,
DAB8D43D15036BCF00CED3BC /* MasterPassword 1.xcdatamodel */,
);
currentVersion = DA46826C15AB48F100FB09E7 /* MasterPassword 2.xcdatamodel */;
currentVersion = DAF83D6B15E02E04009C8D49 /* MasterPassword 3.xcdatamodel */;
path = MasterPassword.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;

View File

@ -50,13 +50,14 @@
</MacroExpansion>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
enableOpenGLFrameCaptureMode = "0"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableReference

View File

@ -47,6 +47,9 @@
- (MPUserEntity *)activeUser {
if (!self.activeUserID)
return nil;
return (MPUserEntity *)[self.managedObjectContextIfReady objectWithID:self.activeUserID];
}

View File

@ -28,8 +28,9 @@ typedef enum {
- (UbiquityStoreManager *)storeManager;
- (void)saveContext;
- (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation;
- (MPImportResult)importSites:(NSString *)importedSitesString
askImportPassword:(NSString *(^)(NSString *userName))importPassword
askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword;
- (NSString *)exportSitesShowingPasswords:(BOOL)showPasswords;
@end

View File

@ -183,8 +183,12 @@
#pragma mark - Import / Export
- (MPImportResult)importSites:(NSString *)importedSitesString withPassword:(NSString *)password
askConfirmation:(BOOL(^)(NSUInteger importCount, NSUInteger deleteCount))confirmation {
- (MPImportResult)importSites:(NSString *)importedSitesString
askImportPassword:(NSString *(^)(NSString *userName))importPassword
askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword {
while (![self managedObjectContextIfReady])
usleep((useconds_t)(USEC_PER_SEC * 0.2));
inf(@"Importing sites.");
@ -205,14 +209,15 @@
if (!headerPattern || !sitePattern)
return MPImportResultInternalError;
MPKey *key = nil;
__block MPUserEntity *user = nil;
NSString *bundleVersion = nil, *keyIDHex = nil, *userName = nil;
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
__block MPUserEntity *user = nil;
id<MPAlgorithm> importAlgorithm = nil;
NSString *importBundleVersion = nil, *importUserName = nil;
NSData *importKeyID = nil;
BOOL headerStarted = NO, headerEnded = NO, clearText = NO;
NSArray *importedSiteLines = [importedSitesString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableSet *elementsToDelete = [NSMutableSet set];
NSMutableArray *importedSiteElements = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
NSFetchRequest *elementFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPElementEntity class])];
for (NSString *importedSiteLine in importedSiteLines) {
if ([importedSiteLine hasPrefix:@"#"]) {
// Comment or header
@ -238,20 +243,20 @@
NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
if ([headerName isEqualToString:@"User Name"]) {
userName = headerValue;
importUserName = headerValue;
NSFetchRequest *userFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", userName];
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName];
__block NSArray *users = nil;
[self.managedObjectContextIfReady performBlockAndWait:^{
users = [self.managedObjectContextIfReady executeFetchRequest:userFetchRequest error:&error];
}];
if (!users) {
err(@"While looking for user: %@, error: %@", userName, error);
err(@"While looking for user: %@, error: %@", importUserName, error);
return MPImportResultInternalError;
}
if ([users count] > 1) {
err(@"While looking for user: %@, found more than one: %u", userName, [users count]);
err(@"While looking for user: %@, found more than one: %u", importUserName, [users count]);
return MPImportResultInternalError;
}
@ -259,9 +264,11 @@
dbg(@"Found user: %@", [user debugDescription]);
}
if ([headerName isEqualToString:@"Key ID"])
keyIDHex = headerValue;
if ([headerName isEqualToString:@"Version"])
bundleVersion = headerValue;
importKeyID = [headerValue decodeHex];
if ([headerName isEqualToString:@"Version"]) {
importBundleVersion = headerValue;
importAlgorithm = MPAlgorithmDefaultForBundleVersion(importBundleVersion);
}
if ([headerName isEqualToString:@"Passwords"]) {
if ([headerValue isEqualToString:@"VISIBLE"])
clearText = YES;
@ -271,13 +278,8 @@
}
if (!headerEnded)
continue;
if (!keyIDHex || ![userName length])
if (!importKeyID || ![importUserName length])
return MPImportResultMalformedInput;
if (!key) {
key = [MPAlgorithmDefaultForBundleVersion(bundleVersion) keyForPassword:password ofUserNamed:userName];
if (![keyIDHex isEqualToString:[key.keyID encodeHex]])
return MPImportResultInvalidPassword;
}
if (![importedSiteLine length])
continue;
@ -297,10 +299,10 @@
// Find existing site.
if (user) {
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user];
elementFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", name, user];
__block NSArray *existingSites = nil;
[self.managedObjectContextIfReady performBlockAndWait:^{
existingSites = [self.managedObjectContextIfReady executeFetchRequest:fetchRequest error:&error];
existingSites = [self.managedObjectContextIfReady executeFetchRequest:elementFetchRequest error:&error];
}];
if (!existingSites) {
err(@"Lookup of existing sites failed for site: %@, user: %@, error: %@", name, user.userID, error);
@ -314,14 +316,19 @@
}
}
inf(@"Importing %u sites, deleting %u sites, for user: %@",
[importedSiteElements count], [elementsToDelete count], [MPUserEntity idFor:userName]);
// Ask for confirmation to import these sites.
if (!confirmation([importedSiteElements count], [elementsToDelete count])) {
// Ask for confirmation to import these sites and the master password of the user.
inf(@"Importing %u sites, deleting %u sites, for user: %@", [importedSiteElements count], [elementsToDelete count], [MPUserEntity idFor:importUserName]);
NSString *userMasterPassword = userPassword(user.name, [importedSiteElements count], [elementsToDelete count]);
if (!userMasterPassword) {
inf(@"Import cancelled.");
return MPImportResultCancelled;
}
MPKey *userKey = [MPAlgorithmDefault keyForPassword:userMasterPassword ofUserNamed:user.name];
if (![userKey.keyID isEqualToData:user.keyID])
return MPImportResultInvalidPassword;
__block MPKey *importKey = userKey;
if ([importKey.keyID isEqualToData:importKeyID])
importKey = nil;
BOOL success = NO;
[self.managedObjectContextIfReady.undoManager beginUndoGrouping];
@ -337,16 +344,19 @@
}];
}];
// Import new sites.
// Make sure there is a user.
if (!user) {
[self.managedObjectContextIfReady performBlockAndWait:^{
user = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPUserEntity class])
inManagedObjectContext:self.managedObjectContextIfReady];
user.name = userName;
user.keyID = [keyIDHex decodeHex];
user.name = importUserName;
user.keyID = importKeyID;
}];
dbg(@"Created User: %@", [user debugDescription]);
}
[self saveContext];
// Import new sites.
for (NSArray *siteElements in importedSiteElements) {
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:[siteElements objectAtIndex:0]];
NSUInteger uses = (unsigned)[[siteElements objectAtIndex:1] integerValue];
@ -356,9 +366,10 @@
NSString *exportContent = [siteElements objectAtIndex:5];
// Create new site.
__block MPImportResult result = MPImportResultSuccess;
[self.managedObjectContextIfReady performBlockAndWait:^{
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[key.algorithm classNameOfType:type]
inManagedObjectContext:self.managedObjectContextIfReady];
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:[MPAlgorithmForVersion(version) classNameOfType:type]
inManagedObjectContext:self.managedObjectContextIfReady];
element.name = name;
element.user = user;
element.type = type;
@ -367,12 +378,23 @@
element.version = version;
if ([exportContent length]) {
if (clearText)
[element importClearTextContent:exportContent usingKey:key];
else
[element importProtectedContent:exportContent];
[element importClearTextContent:exportContent usingKey:userKey];
else {
if (!importKey)
importKey = [importAlgorithm keyForPassword:importPassword(user.name) ofUserNamed:user.name];
if (![importKey.keyID isEqualToData:importKeyID]) {
result = MPImportResultInvalidPassword;
return;
}
[element importProtectedContent:exportContent protectedByKey:importKey usingKey:userKey];
}
}
dbg(@"Created Element: %@", [element debugDescription]);
}];
if (result != MPImportResultSuccess)
return result;
}
[self saveContext];

View File

@ -2,7 +2,7 @@
// MPElementEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 17/07/12.
// Created by Maarten Billemont on 2012-08-19.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
@ -15,10 +15,10 @@
@property (nonatomic, retain) id content;
@property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, retain) NSString * loginName;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * requiresExplicitMigration_;
@property (nonatomic, retain) NSNumber * type_;
@property (nonatomic, retain) NSString * userName;
@property (nonatomic, retain) NSNumber * uses_;
@property (nonatomic, retain) NSNumber * version_;
@property (nonatomic, retain) MPUserEntity *user;

View File

@ -2,7 +2,7 @@
// MPElementEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 17/07/12.
// Created by Maarten Billemont on 2012-08-19.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
@ -14,10 +14,10 @@
@dynamic content;
@dynamic lastUsed;
@dynamic loginName;
@dynamic name;
@dynamic requiresExplicitMigration_;
@dynamic type_;
@dynamic userName;
@dynamic uses_;
@dynamic version_;
@dynamic user;

View File

@ -2,7 +2,7 @@
// MPElementGeneratedEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 17/07/12.
// Created by Maarten Billemont on 2012-08-19.
// Copyright (c) 2012 Lyndir. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// MPElementGeneratedEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 17/07/12.
// Created by Maarten Billemont on 2012-08-19.
// Copyright (c) 2012 Lyndir. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// MPElementStoredEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 17/07/12.
// Created by Maarten Billemont on 2012-08-19.
// Copyright (c) 2012 Lyndir. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// MPElementStoredEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 17/07/12.
// Created by Maarten Billemont on 2012-08-19.
// Copyright (c) 2012 Lyndir. All rights reserved.
//

View File

@ -29,7 +29,7 @@
- (NSUInteger)use;
- (NSString *)exportContent;
- (void)importProtectedContent:(NSString *)protectedContent;
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)contentProtectionKey usingKey:(MPKey *)key2;
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key;
- (BOOL)migrateExplicitly:(BOOL)explicit;

View File

@ -92,7 +92,7 @@
return nil;
}
- (void)importProtectedContent:(NSString *)content {
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)contentProtectionKey usingKey:(MPKey *)key {
}
@ -107,9 +107,9 @@
- (NSString *)debugDescription {
return PearlString(@"{%@: name=%@, user=%@, type=%d, uses=%d, lastUsed=%@, version=%d, userName=%@, requiresExplicitMigration=%d}",
return PearlString(@"{%@: name=%@, user=%@, type=%d, uses=%d, lastUsed=%@, version=%d, loginName=%@, requiresExplicitMigration=%d}",
NSStringFromClass([self class]), self.name, self.user.name, self.type, self.uses, self.lastUsed, self.version,
self.userName, self.requiresExplicitMigration);
self.loginName, self.requiresExplicitMigration);
}
- (BOOL)migrateExplicitly:(BOOL)explicit {
@ -185,7 +185,6 @@
if (!key)
return;
assert([key.keyID isEqualToData:self.user.keyID]);
[self setContent:content usingKey:key];
}
@ -202,17 +201,23 @@
NSData *decryptedContent = nil;
if ([encryptedContent length])
decryptedContent = [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
decryptedContent = [self decryptContent:encryptedContent usingKey:key];
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
}
- (NSData *)decryptContent:(NSData *)encryptedContent usingKey:(MPKey *)key {
return [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
}
- (void)setContent:(id)content usingKey:(MPKey *)key {
assert(self.type & MPElementTypeClassStored);
assert(key);
assert([key.keyID isEqualToData:self.user.keyID]);
NSData *encryptedContent = [[content description] encryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
NSData *encryptedContent = [[[content description] dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
if (self.type & MPElementFeatureDevicePrivate) {
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
@ -233,9 +238,18 @@
return [self.contentObject encodeBase64];
}
- (void)importProtectedContent:(NSString *)protectedContent {
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)contentProtectionKey usingKey:(MPKey *)key {
self.contentObject = [protectedContent decodeBase64];
if ([contentProtectionKey.keyID isEqualToData:key.keyID])
self.contentObject = [protectedContent decodeBase64];
else {
NSString *clearContent = [[NSString alloc] initWithData:[self decryptContent:[protectedContent decodeBase64]
usingKey:contentProtectionKey]
encoding:NSUTF8StringEncoding];
[self importClearTextContent:clearContent usingKey:key];
}
}
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key {

View File

@ -43,11 +43,11 @@ typedef enum {
#define MPCheckpointAction @"MPCheckpointAction"
#define MPCheckpointHelpChapter @"MPCheckpointHelpChapter"
#define MPCheckpointCopyToPasteboard @"MPCheckpointCopyToPasteboard"
#define MPCheckpointCopyUserNameToPasteboard @"MPCheckpointCopyUserNameToPasteboard"
#define MPCheckpointCopyLoginNameToPasteboard @"MPCheckpointCopyLoginNameToPasteboard"
#define MPCheckpointResetPasswordCounter @"MPCheckpointResetPasswordCounter"
#define MPCheckpointIncrementPasswordCounter @"MPCheckpointIncrementPasswordCounter"
#define MPCheckpointEditPassword @"MPCheckpointEditPassword"
#define MPCheckpointEditUserName @"MPCheckpointEditUserName"
#define MPCheckpointEditLoginName @"MPCheckpointEditLoginName"
#define MPCheckpointCloseAlert @"MPCheckpointCloseAlert"
#define MPCheckpointCloseOutdatedAlert @"MPCheckpointCloseOutdatedAlert"
#define MPCheckpointUseType @"MPCheckpointUseType"

View File

@ -2,7 +2,7 @@
// MPUserEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 17/07/12.
// Created by Maarten Billemont on 2012-08-19.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
@ -18,8 +18,8 @@
@property (nonatomic, retain) NSData * keyID;
@property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * saveKey_;
@property (nonatomic, retain) NSNumber * requiresExplicitMigration_;
@property (nonatomic, retain) NSNumber * saveKey_;
@property (nonatomic, retain) NSSet *elements;
@end

View File

@ -2,7 +2,7 @@
// MPUserEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 17/07/12.
// Created by Maarten Billemont on 2012-08-19.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
@ -17,8 +17,8 @@
@dynamic keyID;
@dynamic lastUsed;
@dynamic name;
@dynamic saveKey_;
@dynamic requiresExplicitMigration_;
@dynamic saveKey_;
@dynamic elements;
@end

View File

@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>MasterPassword 2.xcdatamodel</string>
<string>MasterPassword 3.xcdatamodel</string>
</dict>
</plist>

View File

@ -32,9 +32,9 @@
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
</entity>
<elements>
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="180"/>
<element name="MPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/>
<element name="MPElementStoredEntity" positionX="160" positionY="192" width="128" height="60"/>
<element name="MPUserEntity" positionX="160" positionY="192" width="128" height="150"/>
<element name="MPElementEntity" positionX="0" positionY="0" width="0" height="0"/>
<element name="MPElementGeneratedEntity" positionX="0" positionY="0" width="0" height="0"/>
<element name="MPElementStoredEntity" positionX="0" positionY="0" width="0" height="0"/>
<element name="MPUserEntity" positionX="0" positionY="0" width="0" height="0"/>
</elements>
</model>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1487" systemVersion="12A269" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
<attribute name="loginName" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
<attribute name="requiresExplicitMigration_" attributeType="Boolean" defaultValueString="NO">
<userInfo/>
</attribute>
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" indexed="YES" syncable="YES"/>
<attribute name="version_" attributeType="Integer 16" minValueString="0" defaultValueString="0" syncable="YES"/>
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" syncable="YES"/>
</entity>
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
</entity>
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
</entity>
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<attribute name="defaultType_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
<attribute name="keyID" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="lastUsed" optional="YES" attributeType="Date" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="requiresExplicitMigration_" transient="YES" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO">
<userInfo/>
</attribute>
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
</entity>
<elements>
<element name="MPElementEntity" positionX="160" positionY="192" width="128" height="180"/>
<element name="MPElementGeneratedEntity" positionX="160" positionY="192" width="128" height="60"/>
<element name="MPElementStoredEntity" positionX="160" positionY="192" width="128" height="60"/>
<element name="MPUserEntity" positionX="160" positionY="192" width="128" height="150"/>
</elements>
</model>

View File

@ -220,67 +220,98 @@
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
__autoreleasing NSError *error;
__autoreleasing NSURLResponse *response;
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
returningResponse:&response error:&error];
if (error)
err(@"While reading imported sites from %@: %@", url, error);
if (!importedSitesData)
return NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
PearlAlert *activityAlert = [PearlAlert showAlertWithTitle:@"Importing" message:@"\n\n"
viewStyle:UIAlertViewStyleDefault initAlert:
^(UIAlertView *alert, UITextField *firstField) {
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityIndicator.center = CGPointMake(140, 90);
[activityIndicator startAnimating];
[alert addSubview:activityIndicator];
}
tappedButtonBlock:nil cancelTitle:nil otherTitles:nil];
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
[PearlAlert showAlertWithTitle:@"Import Password" message:
@"Enter the master password for this export:"
viewStyle:UIAlertViewStyleSecureTextInput initAlert:nil tappedButtonBlock:
^(UIAlertView *alert, NSInteger buttonIndex) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
MPImportResult result = [self importSites:importedSitesString withPassword:[alert textFieldAtIndex:0].text
askConfirmation:^BOOL(NSUInteger importCount, NSUInteger deleteCount) {
__block BOOL confirmation = NO;
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);
if (!importedSitesData)
return;
dispatch_group_t confirmationGroup = dispatch_group_create();
dispatch_group_enter(confirmationGroup);
dispatch_async(dispatch_get_main_queue(), ^{
[PearlAlert showAlertWithTitle:@"Import Sites?"
message:PearlString(
@"Import %d sites, overwriting %d existing sites?",
importCount, deleteCount)
viewStyle:UIAlertViewStyleDefault
initAlert:nil
tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
if (buttonIndex_
!= [alert_ cancelButtonIndex])
confirmation = YES;
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
__block NSString *masterPassword = nil;
dispatch_group_leave(confirmationGroup);
}
cancelTitle:[PearlStrings get].commonButtonCancel
otherTitles:@"Import", nil];
});
dispatch_group_wait(
confirmationGroup, DISPATCH_TIME_FOREVER);
dispatch_group_t importPasswordGroup = dispatch_group_create();
dispatch_group_enter(importPasswordGroup);
dispatch_async(dispatch_get_main_queue(), ^{
[PearlAlert showAlertWithTitle:@"Import File's Master Password"
message:PearlString(@"%@'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;
return confirmation;
}];
masterPassword = [alert_ textFieldAtIndex:0].text;
}
@finally {
dispatch_group_leave(importPasswordGroup);
}
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock Import", nil];
});
dispatch_group_wait(importPasswordGroup, DISPATCH_TIME_FOREVER);
switch (result) {
case MPImportResultSuccess:
case MPImportResultCancelled:
break;
case MPImportResultInternalError:
[PearlAlert showError:@"Import failed because of an internal error."];
break;
case MPImportResultMalformedInput:
[PearlAlert showError:@"The import doesn't look like a Master Password export."];
break;
case MPImportResultInvalidPassword:
[PearlAlert showError:@"Incorrect master password for the import sites."];
break;
}
});
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock File", nil];
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(), ^{
[PearlAlert showAlertWithTitle:PearlString(@"Master Password for\n%@", userName)
message:PearlString(@"Imports %d sites, overwriting %d.", importCount,
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);
}
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Import", nil];
});
dispatch_group_wait(userPasswordGroup, DISPATCH_TIME_FOREVER);
return masterPassword;
}];
switch (result) {
case MPImportResultSuccess:
case MPImportResultCancelled:
break;
case MPImportResultInternalError:
[PearlAlert showError:@"Import failed because of an internal error."];
break;
case MPImportResultMalformedInput:
[PearlAlert showError:@"The import doesn't look like a Master Password export."];
break;
case MPImportResultInvalidPassword:
[PearlAlert showError:@"Incorrect master password for the import sites."];
break;
}
[activityAlert dismissAlert];
});
return YES;
}

View File

@ -13,7 +13,7 @@
@interface MPMainViewController : UIViewController<MPTypeDelegate, UITextFieldDelegate, MPSearchResultsDelegate, UIWebViewDelegate, MFMailComposeViewControllerDelegate, UIGestureRecognizerDelegate>
@property (nonatomic, assign) BOOL userNameHidden;
@property (nonatomic, assign) BOOL siteInfoHidden;
@property (strong, nonatomic) MPElementEntity *activeElement;
@property (strong, nonatomic) IBOutlet MPSearchDelegate *searchDelegate;
@property (weak, nonatomic) IBOutlet UITextField *contentField;
@ -28,20 +28,20 @@
@property (weak, nonatomic) IBOutlet UIView *displayContainer;
@property (weak, nonatomic) IBOutlet UIView *helpContainer;
@property (weak, nonatomic) IBOutlet UIView *contentTipContainer;
@property (weak, nonatomic) IBOutlet UIView *userNameTipContainer;
@property (weak, nonatomic) IBOutlet UIView *loginNameTipContainer;
@property (weak, nonatomic) IBOutlet UIView *alertContainer;
@property (weak, nonatomic) IBOutlet UILabel *alertTitle;
@property (weak, nonatomic) IBOutlet UITextView *alertBody;
@property (weak, nonatomic) IBOutlet UILabel *contentTipBody;
@property (weak, nonatomic) IBOutlet UILabel *userNameTipBody;
@property (weak, nonatomic) IBOutlet UILabel *loginNameTipBody;
@property (weak, nonatomic) IBOutlet UIImageView *toolTipEditIcon;
@property (weak, nonatomic) IBOutlet UIView *searchTipContainer;
@property (weak, nonatomic) IBOutlet UIView *actionsTipContainer;
@property (weak, nonatomic) IBOutlet UIView *typeTipContainer;
@property (weak, nonatomic) IBOutlet UIView *toolTipContainer;
@property (weak, nonatomic) IBOutlet UILabel *toolTipBody;
@property (weak, nonatomic) IBOutlet UIView *userNameContainer;
@property (weak, nonatomic) IBOutlet UITextField *userNameField;
@property (weak, nonatomic) IBOutlet UIView *loginNameContainer;
@property (weak, nonatomic) IBOutlet UITextField *loginNameField;
@property (weak, nonatomic) IBOutlet UIButton *passwordUser;
@property (weak, nonatomic) IBOutlet UIView *outdatedAlertContainer;
@property (weak, nonatomic) IBOutlet UIImageView *outdatedAlertBack;
@ -54,7 +54,7 @@
- (IBAction)copyContent;
- (IBAction)incrementPasswordCounter;
- (IBAction)resetPasswordCounter:(UILongPressGestureRecognizer *)sender;
- (IBAction)editUserName:(UILongPressGestureRecognizer *)sender;
- (IBAction)editLoginName:(UILongPressGestureRecognizer *)sender;
- (IBAction)editPassword;
- (IBAction)closeAlert;
- (IBAction)upgradePassword;

View File

@ -14,7 +14,7 @@
@implementation MPMainViewController
@synthesize userNameHidden = _userNameHidden;
@synthesize siteInfoHidden = _siteInfoHidden;
@synthesize activeElement = _activeElement;
@synthesize searchDelegate = _searchDelegate;
@synthesize typeButton = _typeButton;
@ -28,20 +28,20 @@
@synthesize displayContainer = _displayContainer;
@synthesize helpContainer = _helpContainer;
@synthesize contentTipContainer = _copiedContainer;
@synthesize userNameTipContainer = _userNameTipContainer;
@synthesize loginNameTipContainer = _loginNameTipContainer;
@synthesize alertContainer = _alertContainer;
@synthesize alertTitle = _alertTitle;
@synthesize alertBody = _alertBody;
@synthesize contentTipBody = _contentTipBody;
@synthesize userNameTipBody = _userNameTipBody;
@synthesize loginNameTipBody = _loginNameTipBody;
@synthesize toolTipEditIcon = _contentTipEditIcon;
@synthesize searchTipContainer = _searchTipContainer;
@synthesize actionsTipContainer = _actionsTipContainer;
@synthesize typeTipContainer = _typeTipContainer;
@synthesize toolTipContainer = _toolTipContainer;
@synthesize toolTipBody = _toolTipBody;
@synthesize userNameContainer = _userNameContainer;
@synthesize userNameField = _userNameField;
@synthesize loginNameContainer = _loginNameContainer;
@synthesize loginNameField = _loginNameField;
@synthesize passwordUser = _passwordUser;
@synthesize outdatedAlertContainer = _outdatedAlertContainer;
@synthesize outdatedAlertBack = _outdatedAlertBack;
@ -82,9 +82,9 @@
[self.passwordIncrementer addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(resetPasswordCounter:)]];
[self.userNameContainer addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(editUserName:)]];
[self.userNameContainer addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(copyUserName:)]];
[self.loginNameContainer addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(editLoginName:)]];
[self.loginNameContainer addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(copyLoginName:)]];
[self.outdatedAlertBack addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(infoOutdatedAlert)]];
@ -225,10 +225,10 @@
[self setToolTipContainer:nil];
[self setToolTipBody:nil];
[self setDisplayContainer:nil];
[self setUserNameField:nil];
[self setUserNameTipContainer:nil];
[self setUserNameTipBody:nil];
[self setUserNameContainer:nil];
[self setLoginNameField:nil];
[self setLoginNameTipContainer:nil];
[self setLoginNameTipBody:nil];
[self setLoginNameContainer:nil];
[self setPasswordUser:nil];
[self setOutdatedAlertContainer:nil];
[self setOutdatedAlertCloseButton:nil];
@ -289,9 +289,9 @@
});
});
self.userNameField.enabled = NO;
self.userNameField.text = self.activeElement.userName;
self.userNameHidden = !self.activeElement || ([[MPiOSConfig get].userNameHidden boolValue] && (self.activeElement.userName
self.loginNameField.enabled = NO;
self.loginNameField.text = self.activeElement.loginName;
self.siteInfoHidden = !self.activeElement || ([[MPiOSConfig get].siteInfoHidden boolValue] && (self.activeElement.loginName
== nil));
[self updateUserHiddenAnimated:NO];
}
@ -333,8 +333,8 @@
- (void)toggleUserAnimated:(BOOL)animated {
[MPiOSConfig get].userNameHidden = PearlBool(!self.userNameHidden);
self.userNameHidden = [[MPiOSConfig get].userNameHidden boolValue];
[MPiOSConfig get].siteInfoHidden = PearlBool(!self.siteInfoHidden);
self.siteInfoHidden = [[MPiOSConfig get].siteInfoHidden boolValue];
[self updateUserHiddenAnimated:animated];
}
@ -347,7 +347,7 @@
return;
}
if (self.userNameHidden) {
if (self.siteInfoHidden) {
self.displayContainer.frame = CGRectSetHeight(self.displayContainer.frame, 87);
} else {
self.displayContainer.frame = CGRectSetHeight(self.displayContainer.frame, 137);
@ -402,19 +402,19 @@
});
}
- (void)showUserNameTip:(NSString *)message {
- (void)showLoginNameTip:(NSString *)message {
dispatch_async(dispatch_get_main_queue(), ^{
self.userNameTipBody.text = message;
self.loginNameTipBody.text = message;
[UIView animateWithDuration:0.3f animations:^{
self.userNameTipContainer.alpha = 1;
self.loginNameTipContainer.alpha = 1;
} completion:^(BOOL finished) {
if (finished) {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
[UIView animateWithDuration:0.2f animations:^{
self.userNameTipContainer.alpha = 0;
self.loginNameTipContainer.alpha = 0;
}];
});
}
@ -488,18 +488,18 @@
nil]];
}
- (IBAction)copyUserName:(UITapGestureRecognizer *)sender {
- (IBAction)copyLoginName:(UITapGestureRecognizer *)sender {
if (!self.activeElement.userName)
if (!self.activeElement.loginName)
return;
inf(@"Copying user name for: %@", self.activeElement.name);
[UIPasteboard generalPasteboard].string = [self.activeElement.content description];
[self showUserNameTip:@"Copied!"];
[self showLoginNameTip:@"Copied!"];
[TestFlight passCheckpoint:MPCheckpointCopyUserNameToPasteboard];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyUserNameToPasteboard
[TestFlight passCheckpoint:MPCheckpointCopyLoginNameToPasteboard];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointCopyLoginNameToPasteboard
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.activeElement.typeName, @"type",
PearlUnsignedInteger(self.activeElement.version),
@ -564,7 +564,7 @@
}];
}
- (IBAction)editUserName:(UILongPressGestureRecognizer *)sender {
- (IBAction)editLoginName:(UILongPressGestureRecognizer *)sender {
if (sender.state != UIGestureRecognizerStateBegan)
// Only fire when the gesture was first detected.
@ -573,11 +573,11 @@
if (!self.activeElement)
return;
self.userNameField.enabled = YES;
[self.userNameField becomeFirstResponder];
self.loginNameField.enabled = YES;
[self.loginNameField becomeFirstResponder];
[TestFlight passCheckpoint:MPCheckpointEditUserName];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditUserName attributes:[NSDictionary dictionaryWithObjectsAndKeys:
[TestFlight passCheckpoint:MPCheckpointEditLoginName];
[[LocalyticsSession sharedLocalyticsSession] tagEvent:MPCheckpointEditLoginName attributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.activeElement.typeName,
@"type",
PearlUnsignedInteger(self.activeElement.version),
@ -903,8 +903,8 @@
if (textField == self.contentField)
[self.contentField resignFirstResponder];
if (textField == self.userNameField)
[self.userNameField resignFirstResponder];
if (textField == self.loginNameField)
[self.loginNameField resignFirstResponder];
return YES;
}
@ -926,17 +926,17 @@
}];
}
if (textField == self.userNameField) {
self.userNameField.enabled = NO;
if (![[MPiOSConfig get].userNameTipShown boolValue]) {
[self showUserNameTip:@"Tap to copy or hold to edit."];
[MPiOSConfig get].userNameTipShown = PearlBool(YES);
if (textField == self.loginNameField) {
self.loginNameField.enabled = NO;
if (![[MPiOSConfig get].loginNameTipShown boolValue]) {
[self showLoginNameTip:@"Tap to copy or hold to edit."];
[MPiOSConfig get].loginNameTipShown = PearlBool(YES);
}
if ([self.userNameField.text length])
self.activeElement.userName = self.userNameField.text;
if ([self.loginNameField.text length])
self.activeElement.loginName = self.loginNameField.text;
else
self.activeElement.userName = nil;
self.activeElement.loginName = nil;
[[MPAppDelegate get] saveContext];
}

View File

@ -218,9 +218,15 @@
self.tip.text = @"Tap and hold to delete or reset.";
[self.loadingUsersIndicator stopAnimating];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO]];
NSArray *users = [moc executeFetchRequest:fetchRequest error:nil];
__block NSArray *users = nil;
[moc performBlockAndWait:^{
NSError *error = nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([MPUserEntity class])];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO]];
users = [moc executeFetchRequest:fetchRequest error:&error];
if (!users)
err(@"Failed to load users: %@", error);
}];
// Clean up avatars.
for (UIView *subview in [self.avatarsView subviews])
@ -275,8 +281,10 @@
[self.avatarToUser setObject:NilToNSNull(user) forKey:[NSValue valueWithNonretainedObject:avatar]];
if (self.selectedUser && user == self.selectedUser)
if ([self.selectedUser.objectID isEqual:user.objectID]) {
self.selectedUser = user;
avatar.selected = YES;
}
return avatar;
}

View File

@ -12,11 +12,11 @@
@property (nonatomic, retain) NSNumber *sendInfo;
@property (nonatomic, retain) NSNumber *helpHidden;
@property (nonatomic, retain) NSNumber *userNameHidden;
@property (nonatomic, retain) NSNumber *siteInfoHidden;
@property (nonatomic, retain) NSNumber *showQuickStart;
@property (nonatomic, retain) NSNumber *actionsTipShown;
@property (nonatomic, retain) NSNumber *typeTipShown;
@property (nonatomic, retain) NSNumber *userNameTipShown;
@property (nonatomic, retain) NSNumber *loginNameTipShown;
+ (MPiOSConfig *)get;

View File

@ -7,7 +7,7 @@
//
@implementation MPiOSConfig
@dynamic sendInfo, helpHidden, userNameHidden, showQuickStart, actionsTipShown, typeTipShown, userNameTipShown;
@dynamic sendInfo, helpHidden, siteInfoHidden, showQuickStart, actionsTipShown, typeTipShown, loginNameTipShown;
- (id)init {
@ -17,12 +17,12 @@
[self.defaults registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(sendInfo)),
[NSNumber numberWithBool:NO], NSStringFromSelector(@selector(helpHidden)),
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(userNameHidden)),
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(siteInfoHidden)),
[NSNumber numberWithBool:YES], NSStringFromSelector(@selector(showQuickStart)),
@"510296984", NSStringFromSelector(@selector(iTunesID)),
PearlBoolNot(self.firstRun), NSStringFromSelector(@selector(actionsTipShown)),
PearlBoolNot(self.firstRun), NSStringFromSelector(@selector(typeTipShown)),
PearlBool(NO), NSStringFromSelector(@selector(userNameTipShown)),
PearlBool(NO), NSStringFromSelector(@selector(loginNameTipShown)),
nil]];
return self;

View File

@ -811,12 +811,12 @@ L4m3P4sSw0rD</string>
<nil key="highlightedColor"/>
</label>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" editable="NO" id="HPZ-qz-fpL">
<rect key="frame" x="20" y="60" width="260" height="130"/>
<rect key="frame" x="20" y="52" width="260" height="108"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<string key="text">Some of your sites have outdated passwords. Tap this alert for more info or to find them.
<string key="text">Some of your sites use outdated passwords. Tap this alert for more info or to find them.
You should upgrade these sites and update their account's passwords as soon as is convenient.</string>
These sites should be upgraded and their account's passwords updated as soon as convenient.</string>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
@ -854,7 +854,7 @@ You should upgrade these sites and update their account's passwords as soon as i
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="Sfy-hx-kVU">
<rect key="frame" x="184" y="81" width="22" height="22"/>
<rect key="frame" x="183" y="73" width="22" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" hint="" label="Close"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
@ -911,10 +911,10 @@ You should upgrade these sites and update their account's passwords as soon as i
<outlet property="toolTipEditIcon" destination="KEn-n3-qhX" id="zEV-tR-Egq"/>
<outlet property="typeButton" destination="xYM-e3-BMg" id="xvn-nR-MRV"/>
<outlet property="typeTipContainer" destination="g55-0m-WjS" id="KZ9-KV-NMh"/>
<outlet property="userNameContainer" destination="lrZ-PV-rgu" id="tTh-fp-qaN"/>
<outlet property="userNameField" destination="tj6-Ot-Fuh" id="oFa-xd-UEm"/>
<outlet property="userNameTipBody" destination="coX-1P-dqm" id="MRt-A5-ZMH"/>
<outlet property="userNameTipContainer" destination="b9W-aA-93r" id="heZ-Gu-obO"/>
<outlet property="loginNameContainer" destination="lrZ-PV-rgu" id="tTh-fp-qaN"/>
<outlet property="loginNameField" destination="tj6-Ot-Fuh" id="oFa-xd-UEm"/>
<outlet property="loginNameTipBody" destination="coX-1P-dqm" id="MRt-A5-ZMH"/>
<outlet property="loginNameTipContainer" destination="b9W-aA-93r" id="heZ-Gu-obO"/>
<segue destination="oLN-6u-GLb" kind="push" identifier="UserProfile" id="tKN-Sw-S5J"/>
</connections>
</viewController>
@ -1890,10 +1890,10 @@ You could use the word wall for inspiration in finding a memorable master passw
<relationship kind="outlet" name="toolTipEditIcon" candidateClass="UIImageView"/>
<relationship kind="outlet" name="typeButton" candidateClass="UIButton"/>
<relationship kind="outlet" name="typeTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="userNameContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="userNameField" candidateClass="UITextField"/>
<relationship kind="outlet" name="userNameTipBody" candidateClass="UILabel"/>
<relationship kind="outlet" name="userNameTipContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="loginNameContainer" candidateClass="UIView"/>
<relationship kind="outlet" name="loginNameField" candidateClass="UITextField"/>
<relationship kind="outlet" name="loginNameTipBody" candidateClass="UILabel"/>
<relationship kind="outlet" name="loginNameTipContainer" candidateClass="UIView"/>
</relationships>
</class>
<class className="MPPreferencesViewController" superclassName="UITableViewController">
@ -1951,6 +1951,6 @@ You could use the word wall for inspiration in finding a memorable master passw
<simulatedScreenMetrics key="destination"/>
</simulatedMetricsContainer>
<inferredMetricsTieBreakers>
<segue reference="KIl-ZW-M7G"/>
<segue reference="9Bs-cD-ddF"/>
</inferredMetricsTieBreakers>
</document>
</document>