diff --git a/Crashlytics/Crashlytics.framework/Versions/A/Crashlytics b/Crashlytics/Crashlytics.framework/Versions/A/Crashlytics index 8ab5c6fd..8a09fe3c 100644 Binary files a/Crashlytics/Crashlytics.framework/Versions/A/Crashlytics and b/Crashlytics/Crashlytics.framework/Versions/A/Crashlytics differ diff --git a/Crashlytics/Crashlytics.framework/Versions/A/Resources/Info.plist b/Crashlytics/Crashlytics.framework/Versions/A/Resources/Info.plist index 33687451..975d2452 100644 Binary files a/Crashlytics/Crashlytics.framework/Versions/A/Resources/Info.plist and b/Crashlytics/Crashlytics.framework/Versions/A/Resources/Info.plist differ diff --git a/External/Pearl b/External/Pearl index cc97c1be..5a35cdb7 160000 --- a/External/Pearl +++ b/External/Pearl @@ -1 +1 @@ -Subproject commit cc97c1be4c0f2ee0080af417940244ac8f3e5f47 +Subproject commit 5a35cdb73c57c65f22959accab2f6ddbeeee0b33 diff --git a/MasterPassword-iOS.xcodeproj/project.pbxproj b/MasterPassword-iOS.xcodeproj/project.pbxproj index 895c96ec..1674e1e4 100644 --- a/MasterPassword-iOS.xcodeproj/project.pbxproj +++ b/MasterPassword-iOS.xcodeproj/project.pbxproj @@ -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 = ""; }; DA0E07941577FE490008A67E /* MPEntities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEntities.h; sourceTree = ""; }; DA0E07951577FE490008A67E /* MPEntities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEntities.m; sourceTree = ""; }; - DA0F9F3115B55397007ED9BC /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = ""; }; - DA0F9F3215B55397007ED9BC /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = ""; }; - DA0F9F3415B55397007ED9BC /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = ""; }; - DA0F9F3515B55397007ED9BC /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = ""; }; - DA0F9F3715B55397007ED9BC /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = ""; }; - DA0F9F3815B55397007ED9BC /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = ""; }; - DA0F9F3A15B55397007ED9BC /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = ""; }; - DA0F9F3B15B55397007ED9BC /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = ""; }; DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = ""; }; DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+PearlMutableInfo.m"; sourceTree = ""; }; DA30E9CD15722ECA00A68B4C /* Pearl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pearl.m; sourceTree = ""; }; @@ -998,6 +990,14 @@ DA95D5CD14DF0691008D1B94 /* IASKPSToggleSwitchSpecifierViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKPSToggleSwitchSpecifierViewCell.xib; sourceTree = ""; }; DA95D5CE14DF0691008D1B94 /* IASKSpecifierValuesView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = IASKSpecifierValuesView.xib; sourceTree = ""; }; 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 = ""; }; + DAA096FB15E0C59B00912D63 /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = ""; }; + DAA096FD15E0C59B00912D63 /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = ""; }; + DAA096FE15E0C59B00912D63 /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = ""; }; + DAA0970015E0C59B00912D63 /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = ""; }; + DAA0970115E0C59B00912D63 /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = ""; }; + DAA0970315E0C59B00912D63 /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = ""; }; + DAA0970415E0C59B00912D63 /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = ""; }; 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 = ""; }; DAB8D44015036BCF00CED3BC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -1737,6 +1737,7 @@ DAE4C967157E63BE00EFE047 /* avatar-18.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-18.png"; sourceTree = ""; }; DAE4C968157E63BE00EFE047 /* avatar-18@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-18@2x.png"; sourceTree = ""; }; 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 = ""; }; DAFE45D815039823003ABA7C /* NSObject+PearlExport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+PearlExport.h"; sourceTree = ""; }; DAFE45D915039823003ABA7C /* NSObject+PearlExport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+PearlExport.m"; sourceTree = ""; }; DAFE45DA15039823003ABA7C /* NSString+PearlNSArrayFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+PearlNSArrayFormat.h"; sourceTree = ""; }; @@ -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 = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword (Development).xcscheme b/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword (Development).xcscheme index 33ed0f7c..fab2b241 100644 --- a/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword (Development).xcscheme +++ b/MasterPassword-iOS.xcodeproj/xcshareddata/xcschemes/MasterPassword (Development).xcscheme @@ -50,13 +50,14 @@ 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]; diff --git a/MasterPassword/MPElementEntity.h b/MasterPassword/MPElementEntity.h index 5fd8e213..a1f94a55 100644 --- a/MasterPassword/MPElementEntity.h +++ b/MasterPassword/MPElementEntity.h @@ -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; diff --git a/MasterPassword/MPElementEntity.m b/MasterPassword/MPElementEntity.m index 0429acab..f0abff20 100644 --- a/MasterPassword/MPElementEntity.m +++ b/MasterPassword/MPElementEntity.m @@ -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; diff --git a/MasterPassword/MPElementGeneratedEntity.h b/MasterPassword/MPElementGeneratedEntity.h index e4ff4bc8..a40a7658 100644 --- a/MasterPassword/MPElementGeneratedEntity.h +++ b/MasterPassword/MPElementGeneratedEntity.h @@ -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. // diff --git a/MasterPassword/MPElementGeneratedEntity.m b/MasterPassword/MPElementGeneratedEntity.m index c8aa616b..d59f8de4 100644 --- a/MasterPassword/MPElementGeneratedEntity.m +++ b/MasterPassword/MPElementGeneratedEntity.m @@ -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. // diff --git a/MasterPassword/MPElementStoredEntity.h b/MasterPassword/MPElementStoredEntity.h index 061fec53..2a4f571c 100644 --- a/MasterPassword/MPElementStoredEntity.h +++ b/MasterPassword/MPElementStoredEntity.h @@ -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. // diff --git a/MasterPassword/MPElementStoredEntity.m b/MasterPassword/MPElementStoredEntity.m index b972de5a..36441c54 100644 --- a/MasterPassword/MPElementStoredEntity.m +++ b/MasterPassword/MPElementStoredEntity.m @@ -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. // diff --git a/MasterPassword/MPEntities.h b/MasterPassword/MPEntities.h index bc05539a..787582d8 100644 --- a/MasterPassword/MPEntities.h +++ b/MasterPassword/MPEntities.h @@ -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; diff --git a/MasterPassword/MPEntities.m b/MasterPassword/MPEntities.m index 1a39ba71..c796194c 100644 --- a/MasterPassword/MPEntities.m +++ b/MasterPassword/MPEntities.m @@ -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 { diff --git a/MasterPassword/MPTypes.h b/MasterPassword/MPTypes.h index c2892868..a008af51 100644 --- a/MasterPassword/MPTypes.h +++ b/MasterPassword/MPTypes.h @@ -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" diff --git a/MasterPassword/MPUserEntity.h b/MasterPassword/MPUserEntity.h index 0bcbb6ff..8284c637 100644 --- a/MasterPassword/MPUserEntity.h +++ b/MasterPassword/MPUserEntity.h @@ -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 diff --git a/MasterPassword/MPUserEntity.m b/MasterPassword/MPUserEntity.m index e584c806..3612d715 100644 --- a/MasterPassword/MPUserEntity.m +++ b/MasterPassword/MPUserEntity.m @@ -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 diff --git a/MasterPassword/MasterPassword.xcdatamodeld/.xccurrentversion b/MasterPassword/MasterPassword.xcdatamodeld/.xccurrentversion index d9f27547..7a0e27df 100644 --- a/MasterPassword/MasterPassword.xcdatamodeld/.xccurrentversion +++ b/MasterPassword/MasterPassword.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MasterPassword 2.xcdatamodel + MasterPassword 3.xcdatamodel diff --git a/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 2.xcdatamodel/contents b/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 2.xcdatamodel/contents index 9d55f2e0..e3c92a0d 100644 --- a/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 2.xcdatamodel/contents +++ b/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 2.xcdatamodel/contents @@ -32,9 +32,9 @@ - - - - + + + + \ No newline at end of file diff --git a/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 3.xcdatamodel/contents b/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 3.xcdatamodel/contents new file mode 100644 index 00000000..a724b37a --- /dev/null +++ b/MasterPassword/MasterPassword.xcdatamodeld/MasterPassword 3.xcdatamodel/contents @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MasterPassword/iOS/MPAppDelegate.m b/MasterPassword/iOS/MPAppDelegate.m index 57bf674f..c6e30d63 100644 --- a/MasterPassword/iOS/MPAppDelegate.m +++ b/MasterPassword/iOS/MPAppDelegate.m @@ -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; } diff --git a/MasterPassword/iOS/MPMainViewController.h b/MasterPassword/iOS/MPMainViewController.h index b9884e09..b60e7738 100644 --- a/MasterPassword/iOS/MPMainViewController.h +++ b/MasterPassword/iOS/MPMainViewController.h @@ -13,7 +13,7 @@ @interface MPMainViewController : UIViewController -@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; diff --git a/MasterPassword/iOS/MPMainViewController.m b/MasterPassword/iOS/MPMainViewController.m index 2de8f9f1..d51d324f 100644 --- a/MasterPassword/iOS/MPMainViewController.m +++ b/MasterPassword/iOS/MPMainViewController.m @@ -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]; } diff --git a/MasterPassword/iOS/MPUnlockViewController.m b/MasterPassword/iOS/MPUnlockViewController.m index c358265c..2709bbd2 100644 --- a/MasterPassword/iOS/MPUnlockViewController.m +++ b/MasterPassword/iOS/MPUnlockViewController.m @@ -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; } diff --git a/MasterPassword/iOS/MPiOSConfig.h b/MasterPassword/iOS/MPiOSConfig.h index 673f806c..f74ea05e 100644 --- a/MasterPassword/iOS/MPiOSConfig.h +++ b/MasterPassword/iOS/MPiOSConfig.h @@ -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; diff --git a/MasterPassword/iOS/MPiOSConfig.m b/MasterPassword/iOS/MPiOSConfig.m index 0a28e32b..239d95ce 100644 --- a/MasterPassword/iOS/MPiOSConfig.m +++ b/MasterPassword/iOS/MPiOSConfig.m @@ -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; diff --git a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard index 8d14e069..83fb207c 100644 --- a/MasterPassword/iOS/MainStoryboard_iPhone.storyboard +++ b/MasterPassword/iOS/MainStoryboard_iPhone.storyboard @@ -811,12 +811,12 @@ L4m3P4sSw0rD - + - Some of your sites have outdated passwords. Tap this alert for more info or to find them. + 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. +These sites should be upgraded and their account's passwords updated as soon as convenient. @@ -854,7 +854,7 @@ You should upgrade these sites and update their account's passwords as soon as i