2
0

Compare commits

...

38 Commits
2.1 ... 2.1-b2

Author SHA1 Message Date
Maarten Billemont
f3f25f5890 Add question about hiding passwords to setup flow. 2014-09-28 22:57:38 -04:00
Maarten Billemont
3065433a37 Stop showing thanks tips after user opens thanks page. 2014-09-28 22:25:48 -04:00
Maarten Billemont
41b3964363 Lots of UI improvements and tips + parental gate, guide update.
[IMPROVED]  Emergency VC can now scroll when keyboard is up.
[IMPROVED]  Language of the guide + new updated screenshots.
[FIXED]     Size of guide cells on different devices.
[IMPROVED]  Don't show messages claiming login name was updated when nothing changed.
[FIXED]     Weird back-toggle bug when toggling site settings.
[ADDED]     Lots of handy tips throughout.
[ADDED]     Notification of new store features.
[FIXED]     Weird sizing issue & animation with store cells.
[ADDED]     Loading spinner while loading store products.
[ADDED]     Thanks link to store footer.
[FIXED]     Bought products should not respond to click, non-bought ones should.
[FIXED]     Fuel elapsed time counter was backward.
[ADDED]     Parental gate when deleting or resetting users.
[UPDATED]   App Icon background texture.
2014-09-28 22:15:55 -04:00
Maarten Billemont
5e8810c535 Dismiss popdown on sign out, fuel check date not recorded, avatar improvements. 2014-09-28 10:05:36 -04:00
Maarten Billemont
8c3dfc8510 Update of icon and launch image + background for iPhone 6 / 6+ 2014-09-28 01:53:50 -04:00
Maarten Billemont
b4b9ee3cb9 Import sites from pasteboard. 2014-09-27 20:29:58 -04:00
Maarten Billemont
da4bad7977 Fix a few build warnings, two-way site question relationship. 2014-09-27 16:30:17 -04:00
Maarten Billemont
984434cca4 Remove header hack to inset password cells, use collection layout instead. 2014-09-27 14:53:30 -04:00
Maarten Billemont
064122f36d Build fixes. 2014-09-27 12:52:17 -04:00
Maarten Billemont
5db083bf7c Improve notification registration and cleanup + fix removal of site questions. 2014-09-27 01:27:05 -04:00
Maarten Billemont
44f91e0618 Merge branch 'master' of github.com:Lyndir/MasterPassword 2014-09-26 00:32:19 -04:00
Maarten Billemont
6050b5d6fd Development fuel, store improvements and navigation fixes. 2014-09-26 00:32:07 -04:00
Maarten Billemont
8e3e77c2c1 Remove UbiquityStoreManager 2014-09-24 16:04:35 -04:00
Maarten Billemont
a2e71aa94d Merge branch 'master' of github.com:Lyndir/MasterPassword
Conflicts:
	MasterPassword/ObjC/iOS/MPPasswordsViewController.m
2014-09-24 16:02:29 -04:00
Maarten Billemont
a5bc2eb584 Store product thumbnails. 2014-09-24 08:00:10 -04:00
Maarten Billemont
9bb613a3b6 MPAlgorithm V2: handle multi-byte UTF-8 correctly by counting bytes, not characters. 2014-09-24 07:58:23 -04:00
Maarten Billemont
466863f8fd Improved overlay navigation, store refactoring and automatic sizing of store cells. 2014-09-24 01:07:02 -04:00
Maarten Billemont
fe5828c724 Fix removal of questions. Blast you Core Data. 2014-09-22 22:32:31 -04:00
Maarten Billemont
b3ec7a848d Make answers VC a pop-over. 2014-09-22 08:48:51 -04:00
Maarten Billemont
17734652b4 Completed answers generation. 2014-09-21 23:48:49 -04:00
Maarten Billemont
9e742fa40f Use fullDescription for all error logging. 2014-09-21 23:28:50 -04:00
Maarten Billemont
d03b1746e0 Handle failure to load store. 2014-09-21 23:11:05 -04:00
Maarten Billemont
58156be793 Generating security question answers for sites. 2014-09-21 22:45:21 -04:00
Maarten Billemont
d5a5cd7de4 Fix a few issues after element->site rename. 2014-09-21 14:09:43 -04:00
Maarten Billemont
2100662fb3 Add a model version for MPSiteQuestionEntity and element->site renames. 2014-09-21 13:56:37 -04:00
Maarten Billemont
248627aa92 Project cleanup. 2014-09-21 13:45:33 -04:00
Maarten Billemont
449ccaa3d4 Storyboard fixups. 2014-09-21 13:39:47 -04:00
Maarten Billemont
0a7465282b Prepare generate answers product. 2014-09-21 12:54:48 -04:00
Maarten Billemont
5b85ba3a4b Element -> Site 2014-09-21 11:47:53 -04:00
Maarten Billemont
b3a0b6a7c0 Element -> Site, site security question answers 2014-09-21 10:49:57 -04:00
Maarten Billemont
4396ce436e Element -> Site WIP 2014-09-21 10:39:09 -04:00
Maarten Billemont
68e6106ee7 Extract In-App logic into app delegate category & improvements to import file handling and advanced export + store fixes. 2014-09-21 10:29:18 -04:00
Maarten Billemont
4c12f368f5 Ability to generate pass phrases as well as names. 2014-09-21 01:57:45 -04:00
Maarten Billemont
0156f8c3c8 More store work.
[FIXED]     A strange issue with reloading password cells.
[FIXED]     Product identifiers and showing the first product in the store.
[ADDED]     Restoring purchases made from other devices.
[REMOVED]   iCloud entitlements.
2014-09-17 20:59:03 -04:00
Maarten Billemont
2e5cbac761 Added in-app purchase store and made generated logins a product. 2014-09-17 02:00:33 -04:00
Maarten Billemont
a043b7c049 Fixes to new store loading if not migrated. 2014-09-16 07:53:31 -04:00
Maarten Billemont
06c62f70ed Fixes to V0 algorithm debug log output. 2014-09-16 00:45:39 -04:00
Maarten Billemont
bc88daf08d Don't use old section info from an old NSFetchedResultsSectionInfo object. 2014-09-02 14:51:04 -04:00
241 changed files with 6198 additions and 3061 deletions

9
.gitmodules vendored
View File

@@ -4,9 +4,12 @@
[submodule "External/InAppSettingsKit"]
path = External/InAppSettingsKit
url = git://github.com/lhunath/InAppSettingsKit.git
[submodule "External/UbiquityStoreManager"]
path = External/UbiquityStoreManager
url = git://github.com/lhunath/UbiquityStoreManager.git
[submodule "External/RHStatusItemView"]
path = External/RHStatusItemView
url = git://github.com/lhunath/RHStatusItemView.git
[submodule "External/KCOrderedAccessorFix"]
path = External/KCOrderedAccessorFix
url = https://github.com/CFKevinRef/KCOrderedAccessorFix.git
[submodule "External/AttributedMarkdown"]
path = External/AttributedMarkdown
url = https://github.com/dreamwieber/AttributedMarkdown.git

1
External/AttributedMarkdown vendored Submodule

2
External/Pearl vendored

View File

@@ -18,8 +18,12 @@
<string>github.com:Lyndir/Lyndir.git</string>
<key>2FE140B36B7D26140DC8D5E5C639DC5900EFCF35</key>
<string>git://github.com/lhunath/uicolor-utilities.git</string>
<key>304AD0F97EA7B4893D91DFB8C3413D4E627B9472</key>
<string>https://github.com/CFKevinRef/KCOrderedAccessorFix.git</string>
<key>3E67FB08419C920516AAC3B00DAAF23073B8CF77</key>
<string>git://github.com/lhunath/RHStatusItemView.git</string>
<key>3ED8592497DB6A564366943C9AAD5A46341B5076</key>
<string>https://github.com/dreamwieber/AttributedMarkdown.git</string>
<key>4DDCFFD91B41F00326AD14553BD66CFD366ABD91</key>
<string>ssh://github.com/Lyndir/Pearl.git</string>
<key>8A15A8EA0B3D0B497C4883425BC74DF995224BB3</key>
@@ -41,8 +45,12 @@
<string>../..</string>
<key>2FE140B36B7D26140DC8D5E5C639DC5900EFCF35</key>
<string>../External/Pearl/External/uicolor-utilities</string>
<key>304AD0F97EA7B4893D91DFB8C3413D4E627B9472</key>
<string>../External/KCOrderedAccessorFix</string>
<key>3E67FB08419C920516AAC3B00DAAF23073B8CF77</key>
<string>../External/RHStatusItemView</string>
<key>3ED8592497DB6A564366943C9AAD5A46341B5076</key>
<string>../External/AttributedMarkdown/</string>
<key>4DDCFFD91B41F00326AD14553BD66CFD366ABD91</key>
<string>../External/Pearl</string>
<key>8A15A8EA0B3D0B497C4883425BC74DF995224BB3</key>
@@ -68,6 +76,14 @@
<key>IDESourceControlWCCName</key>
<string></string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>3ED8592497DB6A564366943C9AAD5A46341B5076</string>
<key>IDESourceControlWCCName</key>
<string>AttributedMarkdown</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
@@ -84,6 +100,14 @@
<key>IDESourceControlWCCName</key>
<string>jrswizzle</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>304AD0F97EA7B4893D91DFB8C3413D4E627B9472</string>
<key>IDESourceControlWCCName</key>
<string>KCOrderedAccessorFix</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>

View File

@@ -44,8 +44,9 @@ void usage() {
" m, med, medium | Copy-friendly, 8 characters, contains symbols.\n"
" b, basic | 8 characters, no symbols.\n"
" s, short | Copy-friendly, 4 characters, no symbols.\n"
" p, pin | 4 numbers.\n"
" n, name | 9 letter name.\n\n", MP_env_sitetype);
" i, pin | 4 numbers.\n"
" n, name | 9 letter name.\n"
" p, phrase | 20 character sentence.\n\n", MP_env_sitetype);
fprintf(stderr, " -c counter The value of the counter.\n"
" Defaults to %s in env or '1'.\n\n", MP_env_sitecounter);
fprintf(stderr, " -v variant The kind of content to generate.\n"

View File

@@ -31,10 +31,12 @@ const MPElementType TypeWithName(const char *typeName) {
return MPElementTypeGeneratedBasic;
if (0 == strcmp(lowerTypeName, "s") || 0 == strcmp(lowerTypeName, "short"))
return MPElementTypeGeneratedShort;
if (0 == strcmp(lowerTypeName, "p") || 0 == strcmp(lowerTypeName, "pin"))
if (0 == strcmp(lowerTypeName, "i") || 0 == strcmp(lowerTypeName, "pin"))
return MPElementTypeGeneratedPIN;
if (0 == strcmp(lowerTypeName, "n") || 0 == strcmp(lowerTypeName, "name"))
return MPElementTypeGeneratedName;
if (0 == strcmp(lowerTypeName, "p") || 0 == strcmp(lowerTypeName, "phrase"))
return MPElementTypeGeneratedPhrase;
fprintf(stderr, "Not a generated type name: %s", lowerTypeName);
abort();
@@ -72,6 +74,10 @@ const char *CipherForType(MPElementType type, uint8_t seedByte) {
case MPElementTypeGeneratedName: {
return "cvccvcvcv";
}
case MPElementTypeGeneratedPhrase: {
char *ciphers[] = { "cvcc cvc cvccvcv cvc", "cvc cvccvcvcv cvcv", "cv cvccv cvc cvcvccv" };
return ciphers[seedByte % 3];
}
default: {
fprintf(stderr, "Unknown generated type: %d", type);
abort();
@@ -148,6 +154,10 @@ const char CharacterFromClass(char characterClass, uint8_t seedByte) {
classCharacters = "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()";
break;
}
case ' ': {
classCharacters = " ";
break;
}
default: {
fprintf(stderr, "Unknown character class: %c", characterClass);
abort();

View File

@@ -34,7 +34,8 @@ typedef enum {
MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedName = 0xF | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedName = 0xE | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedPhrase = 0xF | MPElementTypeClassGenerated | 0x0,
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,

View File

@@ -16,10 +16,11 @@
//
#import "MPKey.h"
#import "MPElementStoredEntity.h"
#import "MPElementGeneratedEntity.h"
#import "MPStoredSiteEntity.h"
#import "MPGeneratedSiteEntity.h"
#import "MPSiteQuestionEntity.h"
#define MPAlgorithmDefaultVersion 1
#define MPAlgorithmDefaultVersion 2
#define MPAlgorithmDefault MPAlgorithmForVersion(MPAlgorithmDefaultVersion)
id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version);
@@ -43,49 +44,56 @@ NSString *NSStringFromTimeToCrack(TimeToCrack timeToCrack);
@required
- (NSUInteger)version;
- (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit;
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc;
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit;
- (MPKey *)keyForPassword:(NSString *)password ofUserNamed:(NSString *)userName;
- (MPKey *)keyFromKeyData:(NSData *)keyData;
- (NSData *)keyIDForKeyData:(NSData *)keyData;
- (NSString *)scopeForVariant:(MPElementVariant)variant;
- (NSString *)nameOfType:(MPElementType)type;
- (NSString *)shortNameOfType:(MPElementType)type;
- (NSString *)classNameOfType:(MPElementType)type;
- (Class)classOfType:(MPElementType)type;
- (NSString *)scopeForVariant:(MPSiteVariant)variant;
- (NSString *)nameOfType:(MPSiteType)type;
- (NSString *)shortNameOfType:(MPSiteType)type;
- (NSString *)classNameOfType:(MPSiteType)type;
- (Class)classOfType:(MPSiteType)type;
- (NSArray *)allTypes;
- (NSArray *)allTypesStartingWith:(MPElementType)startingType;
- (MPElementType)nextType:(MPElementType)type;
- (MPElementType)previousType:(MPElementType)type;
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType;
- (MPSiteType)nextType:(MPSiteType)type;
- (MPSiteType)previousType:(MPSiteType)type;
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key;
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
usingKey:(MPKey *)key;
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter
variant:(MPElementVariant)variant usingKey:(MPKey *)key;
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key;
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key;
- (NSString *)storedLoginForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key;
- (NSString *)storedPasswordForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key;
- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key;
- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key;
- (BOOL)savePassword:(NSString *)clearPassword toElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
- (BOOL)savePassword:(NSString *)clearPassword toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (NSString *)resolveLoginForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
- (NSString *)resolvePasswordForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey;
- (void)resolveLoginForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
result:(void ( ^ )(NSString *result))resultBlock;
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
result:(void ( ^ )(NSString *result))resultBlock;
- (void)resolvePasswordForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey
result:(void ( ^ )(NSString *result))resultBlock;
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey
result:(void ( ^ )(NSString *result))resultBlock;
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
result:(void ( ^ )(NSString *result))resultBlock;
- (void)importProtectedPassword:(NSString *)protectedPassword protectedByKey:(MPKey *)importKey
intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
- (void)importClearTextPassword:(NSString *)clearPassword intoElement:(MPElementEntity *)element
usingKey:(MPKey *)elementKey;
- (NSString *)exportPasswordForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (void)importClearTextPassword:(NSString *)clearPassword intoSite:(MPSiteEntity *)site
usingKey:(MPKey *)siteKey;
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey;
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPElementType)type byAttacker:(MPAttacker)attacker;
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker;
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordString:(NSString *)password byAttacker:(MPAttacker)attacker;
@end

View File

@@ -1,12 +1,12 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithm
@@ -24,7 +24,7 @@ id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version) {
versionToAlgorithm = [NSMutableDictionary dictionary];
id<MPAlgorithm> algorithm = versionToAlgorithm[@(version)];
if (!algorithm) if ((algorithm = [NSClassFromString( strf( @"MPAlgorithmV%lu", (unsigned long)version ) ) new]))
if (!algorithm && (algorithm = (id<MPAlgorithm>)[NSClassFromString( strf( @"MPAlgorithmV%lu", (unsigned long)version ) ) new]))
versionToAlgorithm[@(version)] = algorithm;
return algorithm;
@@ -33,8 +33,11 @@ id<MPAlgorithm> MPAlgorithmForVersion(NSUInteger version) {
id<MPAlgorithm> MPAlgorithmDefaultForBundleVersion(NSString *bundleVersion) {
if (PearlCFBundleVersionCompare( bundleVersion, @"1.3" ) == NSOrderedAscending)
// Pre-1.3
// Pre-1.3
return MPAlgorithmForVersion( 0 );
if (PearlCFBundleVersionCompare( bundleVersion, @"2.1" ) == NSOrderedAscending)
// Pre-2.1
return MPAlgorithmForVersion( 1 );
return MPAlgorithmDefault;
}

View File

@@ -20,7 +20,7 @@
@interface MPAlgorithmV0 : NSObject<MPAlgorithm>
- (NSDictionary *)allCiphers;
- (NSArray *)ciphersForType:(MPElementType)type;
- (NSArray *)ciphersForType:(MPSiteType)type;
- (NSArray *)cipherClasses;
- (NSArray *)cipherClassCharacters;
- (NSString *)charactersForCipherClass:(NSString *)cipherClass;

View File

@@ -17,6 +17,9 @@
#import "MPAlgorithmV0.h"
#import "MPEntities.h"
#import "MPAppDelegate_Shared.h"
#import "MPAppDelegate_InApp.h"
#import "MPSiteQuestionEntity.h"
#include <openssl/bn.h>
#include <openssl/err.h>
@@ -70,40 +73,40 @@
return [(id<MPAlgorithm>)other version] == [self version];
}
- (BOOL)migrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
- (BOOL)tryMigrateUser:(MPUserEntity *)user inContext:(NSManagedObjectContext *)moc {
NSError *error = nil;
NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
NSFetchRequest *migrationRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
migrationRequest.predicate = [NSPredicate predicateWithFormat:@"version_ < %d AND user == %@", self.version, user];
NSArray *migrationElements = [moc executeFetchRequest:migrationRequest error:&error];
if (!migrationElements) {
err( @"While looking for elements to migrate: %@", error );
NSArray *migrationSites = [moc executeFetchRequest:migrationRequest error:&error];
if (!migrationSites) {
err( @"While looking for sites to migrate: %@", [error fullDescription] );
return NO;
}
BOOL requiresExplicitMigration = NO;
for (MPElementEntity *migrationElement in migrationElements)
if (![migrationElement migrateExplicitly:NO])
requiresExplicitMigration = YES;
BOOL success = YES;
for (MPSiteEntity *migrationSite in migrationSites)
if (![migrationSite tryMigrateExplicitly:NO])
success = NO;
return requiresExplicitMigration;
return success;
}
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
if (element.version != [self version] - 1)
if (site.version != [self version] - 1)
// Only migrate from previous version.
return NO;
if (!explicit) {
// This migration requires explicit permission.
element.requiresExplicitMigration = YES;
site.requiresExplicitMigration = YES;
return NO;
}
// Apply migration.
element.requiresExplicitMigration = NO;
element.version = [self version];
site.requiresExplicitMigration = NO;
site.version = [self version];
return YES;
}
@@ -136,129 +139,140 @@
return [keyData hashWith:MP_hash];
}
- (NSString *)scopeForVariant:(MPElementVariant)variant {
- (NSString *)scopeForVariant:(MPSiteVariant)variant {
switch (variant) {
case MPElementVariantPassword:
case MPSiteVariantPassword:
return @"com.lyndir.masterpassword";
case MPElementVariantLogin:
case MPSiteVariantLogin:
return @"com.lyndir.masterpassword.login";
case MPSiteVariantAnswer:
return @"com.lyndir.masterpassword.answer";
}
Throw( @"Unsupported variant: %ld", (long)variant );
}
- (NSString *)nameOfType:(MPElementType)type {
- (NSString *)nameOfType:(MPSiteType)type {
if (!type)
return nil;
switch (type) {
case MPElementTypeGeneratedMaximum:
case MPSiteTypeGeneratedMaximum:
return @"Maximum Security Password";
case MPElementTypeGeneratedLong:
case MPSiteTypeGeneratedLong:
return @"Long Password";
case MPElementTypeGeneratedMedium:
case MPSiteTypeGeneratedMedium:
return @"Medium Password";
case MPElementTypeGeneratedBasic:
case MPSiteTypeGeneratedBasic:
return @"Basic Password";
case MPElementTypeGeneratedShort:
case MPSiteTypeGeneratedShort:
return @"Short Password";
case MPElementTypeGeneratedPIN:
case MPSiteTypeGeneratedPIN:
return @"PIN";
case MPElementTypeGeneratedName:
case MPSiteTypeGeneratedName:
return @"Login Name";
case MPElementTypeStoredPersonal:
case MPSiteTypeGeneratedPhrase:
return @"Phrase";
case MPSiteTypeStoredPersonal:
return @"Personal Password";
case MPElementTypeStoredDevicePrivate:
case MPSiteTypeStoredDevicePrivate:
return @"Device Private Password";
}
Throw( @"Type not supported: %lu", (long)type );
}
- (NSString *)shortNameOfType:(MPElementType)type {
- (NSString *)shortNameOfType:(MPSiteType)type {
if (!type)
return nil;
switch (type) {
case MPElementTypeGeneratedMaximum:
case MPSiteTypeGeneratedMaximum:
return @"Maximum";
case MPElementTypeGeneratedLong:
case MPSiteTypeGeneratedLong:
return @"Long";
case MPElementTypeGeneratedMedium:
case MPSiteTypeGeneratedMedium:
return @"Medium";
case MPElementTypeGeneratedBasic:
case MPSiteTypeGeneratedBasic:
return @"Basic";
case MPElementTypeGeneratedShort:
case MPSiteTypeGeneratedShort:
return @"Short";
case MPElementTypeGeneratedPIN:
case MPSiteTypeGeneratedPIN:
return @"PIN";
case MPElementTypeGeneratedName:
case MPSiteTypeGeneratedName:
return @"Name";
case MPElementTypeStoredPersonal:
case MPSiteTypeGeneratedPhrase:
return @"Phrase";
case MPSiteTypeStoredPersonal:
return @"Personal";
case MPElementTypeStoredDevicePrivate:
case MPSiteTypeStoredDevicePrivate:
return @"Device";
}
Throw( @"Type not supported: %lu", (long)type );
}
- (NSString *)classNameOfType:(MPElementType)type {
- (NSString *)classNameOfType:(MPSiteType)type {
return NSStringFromClass( [self classOfType:type] );
}
- (Class)classOfType:(MPElementType)type {
- (Class)classOfType:(MPSiteType)type {
if (!type)
Throw( @"No type given." );
switch (type) {
case MPElementTypeGeneratedMaximum:
return [MPElementGeneratedEntity class];
case MPSiteTypeGeneratedMaximum:
return [MPGeneratedSiteEntity class];
case MPElementTypeGeneratedLong:
return [MPElementGeneratedEntity class];
case MPSiteTypeGeneratedLong:
return [MPGeneratedSiteEntity class];
case MPElementTypeGeneratedMedium:
return [MPElementGeneratedEntity class];
case MPSiteTypeGeneratedMedium:
return [MPGeneratedSiteEntity class];
case MPElementTypeGeneratedBasic:
return [MPElementGeneratedEntity class];
case MPSiteTypeGeneratedBasic:
return [MPGeneratedSiteEntity class];
case MPElementTypeGeneratedShort:
return [MPElementGeneratedEntity class];
case MPSiteTypeGeneratedShort:
return [MPGeneratedSiteEntity class];
case MPElementTypeGeneratedPIN:
return [MPElementGeneratedEntity class];
case MPSiteTypeGeneratedPIN:
return [MPGeneratedSiteEntity class];
case MPElementTypeGeneratedName:
return [MPElementGeneratedEntity class];
case MPSiteTypeGeneratedName:
return [MPGeneratedSiteEntity class];
case MPElementTypeStoredPersonal:
return [MPElementStoredEntity class];
case MPSiteTypeGeneratedPhrase:
return [MPGeneratedSiteEntity class];
case MPElementTypeStoredDevicePrivate:
return [MPElementStoredEntity class];
case MPSiteTypeStoredPersonal:
return [MPStoredSiteEntity class];
case MPSiteTypeStoredDevicePrivate:
return [MPStoredSiteEntity class];
}
Throw( @"Type not supported: %lu", (long)type );
@@ -266,13 +280,13 @@
- (NSArray *)allTypes {
return [self allTypesStartingWith:MPElementTypeGeneratedMaximum];
return [self allTypesStartingWith:MPSiteTypeGeneratedMaximum];
}
- (NSArray *)allTypesStartingWith:(MPElementType)startingType {
- (NSArray *)allTypesStartingWith:(MPSiteType)startingType {
NSMutableArray *allTypes = [[NSMutableArray alloc] initWithCapacity:8];
MPElementType currentType = startingType;
MPSiteType currentType = startingType;
do {
[allTypes addObject:@(currentType)];
} while ((currentType = [self nextType:currentType]) != startingType);
@@ -280,33 +294,33 @@
return allTypes;
}
- (MPElementType)nextType:(MPElementType)type {
- (MPSiteType)nextType:(MPSiteType)type {
switch (type) {
case MPElementTypeGeneratedMaximum:
return MPElementTypeGeneratedLong;
case MPElementTypeGeneratedLong:
return MPElementTypeGeneratedMedium;
case MPElementTypeGeneratedMedium:
return MPElementTypeGeneratedBasic;
case MPElementTypeGeneratedBasic:
return MPElementTypeGeneratedShort;
case MPElementTypeGeneratedShort:
return MPElementTypeGeneratedPIN;
case MPElementTypeGeneratedPIN:
return MPElementTypeStoredPersonal;
case MPElementTypeStoredPersonal:
return MPElementTypeStoredDevicePrivate;
case MPElementTypeStoredDevicePrivate:
return MPElementTypeGeneratedMaximum;
case MPSiteTypeGeneratedMaximum:
return MPSiteTypeGeneratedLong;
case MPSiteTypeGeneratedLong:
return MPSiteTypeGeneratedMedium;
case MPSiteTypeGeneratedMedium:
return MPSiteTypeGeneratedBasic;
case MPSiteTypeGeneratedBasic:
return MPSiteTypeGeneratedShort;
case MPSiteTypeGeneratedShort:
return MPSiteTypeGeneratedPIN;
case MPSiteTypeGeneratedPIN:
return MPSiteTypeStoredPersonal;
case MPSiteTypeStoredPersonal:
return MPSiteTypeStoredDevicePrivate;
case MPSiteTypeStoredDevicePrivate:
return MPSiteTypeGeneratedMaximum;
default:
return MPElementTypeGeneratedLong;
return MPSiteTypeGeneratedLong;
}
}
- (MPElementType)previousType:(MPElementType)type {
- (MPSiteType)previousType:(MPSiteType)type {
MPElementType previousType = type, nextType = type;
MPSiteType previousType = type, nextType = type;
while ((nextType = [self nextType:nextType]) != type)
previousType = nextType;
@@ -325,7 +339,7 @@
return ciphers;
}
- (NSArray *)ciphersForType:(MPElementType)type {
- (NSArray *)ciphersForType:(MPSiteType)type {
NSString *typeClass = [self classNameOfType:type];
NSString *typeName = [self nameOfType:type];
@@ -349,42 +363,51 @@
- (NSString *)generateLoginForSiteNamed:(NSString *)name usingKey:(MPKey *)key {
return [self generateContentForSiteNamed:name ofType:MPElementTypeGeneratedName withCounter:1
variant:MPElementVariantLogin usingKey:key];
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedName withCounter:1
variant:MPSiteVariantLogin context:nil usingKey:key];
}
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter
- (NSString *)generatePasswordForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
usingKey:(MPKey *)key {
return [self generateContentForSiteNamed:name ofType:type withCounter:counter
variant:MPElementVariantPassword usingKey:key];
variant:MPSiteVariantPassword context:nil usingKey:key];
}
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter
variant:(MPElementVariant)variant usingKey:(MPKey *)key {
- (NSString *)generateAnswerForSiteNamed:(NSString *)name onQuestion:(NSString *)question usingKey:(MPKey *)key {
return [self generateContentForSiteNamed:name ofType:MPSiteTypeGeneratedPhrase withCounter:1
variant:MPSiteVariantAnswer context:question usingKey:key];
}
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
// Determine the seed whose bytes will be used for calculating a password
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length );
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ), ncontextLength = htonl( context.length );
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
NSString *scope = [self scopeForVariant:variant];;
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)",
[key.keyData encodeBase64], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] );
NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )];
NSString *scope = [self scopeForVariant:variant];
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@ | %@)",
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex], context );
NSData *seed = [[NSData dataByConcatenatingDatas:
[scope dataUsingEncoding:NSUTF8StringEncoding],
nameLengthBytes,
[name dataUsingEncoding:NSUTF8StringEncoding],
counterBytes,
context? contextLengthBytes: nil,
[context dataUsingEncoding:NSUTF8StringEncoding],
nil]
hmacWith:PearlHashSHA256 key:key.keyData];
trc( @"seed is: %@", [seed encodeBase64] );
trc( @"seed is: %@", [seed encodeHex] );
const char *seedBytes = seed.bytes;
// Determine the cipher from the first seed byte.
NSAssert( [seed length], @"Missing seed." );
NSArray *typeCiphers = [self ciphersForType:type];
NSString *cipher = typeCiphers[htons( seedBytes[0] ) % [typeCiphers count]];
trc( @"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher );
trc( @"type %@ (%lu), ciphers: %@, selected: %@", [self nameOfType:type], (unsigned long)type, typeCiphers, cipher );
// Encode the content, character by character, using subsequent seed bytes and the cipher.
NSAssert( [seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher." );
@@ -402,79 +425,80 @@
return content;
}
- (NSString *)storedLoginForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key {
- (NSString *)storedLoginForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key {
return nil;
}
- (NSString *)storedPasswordForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key {
- (NSString *)storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:(MPKey *)key {
return [self decryptContent:element.contentObject usingKey:key];
return [self decryptContent:site.contentObject usingKey:key];
}
- (BOOL)savePassword:(NSString *)clearContent toElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
- (BOOL)savePassword:(NSString *)clearContent toSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
switch (element.type) {
case MPElementTypeGeneratedMaximum:
case MPElementTypeGeneratedLong:
case MPElementTypeGeneratedMedium:
case MPElementTypeGeneratedBasic:
case MPElementTypeGeneratedShort:
case MPElementTypeGeneratedPIN:
case MPElementTypeGeneratedName: {
NSAssert( NO, @"Cannot save content to element with generated type %lu.", (long)element.type );
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase: {
wrn( @"Cannot save content to site with generated type %lu.", (long)site.type );
return NO;
}
case MPElementTypeStoredPersonal: {
if (![element isKindOfClass:[MPElementStoredEntity class]]) {
wrn( @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.",
(long)element.type, [element class] );
case MPSiteTypeStoredPersonal: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
return NO;
}
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
if ([((MPElementStoredEntity *)element).contentObject isEqualToData:encryptedContent])
encryptWithSymmetricKey:[siteKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
if ([((MPStoredSiteEntity *)site).contentObject isEqualToData:encryptedContent])
return NO;
((MPElementStoredEntity *)element).contentObject = encryptedContent;
((MPStoredSiteEntity *)site).contentObject = encryptedContent;
return YES;
}
case MPElementTypeStoredDevicePrivate: {
if (![element isKindOfClass:[MPElementStoredEntity class]]) {
wrn( @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.",
(long)element.type, [element class] );
case MPSiteTypeStoredDevicePrivate: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
return NO;
}
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
encryptWithSymmetricKey:[siteKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
if (!encryptedContent)
[PearlKeyChain deleteItemForQuery:elementQuery];
[PearlKeyChain deleteItemForQuery:siteQuery];
else
[PearlKeyChain addOrUpdateItemForQuery:elementQuery withAttributes:@{
[PearlKeyChain addOrUpdateItemForQuery:siteQuery withAttributes:@{
(__bridge id)kSecValueData : encryptedContent,
#if TARGET_OS_IPHONE
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
#endif
}];
((MPElementStoredEntity *)element).contentObject = nil;
((MPStoredSiteEntity *)site).contentObject = nil;
return YES;
}
}
Throw( @"Unsupported type: %ld", (long)element.type );
Throw( @"Unsupported type: %ld", (long)site.type );
}
- (NSString *)resolveLoginForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
- (NSString *)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter( group );
__block NSString *result = nil;
[self resolveLoginForElement:element usingKey:elementKey result:^(NSString *result_) {
[self resolveLoginForSite:site usingKey:siteKey result:^(NSString *result_) {
result = result_;
dispatch_group_leave( group );
}];
@@ -483,12 +507,12 @@
return result;
}
- (NSString *)resolvePasswordForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
- (NSString *)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter( group );
__block NSString *result = nil;
[self resolvePasswordForElement:element usingKey:elementKey result:^(NSString *result_) {
[self resolvePasswordForSite:site usingKey:siteKey result:^(NSString *result_) {
result = result_;
dispatch_group_leave( group );
}];
@@ -497,88 +521,117 @@
return result;
}
- (void)resolveLoginForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey result:(void ( ^ )(NSString *result))resultBlock {
- (NSString *)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
NSString *name = element.name;
BOOL loginGenerated = element.loginGenerated;
NSString *loginName = loginGenerated? nil: element.loginName;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter( group );
__block NSString *result = nil;
[self resolveAnswerForSite:site usingKey:siteKey result:^(NSString *result_) {
result = result_;
dispatch_group_leave( group );
}];
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
return result;
}
- (NSString *)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter( group );
__block NSString *result = nil;
[self resolveAnswerForQuestion:question usingKey:siteKey result:^(NSString *result_) {
result = result_;
dispatch_group_leave( group );
}];
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
return result;
}
- (void)resolveLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSString *name = site.name;
BOOL loginGenerated = site.loginGenerated && [[MPAppDelegate_Shared get] isFeatureUnlocked:MPProductGenerateLogins];
NSString *loginName = loginGenerated? nil: site.loginName;
id<MPAlgorithm> algorithm = nil;
if (!name.length)
err( @"Missing name." );
else if (!elementKey.keyData.length)
else if (!siteKey.keyData.length)
err( @"Missing key." );
else
algorithm = element.algorithm;
algorithm = site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
if (loginGenerated)
resultBlock( [algorithm generateLoginForSiteNamed:name usingKey:elementKey] );
resultBlock( [algorithm generateLoginForSiteNamed:name usingKey:siteKey] );
else
resultBlock( loginName );
} );
}
- (void)resolvePasswordForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey result:(void ( ^ )(NSString *result))resultBlock {
- (void)resolvePasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
switch (element.type) {
case MPElementTypeGeneratedMaximum:
case MPElementTypeGeneratedLong:
case MPElementTypeGeneratedMedium:
case MPElementTypeGeneratedBasic:
case MPElementTypeGeneratedShort:
case MPElementTypeGeneratedPIN:
case MPElementTypeGeneratedName: {
if (![element isKindOfClass:[MPElementGeneratedEntity class]]) {
wrn( @"Element with generated type %lu is not an MPElementGeneratedEntity, but a %@.",
(long)element.type, [element class] );
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase: {
if (![site isKindOfClass:[MPGeneratedSiteEntity class]]) {
wrn( @"Site with generated type %lu is not an MPGeneratedSiteEntity, but a %@.",
(long)site.type, [site class] );
break;
}
NSString *name = element.name;
MPElementType type = element.type;
NSUInteger counter = ((MPElementGeneratedEntity *)element).counter;
NSString *name = site.name;
MPSiteType type = site.type;
NSUInteger counter = ((MPGeneratedSiteEntity *)site).counter;
id<MPAlgorithm> algorithm = nil;
if (!element.name.length)
if (!site.name.length)
err( @"Missing name." );
else if (!elementKey.keyData.length)
else if (!siteKey.keyData.length)
err( @"Missing key." );
else
algorithm = element.algorithm;
algorithm = site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSString *result = [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:elementKey];
NSString *result = [algorithm generatePasswordForSiteNamed:name ofType:type withCounter:counter usingKey:siteKey];
resultBlock( result );
} );
break;
}
case MPElementTypeStoredPersonal: {
if (![element isKindOfClass:[MPElementStoredEntity class]]) {
wrn( @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.",
(long)element.type, [element class] );
case MPSiteTypeStoredPersonal: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
break;
}
NSData *encryptedContent = ((MPElementStoredEntity *)element).contentObject;
NSData *encryptedContent = ((MPStoredSiteEntity *)site).contentObject;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSString *result = [self decryptContent:encryptedContent usingKey:elementKey];
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
resultBlock( result );
} );
break;
}
case MPElementTypeStoredDevicePrivate: {
NSAssert( [element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type,
[element class] );
case MPSiteTypeStoredDevicePrivate: {
NSAssert( [site isKindOfClass:[MPStoredSiteEntity class]],
@"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.", (long)site.type,
[site class] );
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:elementQuery];
NSDictionary *siteQuery = [self queryForDevicePrivateSiteNamed:site.name];
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:siteQuery];
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSString *result = [self decryptContent:encryptedContent usingKey:elementKey];
NSString *result = [self decryptContent:encryptedContent usingKey:siteKey];
resultBlock( result );
} );
break;
@@ -586,94 +639,135 @@
}
}
- (void)resolveAnswerForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
NSString *name = site.name;
id<MPAlgorithm> algorithm = nil;
if (!site.name.length)
err( @"Missing name." );
else if (!siteKey.keyData.length)
err( @"Missing key." );
else
algorithm = site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:nil usingKey:siteKey];
resultBlock( result );
} );
}
- (void)resolveAnswerForQuestion:(MPSiteQuestionEntity *)question usingKey:(MPKey *)siteKey
result:(void ( ^ )(NSString *result))resultBlock {
NSAssert( [siteKey.keyID isEqualToData:question.site.user.keyID], @"Site does not belong to current user." );
NSString *name = question.site.name;
NSString *keyword = question.keyword;
id<MPAlgorithm> algorithm = nil;
if (!name.length)
err( @"Missing name." );
else if (!siteKey.keyData.length)
err( @"Missing key." );
else
algorithm = question.site.algorithm;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
NSString *result = [algorithm generateAnswerForSiteNamed:name onQuestion:keyword usingKey:siteKey];
resultBlock( result );
} );
}
- (void)importProtectedPassword:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
switch (element.type) {
case MPElementTypeGeneratedMaximum:
case MPElementTypeGeneratedLong:
case MPElementTypeGeneratedMedium:
case MPElementTypeGeneratedBasic:
case MPElementTypeGeneratedShort:
case MPElementTypeGeneratedPIN:
case MPElementTypeGeneratedName:
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase:
break;
case MPElementTypeStoredPersonal: {
if (![element isKindOfClass:[MPElementStoredEntity class]]) {
wrn( @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.",
(long)element.type, [element class] );
case MPSiteTypeStoredPersonal: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
break;
}
if ([importKey.keyID isEqualToData:elementKey.keyID])
((MPElementStoredEntity *)element).contentObject = [protectedContent decodeBase64];
if ([importKey.keyID isEqualToData:siteKey.keyID])
((MPStoredSiteEntity *)site).contentObject = [protectedContent decodeBase64];
else {
NSString *clearContent = [self decryptContent:[protectedContent decodeBase64] usingKey:importKey];
[self importClearTextPassword:clearContent intoElement:element usingKey:elementKey];
[self importClearTextPassword:clearContent intoSite:site usingKey:siteKey];
}
break;
}
case MPElementTypeStoredDevicePrivate:
case MPSiteTypeStoredDevicePrivate:
break;
}
}
- (void)importClearTextPassword:(NSString *)clearContent intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
- (void)importClearTextPassword:(NSString *)clearContent intoSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
switch (element.type) {
case MPElementTypeGeneratedMaximum:
case MPElementTypeGeneratedLong:
case MPElementTypeGeneratedMedium:
case MPElementTypeGeneratedBasic:
case MPElementTypeGeneratedShort:
case MPElementTypeGeneratedPIN:
case MPElementTypeGeneratedName:
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase:
break;
case MPElementTypeStoredPersonal: {
[self savePassword:clearContent toElement:element usingKey:elementKey];
case MPSiteTypeStoredPersonal: {
[self savePassword:clearContent toSite:site usingKey:siteKey];
break;
}
case MPElementTypeStoredDevicePrivate:
case MPSiteTypeStoredDevicePrivate:
break;
}
}
- (NSString *)exportPasswordForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
- (NSString *)exportPasswordForSite:(MPSiteEntity *)site usingKey:(MPKey *)siteKey {
NSAssert( [elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user." );
if (!(element.type & MPElementFeatureExportContent))
NSAssert( [siteKey.keyID isEqualToData:site.user.keyID], @"Site does not belong to current user." );
if (!(site.type & MPSiteFeatureExportContent))
return nil;
NSString *result = nil;
switch (element.type) {
case MPElementTypeGeneratedMaximum:
case MPElementTypeGeneratedLong:
case MPElementTypeGeneratedMedium:
case MPElementTypeGeneratedBasic:
case MPElementTypeGeneratedShort:
case MPElementTypeGeneratedPIN:
case MPElementTypeGeneratedName: {
switch (site.type) {
case MPSiteTypeGeneratedMaximum:
case MPSiteTypeGeneratedLong:
case MPSiteTypeGeneratedMedium:
case MPSiteTypeGeneratedBasic:
case MPSiteTypeGeneratedShort:
case MPSiteTypeGeneratedPIN:
case MPSiteTypeGeneratedName:
case MPSiteTypeGeneratedPhrase: {
result = nil;
break;
}
case MPElementTypeStoredPersonal: {
if (![element isKindOfClass:[MPElementStoredEntity class]]) {
wrn( @"Element with stored type %lu is not an MPElementStoredEntity, but a %@.",
(long)element.type, [element class] );
case MPSiteTypeStoredPersonal: {
if (![site isKindOfClass:[MPStoredSiteEntity class]]) {
wrn( @"Site with stored type %lu is not an MPStoredSiteEntity, but a %@.",
(long)site.type, [site class] );
break;
}
result = [((MPElementStoredEntity *)element).contentObject encodeBase64];
result = [((MPStoredSiteEntity *)site).contentObject encodeBase64];
break;
}
case MPElementTypeStoredDevicePrivate: {
case MPSiteTypeStoredDevicePrivate: {
result = nil;
break;
}
@@ -687,7 +781,7 @@
return NO;
}
- (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
- (NSDictionary *)queryForDevicePrivateSiteNamed:(NSString *)name {
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:@{
@@ -708,7 +802,7 @@
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
}
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPElementType)type byAttacker:(MPAttacker)attacker {
- (BOOL)timeToCrack:(out TimeToCrack *)timeToCrack passwordOfType:(MPSiteType)type byAttacker:(MPAttacker)attacker {
if (!type)
return NO;

View File

@@ -25,33 +25,34 @@
return 1;
}
- (BOOL)migrateElement:(MPElementEntity *)element explicit:(BOOL)explicit {
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
if (element.version != [self version] - 1)
if (site.version != [self version] - 1)
// Only migrate from previous version.
return NO;
if (!explicit) {
if (element.type & MPElementTypeClassGenerated) {
if (site.type & MPSiteTypeClassGenerated) {
// This migration requires explicit permission for types of the generated class.
element.requiresExplicitMigration = YES;
site.requiresExplicitMigration = YES;
return NO;
}
}
// Apply migration.
element.requiresExplicitMigration = NO;
element.version = [self version];
site.requiresExplicitMigration = NO;
site.version = [self version];
return YES;
}
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter
variant:(MPElementVariant)variant usingKey:(MPKey *)key {
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
// Determine the seed whose bytes will be used for calculating a password
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length );
uint32_t ncounter = htonl( counter ), nnameLength = htonl( name.length ), ncontextLength = htonl( context.length );
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )];
NSString *scope = [self scopeForVariant:variant];
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)",
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] );
@@ -60,6 +61,8 @@
nameLengthBytes,
[name dataUsingEncoding:NSUTF8StringEncoding],
counterBytes,
context? contextLengthBytes: nil,
[context dataUsingEncoding:NSUTF8StringEncoding],
nil]
hmacWith:PearlHashSHA256 key:key.keyData];
trc( @"seed is: %@", [seed encodeHex] );
@@ -69,7 +72,7 @@
NSAssert( [seed length], @"Missing seed." );
NSArray *typeCiphers = [self ciphersForType:type];
NSString *cipher = typeCiphers[seedBytes[0] % [typeCiphers count]];
trc( @"type %@ (%d), ciphers: %@, selected: %@", [self nameOfType:type], type, typeCiphers, cipher );
trc( @"type %@ (%lu), ciphers: %@, selected: %@", [self nameOfType:type], (unsigned long)type, typeCiphers, cipher );
// Encode the content, character by character, using subsequent seed bytes and the cipher.
NSAssert( [seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher." );

View File

@@ -0,0 +1,21 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithmV1
//
// Created by Maarten Billemont on 17/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import "MPAlgorithmV1.h"
@interface MPAlgorithmV2 : MPAlgorithmV1
@end

View File

@@ -0,0 +1,97 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPAlgorithmV1
//
// Created by Maarten Billemont on 17/07/12.
// Copyright 2012 lhunath (Maarten Billemont). All rights reserved.
//
#import <objc/runtime.h>
#import "MPAlgorithmV2.h"
#import "MPEntities.h"
@implementation MPAlgorithmV2
- (NSUInteger)version {
return 2;
}
- (BOOL)tryMigrateSite:(MPSiteEntity *)site explicit:(BOOL)explicit {
if (site.version != [self version] - 1)
// Only migrate from previous version.
return NO;
if (!explicit) {
if (site.type & MPSiteTypeClassGenerated && site.name.length != [site.name dataUsingEncoding:NSUTF8StringEncoding].length) {
// This migration requires explicit permission for types of the generated class.
site.requiresExplicitMigration = YES;
return NO;
}
}
// Apply migration.
site.requiresExplicitMigration = NO;
site.version = [self version];
return YES;
}
- (NSString *)generateContentForSiteNamed:(NSString *)name ofType:(MPSiteType)type withCounter:(NSUInteger)counter
variant:(MPSiteVariant)variant context:(NSString *)context usingKey:(MPKey *)key {
// Determine the seed whose bytes will be used for calculating a password
NSData *nameBytes = [name dataUsingEncoding:NSUTF8StringEncoding];
NSData *contextBytes = [context dataUsingEncoding:NSUTF8StringEncoding];
uint32_t ncounter = htonl( counter ), nnameLength = htonl( nameBytes.length ), ncontextLength = htonl( contextBytes.length );
NSData *counterBytes = [NSData dataWithBytes:&ncounter length:sizeof( ncounter )];
NSData *nameLengthBytes = [NSData dataWithBytes:&nnameLength length:sizeof( nnameLength )];
NSData *contextLengthBytes = [NSData dataWithBytes:&ncontextLength length:sizeof( ncontextLength )];
NSString *scope = [self scopeForVariant:variant];
NSData *scopeBytes = [scope dataUsingEncoding:NSUTF8StringEncoding];
trc( @"seed from: hmac-sha256(%@, %@ | %@ | %@ | %@)",
[[key keyID] encodeHex], scope, [nameLengthBytes encodeHex], name, [counterBytes encodeHex] );
NSData *seed = [[NSData dataByConcatenatingDatas:
scopeBytes,
nameLengthBytes,
nameBytes,
counterBytes,
context? contextLengthBytes: nil,
contextBytes,
nil]
hmacWith:PearlHashSHA256 key:key.keyData];
trc( @"seed is: %@", [seed encodeHex] );
const unsigned char *seedBytes = seed.bytes;
// Determine the cipher from the first seed byte.
NSAssert( [seed length], @"Missing seed." );
NSArray *typeCiphers = [self ciphersForType:type];
NSString *cipher = typeCiphers[seedBytes[0] % [typeCiphers count]];
trc( @"type %@ (%lu), ciphers: %@, selected: %@", [self nameOfType:type], (unsigned long)type, typeCiphers, cipher );
// Encode the content, character by character, using subsequent seed bytes and the cipher.
NSAssert( [seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher." );
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
for (NSUInteger c = 0; c < [cipher length]; ++c) {
uint16_t keyByte = seedBytes[c + 1];
NSString *cipherClass = [cipher substringWithRange:NSMakeRange( c, 1 )];
NSString *cipherClassCharacters = [self charactersForCipherClass:cipherClass];
NSString *character = [cipherClassCharacters substringWithRange:NSMakeRange( keyByte % [cipherClassCharacters length], 1 )];
trc( @"class %@ has characters: %@, index: %u, selected: %@", cipherClass, cipherClassCharacters, keyByte, character );
[content appendString:character];
}
return content;
}
@end

View File

@@ -0,0 +1,39 @@
//
// MPAppDelegate_Key.h
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import <StoreKit/StoreKit.h>
#import "MPAppDelegate_Shared.h"
#define MPProductGenerateLogins @"com.lyndir.masterpassword.products.generatelogins"
#define MPProductGenerateAnswers @"com.lyndir.masterpassword.products.generateanswers"
#define MPProductOSIntegration @"com.lyndir.masterpassword.products.osintegration"
#define MPProductTouchID @"com.lyndir.masterpassword.products.touchid"
#define MPProductFuel @"com.lyndir.masterpassword.products.fuel"
#define MP_FUEL_HOURLY_RATE 30.f /* Tier 1 purchases/h ~> USD/h */
@protocol MPInAppDelegate
- (void)updateWithProducts:(NSArray /* SKProduct */ *)products;
- (void)updateWithTransaction:(SKPaymentTransaction *)transaction;
@end
@interface MPAppDelegate_Shared(InApp)
- (void)registerProductsObserver:(id<MPInAppDelegate>)delegate;
- (void)removeProductsObserver:(id<MPInAppDelegate>)delegate;
- (void)reloadProducts;
- (BOOL)canMakePayments;
- (BOOL)isFeatureUnlocked:(NSString *)productIdentifier;
- (void)restoreCompletedTransactions;
- (void)purchaseProductWithIdentifier:(NSString *)productIdentifier quantity:(NSInteger)quantity;
@end

View File

@@ -0,0 +1,179 @@
//
// MPAppDelegate.m
// MasterPassword
//
// Created by Maarten Billemont on 24/11/11.
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import "MPAppDelegate_InApp.h"
@interface MPAppDelegate_Shared(InApp_Private)<SKProductsRequestDelegate, SKPaymentTransactionObserver>
@end
@implementation MPAppDelegate_Shared(InApp)
PearlAssociatedObjectProperty( NSArray*, Products, products );
PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObservers );
- (void)registerProductsObserver:(id<MPInAppDelegate>)delegate {
if (!self.productObservers)
self.productObservers = [NSMutableArray array];
[self.productObservers addObject:delegate];
if (self.products)
[delegate updateWithProducts:self.products];
else
[self reloadProducts];
}
- (void)removeProductsObserver:(id<MPInAppDelegate>)delegate {
[self.productObservers removeObject:delegate];
}
- (void)reloadProducts {
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:
[[NSSet alloc] initWithObjects:MPProductGenerateLogins, MPProductGenerateAnswers, MPProductFuel, nil]];
productsRequest.delegate = self;
[productsRequest start];
}
- (SKPaymentQueue *)paymentQueue {
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
} );
return [SKPaymentQueue defaultQueue];
}
- (BOOL)canMakePayments {
return [SKPaymentQueue canMakePayments];
}
- (BOOL)isFeatureUnlocked:(NSString *)productIdentifier {
if (![productIdentifier length])
// Missing a product.
return NO;
if ([productIdentifier isEqualToString:MPProductFuel])
// Consumable product.
return NO;
#if ADHOC || DEBUG
// All features are unlocked for beta / debug versions.
return YES;
#else
// Check if product is purchased.
return [[NSUserDefaults standardUserDefaults] objectForKey:productIdentifier] != nil;
#endif
}
- (void)restoreCompletedTransactions {
[[self paymentQueue] restoreCompletedTransactions];
}
- (void)purchaseProductWithIdentifier:(NSString *)productIdentifier quantity:(NSInteger)quantity {
#if TARGET_OS_IPHONE
if (![[MPAppDelegate_Shared get] canMakePayments]) {
[PearlAlert showAlertWithTitle:@"Store Not Set Up" message:
@"Try logging using the App Store or from Settings."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:nil cancelTitle:@"Thanks" otherTitles:nil];
return;
}
#endif
for (SKProduct *product in self.products)
if ([product.productIdentifier isEqualToString:productIdentifier]) {
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
payment.quantity = quantity;
[[self paymentQueue] addPayment:payment];
return;
}
}
#pragma mark - SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
inf( @"products: %@, invalid: %@", response.products, response.invalidProductIdentifiers );
self.products = response.products;
for (id<MPInAppDelegate> productObserver in self.productObservers)
[productObserver updateWithProducts:self.products];
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
#if TARGET_OS_IPHONE
[PearlAlert showAlertWithTitle:@"Purchase Failed" message:
strf( @"%@\n\n%@", error.localizedDescription,
@"Ensure you are online and try logging out and back into iTunes from your device's Settings." )
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
cancelTitle:@"OK" otherTitles:nil];
#else
#endif
err( @"StoreKit request (%@) failed: %@", request, [error fullDescription] );
}
- (void)requestDidFinish:(SKRequest *)request {
dbg( @"StoreKit request (%@) finished.", request );
}
#pragma mark - SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
dbg( @"transaction updated: %@ -> %d", transaction.payment.productIdentifier, (int)(transaction.transactionState) );
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased: {
inf( @"purchased: %@", transaction.payment.productIdentifier );
if ([transaction.payment.productIdentifier isEqualToString:MPProductFuel]) {
float currentFuel = [[MPiOSConfig get].developmentFuel floatValue];
float purchasedFuel = transaction.payment.quantity / MP_FUEL_HOURLY_RATE;
[MPiOSConfig get].developmentFuel = @(currentFuel + purchasedFuel);
if (![MPiOSConfig get].developmentFuelChecked || !currentFuel)
[MPiOSConfig get].developmentFuelChecked = [NSDate date];
}
[[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier
forKey:transaction.payment.productIdentifier];
[queue finishTransaction:transaction];
break;
}
case SKPaymentTransactionStateRestored: {
inf( @"restored: %@", transaction.payment.productIdentifier );
[[NSUserDefaults standardUserDefaults] setObject:transaction.transactionIdentifier
forKey:transaction.payment.productIdentifier];
[queue finishTransaction:transaction];
break;
}
case SKPaymentTransactionStatePurchasing:
case SKPaymentTransactionStateDeferred:
break;
case SKPaymentTransactionStateFailed:
err( @"Transaction failed: %@, reason: %@", transaction.payment.productIdentifier, [transaction.error fullDescription] );
[queue finishTransaction:transaction];
break;
}
for (id<MPInAppDelegate> productObserver in self.productObservers)
[productObserver updateWithTransaction:transaction];
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
err( @"StoreKit restore failed: %@", [error fullDescription] );
}
@end

View File

@@ -9,6 +9,12 @@
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
@interface MPAppDelegate_Shared()
@property(strong, nonatomic) MPKey *key;
@end
@implementation MPAppDelegate_Shared(Key)
static NSDictionary *keyQuery(MPUserEntity *user) {
@@ -85,8 +91,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
if ([password length] && (tryKey = [MPAlgorithmDefault keyForPassword:password ofUserNamed:user.name])) {
user.keyID = tryKey.keyID;
// Migrate existing elements.
[self migrateElementsForUser:user saveInContext:moc toKey:tryKey];
// Migrate existing sites.
[self migrateSitesForUser:user saveInContext:moc toKey:tryKey];
}
}
@@ -158,23 +164,23 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
return YES;
}
- (void)migrateElementsForUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc toKey:(MPKey *)newKey {
- (void)migrateSitesForUser:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc toKey:(MPKey *)newKey {
if (![user.elements count])
if (![user.sites count])
// Nothing to migrate.
return;
MPKey *recoverKey = newKey;
#ifdef PEARL_UIKIT
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:PearlString( @"Migrating %ld sites...",
(long)[user.elements count] )];
(long)[user.sites count] )];
#endif
for (MPElementEntity *element in user.elements) {
if (element.type & MPElementTypeClassStored) {
for (MPSiteEntity *site in user.sites) {
if (site.type & MPSiteTypeClassStored) {
NSString *content;
while (!(content = [element.algorithm storedPasswordForElement:(MPElementStoredEntity *)element usingKey:recoverKey])) {
// Failed to decrypt element with the current recoveryKey. Ask user for a new one to use.
while (!(content = [site.algorithm storedPasswordForSite:(MPStoredSiteEntity *)site usingKey:recoverKey])) {
// Failed to decrypt site with the current recoveryKey. Ask user for a new one to use.
__block NSString *masterPassword = nil;
#ifdef PEARL_UIKIT
@@ -182,7 +188,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
dispatch_group_enter( recoverPasswordGroup );
[PearlAlert showAlertWithTitle:@"Enter Old Master Password"
message:PearlString( @"Your old master password is required to migrate the stored password for %@",
element.name )
site.name )
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
@try {
@@ -202,7 +208,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
// Don't Migrate
break;
recoverKey = [element.algorithm keyForPassword:masterPassword ofUserNamed:user.name];
recoverKey = [site.algorithm keyForPassword:masterPassword ofUserNamed:user.name];
}
if (!content)
@@ -210,7 +216,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
break;
if (![recoverKey isEqualToKey:newKey])
[element.algorithm savePassword:content toElement:element usingKey:newKey];
[site.algorithm savePassword:content toSite:site usingKey:newKey];
}
}

View File

@@ -9,19 +9,19 @@
#import "MPEntities.h"
#if TARGET_OS_IPHONE
@interface MPAppDelegate_Shared : PearlAppDelegate
#else
@interface MPAppDelegate_Shared : NSObject <PearlConfigDelegate>
#endif
@property(strong, nonatomic) MPKey *key;
@property(strong, nonatomic) NSManagedObjectID *activeUserOID;
@property(strong, nonatomic, readonly) MPKey *key;
@property(strong, nonatomic, readonly) NSManagedObjectID *activeUserOID;
+ (instancetype)get;
- (MPUserEntity *)activeUserForMainThread;
- (MPUserEntity *)activeUserInContext:(NSManagedObjectContext *)context;
- (void)setActiveUser:(MPUserEntity *)activeUser;
- (void)handleCoordinatorError:(NSError *)error;
@end

View File

@@ -6,10 +6,18 @@
// Copyright (c) 2011 Lyndir. All rights reserved.
//
#import <StoreKit/StoreKit.h>
#import "MPAppDelegate_Shared.h"
#import "MPAppDelegate_Store.h"
#import "MPAppDelegate_Key.h"
@interface MPAppDelegate_Shared ()
@property(strong, nonatomic) MPKey *key;
@property(strong, nonatomic) NSManagedObjectID *activeUserOID;
@end
@implementation MPAppDelegate_Shared
+ (MPAppDelegate_Shared *)get {
@@ -45,9 +53,13 @@
NSError *error;
if (activeUser.objectID.isTemporaryID && ![activeUser.managedObjectContext obtainPermanentIDsForObjects:@[ activeUser ] error:&error])
err(@"Failed to obtain a permanent object ID after setting active user: %@", error);
err(@"Failed to obtain a permanent object ID after setting active user: %@", [error fullDescription]);
self.activeUserOID = activeUser.objectID;
}
- (void)handleCoordinatorError:(NSError *)error {
}
@end

View File

@@ -27,10 +27,11 @@ typedef NS_ENUM( NSUInteger, MPImportResult ) {
+ (BOOL)managedObjectContextPerformBlockAndWait:(void (^)(NSManagedObjectContext *context))mocBlock;
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
- (void)deleteAndResetStore;
/** @param completion The block to execute after adding the element, executed from the main thread with the new element in the main MOC. */
- (void)addElementNamed:(NSString *)siteName completion:(void ( ^ )(MPElementEntity *element, NSManagedObjectContext *context))completion;
- (MPElementEntity *)changeElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context toType:(MPElementType)type;
/** @param completion The block to execute after adding the site, executed from the main thread with the new site in the main MOC. */
- (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion;
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type;
- (MPImportResult)importSites:(NSString *)importedSitesString
askImportPassword:(NSString *(^)(NSString *userName))importPassword
askUserPassword:(NSString *(^)(NSString *userName, NSUInteger importCount, NSUInteger deleteCount))userPassword;

View File

@@ -7,6 +7,8 @@
//
#import "MPAppDelegate_Store.h"
#import "MPGeneratedSiteEntity.h"
#import "NSManagedObjectModel+KCOrderedAccessorFix.h"
#if TARGET_OS_IPHONE
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
@@ -33,6 +35,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, PrivateManagedObjectCont
PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext, mainManagedObjectContext );
PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
#pragma mark - Core Data setup
+ (NSManagedObjectContext *)managedObjectContextForMainThreadIfReady {
@@ -142,19 +146,39 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
self.privateManagedObjectContext = nil;
}];
// Don't load when the store is corrupted.
if ([self.storeCorrupted boolValue])
return;
// Check if migration is necessary.
[self migrateStore];
// Create a new store coordinator.
self.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:
[NSManagedObjectModel mergedModelFromBundles:nil]];
if (!self.persistentStoreCoordinator) {
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
[model kc_generateOrderedSetAccessors];
self.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
}
NSError *error = nil;
[self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self localStoreURL]
options:@{
NSMigratePersistentStoresAutomaticallyOption : @YES,
NSInferMappingModelAutomaticallyOption : @YES,
STORE_OPTIONS
} error:&error];
NSURL *localStoreURL = [self localStoreURL];
if (![[NSFileManager defaultManager] createDirectoryAtURL:[localStoreURL URLByDeletingLastPathComponent]
withIntermediateDirectories:YES attributes:nil error:&error]) {
err( @"Couldn't create our application support directory: %@", [error fullDescription] );
return;
}
if (![self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self localStoreURL]
options:@{
NSMigratePersistentStoresAutomaticallyOption : @YES,
NSInferMappingModelAutomaticallyOption : @YES,
STORE_OPTIONS
} error:&error]) {
err( @"Failed to open store: %@", [error fullDescription] );
self.storeCorrupted = @YES;
[self handleCoordinatorError:error];
return;
}
self.storeCorrupted = @NO;
// Create our contexts and observer.
self.privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
@@ -176,22 +200,19 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
}];
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:UIApp
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
[self.mainManagedObjectContext saveToStore];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification object:UIApp
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
[self.mainManagedObjectContext saveToStore];
}];
PearlAddNotificationObserver( UIApplicationWillTerminateNotification, UIApp, [NSOperationQueue mainQueue],
^(MPAppDelegate_Shared *self, NSNotification *note) {
[self.mainManagedObjectContext saveToStore];
} );
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, UIApp, [NSOperationQueue mainQueue],
^(MPAppDelegate_Shared *self, NSNotification *note) {
[self.mainManagedObjectContext saveToStore];
} );
#else
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillTerminateNotification object:NSApp
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
[self.mainManagedObjectContext saveToStore];
}];
PearlAddNotificationObserver( NSApplicationWillTerminateNotification, NSApp, [NSOperationQueue mainQueue],
^(MPAppDelegate_Shared *self, NSNotification *note) {
[self.mainManagedObjectContext saveToStore];
} );
#endif
// Perform a data sanity check on the newly loaded store to find and fix any issues.
@@ -202,6 +223,33 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
}
}
- (void)deleteAndResetStore {
@synchronized (self) {
// Unregister any existing observers and contexts.
if (self.saveObserver)
[[NSNotificationCenter defaultCenter] removeObserver:self.saveObserver];
[self.mainManagedObjectContext performBlockAndWait:^{
[self.mainManagedObjectContext reset];
self.mainManagedObjectContext = nil;
}];
[self.privateManagedObjectContext performBlockAndWait:^{
[self.privateManagedObjectContext reset];
self.privateManagedObjectContext = nil;
}];
NSError *error = nil;
for (NSPersistentStore *store in self.persistentStoreCoordinator.persistentStores) {
if (![self.persistentStoreCoordinator removePersistentStore:store error:&error])
err( @"Couldn't remove persistence store from coordinator: %@", [error fullDescription] );
}
self.persistentStoreCoordinator = nil;
if (![[NSFileManager defaultManager] removeItemAtURL:self.localStoreURL error:&error])
err( @"Couldn't remove persistence store at URL %@: %@", self.localStoreURL, [error fullDescription] );
[self loadStore];
}
}
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context {
NSError *error = nil;
@@ -214,7 +262,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
fetchRequest.entity = entity;
NSArray *objects = [context executeFetchRequest:fetchRequest error:&error];
if (!objects) {
err( @"Failed to fetch %@ objects: %@", entity, error );
err( @"Failed to fetch %@ objects: %@", entity, [error fullDescription] );
continue;
}
@@ -326,7 +374,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
#pragma mark - Utilities
- (void)addElementNamed:(NSString *)siteName completion:(void ( ^ )(MPElementEntity *element, NSManagedObjectContext *context))completion {
- (void)addSiteNamed:(NSString *)siteName completion:(void ( ^ )(MPSiteEntity *site, NSManagedObjectContext *context))completion {
if (![siteName length]) {
completion( nil, nil );
@@ -341,61 +389,61 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
return;
}
MPElementType type = activeUser.defaultType;
MPSiteType type = activeUser.defaultType;
NSString *typeEntityName = [MPAlgorithmDefault classNameOfType:type];
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
element.name = siteName;
element.user = activeUser;
element.type = type;
element.lastUsed = [NSDate date];
element.version = MPAlgorithmDefaultVersion;
MPSiteEntity *site = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
site.name = siteName;
site.user = activeUser;
site.type = type;
site.lastUsed = [NSDate date];
site.version = MPAlgorithmDefaultVersion;
NSError *error = nil;
if (element.objectID.isTemporaryID && ![context obtainPermanentIDsForObjects:@[ element ] error:&error])
err( @"Failed to obtain a permanent object ID after creating new element: %@", error );
if (site.objectID.isTemporaryID && ![context obtainPermanentIDsForObjects:@[ site ] error:&error])
err( @"Failed to obtain a permanent object ID after creating new site: %@", [error fullDescription] );
[context saveToStore];
completion( element, context );
completion( site, context );
}];
}
- (MPElementEntity *)changeElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context toType:(MPElementType)type {
- (MPSiteEntity *)changeSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context toType:(MPSiteType)type {
if (element.type == type)
return element;
if (site.type == type)
return site;
if ([element.algorithm classOfType:type] == element.typeClass) {
element.type = type;
if ([site.algorithm classOfType:type] == site.typeClass) {
site.type = type;
[context saveToStore];
}
else {
// Type requires a different class of element. Recreate the element.
NSString *typeEntityName = [element.algorithm classNameOfType:type];
MPElementEntity *newElement = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
newElement.type = type;
newElement.name = element.name;
newElement.user = element.user;
newElement.uses = element.uses;
newElement.lastUsed = element.lastUsed;
newElement.version = element.version;
newElement.loginName = element.loginName;
// Type requires a different class of site. Recreate the site.
NSString *typeEntityName = [site.algorithm classNameOfType:type];
MPSiteEntity *newSite = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
newSite.type = type;
newSite.name = site.name;
newSite.user = site.user;
newSite.uses = site.uses;
newSite.lastUsed = site.lastUsed;
newSite.version = site.version;
newSite.loginName = site.loginName;
NSError *error = nil;
if (![context obtainPermanentIDsForObjects:@[ newElement ] error:&error])
err( @"Failed to obtain a permanent object ID after changing object type: %@", error );
if (![context obtainPermanentIDsForObjects:@[ newSite ] error:&error])
err( @"Failed to obtain a permanent object ID after changing object type: %@", [error fullDescription] );
[context deleteObject:element];
[context deleteObject:site];
[context saveToStore];
[[NSNotificationCenter defaultCenter] postNotificationName:MPElementUpdatedNotification object:element.objectID];
element = newElement;
[[NSNotificationCenter defaultCenter] postNotificationName:MPSiteUpdatedNotification object:site.objectID];
site = newSite;
}
[[NSNotificationCenter defaultCenter] postNotificationName:MPElementUpdatedNotification object:element.objectID];
return element;
[[NSNotificationCenter defaultCenter] postNotificationName:MPSiteUpdatedNotification object:site.objectID];
return site;
}
- (MPImportResult)importSites:(NSString *)importedSitesString
@@ -431,7 +479,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
initWithPattern:@"^#[[:space:]]*([^:]+): (.*)"
options:(NSRegularExpressionOptions)0 error:&error];
if (error) {
err( @"Error loading the header pattern: %@", error );
err( @"Error loading the header pattern: %@", [error fullDescription] );
return MPImportResultInternalError;
}
}
@@ -445,7 +493,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
options:(NSRegularExpressionOptions)0 error:&error]
];
if (error) {
err( @"Error loading the site patterns: %@", error );
err( @"Error loading the site patterns: %@", [error fullDescription] );
return MPImportResultInternalError;
}
}
@@ -460,9 +508,9 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
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 *elementFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
NSMutableSet *sitesToDelete = [NSMutableSet set];
NSMutableArray *importedSiteSites = [NSMutableArray arrayWithCapacity:[importedSiteLines count]];
NSFetchRequest *siteFetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
for (NSString *importedSiteLine in importedSiteLines) {
if ([importedSiteLine hasPrefix:@"#"]) {
// Comment or header
@@ -484,10 +532,10 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
err( @"Invalid header format in line: %@", importedSiteLine );
return MPImportResultMalformedInput;
}
NSTextCheckingResult *headerElements = [[headerPattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
NSString *headerName = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:1]];
NSString *headerValue = [importedSiteLine substringWithRange:[headerElements rangeAtIndex:2]];
NSTextCheckingResult *headerSites = [[headerPattern matchesInString:importedSiteLine options:(NSMatchingOptions)0
range:NSMakeRange( 0, [importedSiteLine length] )] lastObject];
NSString *headerName = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:1]];
NSString *headerValue = [importedSiteLine substringWithRange:[headerSites rangeAtIndex:2]];
if ([headerName isEqualToString:@"User Name"]) {
importUserName = headerValue;
@@ -495,7 +543,7 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
userFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", importUserName];
NSArray *users = [context executeFetchRequest:userFetchRequest error:&error];
if (!users) {
err( @"While looking for user: %@, error: %@", importUserName, error );
err( @"While looking for user: %@, error: %@", importUserName, [error fullDescription] );
return MPImportResultInternalError;
}
if ([users count] > 1) {
@@ -579,27 +627,27 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
// Find existing site.
if (user) {
elementFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", siteName, user];
NSArray *existingSites = [context executeFetchRequest:elementFetchRequest error:&error];
siteFetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@ AND user == %@", siteName, user];
NSArray *existingSites = [context executeFetchRequest:siteFetchRequest error:&error];
if (!existingSites) {
err( @"Lookup of existing sites failed for site: %@, user: %@, error: %@", siteName, user.userID, error );
err( @"Lookup of existing sites failed for site: %@, user: %@, error: %@", siteName, user.userID, [error fullDescription] );
return MPImportResultInternalError;
}
if ([existingSites count]) {
dbg( @"Existing sites: %@", existingSites );
[elementsToDelete addObjectsFromArray:existingSites];
[sitesToDelete addObjectsFromArray:existingSites];
}
}
[importedSiteElements addObject:@[ lastUsed, uses, type, version, counter, loginName, siteName, exportContent ]];
[importedSiteSites addObject:@[ lastUsed, uses, type, version, counter, loginName, siteName, exportContent ]];
dbg( @"Will import site: lastUsed=%@, uses=%@, type=%@, version=%@, counter=%@, loginName=%@, siteName=%@, exportContent=%@",
lastUsed, uses, type, version, counter, loginName, siteName, exportContent );
}
// Ask for confirmation to import these sites and the master password of the user.
inf( @"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteElements count],
(unsigned long)[elementsToDelete count], [MPUserEntity idFor:importUserName] );
NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteElements count],
[elementsToDelete count] );
inf( @"Importing %lu sites, deleting %lu sites, for user: %@", (unsigned long)[importedSiteSites count],
(unsigned long)[sitesToDelete count], [MPUserEntity idFor:importUserName] );
NSString *userMasterPassword = askUserPassword( user? user.name: importUserName, [importedSiteSites count],
[sitesToDelete count] );
if (!userMasterPassword) {
inf( @"Import cancelled." );
return MPImportResultCancelled;
@@ -615,8 +663,8 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
// Delete existing sites.
if (elementsToDelete.count)
[elementsToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
if (sitesToDelete.count)
[sitesToDelete enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
inf( @"Deleting site: %@, it will be replaced by an imported site.", [obj name] );
[context deleteObject:obj];
}];
@@ -637,10 +685,10 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
}
// Import new sites.
for (NSArray *siteElements in importedSiteElements) {
for (NSArray *siteElements in importedSiteSites) {
NSDate *lastUsed = [[NSDateFormatter rfc3339DateFormatter] dateFromString:siteElements[0]];
NSUInteger uses = (unsigned)[siteElements[1] integerValue];
MPElementType type = (MPElementType)[siteElements[2] integerValue];
MPSiteType type = (MPSiteType)[siteElements[2] integerValue];
NSUInteger version = (unsigned)[siteElements[3] integerValue];
NSUInteger counter = [siteElements[4] length]? (unsigned)[siteElements[4] integerValue]: NSNotFound;
NSString *loginName = [siteElements[5] length]? siteElements[5]: nil;
@@ -649,24 +697,24 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
// Create new site.
NSString *typeEntityName = [MPAlgorithmForVersion( version ) classNameOfType:type];
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
element.name = siteName;
element.loginName = loginName;
element.user = user;
element.type = type;
element.uses = uses;
element.lastUsed = lastUsed;
element.version = version;
MPSiteEntity *site = [NSEntityDescription insertNewObjectForEntityForName:typeEntityName inManagedObjectContext:context];
site.name = siteName;
site.loginName = loginName;
site.user = user;
site.type = type;
site.uses = uses;
site.lastUsed = lastUsed;
site.version = version;
if ([exportContent length]) {
if (clearText)
[element.algorithm importClearTextPassword:exportContent intoElement:element usingKey:userKey];
[site.algorithm importClearTextPassword:exportContent intoSite:site usingKey:userKey];
else
[element.algorithm importProtectedPassword:exportContent protectedByKey:importKey intoElement:element usingKey:userKey];
[site.algorithm importProtectedPassword:exportContent protectedByKey:importKey intoSite:site usingKey:userKey];
}
if ([element isKindOfClass:[MPElementGeneratedEntity class]] && counter != NSNotFound)
((MPElementGeneratedEntity *)element).counter = counter;
if ([site isKindOfClass:[MPGeneratedSiteEntity class]] && counter != NSNotFound)
((MPGeneratedSiteEntity *)site).counter = counter;
dbg( @"Created Element: %@", [element debugDescription] );
dbg( @"Created Site: %@", [site debugDescription] );
}
if (![context saveToStore])
@@ -712,27 +760,27 @@ PearlAssociatedObjectProperty( NSManagedObjectContext*, MainManagedObjectContext
[export appendFormat:@"# used used type name\t name\tpassword\n"];
// Sites.
for (MPElementEntity *element in activeUser.elements) {
NSDate *lastUsed = element.lastUsed;
NSUInteger uses = element.uses;
MPElementType type = element.type;
NSUInteger version = element.version;
for (MPSiteEntity *site in activeUser.sites) {
NSDate *lastUsed = site.lastUsed;
NSUInteger uses = site.uses;
MPSiteType type = site.type;
NSUInteger version = site.version;
NSUInteger counter = 0;
NSString *loginName = element.loginName;
NSString *siteName = element.name;
NSString *loginName = site.loginName;
NSString *siteName = site.name;
NSString *content = nil;
// Generated-specific
if ([element isKindOfClass:[MPElementGeneratedEntity class]])
counter = ((MPElementGeneratedEntity *)element).counter;
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
counter = ((MPGeneratedSiteEntity *)site).counter;
// Determine the content to export.
if (!(type & MPElementFeatureDevicePrivate)) {
if (!(type & MPSiteFeatureDevicePrivate)) {
if (revealPasswords)
content = [element.algorithm resolvePasswordForElement:element usingKey:self.key];
else if (type & MPElementFeatureExportContent)
content = [element.algorithm exportPasswordForElement:element usingKey:self.key];
content = [site.algorithm resolvePasswordForSite:site usingKey:self.key];
else if (type & MPSiteFeatureExportContent)
content = [site.algorithm exportPasswordForSite:site usingKey:self.key];
}
[export appendFormat:@"%@ %8ld %8s %25s\t%25s\t%@\n",

View File

@@ -1,27 +0,0 @@
//
// MPElementEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 2014-09-14.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@class MPUserEntity;
@interface MPElementEntity : NSManagedObject
//@property (nonatomic, retain) id content; // Hide here, reveal in MPElementStoredEntity
@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) NSNumber * uses_;
@property (nonatomic, retain) NSNumber * version_;
@property (nonatomic, retain) NSNumber * loginGenerated_;
@property (nonatomic, retain) MPUserEntity *user;
@end

View File

@@ -1,16 +0,0 @@
//
// MPElementGeneratedEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 2014-09-14.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import "MPElementGeneratedEntity.h"
@implementation MPElementGeneratedEntity
@dynamic counter_;
@end

View File

@@ -1,18 +0,0 @@
//
// MPElementStoredEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 2014-09-14.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "MPElementEntity.h"
@interface MPElementStoredEntity : MPElementEntity
@property (nonatomic, retain) NSData * contentObject;
@end

View File

@@ -1,16 +0,0 @@
//
// MPElementStoredEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 2014-09-14.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import "MPElementStoredEntity.h"
@implementation MPElementStoredEntity
@dynamic contentObject;
@end

View File

@@ -1,5 +1,5 @@
//
// MPElementEntities.h
// MPEntities.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 31/05/12.
@@ -7,9 +7,9 @@
//
#import <Foundation/Foundation.h>
#import "MPElementEntity.h"
#import "MPElementStoredEntity.h"
#import "MPElementGeneratedEntity.h"
#import "MPSiteEntity.h"
#import "MPStoredSiteEntity.h"
#import "MPGeneratedSiteEntity.h"
#import "MPUserEntity.h"
#import "MPAlgorithm.h"
#import "MPFixable.h"
@@ -22,10 +22,10 @@
@end
@interface MPElementEntity(MP)<MPFixable>
@interface MPSiteEntity(MP)<MPFixable>
@property(assign) BOOL loginGenerated;
@property(assign) MPElementType type;
@property(assign) MPSiteType type;
@property(readonly) NSString *typeName;
@property(readonly) NSString *typeShortName;
@property(readonly) NSString *typeClassName;
@@ -36,7 +36,7 @@
@property(readonly) id<MPAlgorithm> algorithm;
- (NSUInteger)use;
- (BOOL)migrateExplicitly:(BOOL)explicit;
- (BOOL)tryMigrateExplicitly:(BOOL)explicit;
- (NSString *)resolveLoginUsingKey:(MPKey *)key;
- (NSString *)resolvePasswordUsingKey:(MPKey *)key;
- (void)resolveLoginUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result;
@@ -44,7 +44,7 @@
@end
@interface MPElementGeneratedEntity(MP)
@interface MPGeneratedSiteEntity(MP)
@property(assign) NSUInteger counter;
@@ -54,7 +54,7 @@
@property(assign) NSUInteger avatar;
@property(assign) BOOL saveKey;
@property(assign) MPElementType defaultType;
@property(assign) MPSiteType defaultType;
@property(readonly) NSString *userID;
+ (NSString *)idFor:(NSString *)userName;

View File

@@ -1,5 +1,5 @@
//
// MPElementEntities.m
// MPEntities.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 31/05/12.
@@ -8,7 +8,6 @@
#import "MPEntities.h"
#import "MPAppDelegate_Shared.h"
#import "MPAppDelegate_Store.h"
@implementation NSManagedObjectContext(MP)
@@ -34,16 +33,16 @@
@end
@implementation MPElementEntity(MP)
@implementation MPSiteEntity(MP)
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
return MPFixableResultNoProblems;
}
- (MPElementType)type {
- (MPSiteType)type {
return (MPElementType)[self.type_ unsignedIntegerValue];
return (MPSiteType)[self.type_ unsignedIntegerValue];
}
- (void)setLoginGenerated:(BOOL)aLoginGenerated {
@@ -56,7 +55,7 @@
return [self.loginGenerated_ boolValue];
}
- (void)setType:(MPElementType)aType {
- (void)setType:(MPSiteType)aType {
self.type_ = @(aType);
}
@@ -135,50 +134,52 @@
self.loginName, self.requiresExplicitMigration );
}
- (BOOL)migrateExplicitly:(BOOL)explicit {
- (BOOL)tryMigrateExplicitly:(BOOL)explicit {
while (self.version < MPAlgorithmDefaultVersion)
if ([MPAlgorithmForVersion( self.version + 1 ) migrateElement:self explicit:explicit])
inf( @"%@ migration to version: %ld succeeded for element: %@",
explicit? @"Explicit": @"Automatic", (long)self.version + 1, self );
else {
wrn( @"%@ migration to version: %ld failed for element: %@",
explicit? @"Explicit": @"Automatic", (long)self.version + 1, self );
while (self.version < MPAlgorithmDefaultVersion) {
NSUInteger toVersion = self.version + 1;
if (![MPAlgorithmForVersion( toVersion ) tryMigrateSite:self explicit:explicit]) {
wrn( @"%@ migration to version: %ld failed for site: %@",
explicit? @"Explicit": @"Automatic", (long)toVersion, self );
return NO;
}
inf( @"%@ migration to version: %ld succeeded for site: %@",
explicit? @"Explicit": @"Automatic", (long)toVersion, self );
}
return YES;
}
- (NSString *)resolveLoginUsingKey:(MPKey *)key {
return [self.algorithm resolveLoginForElement:self usingKey:key];
return [self.algorithm resolveLoginForSite:self usingKey:key];
}
- (NSString *)resolvePasswordUsingKey:(MPKey *)key {
return [self.algorithm resolvePasswordForElement:self usingKey:key];
return [self.algorithm resolvePasswordForSite:self usingKey:key];
}
- (void)resolveLoginUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
[self.algorithm resolveLoginForElement:self usingKey:key result:result];
[self.algorithm resolveLoginForSite:self usingKey:key result:result];
}
- (void)resolvePasswordUsingKey:(MPKey *)key result:(void ( ^ )(NSString *))result {
[self.algorithm resolvePasswordForElement:self usingKey:key result:result];
[self.algorithm resolvePasswordForSite:self usingKey:key result:result];
}
@end
@implementation MPElementGeneratedEntity(MP)
@implementation MPGeneratedSiteEntity(MP)
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
MPFixableResult result = [super findAndFixInconsistenciesInContext:context];
if (!self.type || self.type == (MPElementType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
if (!self.type || self.type == (MPSiteType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
// Invalid self.type
result = MPApplyFix( result, ^MPFixableResult {
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
@@ -186,18 +187,18 @@
self.type = self.user.defaultType;
return MPFixableResultProblemsFixed;
} );
if (!self.type || self.type == (MPElementType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
if (!self.type || self.type == (MPSiteType)NSNotFound || ![[self.algorithm allTypes] containsObject:self.type_])
// Invalid self.user.defaultType
result = MPApplyFix( result, ^MPFixableResult {
wrn( @"Invalid type for: %@ of %@, type: %ld. Will use %ld instead.",
self.name, self.user.name, (long)self.type, (long)MPElementTypeGeneratedLong );
self.type = MPElementTypeGeneratedLong;
self.name, self.user.name, (long)self.type, (long)MPSiteTypeGeneratedLong );
self.type = MPSiteTypeGeneratedLong;
return MPFixableResultProblemsFixed;
} );
if (![self isKindOfClass:[self.algorithm classOfType:self.type]])
// Mismatch between self.type and self.class
result = MPApplyFix( result, ^MPFixableResult {
for (MPElementType newType = self.type; self.type != (newType = [self.algorithm nextType:newType]);)
for (MPSiteType newType = self.type; self.type != (newType = [self.algorithm nextType:newType]);)
if ([self isKindOfClass:[self.algorithm classOfType:newType]]) {
wrn( @"Mismatching type for: %@ of %@, type: %lu, class: %@. Will use %ld instead.",
self.name, self.user.name, (long)self.type, self.class, (long)newType );
@@ -225,7 +226,7 @@
@end
@implementation MPElementStoredEntity(MP)
@implementation MPStoredSiteEntity(MP)
- (MPFixableResult)findAndFixInconsistenciesInContext:(NSManagedObjectContext *)context {
@@ -236,7 +237,7 @@
MPKey *key = [MPAppDelegate_Shared get].key;
if (key && [[MPAppDelegate_Shared get] activeUserInContext:context] == self.user) {
wrn( @"Content object not encrypted for: %@ of %@. Will re-encrypt.", self.name, self.user.name );
[self.algorithm savePassword:[self.contentObject description] toElement:self usingKey:key];
[self.algorithm savePassword:[self.contentObject description] toSite:self usingKey:key];
return MPFixableResultProblemsFixed;
}
@@ -271,12 +272,12 @@
self.saveKey_ = @(aSaveKey);
}
- (MPElementType)defaultType {
- (MPSiteType)defaultType {
return (MPElementType)[self.defaultType_ unsignedIntegerValue]?: MPElementTypeGeneratedLong;
return (MPSiteType)[self.defaultType_ unsignedIntegerValue]?: MPSiteTypeGeneratedLong;
}
- (void)setDefaultType:(MPElementType)aDefaultType {
- (void)setDefaultType:(MPSiteType)aDefaultType {
self.defaultType_ = @(aDefaultType);
}

View File

@@ -1,17 +1,17 @@
//
// MPElementGeneratedEntity.h
// MasterPassword-iOS
// MPGeneratedSiteEntity.h
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-14.
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "MPElementEntity.h"
#import "MPSiteEntity.h"
@interface MPElementGeneratedEntity : MPElementEntity
@interface MPGeneratedSiteEntity : MPSiteEntity
@property (nonatomic, retain) NSNumber * counter_;

View File

@@ -0,0 +1,16 @@
//
// MPGeneratedSiteEntity.m
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import "MPGeneratedSiteEntity.h"
@implementation MPGeneratedSiteEntity
@dynamic counter_;
@end

View File

@@ -0,0 +1,41 @@
//
// MPSiteEntity.h
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@class MPSiteQuestionEntity, MPUserEntity;
@interface MPSiteEntity : NSManagedObject
//@property (nonatomic, retain) id content; // Hide here, reveal in MPStoredSiteEntity
@property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, retain) NSNumber * loginGenerated_;
@property (nonatomic, retain) NSString * loginName;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * requiresExplicitMigration_;
@property (nonatomic, retain) NSNumber * type_;
@property (nonatomic, retain) NSNumber * uses_;
@property (nonatomic, retain) NSNumber * version_;
@property (nonatomic, retain) NSOrderedSet *questions;
@property (nonatomic, retain) MPUserEntity *user;
@end
@interface MPSiteEntity (CoreDataGeneratedAccessors)
- (void)insertObject:(MPSiteQuestionEntity *)value inQuestionsAtIndex:(NSUInteger)idx;
- (void)removeObjectFromQuestionsAtIndex:(NSUInteger)idx;
- (void)insertQuestions:(NSArray *)value atIndexes:(NSIndexSet *)indexes;
- (void)removeQuestionsAtIndexes:(NSIndexSet *)indexes;
- (void)replaceObjectInQuestionsAtIndex:(NSUInteger)idx withObject:(MPSiteQuestionEntity *)value;
- (void)replaceQuestionsAtIndexes:(NSIndexSet *)indexes withQuestions:(NSArray *)values;
- (void)addQuestionsObject:(MPSiteQuestionEntity *)value;
- (void)removeQuestionsObject:(MPSiteQuestionEntity *)value;
- (void)addQuestions:(NSOrderedSet *)values;
- (void)removeQuestions:(NSOrderedSet *)values;
@end

View File

@@ -1,26 +1,28 @@
//
// MPElementEntity.m
// MasterPassword-iOS
// MPSiteEntity.m
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-14.
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import "MPElementEntity.h"
#import "MPSiteEntity.h"
#import "MPSiteQuestionEntity.h"
#import "MPUserEntity.h"
@implementation MPElementEntity
@implementation MPSiteEntity
//@dynamic content;
@dynamic lastUsed;
@dynamic loginGenerated_;
@dynamic loginName;
@dynamic name;
@dynamic requiresExplicitMigration_;
@dynamic type_;
@dynamic uses_;
@dynamic version_;
@dynamic loginGenerated_;
@dynamic questions;
@dynamic user;
@end

View File

@@ -0,0 +1,19 @@
//
// MPSiteQuestionEntity.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 2014-09-27.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@class MPSiteEntity;
@interface MPSiteQuestionEntity : NSManagedObject
@property (nonatomic, retain) NSString * keyword;
@property (nonatomic, retain) MPSiteEntity *site;
@end

View File

@@ -0,0 +1,18 @@
//
// MPSiteQuestionEntity.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 2014-09-27.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import "MPSiteQuestionEntity.h"
#import "MPSiteEntity.h"
@implementation MPSiteQuestionEntity
@dynamic keyword;
@dynamic site;
@end

View File

@@ -0,0 +1,18 @@
//
// MPStoredSiteEntity.h
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "MPSiteEntity.h"
@interface MPStoredSiteEntity : MPSiteEntity
@property (nonatomic, retain) NSData *contentObject;
@end

View File

@@ -0,0 +1,16 @@
//
// MPStoredSiteEntity.m
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import "MPStoredSiteEntity.h"
@implementation MPStoredSiteEntity
@dynamic contentObject;
@end

View File

@@ -8,38 +8,41 @@
#import "MPKey.h"
typedef NS_ENUM(NSUInteger, MPElementTypeClass) {
typedef NS_ENUM( NSUInteger, MPSiteTypeClass ) {
/** Generate the password. */
MPElementTypeClassGenerated = 1 << 4,
MPSiteTypeClassGenerated = 1 << 4,
/** Store the password. */
MPElementTypeClassStored = 1 << 5,
MPSiteTypeClassStored = 1 << 5,
};
typedef NS_ENUM(NSUInteger, MPElementVariant) {
typedef NS_ENUM( NSUInteger, MPSiteVariant ) {
/** Generate the password. */
MPElementVariantPassword,
MPSiteVariantPassword,
/** Generate the login name. */
MPElementVariantLogin,
MPSiteVariantLogin,
/** Generate a security answer. */
MPSiteVariantAnswer,
};
typedef NS_ENUM(NSUInteger, MPElementFeature) {
typedef NS_ENUM( NSUInteger, MPSiteFeature ) {
/** Export the key-protected content data. */
MPElementFeatureExportContent = 1 << 10,
MPSiteFeatureExportContent = 1 << 10,
/** Never export content. */
MPElementFeatureDevicePrivate = 1 << 11,
MPSiteFeatureDevicePrivate = 1 << 11,
};
typedef NS_ENUM(NSUInteger, MPElementType) {
MPElementTypeGeneratedMaximum = 0x0 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedLong = 0x1 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedMedium = 0x2 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedBasic = 0x4 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedShort = 0x3 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedPIN = 0x5 | MPElementTypeClassGenerated | 0x0,
MPElementTypeGeneratedName = 0xF | MPElementTypeClassGenerated | 0x0,
typedef NS_ENUM(NSUInteger, MPSiteType) {
MPSiteTypeGeneratedMaximum = 0x0 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedLong = 0x1 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedMedium = 0x2 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedBasic = 0x4 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedShort = 0x3 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedPIN = 0x5 | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedName = 0xE | MPSiteTypeClassGenerated | 0x0,
MPSiteTypeGeneratedPhrase = 0xF | MPSiteTypeClassGenerated | 0x0,
MPElementTypeStoredPersonal = 0x0 | MPElementTypeClassStored | MPElementFeatureExportContent,
MPElementTypeStoredDevicePrivate = 0x1 | MPElementTypeClassStored | MPElementFeatureDevicePrivate,
MPSiteTypeStoredPersonal = 0x0 | MPSiteTypeClassStored | MPSiteFeatureExportContent,
MPSiteTypeStoredDevicePrivate = 0x1 | MPSiteTypeClassStored | MPSiteFeatureDevicePrivate,
};
#define MPErrorDomain @"MPErrorDomain"
@@ -52,7 +55,7 @@ typedef NS_ENUM(NSUInteger, MPElementType) {
#define MPCheckpointEditPassword @"MPCheckpointEditPassword"
#define MPCheckpointEditLoginName @"MPCheckpointEditLoginName"
#define MPCheckpointUseType @"MPCheckpointUseType"
#define MPCheckpointDeleteElement @"MPCheckpointDeleteElement"
#define MPCheckpointDeleteSite @"MPCheckpointDeleteSite"
#define MPCheckpointShowGuide @"MPCheckpointShowGuide"
#define MPCheckpointShowSetup @"MPCheckpointShowSetup"
#define MPCheckpointChangeMP @"MPCheckpointChangeMP"
@@ -76,7 +79,7 @@ typedef NS_ENUM(NSUInteger, MPElementType) {
#define MPSignedInNotification @"MPSignedInNotification"
#define MPSignedOutNotification @"MPSignedOutNotification"
#define MPKeyForgottenNotification @"MPKeyForgottenNotification"
#define MPElementUpdatedNotification @"MPElementUpdatedNotification"
#define MPSiteUpdatedNotification @"MPSiteUpdatedNotification"
#define MPCheckConfigNotification @"MPCheckConfigNotification"
#define MPSitesImportedNotification @"MPSitesImportedNotification"
#define MPFoundInconsistenciesNotification @"MPFoundInconsistenciesNotification"

View File

@@ -1,15 +1,15 @@
//
// MPUserEntity.h
// MasterPassword-iOS
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-14.
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@class MPElementEntity;
@class MPSiteEntity;
@interface MPUserEntity : NSManagedObject
@@ -19,14 +19,14 @@
@property (nonatomic, retain) NSDate * lastUsed;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * saveKey_;
@property (nonatomic, retain) NSSet *elements;
@property (nonatomic, retain) NSSet *sites;
@end
@interface MPUserEntity (CoreDataGeneratedAccessors)
- (void)addElementsObject:(MPElementEntity *)value;
- (void)removeElementsObject:(MPElementEntity *)value;
- (void)addElements:(NSSet *)values;
- (void)removeElements:(NSSet *)values;
- (void)addSitesObject:(MPSiteEntity *)value;
- (void)removeSitesObject:(MPSiteEntity *)value;
- (void)addSites:(NSSet *)values;
- (void)removeSites:(NSSet *)values;
@end

View File

@@ -1,13 +1,13 @@
//
// MPUserEntity.m
// MasterPassword-iOS
// MasterPassword-Mac
//
// Created by Maarten Billemont on 2014-09-14.
// Created by Maarten Billemont on 2014-09-21.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import "MPUserEntity.h"
#import "MPElementEntity.h"
#import "MPSiteEntity.h"
@implementation MPUserEntity
@@ -18,6 +18,6 @@
@dynamic lastUsed;
@dynamic name;
@dynamic saveKey_;
@dynamic elements;
@dynamic sites;
@end

View File

@@ -229,7 +229,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
returningResponse:&response error:&error];
if (error)
err( @"While reading imported sites from %@: %@", url, error );
err( @"While reading imported sites from %@: %@", url, [error fullDescription] );
if (!importedSitesData)
return;
@@ -346,7 +346,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
[moc saveToStore];
NSError *error = nil;
if (![moc obtainPermanentIDsForObjects:@[ newUser ] error:&error])
err( @"Failed to obtain permanent object ID for new user: %@", error );
err( @"Failed to obtain permanent object ID for new user: %@", [error fullDescription] );
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateUsers];
@@ -510,7 +510,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
fetchRequest.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"lastUsed" ascending:NO] ];
NSArray *users = [mainContext executeFetchRequest:fetchRequest error:&error];
if (!users)
err( @"Failed to load users: %@", error );
err( @"Failed to load users: %@", [error fullDescription] );
if (![users count]) {
NSMenuItem *noUsersItem = [self.usersItem.submenu addItemWithTitle:@"No users" action:NULL keyEquivalent:@""];

View File

@@ -17,26 +17,26 @@
//
#import <Cocoa/Cocoa.h>
#import "MPElementModel.h"
#import "MPElementsTableView.h"
#import "MPSiteModel.h"
#import "MPSitesTableView.h"
@class MPMacAppDelegate;
@interface MPPasswordWindowController : NSWindowController<NSTextViewDelegate, NSTextFieldDelegate, NSTableViewDataSource, NSTableViewDelegate>
@property(nonatomic) NSMutableArray *elements;
@property(nonatomic) NSMutableArray *sites;
@property(nonatomic) NSString *masterPassword;
@property(nonatomic) BOOL alternatePressed;
@property(nonatomic) BOOL locked;
@property(nonatomic) BOOL newUser;
@property(nonatomic, weak) IBOutlet NSArrayController *elementsController;
@property(nonatomic, weak) IBOutlet NSArrayController *sitesController;
@property(nonatomic, weak) IBOutlet NSImageView *blurView;
@property(nonatomic, weak) IBOutlet NSTextField *inputLabel;
@property(nonatomic, weak) IBOutlet NSTextField *securePasswordField;
@property(nonatomic, weak) IBOutlet NSTextField *revealPasswordField;
@property(nonatomic, weak) IBOutlet NSSearchField *siteField;
@property(nonatomic, weak) IBOutlet MPElementsTableView *siteTable;
@property(nonatomic, weak) IBOutlet MPSitesTableView *siteTable;
@property(nonatomic, weak) IBOutlet NSProgressIndicator *progressView;
@property(nonatomic, strong) IBOutlet NSBox *passwordTypesBox;

View File

@@ -20,7 +20,7 @@
#import "MPPasswordWindowController.h"
#import "MPMacAppDelegate.h"
#import "MPAppDelegate_Store.h"
#import "MPElementModel.h"
#import "MPSiteModel.h"
#import "MPAppDelegate_Key.h"
#import "PearlProfiler.h"
@@ -77,7 +77,7 @@
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self updateUser];
}];
[self observeKeyPath:@"elementsController.selection"
[self observeKeyPath:@"sitesController.selection"
withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
[_self updateSelection];
}];
@@ -100,7 +100,7 @@
BOOL alternatePressed = (theEvent.modifierFlags & NSAlternateKeyMask) != 0;
if (alternatePressed != self.alternatePressed) {
self.alternatePressed = alternatePressed;
[self.selectedElement updateContent];
[self.selectedSite updateContent];
if (self.locked) {
NSTextField *passwordField = self.securePasswordField;
@@ -169,9 +169,9 @@
}];
}
- (IBAction)doSearchElements:(id)sender {
- (IBAction)doSearchSites:(id)sender {
[self updateElements];
[self updateSites];
}
#pragma mark - NSTextViewDelegate
@@ -186,7 +186,7 @@
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return (NSInteger)[self.elements count];
return (NSInteger)[self.sites count];
}
#pragma mark - NSTableViewDelegate
@@ -229,9 +229,10 @@
switch (returnCode) {
case NSAlertFirstButtonReturn: {
// "Create" button.
[[MPMacAppDelegate get] addElementNamed:[self.siteField stringValue] completion:^(MPElementEntity *element, NSManagedObjectContext *context) {
if (element)
PearlMainQueue( ^{ [self updateElements]; } );
[[MPMacAppDelegate get] addSiteNamed:[self.siteField stringValue] completion:
^(MPSiteEntity *site, NSManagedObjectContext *context) {
if (site)
PearlMainQueue( ^{ [self updateSites]; } );
}];
break;
}
@@ -243,11 +244,11 @@
switch (returnCode) {
case NSAlertFirstButtonReturn: {
// "Save" button.
MPElementType type = (MPElementType)[self.passwordTypesMatrix.selectedCell tag];
MPSiteType type = (MPSiteType)[self.passwordTypesMatrix.selectedCell tag];
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *entity = [[MPMacAppDelegate get] changeElement:[self.selectedElement entityInContext:context]
saveInContext:context toType:type];
if ([entity isKindOfClass:[MPElementStoredEntity class]] && ![(MPElementStoredEntity *)entity contentObject].length)
MPSiteEntity *entity = [[MPMacAppDelegate get] changeSite:[self.selectedSite entityInContext:context]
saveInContext:context toType:type];
if ([entity isKindOfClass:[MPStoredSiteEntity class]] && ![(MPStoredSiteEntity *)entity contentObject].length)
PearlMainQueue( ^{
[self changePassword:nil];
} );
@@ -264,7 +265,7 @@
// "Save" button.
NSString *loginName = [(NSSecureTextField *)alert.accessoryView stringValue];
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *entity = [self.selectedElement entityInContext:context];
MPSiteEntity *entity = [self.selectedSite entityInContext:context];
entity.loginName = loginName;
[context saveToStore];
}];
@@ -280,8 +281,8 @@
// "Save" button.
NSString *password = [(NSSecureTextField *)alert.accessoryView stringValue];
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *entity = [self.selectedElement entityInContext:context];
[entity.algorithm savePassword:password toElement:entity usingKey:[MPMacAppDelegate get].key];
MPSiteEntity *entity = [self.selectedSite entityInContext:context];
[entity.algorithm savePassword:password toSite:entity usingKey:[MPMacAppDelegate get].key];
[context saveToStore];
}];
break;
@@ -295,7 +296,7 @@
case NSAlertFirstButtonReturn: {
// "Delete" button.
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[context deleteObject:[self.selectedElement entityInContext:context]];
[context deleteObject:[self.selectedSite entityInContext:context]];
[context saveToStore];
}];
break;
@@ -313,19 +314,19 @@
return [self.siteField.stringValue stringByReplacingCharactersInRange:self.siteField.currentEditor.selectedRange withString:@""]?: @"";
}
- (void)insertObject:(MPElementModel *)model inElementsAtIndex:(NSUInteger)index {
- (void)insertObject:(MPSiteModel *)model inSitesAtIndex:(NSUInteger)index {
[self.elements insertObject:model atIndex:index];
[self.sites insertObject:model atIndex:index];
}
- (void)removeObjectFromElementsAtIndex:(NSUInteger)index {
- (void)removeObjectFromSitesAtIndex:(NSUInteger)index {
[self.elements removeObjectAtIndex:index];
[self.sites removeObjectAtIndex:index];
}
- (MPElementModel *)selectedElement {
- (MPSiteModel *)selectedSite {
return [self.elementsController.selectedObjects firstObject];
return [self.sitesController.selectedObjects firstObject];
}
#pragma mark - Actions
@@ -336,13 +337,13 @@
[[MPMacAppDelegate get] showPopup:sender];
}
- (IBAction)deleteElement:(id)sender {
- (IBAction)deleteSite:(id)sender {
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Delete"];
[alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:@"Delete Site?"];
[alert setInformativeText:strf( @"Do you want to delete the site named:\n\n%@", self.selectedElement.siteName )];
[alert setInformativeText:strf( @"Do you want to delete the site named:\n\n%@", self.selectedSite.siteName )];
[alert beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertDeleteSite];
}
@@ -353,9 +354,9 @@
[alert addButtonWithTitle:@"Save"];
[alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:@"Change Login Name"];
[alert setInformativeText:strf( @"Enter the login name for: %@", self.selectedElement.siteName )];
[alert setInformativeText:strf( @"Enter the login name for: %@", self.selectedSite.siteName )];
NSTextField *loginField = [[NSTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
loginField.stringValue = self.selectedElement.loginName?: @"";
loginField.stringValue = self.selectedSite.loginName?: @"";
[loginField selectText:self];
[alert setAccessoryView:loginField];
[alert layout];
@@ -380,14 +381,14 @@
- (IBAction)changePassword:(id)sender {
if (!self.selectedElement.stored)
if (!self.selectedSite.stored)
return;
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Save"];
[alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:@"Change Password"];
[alert setInformativeText:strf( @"Enter the new password for: %@", self.selectedElement.siteName )];
[alert setInformativeText:strf( @"Enter the new password for: %@", self.selectedSite.siteName )];
[alert setAccessoryView:[[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]];
[alert layout];
[alert beginSheetModalForWindow:self.window modalDelegate:self
@@ -396,19 +397,19 @@
- (IBAction)changeType:(id)sender {
MPElementModel *element = self.selectedElement;
NSArray *types = [element.algorithm allTypesStartingWith:MPElementTypeGeneratedPIN];
MPSiteModel *site = self.selectedSite;
NSArray *types = [site.algorithm allTypesStartingWith:MPSiteTypeGeneratedPIN];
[self.passwordTypesMatrix renewRows:(NSInteger)[types count] columns:1];
for (NSUInteger t = 0; t < [types count]; ++t) {
MPElementType type = [types[t] unsignedIntegerValue];
NSString *title = [element.algorithm nameOfType:type];
if (type & MPElementTypeClassGenerated)
title = [element.algorithm generatePasswordForSiteNamed:element.siteName ofType:type
withCounter:element.counter usingKey:[MPMacAppDelegate get].key];
MPSiteType type = [types[t] unsignedIntegerValue];
NSString *title = [site.algorithm nameOfType:type];
if (type & MPSiteTypeClassGenerated)
title = [site.algorithm generatePasswordForSiteNamed:site.siteName ofType:type
withCounter:site.counter usingKey:[MPMacAppDelegate get].key];
NSButtonCell *cell = [self.passwordTypesMatrix cellAtRow:(NSInteger)t column:0];
cell.tag = type;
cell.state = type == element.type? NSOnState: NSOffState;
cell.state = type == site.type? NSOnState: NSOffState;
cell.title = title;
}
@@ -416,7 +417,7 @@
[alert addButtonWithTitle:@"Save"];
[alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:@"Change Password Type"];
[alert setInformativeText:strf( @"Choose a new password type for: %@", element.siteName )];
[alert setInformativeText:strf( @"Choose a new password type for: %@", site.siteName )];
[alert setAccessoryView:self.passwordTypesBox];
[alert layout];
[alert beginSheetModalForWindow:self.window modalDelegate:self
@@ -428,11 +429,11 @@
- (BOOL)handleCommand:(SEL)commandSelector {
if (commandSelector == @selector( moveUp: )) {
[self.elementsController selectPrevious:self];
[self.sitesController selectPrevious:self];
return YES;
}
if (commandSelector == @selector( moveDown: )) {
[self.elementsController selectNext:self];
[self.sitesController selectNext:self];
return YES;
}
if (commandSelector == @selector( insertNewline: )) {
@@ -449,19 +450,19 @@
- (void)useSite {
MPElementModel *selectedElement = [self selectedElement];
if (selectedElement) {
MPSiteModel *selectedSite = [self selectedSite];
if (selectedSite) {
// Performing action while content is available. Copy it.
[self copyContent:selectedElement.content];
[self copyContent:selectedSite.content];
[self fadeOut];
NSUserNotification *notification = [NSUserNotification new];
notification.title = @"Password Copied";
if (selectedElement.loginName.length)
notification.subtitle = strf( @"%@ at %@", selectedElement.loginName, selectedElement.siteName );
if (selectedSite.loginName.length)
notification.subtitle = strf( @"%@ at %@", selectedSite.loginName, selectedSite.siteName );
else
notification.subtitle = selectedElement.siteName;
notification.subtitle = selectedSite.siteName;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
}
else {
@@ -499,22 +500,22 @@
}
}
[self updateElements];
[self updateSites];
}];
}
- (void)updateElements {
- (void)updateSites {
if (![MPMacAppDelegate get].key) {
self.elements = nil;
self.sites = nil;
return;
}
PearlProfiler *profiler = [PearlProfiler profilerForTask:@"updateElements"];
PearlProfiler *profiler = [PearlProfiler profilerForTask:@"updateSites"];
NSString *query = [self query];
[profiler finishJob:@"query"];
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
query, query, [[MPMacAppDelegate get] activeUserInContext:context]];
@@ -523,17 +524,17 @@
NSError *error = nil;
NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error];
if (!siteResults) {
err( @"While fetching elements for completion: %@", error );
err( @"While fetching sites for completion: %@", [error fullDescription] );
return;
}
[profiler finishJob:@"do fetch"];
NSMutableArray *newElements = [NSMutableArray arrayWithCapacity:[siteResults count]];
for (MPElementEntity *element in siteResults)
[newElements addObject:[[MPElementModel alloc] initWithEntity:element]];
NSMutableArray *newSites = [NSMutableArray arrayWithCapacity:[siteResults count]];
for (MPSiteEntity *site in siteResults)
[newSites addObject:[[MPSiteModel alloc] initWithEntity:site]];
[profiler finishJob:@"make models"];
self.elements = newElements;
[profiler finishJob:@"update elements"];
self.sites = newSites;
[profiler finishJob:@"update sites"];
}];
[profiler finishJob:@"done"];
}
@@ -545,7 +546,7 @@
return;
}
NSString *siteName = self.selectedElement.siteName;
NSString *siteName = self.selectedSite.siteName;
if (!siteName)
return;
@@ -558,14 +559,14 @@
NSMakeRange( siteNameQueryRange.length, siteName.length - siteNameQueryRange.length );
}
[self.siteTable scrollRowToVisible:(NSInteger)self.elementsController.selectionIndex];
[self.siteTable scrollRowToVisible:(NSInteger)self.sitesController.selectionIndex];
[self updateGradient];
}
- (void)updateGradient {
NSView *siteScrollView = self.siteTable.superview.superview;
NSRect selectedCellFrame = [self.siteTable frameOfCellAtColumn:0 row:((NSInteger)self.elementsController.selectionIndex)];
NSRect selectedCellFrame = [self.siteTable frameOfCellAtColumn:0 row:((NSInteger)self.sitesController.selectionIndex)];
CGFloat selectedOffset = [siteScrollView convertPoint:selectedCellFrame.origin fromView:self.siteTable].y;
CGFloat gradientOpacity = selectedOffset / siteScrollView.bounds.size.height;
self.siteGradient.colors = @[
@@ -596,7 +597,7 @@
}
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
[[self.selectedElement entityInContext:moc] use];
[[self.selectedSite entityInContext:moc] use];
[moc saveToStore];
}];
}

View File

@@ -8,7 +8,7 @@
<customObject id="-2" userLabel="File's Owner" customClass="MPPasswordWindowController">
<connections>
<outlet property="blurView" destination="Bwc-sd-6gm" id="wNV-0x-LJn"/>
<outlet property="elementsController" destination="mcS-ik-b0n" id="cdF-BL-lfg"/>
<outlet property="sitesController" destination="mcS-ik-b0n" id="cdF-BL-lfg"/>
<outlet property="inputLabel" destination="OnR-s6-d4P" id="p6G-Ut-cdu"/>
<outlet property="passwordTypesBox" destination="bZe-7q-i6q" id="Ai3-pt-i6K"/>
<outlet property="passwordTypesMatrix" destination="3fr-Fd-pxx" id="T8g-mS-lxP"/>
@@ -144,7 +144,7 @@
<rect key="frame" x="0.0" y="0.0" width="512" height="147"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" selectionHighlightStyle="sourceList" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="33" rowSizeStyle="automatic" viewBased="YES" floatsGroupRows="NO" id="xvJ-5c-vDp" customClass="MPElementsTableView">
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" selectionHighlightStyle="sourceList" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="33" rowSizeStyle="automatic" viewBased="YES" floatsGroupRows="NO" id="xvJ-5c-vDp" customClass="MPSitesTableView">
<rect key="frame" x="0.0" y="0.0" width="515" height="147"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/>
@@ -262,7 +262,7 @@
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.0" colorSpace="calibratedRGB"/>
</searchFieldCell>
<connections>
<action selector="doSearchElements:" target="-2" id="NJO-iR-OXt"/>
<action selector="doSearchSites:" target="-2" id="NJO-iR-OXt"/>
<binding destination="-2" name="hidden" keyPath="locked" id="fAX-uK-cgn"/>
<outlet property="delegate" destination="-2" id="2WA-uI-asx"/>
</connections>
@@ -371,7 +371,7 @@
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</buttonCell>
<connections>
<action selector="deleteElement:" target="-2" id="mVT-O6-KfN"/>
<action selector="deleteSite:" target="-2" id="mVT-O6-KfN"/>
<binding destination="mcS-ik-b0n" name="hidden" keyPath="canRemove" id="eqU-d2-XhQ">
<dictionary key="options">
<string key="NSValueTransformerName">NSNegateBoolean</string>
@@ -869,9 +869,9 @@
</view>
</window>
<userDefaultsController representsSharedInstance="YES" id="yy2-3W-Ocj"/>
<arrayController objectClassName="MPElementModel" id="mcS-ik-b0n">
<arrayController objectClassName="MPSiteModel" id="mcS-ik-b0n">
<connections>
<binding destination="-2" name="contentArray" keyPath="elements" id="c96-Dv-HK1"/>
<binding destination="-2" name="contentArray" keyPath="sites" id="c96-Dv-HK1"/>
</connections>
</arrayController>
<box autoresizesSubviews="NO" title="Password Types" borderType="line" titlePosition="noTitle" id="bZe-7q-i6q">

View File

@@ -9,20 +9,20 @@
*/
//
// MPElementModel.h
// MPElementModel
// MPSiteModel.h
// MPSiteModel
//
// Created by lhunath on 2/11/2014.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
@class MPElementEntity;
@class MPSiteEntity;
@interface MPElementModel : NSObject
@interface MPSiteModel : NSObject
@property (nonatomic) NSString *siteName;
@property (nonatomic) MPElementType type;
@property (nonatomic) MPSiteType type;
@property (nonatomic) NSString *typeName;
@property (nonatomic) NSString *content;
@property (nonatomic) NSString *contentDisplay;
@@ -34,8 +34,8 @@
@property (nonatomic) BOOL generated;
@property (nonatomic) BOOL stored;
- (id)initWithEntity:(MPElementEntity *)entity;
- (MPElementEntity *)entityInContext:(NSManagedObjectContext *)moc;
- (id)initWithEntity:(MPSiteEntity *)entity;
- (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc;
- (void)updateContent;
@end

View File

@@ -9,26 +9,26 @@
*/
//
// MPElementModel.h
// MPElementModel
// MPSiteModel.h
// MPSiteModel
//
// Created by lhunath on 2/11/2014.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPElementModel.h"
#import "MPElementEntity.h"
#import "MPSiteModel.h"
#import "MPSiteEntity.h"
#import "MPEntities.h"
#import "MPAppDelegate_Shared.h"
#import "MPAppDelegate_Store.h"
#import "MPMacAppDelegate.h"
@implementation MPElementModel {
@implementation MPSiteModel {
NSManagedObjectID *_entityOID;
BOOL _initialized;
}
- (id)initWithEntity:(MPElementEntity *)entity {
- (id)initWithEntity:(MPSiteEntity *)entity {
if (!(self = [super init]))
return nil;
@@ -39,7 +39,7 @@
return self;
}
- (void)setEntity:(MPElementEntity *)entity {
- (void)setEntity:(MPSiteEntity *)entity {
if ([_entityOID isEqual:entity.objectID])
return;
@@ -52,21 +52,21 @@
self.type = entity.type;
self.typeName = entity.typeName;
self.uses = entity.uses_;
self.counter = [entity isKindOfClass:[MPElementGeneratedEntity class]]? [(MPElementGeneratedEntity *)entity counter]: 0;
self.counter = [entity isKindOfClass:[MPGeneratedSiteEntity class]]? [(MPGeneratedSiteEntity *)entity counter]: 0;
// Find all password types and the index of the current type amongst them.
[self updateContent:entity];
}
- (MPElementEntity *)entityInContext:(NSManagedObjectContext *)moc {
- (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc {
if (!_entityOID)
return nil;
NSError *error;
MPElementEntity *entity = (MPElementEntity *)[moc existingObjectWithID:_entityOID error:&error];
MPSiteEntity *entity = (MPSiteEntity *)[moc existingObjectWithID:_entityOID error:&error];
if (!entity)
err( @"Couldn't retrieve active element: %@", error );
err( @"Couldn't retrieve active site: %@", [error fullDescription] );
return entity;
}
@@ -82,9 +82,9 @@
return;
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *entity = [self entityInContext:context];
if ([entity isKindOfClass:[MPElementGeneratedEntity class]]) {
((MPElementGeneratedEntity *)entity).counter = counter;
MPSiteEntity *entity = [self entityInContext:context];
if ([entity isKindOfClass:[MPGeneratedSiteEntity class]]) {
((MPGeneratedSiteEntity *)entity).counter = counter;
[context saveToStore];
[self updateContent:entity];
@@ -94,22 +94,22 @@
- (BOOL)generated {
return self.type & MPElementTypeClassGenerated;
return self.type & MPSiteTypeClassGenerated;
}
- (BOOL)stored {
return self.type & MPElementTypeClassStored;
return self.type & MPSiteTypeClassStored;
}
- (void)updateContent {
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[self updateContent:[MPElementEntity existingObjectWithID:_entityOID inContext:context]];
[self updateContent:[MPSiteEntity existingObjectWithID:_entityOID inContext:context]];
}];
}
- (void)updateContent:(MPElementEntity *)entity {
- (void)updateContent:(MPSiteEntity *)entity {
static NSRegularExpression *re_anyChar;
static dispatch_once_t once = 0;

View File

@@ -9,8 +9,8 @@
*/
//
// MPElementsTableView.h
// MPElementsTableView
// MPSitesTableView.h
// MPSitesTableView
//
// Created by lhunath on 2014-06-30.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
@@ -20,7 +20,7 @@
@class MPPasswordWindowController;
@interface MPElementsTableView : NSTableView
@interface MPSitesTableView : NSTableView
@property(nonatomic, weak) MPPasswordWindowController *controller;

View File

@@ -9,17 +9,17 @@
*/
//
// MPElementsTableView.h
// MPElementsTableView
// MPSitesTableView.h
// MPSitesTableView
//
// Created by lhunath on 2014-06-30.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPElementsTableView.h"
#import "MPSitesTableView.h"
#import "MPPasswordWindowController.h"
@implementation MPElementsTableView
@implementation MPSitesTableView
- (void)doCommandBySelector:(SEL)aSelector {

View File

@@ -19,6 +19,10 @@
<string></string>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>text/plain</string>
</array>
<key>CFBundleTypeName</key>
<string>Master Password sites</string>
<key>CFBundleTypeRole</key>
@@ -27,8 +31,10 @@
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.lyndir.lhunath.MasterPassword.sites</string>
<string>com.lyndir.masterpassword.sites</string>
</array>
<key>LSTypeIsPackage</key>
<integer>0</integer>
</dict>
</array>
<key>CFBundleExecutable</key>
@@ -47,19 +53,6 @@
<string>[auto]</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.lyndir.lhunath.MasterPassword</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.lyndir.lhunath.MasterPassword</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>[auto]</string>
<key>LSApplicationCategoryType</key>
@@ -77,12 +70,16 @@
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.utf8-plain-text</string>
</array>
<key>UTTypeDescription</key>
<string>Master Password sites</string>
<key>UTTypeIconFile</key>
<string></string>
<key>UTTypeIdentifier</key>
<string>com.lyndir.lhunath.MasterPassword.sites</string>
<string>com.lyndir.masterpassword.sites</string>
<key>UTTypeSize320IconFile</key>
<string></string>
<key>UTTypeSize64IconFile</key>

View File

@@ -9,12 +9,12 @@
/* Begin PBXBuildFile section */
93D390C676DF52DA7E459F19 /* MPPasswordWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D9D0061FF1159998F06 /* MPPasswordWindow.m */; };
93D392EC39DA43C46C692C12 /* NSDictionary+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */; };
93D394C4254EEB45FB335AFB /* MPElementsTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39423D7BF4FD31FE6D27C /* MPElementsTableView.m */; };
93D394C4254EEB45FB335AFB /* MPSitesTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39423D7BF4FD31FE6D27C /* MPSitesTableView.m */; };
93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; };
93D3970BCF85F7902E611168 /* PearlProfiler.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DB3A8ADED08C39A6228 /* PearlProfiler.m */; };
93D39784E725A34D1EE3FB3B /* MPInitialWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D3CB30874147D9A9E1B /* MPInitialWindowController.m */; };
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
93D39C5789EFA607CF788082 /* MPElementModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E73BF5CBF8E5B005CD3 /* MPElementModel.m */; };
93D39C5789EFA607CF788082 /* MPSiteModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39E73BF5CBF8E5B005CD3 /* MPSiteModel.m */; };
93D39D304F73B3BBA031522A /* PearlProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D394EEFF5BF555A55AF361 /* PearlProfiler.h */; };
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
93D39F833DEC1C89B2F795AC /* MPPasswordWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */; };
@@ -43,6 +43,11 @@
DA30E9D215722EE500A68B4C /* Pearl-Crypto.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D115722EE500A68B4C /* Pearl-Crypto.m */; };
DA30E9D715723E6900A68B4C /* PearlLazy.h in Headers */ = {isa = PBXBuildFile; fileRef = DA30E9D515723E6900A68B4C /* PearlLazy.h */; };
DA30E9D815723E6900A68B4C /* PearlLazy.m in Sources */ = {isa = PBXBuildFile; fileRef = DA30E9D615723E6900A68B4C /* PearlLazy.m */; };
DA32CFD919CF1C70004F3F0E /* MPGeneratedSiteEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFD819CF1C70004F3F0E /* MPGeneratedSiteEntity.m */; };
DA32CFDC19CF1C70004F3F0E /* MPStoredSiteEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFDB19CF1C70004F3F0E /* MPStoredSiteEntity.m */; };
DA32CFDF19CF1C70004F3F0E /* MPSiteEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFDE19CF1C70004F3F0E /* MPSiteEntity.m */; };
DA32CFE219CF1C71004F3F0E /* MPSiteQuestionEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFE119CF1C71004F3F0E /* MPSiteQuestionEntity.m */; };
DA32CFE519CF1C71004F3F0E /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA32CFE419CF1C71004F3F0E /* MPUserEntity.m */; };
DA3509FE15F101A500C14A8E /* PearlQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = DA3509FC15F101A500C14A8E /* PearlQueue.h */; };
DA3509FF15F101A500C14A8E /* PearlQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = DA3509FD15F101A500C14A8E /* PearlQueue.m */; };
DA3B844F190FC60900246EEA /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA3B844A190FC5A900246EEA /* Crashlytics.framework */; };
@@ -60,12 +65,8 @@
DA5E5CFA1724A667003798D8 /* MPAppDelegate_Shared.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CA01724A667003798D8 /* MPAppDelegate_Shared.m */; };
DA5E5CFB1724A667003798D8 /* MPAppDelegate_Store.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CA21724A667003798D8 /* MPAppDelegate_Store.m */; };
DA5E5CFC1724A667003798D8 /* MPConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CA41724A667003798D8 /* MPConfig.m */; };
DA5E5CFD1724A667003798D8 /* MPElementEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CA61724A667003798D8 /* MPElementEntity.m */; };
DA5E5CFE1724A667003798D8 /* MPElementGeneratedEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CA81724A667003798D8 /* MPElementGeneratedEntity.m */; };
DA5E5CFF1724A667003798D8 /* MPElementStoredEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CAA1724A667003798D8 /* MPElementStoredEntity.m */; };
DA5E5D001724A667003798D8 /* MPEntities.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CAC1724A667003798D8 /* MPEntities.m */; };
DA5E5D011724A667003798D8 /* MPKey.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CAE1724A667003798D8 /* MPKey.m */; };
DA5E5D021724A667003798D8 /* MPUserEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CB11724A667003798D8 /* MPUserEntity.m */; };
DA5E5D031724A667003798D8 /* MPMacAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CB41724A667003798D8 /* MPMacAppDelegate.m */; };
DA5E5D041724A667003798D8 /* MPMacConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CB61724A667003798D8 /* MPMacConfig.m */; };
DA5E5D081724A667003798D8 /* MasterPassword.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5CBF1724A667003798D8 /* MasterPassword.entitlements */; };
@@ -88,8 +89,6 @@
DACA22BC1705DE7D002C6C22 /* NSError+UbiquityStoreManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DACA22B81705DE7D002C6C22 /* NSError+UbiquityStoreManager.h */; };
DACA22BD1705DE7D002C6C22 /* NSError+UbiquityStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = DACA22B91705DE7D002C6C22 /* NSError+UbiquityStoreManager.m */; };
DACA22BE1705DE7D002C6C22 /* UbiquityStoreManager.h in Headers */ = {isa = PBXBuildFile; fileRef = DACA22BA1705DE7D002C6C22 /* UbiquityStoreManager.h */; };
DACA26F91705DF81002C6C22 /* background@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24171705DF7D002C6C22 /* background@2x.png */; };
DACA26FA1705DF81002C6C22 /* background.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24181705DF7D002C6C22 /* background.png */; };
DACA26FE1705DF81002C6C22 /* logo-bare.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA241C1705DF7D002C6C22 /* logo-bare.png */; };
DACA27121705DF81002C6C22 /* avatar-13@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24321705DF7D002C6C22 /* avatar-13@2x.png */; };
DACA27131705DF81002C6C22 /* avatar-3@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DACA24331705DF7D002C6C22 /* avatar-3@2x.png */; };
@@ -229,21 +228,21 @@
/* Begin PBXFileReference section */
93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Indexing.m"; sourceTree = "<group>"; };
93D39240B5143E01F0B75E96 /* MPElementModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementModel.h; sourceTree = "<group>"; };
93D39240B5143E01F0B75E96 /* MPSiteModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSiteModel.h; sourceTree = "<group>"; };
93D392C3918763B3B72CF366 /* MPPasswordWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordWindowController.h; sourceTree = "<group>"; };
93D39368EF3CBFEF2AFCA15A /* MPInitialWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPInitialWindowController.h; sourceTree = "<group>"; };
93D393B97158D7BE9332EA53 /* NSDictionary+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Indexing.h"; sourceTree = "<group>"; };
93D39423D7BF4FD31FE6D27C /* MPElementsTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementsTableView.m; sourceTree = "<group>"; };
93D39423D7BF4FD31FE6D27C /* MPSitesTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSitesTableView.m; sourceTree = "<group>"; };
93D394EEFF5BF555A55AF361 /* PearlProfiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PearlProfiler.h; path = ../../../External/Pearl/Pearl/PearlProfiler.h; sourceTree = "<group>"; };
93D396D04E57792A54D437AC /* NSArray+Indexing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Indexing.h"; sourceTree = "<group>"; };
93D3977484534E99F9BA579D /* MPPasswordWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPasswordWindow.h; sourceTree = "<group>"; };
93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordWindowController.m; sourceTree = "<group>"; };
93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = "<group>"; };
93D39AC6360DDC16AEAA4119 /* MPElementsTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementsTableView.h; sourceTree = "<group>"; };
93D39AC6360DDC16AEAA4119 /* MPSitesTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSitesTableView.h; sourceTree = "<group>"; };
93D39D3CB30874147D9A9E1B /* MPInitialWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPInitialWindowController.m; sourceTree = "<group>"; };
93D39D9D0061FF1159998F06 /* MPPasswordWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPasswordWindow.m; sourceTree = "<group>"; };
93D39DB3A8ADED08C39A6228 /* PearlProfiler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PearlProfiler.m; path = ../../../External/Pearl/Pearl/PearlProfiler.m; sourceTree = "<group>"; };
93D39E73BF5CBF8E5B005CD3 /* MPElementModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementModel.m; sourceTree = "<group>"; };
93D39E73BF5CBF8E5B005CD3 /* MPSiteModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSiteModel.m; sourceTree = "<group>"; };
DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MPInitialWindow.xib; sourceTree = "<group>"; };
DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "shot-laptop-leaning-iphone.png"; sourceTree = "<group>"; };
DA0933CF1747B91B00DE1CEF /* appstore.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = appstore.png; sourceTree = "<group>"; };
@@ -274,6 +273,17 @@
DA30E9D115722EE500A68B4C /* Pearl-Crypto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Pearl-Crypto.m"; sourceTree = "<group>"; };
DA30E9D515723E6900A68B4C /* PearlLazy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlLazy.h; sourceTree = "<group>"; };
DA30E9D615723E6900A68B4C /* PearlLazy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlLazy.m; sourceTree = "<group>"; };
DA32CFD719CF1C70004F3F0E /* MPGeneratedSiteEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPGeneratedSiteEntity.h; sourceTree = "<group>"; };
DA32CFD819CF1C70004F3F0E /* MPGeneratedSiteEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPGeneratedSiteEntity.m; sourceTree = "<group>"; };
DA32CFDA19CF1C70004F3F0E /* MPStoredSiteEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPStoredSiteEntity.h; sourceTree = "<group>"; };
DA32CFDB19CF1C70004F3F0E /* MPStoredSiteEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPStoredSiteEntity.m; sourceTree = "<group>"; };
DA32CFDD19CF1C70004F3F0E /* MPSiteEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSiteEntity.h; sourceTree = "<group>"; };
DA32CFDE19CF1C70004F3F0E /* MPSiteEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSiteEntity.m; sourceTree = "<group>"; };
DA32CFE019CF1C71004F3F0E /* MPSiteQuestionEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSiteQuestionEntity.h; sourceTree = "<group>"; };
DA32CFE119CF1C71004F3F0E /* MPSiteQuestionEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSiteQuestionEntity.m; sourceTree = "<group>"; };
DA32CFE319CF1C71004F3F0E /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
DA32CFE419CF1C71004F3F0E /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
DA32D00019CF470E004F3F0E /* MasterPassword 6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MasterPassword 6.xcdatamodel"; sourceTree = "<group>"; };
DA3509FC15F101A500C14A8E /* PearlQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlQueue.h; sourceTree = "<group>"; };
DA3509FD15F101A500C14A8E /* PearlQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PearlQueue.m; sourceTree = "<group>"; };
DA3B844A190FC5A900246EEA /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Crashlytics.framework; sourceTree = "<group>"; };
@@ -302,19 +312,11 @@
DA5E5CA21724A667003798D8 /* MPAppDelegate_Store.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPAppDelegate_Store.m; sourceTree = "<group>"; };
DA5E5CA31724A667003798D8 /* MPConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPConfig.h; sourceTree = "<group>"; };
DA5E5CA41724A667003798D8 /* MPConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPConfig.m; sourceTree = "<group>"; };
DA5E5CA51724A667003798D8 /* MPElementEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementEntity.h; sourceTree = "<group>"; };
DA5E5CA61724A667003798D8 /* MPElementEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementEntity.m; sourceTree = "<group>"; };
DA5E5CA71724A667003798D8 /* MPElementGeneratedEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementGeneratedEntity.h; sourceTree = "<group>"; };
DA5E5CA81724A667003798D8 /* MPElementGeneratedEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementGeneratedEntity.m; sourceTree = "<group>"; };
DA5E5CA91724A667003798D8 /* MPElementStoredEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPElementStoredEntity.h; sourceTree = "<group>"; };
DA5E5CAA1724A667003798D8 /* MPElementStoredEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPElementStoredEntity.m; sourceTree = "<group>"; };
DA5E5CAB1724A667003798D8 /* MPEntities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPEntities.h; sourceTree = "<group>"; };
DA5E5CAC1724A667003798D8 /* MPEntities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPEntities.m; sourceTree = "<group>"; };
DA5E5CAD1724A667003798D8 /* MPKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPKey.h; sourceTree = "<group>"; };
DA5E5CAE1724A667003798D8 /* MPKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPKey.m; sourceTree = "<group>"; };
DA5E5CAF1724A667003798D8 /* MPTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTypes.h; sourceTree = "<group>"; };
DA5E5CB01724A667003798D8 /* MPUserEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPUserEntity.h; sourceTree = "<group>"; };
DA5E5CB11724A667003798D8 /* MPUserEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPUserEntity.m; sourceTree = "<group>"; };
DA5E5CB31724A667003798D8 /* MPMacAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMacAppDelegate.h; sourceTree = "<group>"; };
DA5E5CB41724A667003798D8 /* MPMacAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMacAppDelegate.m; sourceTree = "<group>"; };
DA5E5CB51724A667003798D8 /* MPMacConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMacConfig.h; sourceTree = "<group>"; };
@@ -747,8 +749,6 @@
DACA22B81705DE7D002C6C22 /* NSError+UbiquityStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+UbiquityStoreManager.h"; sourceTree = "<group>"; };
DACA22B91705DE7D002C6C22 /* NSError+UbiquityStoreManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+UbiquityStoreManager.m"; sourceTree = "<group>"; };
DACA22BA1705DE7D002C6C22 /* UbiquityStoreManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UbiquityStoreManager.h; sourceTree = "<group>"; };
DACA24171705DF7D002C6C22 /* background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "background@2x.png"; sourceTree = "<group>"; };
DACA24181705DF7D002C6C22 /* background.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = background.png; sourceTree = "<group>"; };
DACA241C1705DF7D002C6C22 /* logo-bare.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "logo-bare.png"; sourceTree = "<group>"; };
DACA24321705DF7D002C6C22 /* avatar-13@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-13@2x.png"; sourceTree = "<group>"; };
DACA24331705DF7D002C6C22 /* avatar-3@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-3@2x.png"; sourceTree = "<group>"; };
@@ -995,6 +995,16 @@
DA5E5C961724A667003798D8 /* ObjC */ = {
isa = PBXGroup;
children = (
DA32CFE319CF1C71004F3F0E /* MPUserEntity.h */,
DA32CFE419CF1C71004F3F0E /* MPUserEntity.m */,
DA32CFE019CF1C71004F3F0E /* MPSiteQuestionEntity.h */,
DA32CFE119CF1C71004F3F0E /* MPSiteQuestionEntity.m */,
DA32CFDD19CF1C70004F3F0E /* MPSiteEntity.h */,
DA32CFDE19CF1C70004F3F0E /* MPSiteEntity.m */,
DA32CFDA19CF1C70004F3F0E /* MPStoredSiteEntity.h */,
DA32CFDB19CF1C70004F3F0E /* MPStoredSiteEntity.m */,
DA32CFD719CF1C70004F3F0E /* MPGeneratedSiteEntity.h */,
DA32CFD819CF1C70004F3F0E /* MPGeneratedSiteEntity.m */,
DA3B8454190FC89700246EEA /* MPFixable.m */,
DA3B8455190FC89700246EEA /* MPFixable.h */,
DA5E5CB21724A667003798D8 /* Mac */,
@@ -1012,19 +1022,11 @@
DA5E5CA21724A667003798D8 /* MPAppDelegate_Store.m */,
DA5E5CA31724A667003798D8 /* MPConfig.h */,
DA5E5CA41724A667003798D8 /* MPConfig.m */,
DA5E5CA51724A667003798D8 /* MPElementEntity.h */,
DA5E5CA61724A667003798D8 /* MPElementEntity.m */,
DA5E5CA71724A667003798D8 /* MPElementGeneratedEntity.h */,
DA5E5CA81724A667003798D8 /* MPElementGeneratedEntity.m */,
DA5E5CA91724A667003798D8 /* MPElementStoredEntity.h */,
DA5E5CAA1724A667003798D8 /* MPElementStoredEntity.m */,
DA5E5CAB1724A667003798D8 /* MPEntities.h */,
DA5E5CAC1724A667003798D8 /* MPEntities.m */,
DA5E5CAD1724A667003798D8 /* MPKey.h */,
DA5E5CAE1724A667003798D8 /* MPKey.m */,
DA5E5CAF1724A667003798D8 /* MPTypes.h */,
DA5E5CB01724A667003798D8 /* MPUserEntity.h */,
DA5E5CB11724A667003798D8 /* MPUserEntity.m */,
DA29992619C6A89900AF7DF1 /* MasterPassword.xcdatamodeld */,
);
name = ObjC;
@@ -1046,8 +1048,8 @@
DA5E5CC41724A667003798D8 /* MainMenu.xib */,
DA5E5CC61724A667003798D8 /* main.m */,
DA0933C91747A56A00DE1CEF /* MPInitialWindow.xib */,
93D39E73BF5CBF8E5B005CD3 /* MPElementModel.m */,
93D39240B5143E01F0B75E96 /* MPElementModel.h */,
93D39E73BF5CBF8E5B005CD3 /* MPSiteModel.m */,
93D39240B5143E01F0B75E96 /* MPSiteModel.h */,
DA2508F019511D3600AC23F1 /* MPPasswordWindowController.xib */,
93D39A57A7823DE98A0FF83C /* MPPasswordWindowController.m */,
93D392C3918763B3B72CF366 /* MPPasswordWindowController.h */,
@@ -1055,8 +1057,8 @@
93D3977484534E99F9BA579D /* MPPasswordWindow.h */,
93D39D3CB30874147D9A9E1B /* MPInitialWindowController.m */,
93D39368EF3CBFEF2AFCA15A /* MPInitialWindowController.h */,
93D39423D7BF4FD31FE6D27C /* MPElementsTableView.m */,
93D39AC6360DDC16AEAA4119 /* MPElementsTableView.h */,
93D39423D7BF4FD31FE6D27C /* MPSitesTableView.m */,
93D39AC6360DDC16AEAA4119 /* MPSitesTableView.h */,
);
path = Mac;
sourceTree = "<group>";
@@ -1544,7 +1546,6 @@
DA2509271951B86C00AC23F1 /* screen.png */,
DA0933CF1747B91B00DE1CEF /* appstore.png */,
DA0933CB1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png */,
DACA24161705DF7D002C6C22 /* Background */,
DACA24311705DF7D002C6C22 /* Avatars */,
DACA241C1705DF7D002C6C22 /* logo-bare.png */,
DACA24581705DF7D002C6C22 /* menu-icon.png */,
@@ -1553,15 +1554,6 @@
path = Media;
sourceTree = "<group>";
};
DACA24161705DF7D002C6C22 /* Background */ = {
isa = PBXGroup;
children = (
DACA24171705DF7D002C6C22 /* background@2x.png */,
DACA24181705DF7D002C6C22 /* background.png */,
);
path = Background;
sourceTree = "<group>";
};
DACA24311705DF7D002C6C22 /* Avatars */ = {
isa = PBXGroup;
children = (
@@ -1943,7 +1935,7 @@
attributes = {
CLASSPREFIX = MP;
LastTestingUpgradeCheck = 0510;
LastUpgradeCheck = 0510;
LastUpgradeCheck = 0600;
ORGANIZATIONNAME = Lyndir;
TargetAttributes = {
DA5BFA43147E415C00F98B1E = {
@@ -1958,12 +1950,10 @@
};
buildConfigurationList = DA5BFA3E147E415C00F98B1E /* Build configuration list for PBXProject "MasterPassword-Mac" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
English,
es,
);
mainGroup = DA5BFA39147E415C00F98B1E;
productRefGroup = DA5BFA45147E415C00F98B1E /* Products */;
@@ -2002,8 +1992,6 @@
buildActionMask = 2147483647;
files = (
DAFE4A5A1503982E003ABA7C /* Pearl.strings in Resources */,
DACA26F91705DF81002C6C22 /* background@2x.png in Resources */,
DACA26FA1705DF81002C6C22 /* background.png in Resources */,
DACA26FE1705DF81002C6C22 /* logo-bare.png in Resources */,
DACA27121705DF81002C6C22 /* avatar-13@2x.png in Resources */,
DACA27131705DF81002C6C22 /* avatar-3@2x.png in Resources */,
@@ -2138,28 +2126,29 @@
buildActionMask = 2147483647;
files = (
DA5E5CF61724A667003798D8 /* MPAlgorithm.m in Sources */,
DA32CFE219CF1C71004F3F0E /* MPSiteQuestionEntity.m in Sources */,
DA32CFE519CF1C71004F3F0E /* MPUserEntity.m in Sources */,
DA5E5CF71724A667003798D8 /* MPAlgorithmV0.m in Sources */,
DA5E5CF81724A667003798D8 /* MPAlgorithmV1.m in Sources */,
DA5E5CF91724A667003798D8 /* MPAppDelegate_Key.m in Sources */,
DA5E5CFA1724A667003798D8 /* MPAppDelegate_Shared.m in Sources */,
DA5E5CFB1724A667003798D8 /* MPAppDelegate_Store.m in Sources */,
DA5E5CFC1724A667003798D8 /* MPConfig.m in Sources */,
DA5E5CFD1724A667003798D8 /* MPElementEntity.m in Sources */,
DA5E5CFE1724A667003798D8 /* MPElementGeneratedEntity.m in Sources */,
DA5E5CFF1724A667003798D8 /* MPElementStoredEntity.m in Sources */,
DA29992C19C6A89900AF7DF1 /* MasterPassword.xcdatamodeld in Sources */,
DA3B8456190FC89700246EEA /* MPFixable.m in Sources */,
DA5E5D001724A667003798D8 /* MPEntities.m in Sources */,
DA5E5D011724A667003798D8 /* MPKey.m in Sources */,
DA5E5D021724A667003798D8 /* MPUserEntity.m in Sources */,
DA32CFDC19CF1C70004F3F0E /* MPStoredSiteEntity.m in Sources */,
DA5E5D031724A667003798D8 /* MPMacAppDelegate.m in Sources */,
DA5E5D041724A667003798D8 /* MPMacConfig.m in Sources */,
DA5E5D0C1724A667003798D8 /* main.m in Sources */,
93D39C5789EFA607CF788082 /* MPElementModel.m in Sources */,
93D39C5789EFA607CF788082 /* MPSiteModel.m in Sources */,
93D39F833DEC1C89B2F795AC /* MPPasswordWindowController.m in Sources */,
DA32CFD919CF1C70004F3F0E /* MPGeneratedSiteEntity.m in Sources */,
93D390C676DF52DA7E459F19 /* MPPasswordWindow.m in Sources */,
93D39784E725A34D1EE3FB3B /* MPInitialWindowController.m in Sources */,
93D394C4254EEB45FB335AFB /* MPElementsTableView.m in Sources */,
DA32CFDF19CF1C70004F3F0E /* MPSiteEntity.m in Sources */,
93D394C4254EEB45FB335AFB /* MPSitesTableView.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2265,6 +2254,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
COMBINE_HIDPI_IMAGES = YES;
};
name = "Debug-Mac";
};
@@ -2272,6 +2262,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
COMBINE_HIDPI_IMAGES = YES;
};
name = "AdHoc-Mac";
};
@@ -2279,6 +2270,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
COMBINE_HIDPI_IMAGES = YES;
};
name = "AppStore-Mac";
};
@@ -2286,6 +2278,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
COMBINE_HIDPI_IMAGES = YES;
};
name = "Debug-Mac";
};
@@ -2293,6 +2286,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
COMBINE_HIDPI_IMAGES = YES;
};
name = "AdHoc-Mac";
};
@@ -2300,6 +2294,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
COMBINE_HIDPI_IMAGES = YES;
};
name = "AppStore-Mac";
};
@@ -2320,11 +2315,13 @@
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
@@ -2393,11 +2390,13 @@
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
@@ -2515,11 +2514,13 @@
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__ARC_BRIDGE_CAST_NONARC = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
@@ -2600,6 +2601,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
COMBINE_HIDPI_IMAGES = YES;
DSTROOT = /tmp/Pearl.dst;
GCC_PREFIX_HEADER = "../Pearl/Pearl-Prefix.pch";
LIBRARY_SEARCH_PATHS = (
@@ -2619,6 +2621,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
COMBINE_HIDPI_IMAGES = YES;
DSTROOT = /tmp/jrswizzle.dst;
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
OTHER_LDFLAGS = "-ObjC";
@@ -2630,18 +2633,21 @@
DABC6C0B175D8C85000C15D4 /* Debug-Mac */ = {
isa = XCBuildConfiguration;
buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
};
name = "Debug-Mac";
};
DABC6C0C175D8C85000C15D4 /* AdHoc-Mac */ = {
isa = XCBuildConfiguration;
buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
};
name = "AdHoc-Mac";
};
DABC6C0D175D8C85000C15D4 /* AppStore-Mac */ = {
isa = XCBuildConfiguration;
buildSettings = {
COMBINE_HIDPI_IMAGES = YES;
};
name = "AppStore-Mac";
};
@@ -2649,6 +2655,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
COMBINE_HIDPI_IMAGES = YES;
DSTROOT = /tmp/jrswizzle.dst;
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
OTHER_LDFLAGS = "-ObjC";
@@ -2661,6 +2668,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
COMBINE_HIDPI_IMAGES = YES;
DSTROOT = /tmp/jrswizzle.dst;
GCC_WARN_INHIBIT_ALL_WARNINGS = YES;
OTHER_LDFLAGS = "-ObjC";
@@ -2673,6 +2681,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
COMBINE_HIDPI_IMAGES = YES;
DSTROOT = /tmp/Pearl.dst;
GCC_PREFIX_HEADER = "../Pearl/Pearl-Prefix.pch";
LIBRARY_SEARCH_PATHS = (
@@ -2692,6 +2701,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = YES;
COMBINE_HIDPI_IMAGES = YES;
DSTROOT = /tmp/Pearl.dst;
GCC_PREFIX_HEADER = "../Pearl/Pearl-Prefix.pch";
LIBRARY_SEARCH_PATHS = (
@@ -2791,8 +2801,9 @@
DA29992919C6A89900AF7DF1 /* MasterPassword 3.xcdatamodel */,
DA29992A19C6A89900AF7DF1 /* MasterPassword 4.xcdatamodel */,
DA29992B19C6A89900AF7DF1 /* MasterPassword 5.xcdatamodel */,
DA32D00019CF470E004F3F0E /* MasterPassword 6.xcdatamodel */,
);
currentVersion = DA29992B19C6A89900AF7DF1 /* MasterPassword 5.xcdatamodel */;
currentVersion = DA32D00019CF470E004F3F0E /* MasterPassword 6.xcdatamodel */;
path = MasterPassword.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;

View File

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

View File

@@ -1,30 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0"
lastSavedToolsVersion="1171" systemVersion="11E53" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6244" systemVersion="13E28" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPElementEntity" representedClassName="MPSiteEntity" isAbstract="YES" elementID="58EE245C-6827-4C11-BB7E-5722F2426EC2" syncable="YES">
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
<attribute name="lastUsed" attributeType="Date" syncable="YES"/>
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
<attribute name="type_" attributeType="Integer 16" defaultValueString="17" syncable="YES"/>
<attribute name="uses_" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements"
inverseEntity="MPUserEntity" syncable="YES"/>
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" elementID="FC8AE32E-7BE3-4FA6-8611-B7DC0DB063EF" syncable="YES"/>
</entity>
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
<entity name="MPElementGeneratedEntity" representedClassName="MPGeneratedSiteEntity" parentEntity="MPElementEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
</entity>
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
<entity name="MPElementStoredEntity" representedClassName="MPStoredSiteEntity" parentEntity="MPElementEntity" elementID="BEEF1688-0CCD-4699-A86A-4D860FE2CEB8" syncable="YES">
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
</entity>
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
<entity name="MPUserEntity" representedClassName="MPUserEntity" elementID="D18D6772-040E-4CFE-8F32-A34B08E9E9BC" 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="saveKey_" attributeType="Boolean" defaultValueString="NO"/>
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity"
inverseName="user" inverseEntity="MPElementEntity" 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="135"/>
@@ -32,4 +31,4 @@
<element name="MPElementStoredEntity" positionX="160" positionY="192" width="128" height="60"/>
<element name="MPUserEntity" positionX="160" positionY="192" width="128" height="150"/>
</elements>
</model>
</model>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0"
lastSavedToolsVersion="2057" systemVersion="12C60" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6244" systemVersion="13E28" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPElementEntity" representedClassName="MPSiteEntity" isAbstract="YES" elementID="58EE245C-6827-4C11-BB7E-5722F2426EC2" syncable="YES">
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES"/>
@@ -12,16 +11,15 @@
<attribute name="userName" optional="YES" attributeType="String" 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"/>
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" elementID="FC8AE32E-7BE3-4FA6-8611-B7DC0DB063EF" syncable="YES"/>
</entity>
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
<entity name="MPElementGeneratedEntity" representedClassName="MPGeneratedSiteEntity" parentEntity="MPElementEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
</entity>
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
<entity name="MPElementStoredEntity" representedClassName="MPStoredSiteEntity" parentEntity="MPElementEntity" elementID="BEEF1688-0CCD-4699-A86A-4D860FE2CEB8" syncable="YES">
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
</entity>
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
<entity name="MPUserEntity" representedClassName="MPUserEntity" elementID="D18D6772-040E-4CFE-8F32-A34B08E9E9BC" 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"/>
@@ -31,8 +29,7 @@
<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"/>
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" syncable="YES"/>
</entity>
<elements>
<element name="MPElementEntity" positionX="0" positionY="0" width="128" height="180"/>
@@ -40,4 +37,4 @@
<element name="MPElementStoredEntity" positionX="216" positionY="144" width="128" height="60"/>
<element name="MPUserEntity" positionX="-216" positionY="0" width="128" height="165"/>
</elements>
</model>
</model>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0"
lastSavedToolsVersion="1810" systemVersion="12B19" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6244" systemVersion="13E28" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPElementEntity" representedClassName="MPSiteEntity" isAbstract="YES" elementID="58EE245C-6827-4C11-BB7E-5722F2426EC2" 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" elementID="A1B9F981-D33C-4BFE-9F94-C9D3E1F78E51" syncable="YES"/>
@@ -12,13 +11,12 @@
<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"/>
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" elementID="FC8AE32E-7BE3-4FA6-8611-B7DC0DB063EF" syncable="YES"/>
</entity>
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
<entity name="MPElementGeneratedEntity" representedClassName="MPGeneratedSiteEntity" parentEntity="MPElementEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
</entity>
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
<entity name="MPElementStoredEntity" representedClassName="MPStoredSiteEntity" parentEntity="MPElementEntity" elementID="BEEF1688-0CCD-4699-A86A-4D860FE2CEB8" syncable="YES">
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
</entity>
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
@@ -31,8 +29,7 @@
<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"/>
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" elementID="D18D6772-040E-4CFE-8F32-A34B08E9E9BC" syncable="YES"/>
</entity>
<elements>
<element name="MPElementEntity" positionX="-0" positionY="-286" width="128" height="178"/>
@@ -40,4 +37,4 @@
<element name="MPElementStoredEntity" positionX="214" positionY="-171" width="128" height="58"/>
<element name="MPUserEntity" positionX="-218" positionY="-288" width="128" height="163"/>
</elements>
</model>
</model>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0"
lastSavedToolsVersion="2057" systemVersion="12C60" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6244" systemVersion="13E28" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPElementEntity" representedClassName="MPSiteEntity" isAbstract="YES" elementID="58EE245C-6827-4C11-BB7E-5722F2426EC2" 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" elementID="A1B9F981-D33C-4BFE-9F94-C9D3E1F78E51" syncable="YES"/>
@@ -12,13 +11,12 @@
<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"/>
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" elementID="FC8AE32E-7BE3-4FA6-8611-B7DC0DB063EF" syncable="YES"/>
</entity>
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
<entity name="MPElementGeneratedEntity" representedClassName="MPGeneratedSiteEntity" parentEntity="MPElementEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
</entity>
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
<entity name="MPElementStoredEntity" representedClassName="MPStoredSiteEntity" parentEntity="MPElementEntity" elementID="BEEF1688-0CCD-4699-A86A-4D860FE2CEB8" syncable="YES">
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
</entity>
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
@@ -30,8 +28,7 @@
<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"/>
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" elementID="D18D6772-040E-4CFE-8F32-A34B08E9E9BC" syncable="YES"/>
</entity>
<elements>
<element name="MPElementEntity" positionX="-0" positionY="-286" width="128" height="178"/>
@@ -39,4 +36,4 @@
<element name="MPElementStoredEntity" positionX="214" positionY="-171" width="128" height="58"/>
<element name="MPUserEntity" positionX="-218" positionY="-288" width="128" height="148"/>
</elements>
</model>
</model>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6244" systemVersion="13E28" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" elementID="58EE245C-6827-4C11-BB7E-5722F2426EC2" syncable="YES">
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
<attribute name="loginGenerated_" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
@@ -12,12 +12,12 @@
<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"/>
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="elements" inverseEntity="MPUserEntity" elementID="FC8AE32E-7BE3-4FA6-8611-B7DC0DB063EF" syncable="YES"/>
</entity>
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" syncable="YES">
<entity name="MPElementGeneratedEntity" representedClassName="MPElementGeneratedEntity" parentEntity="MPElementEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
</entity>
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" syncable="YES">
<entity name="MPElementStoredEntity" representedClassName="MPElementStoredEntity" parentEntity="MPElementEntity" elementID="BEEF1688-0CCD-4699-A86A-4D860FE2CEB8" syncable="YES">
<attribute name="contentObject" optional="YES" attributeType="Transformable" storedInTruthFile="YES" syncable="YES"/>
</entity>
<entity name="MPUserEntity" representedClassName="MPUserEntity" syncable="YES">
@@ -29,7 +29,7 @@
<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"/>
<relationship name="elements" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPElementEntity" inverseName="user" inverseEntity="MPElementEntity" elementID="D18D6772-040E-4CFE-8F32-A34B08E9E9BC" syncable="YES"/>
</entity>
<elements>
<element name="MPElementEntity" positionX="-0" positionY="-286" width="128" height="193"/>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="6244" systemVersion="13E28" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="MPGeneratedSiteEntity" representedClassName="MPGeneratedSiteEntity" parentEntity="MPSiteEntity" elementID="789304EA-070D-4982-8C20-54EECFC20CB6" syncable="YES">
<attribute name="counter_" optional="YES" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
</entity>
<entity name="MPSiteEntity" representedClassName="MPSiteEntity" isAbstract="YES" elementID="58EE245C-6827-4C11-BB7E-5722F2426EC2" syncable="YES">
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
<attribute name="lastUsed" attributeType="Date" indexed="YES" syncable="YES"/>
<attribute name="loginGenerated_" attributeType="Boolean" defaultValueString="NO" syncable="YES"/>
<attribute name="loginName" optional="YES" attributeType="String" elementID="A1B9F981-D33C-4BFE-9F94-C9D3E1F78E51" 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="questions" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="MPSiteQuestionEntity" inverseName="site" inverseEntity="MPSiteQuestionEntity" syncable="YES"/>
<relationship name="user" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="MPUserEntity" inverseName="sites" inverseEntity="MPUserEntity" elementID="FC8AE32E-7BE3-4FA6-8611-B7DC0DB063EF" syncable="YES"/>
</entity>
<entity name="MPSiteQuestionEntity" representedClassName="MPSiteQuestionEntity" syncable="YES">
<attribute name="keyword" attributeType="String" syncable="YES"/>
<relationship name="site" maxCount="1" deletionRule="Nullify" destinationEntity="MPSiteEntity" inverseName="questions" inverseEntity="MPSiteEntity" syncable="YES"/>
</entity>
<entity name="MPStoredSiteEntity" representedClassName="MPStoredSiteEntity" parentEntity="MPSiteEntity" elementID="BEEF1688-0CCD-4699-A86A-4D860FE2CEB8" 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="saveKey_" attributeType="Boolean" defaultValueString="NO">
<userInfo/>
</attribute>
<relationship name="sites" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="MPSiteEntity" inverseName="user" inverseEntity="MPSiteEntity" elementID="D18D6772-040E-4CFE-8F32-A34B08E9E9BC" syncable="YES"/>
</entity>
<elements>
<element name="MPGeneratedSiteEntity" positionX="216" positionY="-288" width="128" height="58"/>
<element name="MPSiteEntity" positionX="-0" positionY="-286" width="128" height="208"/>
<element name="MPSiteQuestionEntity" positionX="-2" positionY="-9" width="128" height="73"/>
<element name="MPStoredSiteEntity" positionX="214" positionY="-171" width="128" height="58"/>
<element name="MPUserEntity" positionX="-218" positionY="-288" width="128" height="148"/>
</elements>
</model>

View File

@@ -0,0 +1,13 @@
//
// AttributedMarkdown.h
// AttributedMarkdown
//
// Created by Maarten Billemont on 2014-09-28.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface AttributedMarkdown : NSObject
@end

View File

@@ -0,0 +1,13 @@
//
// AttributedMarkdown.m
// AttributedMarkdown
//
// Created by Maarten Billemont on 2014-09-28.
// Copyright (c) 2014 Lyndir. All rights reserved.
//
#import "AttributedMarkdown.h"
@implementation AttributedMarkdown
@end

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.lyndir.$(PRODUCT_NAME:rfc1034identifier)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@@ -0,0 +1,46 @@
//
// MPPreferencesViewController.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "MPTypeViewController.h"
#import "MPSiteQuestionEntity.h"
@interface MPAnswersViewController : UIViewController
@property (nonatomic) IBOutlet UITableView *tableView;
- (void)setSite:(MPSiteEntity *)site;
- (MPSiteEntity *)siteInContext:(NSManagedObjectContext *)context;
@end
@interface MPGlobalAnswersCell : UITableViewCell
@property (nonatomic) IBOutlet UILabel *titleLabel;
@property (nonatomic) IBOutlet UITextField *answerField;
- (void)setSite:(MPSiteEntity *)site;
@end
@interface MPSendAnswersCell : UITableViewCell
@end
@interface MPMultipleAnswersCell : UITableViewCell <UITextFieldDelegate>
@end
@interface MPAnswersQuestionCell : UITableViewCell
@property(nonatomic) IBOutlet UITextField *questionField;
@property(nonatomic) IBOutlet UITextField *answerField;
- (void)setQuestion:(MPSiteQuestionEntity *)question forSite:(MPSiteEntity *)site;
@end

View File

@@ -0,0 +1,321 @@
//
// MPPreferencesViewController.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPAnswersViewController.h"
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Store.h"
#import "MPOverlayViewController.h"
@interface MPAnswersViewController()
@end
@implementation MPAnswersViewController {
NSManagedObjectID *_siteOID;
BOOL _multiple;
}
#pragma mark - Life
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.tableHeaderView = [UIView new];
self.tableView.tableFooterView = [UIView new];
self.view.backgroundColor = [UIColor clearColor];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
PearlAddNotificationObserver( MPSignedOutNotification, nil, [NSOperationQueue mainQueue],
^(MPAnswersViewController *self, NSNotification *note) {
if (![note.userInfo[@"animated"] boolValue])
[UIView setAnimationsEnabled:NO];
[[MPOverlaySegue dismissViewController:self] perform];
[UIView setAnimationsEnabled:YES];
} );
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
PearlRemoveNotificationObservers();
}
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
#pragma mark - State
- (void)setSite:(MPSiteEntity *)site {
_siteOID = [site objectID];
_multiple = [site.questions count] > 0;
[self.tableView reloadData];
[self updateAnimated:NO];
}
- (void)setMultiple:(BOOL)multiple animated:(BOOL)animated {
_multiple = multiple;
[self updateAnimated:animated];
}
- (MPSiteEntity *)siteInContext:(NSManagedObjectContext *)context {
return [MPSiteEntity existingObjectWithID:_siteOID inContext:context];
}
#pragma mark - UITableViewDelegate
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0)
return 3;
if (!_multiple)
return 0;
return MAX( 2, [[self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].questions count] );
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
if (indexPath.section == 0) {
if (indexPath.item == 0) {
MPGlobalAnswersCell *cell = [MPGlobalAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath];
[cell setSite:site];
return cell;
}
if (indexPath.item == 1)
return [MPSendAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath];
if (indexPath.item == 2) {
MPMultipleAnswersCell *cell = [MPMultipleAnswersCell dequeueCellFromTableView:tableView indexPath:indexPath];
cell.accessoryType = _multiple? UITableViewCellAccessoryCheckmark: UITableViewCellAccessoryNone;
return cell;
}
Throw( @"Unsupported row index: %@", indexPath );
}
MPAnswersQuestionCell *cell = [MPAnswersQuestionCell dequeueCellFromTableView:tableView indexPath:indexPath];
MPSiteQuestionEntity *question = nil;
if ([site.questions count] > indexPath.item)
question = site.questions[indexPath.item];
[cell setQuestion:question forSite:site];
return cell;
}
#pragma mark - UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
if (indexPath.item == 0)
return 133;
return 44;
}
return 130;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([cell isKindOfClass:[MPGlobalAnswersCell class]]) {
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2];
[UIPasteboard generalPasteboard].string = ((MPGlobalAnswersCell *)cell).answerField.text;
}
else if ([cell isKindOfClass:[MPMultipleAnswersCell class]]) {
if (!_multiple)
[self setMultiple:YES animated:YES];
else if (_multiple) {
if (![site.questions count])
[self setMultiple:NO animated:YES];
else
[PearlAlert showAlertWithTitle:@"Remove Site Questions?" message:
@"Do you want to remove the questions you have configured for this site?"
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPSiteEntity *site_ = [self siteInContext:context];
NSOrderedSet *questions = [site_.questions copy];
for (MPSiteQuestionEntity *question in questions)
[context deleteObject:question];
[context saveToStore];
[self setMultiple:NO animated:YES];
}];
} cancelTitle:@"Cancel" otherTitles:@"Remove Questions", nil];
}
}
else if ([cell isKindOfClass:[MPSendAnswersCell class]]) {
NSString *body;
if (!_multiple) {
NSObject *answer = [site.algorithm resolveAnswerForSite:site usingKey:[MPiOSAppDelegate get].key];
body = strf( @"Master Password generated the following security answer for your site: %@\n\n"
@"%@\n"
@"\n\nYou should use this as the answer to each security question the site asks you.\n"
@"Do not share this answer with others!", site.name, answer );
}
else {
NSMutableString *bodyBuilder = [NSMutableString string];
[bodyBuilder appendFormat:@"Master Password generated the following security answers for your site: %@\n\n", site.name];
for (MPSiteQuestionEntity *question in site.questions) {
NSObject *answer = [site.algorithm resolveAnswerForQuestion:question usingKey:[MPiOSAppDelegate get].key];
[bodyBuilder appendFormat:@"For question: '%@', use answer: %@\n", question.keyword, answer];
}
[bodyBuilder appendFormat:@"\n\nUse the answer for the matching security question.\n"
@"Do not share this answer with others!"];
body = bodyBuilder;
}
[PearlEMail sendEMailTo:nil fromVC:self subject:strf( @"Master Password security answers for %@", site.name ) body:body];
}
else if ([cell isKindOfClass:[MPAnswersQuestionCell class]]) {
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Answer Copied" ) dismissAfter:2];
[UIPasteboard generalPasteboard].string = ((MPAnswersQuestionCell *)cell).answerField.text;
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
#pragma mark - Private
- (void)updateAnimated:(BOOL)animated {
PearlMainQueue( ^{
UITableViewCell *multipleAnswersCell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForItem:2 inSection:0]];
multipleAnswersCell.accessoryType = _multiple? UITableViewCellAccessoryCheckmark: UITableViewCellAccessoryNone;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationAutomatic];
}];
} );
}
@end
@implementation MPGlobalAnswersCell
#pragma mark - State
- (void)setSite:(MPSiteEntity *)site {
self.titleLabel.text = strl( @"Answer for %@:", site.name );
self.answerField.text = @"...";
[site.algorithm resolveAnswerForSite:site usingKey:[MPiOSAppDelegate get].key result:^(NSString *result) {
PearlMainQueue( ^{
self.answerField.text = result;
} );
}];
}
@end
@implementation MPSendAnswersCell
@end
@implementation MPMultipleAnswersCell
@end
@implementation MPAnswersQuestionCell {
NSManagedObjectID *_siteOID;
NSManagedObjectID *_questionOID;
}
#pragma mark - State
- (void)setQuestion:(MPSiteQuestionEntity *)question forSite:(MPSiteEntity *)site {
_siteOID = site.objectID;
_questionOID = question.objectID;
[self updateAnswerForQuestion:question ofSite:site];
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return NO;
}
- (IBAction)textFieldDidChange:(UITextField *)textField {
NSString *keyword = textField.text;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPSiteEntity *site = [MPSiteEntity existingObjectWithID:_siteOID inContext:context];
MPSiteQuestionEntity *question = [MPSiteQuestionEntity existingObjectWithID:_questionOID inContext:context];
if (!question) {
[site addQuestionsObject:question = [MPSiteQuestionEntity insertNewObjectInContext:context]];
question.site = site;
}
question.keyword = keyword;
if ([context saveToStore]) {
if ([question.objectID isTemporaryID]) {
NSError *error = nil;
[context obtainPermanentIDsForObjects:@[ question ] error:&error];
if (error)
err( @"Failed to obtain permanent object ID: %@", [error fullDescription] );
}
_questionOID = question.objectID;
[self updateAnswerForQuestion:question ofSite:site];
}
}];
}
#pragma mark - Private
- (void)updateAnswerForQuestion:(MPSiteQuestionEntity *)question ofSite:(MPSiteEntity *)site {
if (!question)
PearlMainQueue( ^{
self.questionField.text = self.answerField.text = nil;
} );
else {
NSString *keyword = question.keyword;
PearlMainQueue( ^{
self.answerField.text = @"...";
} );
[site.algorithm resolveAnswerForQuestion:question usingKey:[MPiOSAppDelegate get].key result:^(NSString *result) {
PearlMainQueue( ^{
self.questionField.text = keyword;
self.answerField.text = result;
} );
}];
}
}
@end

View File

@@ -19,23 +19,6 @@
#import "MPAppSettingsViewController.h"
#import "UIColor+Expanded.h"
@interface MPTableView:UITableView
@end
@implementation MPTableView
- (void)layoutSubviews {
[super layoutSubviews];
}
- (void)setContentInset:(UIEdgeInsets)contentInset {
[super setContentInset:contentInset];
}
@end
@implementation MPAppSettingsViewController {
}

View File

@@ -214,18 +214,20 @@ const long MPAvatarAdd = 10000;
switch (self.mode) {
case MPAvatarModeLowered: {
[self.avatarSizeConstraint updateConstant:self.avatarImageView.image.size.height];
[self.avatarSizeConstraint updateConstant:
self.avatarImageView.image.size.height * (self.visibility * 0.3f + 0.7f)];
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultLow];
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultLow];
self.nameContainer.alpha = self.visibility;
self.nameContainer.backgroundColor = [UIColor clearColor];
self.avatarImageView.alpha = self.visibility / 0.7f + 0.3f;
self.avatarImageView.alpha = self.visibility * 0.7f + 0.3f;
self.avatarImageView.layer.shadowRadius = 15 * self.visibility * self.visibility;
break;
}
case MPAvatarModeRaisedButInactive: {
[self.avatarSizeConstraint updateConstant:self.avatarImageView.image.size.height];
[self.avatarSizeConstraint updateConstant:
self.avatarImageView.image.size.height * (self.visibility * 0.7f + 0.3f)];
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultHigh];
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultLow];
@@ -236,7 +238,8 @@ const long MPAvatarAdd = 10000;
break;
}
case MPAvatarModeRaisedAndActive: {
[self.avatarSizeConstraint updateConstant:self.avatarImageView.image.size.height];
[self.avatarSizeConstraint updateConstant:
self.avatarImageView.image.size.height * (self.visibility * 0.7f + 0.3f)];
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultHigh];
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultHigh];
@@ -247,7 +250,8 @@ const long MPAvatarAdd = 10000;
break;
}
case MPAvatarModeRaisedAndHidden: {
[self.avatarSizeConstraint updateConstant:self.avatarImageView.image.size.height];
[self.avatarSizeConstraint updateConstant:
self.avatarImageView.image.size.height * (self.visibility * 0.7f + 0.3f)];
[self.avatarRaisedConstraint updatePriority:UILayoutPriorityDefaultHigh];
[self.avatarToTopConstraint updatePriority:UILayoutPriorityDefaultLow];
[self.nameToCenterConstraint updatePriority:UILayoutPriorityDefaultHigh];

View File

@@ -16,6 +16,10 @@
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPUsersViewController.h"
#import "MPPasswordsViewController.h"
#import "MPEmergencyViewController.h"
typedef NS_ENUM(NSUInteger, MPCombinedMode) {
MPCombinedModeUserSelection,
MPCombinedModePasswordSelection,
@@ -23,7 +27,9 @@ typedef NS_ENUM(NSUInteger, MPCombinedMode) {
@interface MPCombinedViewController : UIViewController
@property(assign, nonatomic) MPCombinedMode mode;
@property(strong, nonatomic) IBOutlet UIView *usersView;
@property(nonatomic) MPCombinedMode mode;
@property(nonatomic, weak) MPUsersViewController *usersVC;
@property(nonatomic, weak) MPPasswordsViewController *passwordsVC;
@property(nonatomic, weak) MPEmergencyViewController *emergencyVC;
@end

View File

@@ -1,12 +1,12 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPCombinedViewController.h
@@ -19,26 +19,19 @@
#import "MPCombinedViewController.h"
#import "MPUsersViewController.h"
#import "MPPasswordsViewController.h"
#import "MPEmergencySegue.h"
#import "MPEmergencyViewController.h"
#import "MPPasswordsSegue.h"
@interface MPCombinedViewController()
@implementation MPCombinedViewController
@property(nonatomic, weak) MPUsersViewController *usersVC;
@property(nonatomic, weak) MPEmergencyViewController *emergencyVC;
@end
@implementation MPCombinedViewController {
NSArray *_notificationObservers;
MPPasswordsViewController *_passwordsVC;
}
#pragma mark - Life
- (void)viewDidLoad {
[super viewDidLoad];
_mode = MPCombinedModeUserSelection;
[self performSegueWithIdentifier:@"users" sender:self];
}
- (void)viewWillAppear:(BOOL)animated {
@@ -52,14 +45,21 @@
[super viewDidAppear:animated];
[self registerObservers];
PearlAddNotificationObserver( MPSignedInNotification, nil, [NSOperationQueue mainQueue],
^(MPCombinedViewController *self, NSNotification *note) {
[self setMode:MPCombinedModePasswordSelection];
} );
PearlAddNotificationObserver( MPSignedOutNotification, nil, [NSOperationQueue mainQueue],
^(MPCombinedViewController *self, NSNotification *note) {
[self setMode:MPCombinedModeUserSelection animated:[note.userInfo[@"animated"] boolValue]];
} );
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self removeObservers];
PearlRemoveNotificationObservers();
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
@@ -67,9 +67,9 @@
if ([segue.identifier isEqualToString:@"users"])
self.usersVC = segue.destinationViewController;
if ([segue.identifier isEqualToString:@"passwords"]) {
NSAssert([segue isKindOfClass:[MPPasswordsSegue class]], @"passwords segue should be MPPasswordsSegue: %@", segue);
NSAssert([sender isKindOfClass:[NSDictionary class]], @"sender should be dictionary: %@", sender);
NSAssert([[sender objectForKey:@"animated"] isKindOfClass:[NSNumber class]], @"sender should contain 'animated': %@", sender);
NSAssert( [segue isKindOfClass:[MPPasswordsSegue class]], @"passwords segue should be MPPasswordsSegue: %@", segue );
NSAssert( [sender isKindOfClass:[NSDictionary class]], @"sender should be dictionary: %@", sender );
NSAssert( [[sender objectForKey:@"animated"] isKindOfClass:[NSNumber class]], @"sender should contain 'animated': %@", sender );
[(MPPasswordsSegue *)segue setAnimated:[sender[@"animated"] boolValue]];
UIViewController *destinationVC = segue.destinationViewController;
_passwordsVC = [destinationVC isKindOfClass:[MPPasswordsViewController class]]? (MPPasswordsViewController *)destinationVC: nil;
@@ -99,20 +99,14 @@
[self performSegueWithIdentifier:@"emergency" sender:self];
}
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController
fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier {
#pragma mark - Actions
if ([identifier isEqualToString:@"unwind-emergency"]) {
MPEmergencySegue *segue = [[MPEmergencySegue alloc] initWithIdentifier:identifier
source:fromViewController destination:toViewController];
segue.unwind = YES;
dbg_return(segue);
}
- (IBAction)unwindToCombined:(UIStoryboardSegue *)sender {
dbg_return((id)nil);
dbg( @"unwindToCombined:%@", sender );
}
#pragma mark - Properties
#pragma mark - State
- (void)setMode:(MPCombinedMode)mode {
@@ -133,7 +127,7 @@
switch (self.mode) {
case MPCombinedModeUserSelection: {
self.usersView.userInteractionEnabled = YES;
self.usersVC.view.userInteractionEnabled = YES;
[self.usersVC setActive:YES animated:animated];
if (_passwordsVC) {
MPPasswordsSegue *segue = [[MPPasswordsSegue alloc] initWithIdentifier:@"passwords" source:_passwordsVC destination:self];
@@ -143,7 +137,7 @@
break;
}
case MPCombinedModePasswordSelection: {
self.usersView.userInteractionEnabled = NO;
self.usersVC.view.userInteractionEnabled = NO;
[self.usersVC setActive:NO animated:animated];
[self performSegueWithIdentifier:@"passwords" sender:@{ @"animated" : @(animated) }];
break;
@@ -153,35 +147,4 @@
#pragma mark - Private
- (void)registerObservers {
if ([_notificationObservers count])
return;
Weakify(self);
_notificationObservers = @[
[[NSNotificationCenter defaultCenter]
addObserverForName:MPSignedInNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify(self);
[self setMode:MPCombinedModePasswordSelection];
}],
[[NSNotificationCenter defaultCenter]
addObserverForName:MPSignedOutNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify(self);
[self setMode:MPCombinedModeUserSelection animated:[note.userInfo[@"animated"] boolValue]];
}],
];
}
- (void)removeObservers {
for (id observer in _notificationObservers)
[[NSNotificationCenter defaultCenter] removeObserver:observer];
_notificationObservers = nil;
}
@end

View File

@@ -1,57 +0,0 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPEmergencySegue.h
// MPEmergencySegue
//
// Created by lhunath on 2014-04-09.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPEmergencySegue.h"
@implementation MPEmergencySegue {
}
- (void)perform {
UIViewController *sourceViewController = self.sourceViewController;
UIViewController *destinationViewController = self.destinationViewController;
if (!self.unwind) {
// Winding
[sourceViewController addChildViewController:destinationViewController];
[sourceViewController.view addSubview:destinationViewController.view];
CGRectSetY(destinationViewController.view.bounds, sourceViewController.view.frame.size.height);
[UIView transitionWithView:sourceViewController.view duration:0.3f options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
CGRectSetY(destinationViewController.view.bounds, 0);
} completion:^(BOOL finished) {
if (finished)
[destinationViewController didMoveToParentViewController:sourceViewController];
}];
}
else {
// Unwinding
[sourceViewController willMoveToParentViewController:nil];
[UIView transitionWithView:sourceViewController.parentViewController.view duration:0.3f options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
CGRectSetY(sourceViewController.view.bounds, sourceViewController.parentViewController.view.frame.size.height);
} completion:^(BOOL finished) {
if (finished) {
[sourceViewController.view removeFromSuperview];
[sourceViewController removeFromParentViewController];
}
}];
}
}
@end

View File

@@ -19,6 +19,7 @@
@interface MPEmergencyViewController : UIViewController <UITextFieldDelegate>
@property(weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property(weak, nonatomic) IBOutlet UIView *dialogView;
@property(weak, nonatomic) IBOutlet UIView *containerView;
@property(weak, nonatomic) IBOutlet UITextField *userNameField;

View File

@@ -1,12 +1,12 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPCombinedViewController.h
@@ -23,7 +23,6 @@
MPKey *_key;
NSOperationQueue *_emergencyKeyQueue;
NSOperationQueue *_emergencyPasswordQueue;
NSArray *_notificationObservers;
}
- (void)viewDidLoad {
@@ -42,27 +41,26 @@
[super viewWillAppear:animated];
[self reset];
[self registerObservers];
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, nil, [NSOperationQueue mainQueue],
^(MPEmergencyViewController *self, NSNotification *note) {
[self performSegueWithIdentifier:@"unwind-popover" sender:self];
} );
[self.scrollView automaticallyAdjustInsetsForKeyboard];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self removeObservers];
PearlRemoveNotificationObservers();
PearlRemoveNotificationObserversFrom( self.scrollView );
[self reset];
}
- (BOOL)canPerformUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender {
- (UIStatusBarStyle)preferredStatusBarStyle {
return [self respondsToSelector:action];
}
#pragma mark - Actions
- (IBAction)unwindToCombined:(UIStoryboardSegue *)sender {
dbg(@"unwindToCombined:%@", sender);
return UIStatusBarStyleLightContent;
}
#pragma mark - UITextFieldDelegate
@@ -126,7 +124,7 @@
- (void)updatePassword {
NSString *siteName = self.siteField.text;
MPElementType siteType = [self siteType];
MPSiteType siteType = [self siteType];
NSUInteger siteCounter = (NSUInteger)self.counterStepper.value;
self.counterLabel.text = strf( @"%lu", (unsigned long)siteCounter );
@@ -145,23 +143,23 @@
}];
}
- (enum MPElementType)siteType {
- (enum MPSiteType)siteType {
switch (self.typeControl.selectedSegmentIndex) {
case 0:
return MPElementTypeGeneratedMaximum;
return MPSiteTypeGeneratedMaximum;
case 1:
return MPElementTypeGeneratedLong;
return MPSiteTypeGeneratedLong;
case 2:
return MPElementTypeGeneratedMedium;
return MPSiteTypeGeneratedMedium;
case 3:
return MPElementTypeGeneratedBasic;
return MPSiteTypeGeneratedBasic;
case 4:
return MPElementTypeGeneratedShort;
return MPSiteTypeGeneratedShort;
case 5:
return MPElementTypeGeneratedPIN;
return MPSiteTypeGeneratedPIN;
default:
Throw(@"Unsupported type index: %ld", (long)self.typeControl.selectedSegmentIndex);
Throw( @"Unsupported type index: %ld", (long)self.typeControl.selectedSegmentIndex );
}
}
@@ -175,28 +173,4 @@
[self updateKey];
}
- (void)registerObservers {
if ([_notificationObservers count])
return;
Weakify(self);
_notificationObservers = @[
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationWillResignActiveNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify(self);
[self performSegueWithIdentifier:@"unwind-emergency" sender:self];
}],
];
}
- (void)removeObservers {
for (id observer in _notificationObservers)
[[NSNotificationCenter defaultCenter] removeObserver:observer];
_notificationObservers = nil;
}
@end

View File

@@ -7,6 +7,8 @@
//
#import "MPGuideViewController.h"
#import "markdown_lib.h"
#import "NSString+MPMarkDown.h"
@interface MPGuideStep : NSObject
@@ -37,28 +39,50 @@
[super viewDidLoad];
self.steps = @[
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-0"] caption:
@"To begin, tap the \"New User\" icon and add yourself as a user to the application."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-1"] caption:
@"Enter your full name. Double-check that you have spelled your name correctly and capitalized it appropriately. Your passwords will depend on it."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-2"] caption:
@"Choose a master password: Use something new and long. A short sentence is ideal.\nDO NOT FORGET THIS ONE PASSWORD."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-3"] caption:
@"After logging in, you'll see an empty screen with a search box.\nTap the search box to begin adding sites."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-4"] caption:
@"To add a site, just enter its name fully and tap the result. Names can be anything, but we recommend using a site's bare domain name."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-5"] caption:
@"Your sites are easy to find and sorted by recency.\nTap any site to copy its password.\nYou can now switch and paste it in another app."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-6"] caption:
@"The user icon lets you save your site's login.\nThis is useful if you find it hard to remember the user name for this site."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-7"] caption:
[MPGuideStep stepWithImage:[UIImage imageNamed:@"initial"] caption:
@"To begin, tap the *New User* icon and add yourself as a user to the application."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"name_new"] caption:
@"Enter your full name. \n"
@"**Double-check** that you have spelled your name correctly and capitalized it appropriately. \n"
@"Your passwords will depend on it."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"mpw_new"] caption:
@"Choose a master password: Make it *new* and *long*. \n"
@"A short phrase makes a great password. \n"
@"**DO NOT FORGET THIS ONE PASSWORD**."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"login_new"] caption:
@"After logging in, you'll see an empty screen with a search box. \n"
@"Tap the search box to begin adding sites."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"site_new"] caption:
@"To add a site, just enter its name and tap the result. \n"
@"*We recommend* always using a site's **bare** domain name: eg. *apple.com*. \n"
@"(NOT *www.*apple.com or *store.*apple.com)"],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"copy_pw"] caption:
@"Tap any site to copy its password. \n"
@"The first time, change your site's old password into this new one."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"settings"] caption:
@"To make changes to the site password, tap the settings icon or swipe left to reveal extra buttons."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-8"] caption:
@"If you ever need a new password for the site, just tap the plus icon to increment its counter.\nYou can hold down to reset it back to 1."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-9"] caption:
@"Use the list icon to upgrade or downgrade your password's complexity.\nSome sites won't let you use complex passwords."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"image-10"] caption:
@"If you have a password that you cannot change, you can save it as a Personal password. Device Private means the site will not be backed up."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"login_name"] caption:
@"You can save the login name for the site. \n"
@"This is useful if you find it hard to remember your user name for this site."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"counter"] caption:
@"If you ever need a new password for the site, just tap the plus icon to increment its counter. \n"
@"You can hold down to reset it back to 1."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"choose_type"] caption:
@"Use the list icon to upgrade or downgrade your password's complexity. \n"
@"Some sites won't let you use complex passwords."],
[MPGuideStep stepWithImage:[UIImage imageNamed:@"personal_pw"] caption:
@"If you have a password that you cannot change, you can save it as a *personal* password. "
@"*Device private* means the site will not be backed up."],
];
}
@@ -68,9 +92,10 @@
[self.pageControl observeKeyPath:@"currentPage"
withBlock:^(id from, id to, NSKeyValueChange cause, UIPageControl *pageControl) {
MPGuideStep *activeStep = self.steps[pageControl.currentPage];
self.captionLabel.text = activeStep.caption;
}];
MPGuideStep *activeStep = self.steps[pageControl.currentPage];
self.captionLabel.attributedText =
[activeStep.caption attributedMarkdownStringWithFontSize:self.captionLabel.font.pointSize];
}];
[self.collectionView setContentOffset:CGPointZero];
self.pageControl.currentPage = 0;
@@ -117,6 +142,7 @@
MPGuideStepCell *cell = [MPGuideStepCell dequeueCellFromCollectionView:collectionView indexPath:indexPath];
cell.imageView.image = ((MPGuideStep *)self.steps[indexPath.item]).image;
cell.contentView.frame = cell.bounds;
return cell;
}

View File

@@ -1,12 +1,12 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPLogsViewController.h
@@ -27,18 +27,17 @@
[super viewDidLoad];
self.view.backgroundColor = [UIColor clearColor];
[[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0;
}];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
PearlAddNotificationObserver( NSUserDefaultsDidChangeNotification, nil, [NSOperationQueue mainQueue],
^(MPLogsViewController *self, NSNotification *note) {
self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0;
} );
self.logView.contentInset = UIEdgeInsetsMake( 64, 0, 93, 0 );
[self refresh:nil];
@@ -46,13 +45,20 @@
self.levelControl.selectedSegmentIndex = [[MPiOSConfig get].traceMode boolValue]? 1: 0;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
PearlRemoveNotificationObservers();
}
- (IBAction)toggleLevelControl:(UISegmentedControl *)sender {
BOOL traceEnabled = (BOOL)self.levelControl.selectedSegmentIndex;
if (traceEnabled) {
[PearlAlert showAlertWithTitle:@"Enable Trace Mode?" message:
@"Trace mode will log the internal operation of the application.\n"
@"Unless you're looking for the cause of a problem, you should leave this off to save memory."
@"Trace mode will log the internal operation of the application.\n"
@"Unless you're looking for the cause of a problem, you should leave this off to save memory."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
@@ -74,8 +80,8 @@
if ([[MPiOSConfig get].traceMode boolValue]) {
[PearlAlert showAlertWithTitle:@"Hiding Trace Messages" message:
@"Trace-level log messages will not be mailed. "
@"These messages contain sensitive and personal information."
@"Trace-level log messages will not be mailed. "
@"These messages contain sensitive and personal information."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
[[MPiOSAppDelegate get] openFeedbackWithLogs:YES forVC:self];

View File

@@ -0,0 +1,29 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPOverlayViewController.h
// MPOverlayViewController
//
// Created by lhunath on 2014-09-22.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface MPOverlayViewController : UIViewController
@end
@interface MPOverlaySegue : UIStoryboardSegue
+ (instancetype)dismissViewController:(UIViewController *)viewController;
@end

View File

@@ -0,0 +1,162 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPOverlayViewController.h
// MPOverlayViewController
//
// Created by lhunath on 2014-09-22.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPOverlayViewController.h"
@implementation MPOverlayViewController {
NSMutableDictionary *_dismissSegueByButton;
}
- (void)awakeFromNib {
[super awakeFromNib];
_dismissSegueByButton = [NSMutableDictionary dictionary];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self performSegueWithIdentifier:@"root" sender:self];
}
- (UIViewController *)childViewControllerForStatusBarStyle {
return [self.childViewControllers lastObject];
}
- (UIViewController *)childViewControllerForStatusBarHidden {
return self.childViewControllerForStatusBarStyle;
}
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController
fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier {
return [[MPOverlaySegue alloc] initWithIdentifier:identifier source:fromViewController destination:toViewController];
}
- (UIView *)addDismissButtonForSegue:(MPOverlaySegue *)segue {
UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom];
[dismissButton addTarget:self action:@selector( dismissOverlay: ) forControlEvents:UIControlEventTouchUpInside];
dismissButton.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5f];
dismissButton.alpha = 0;
dismissButton.frame = self.view.bounds;
dismissButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_dismissSegueByButton[[NSValue valueWithNonretainedObject:dismissButton]] =
[[MPOverlaySegue alloc] initWithIdentifier:@"dismiss-overlay"
source:segue.destinationViewController destination:segue.sourceViewController];
[self.view addSubview:dismissButton];
return dismissButton;
}
- (void)dismissOverlay:(UIButton *)dismissButton {
NSValue *dismissSegueKey = [NSValue valueWithNonretainedObject:dismissButton];
[((UIStoryboardSegue *)_dismissSegueByButton[dismissSegueKey]) perform];
}
- (void)removeDismissButtonForViewController:(UIViewController *)viewController {
UIButton *dismissButton = nil;
for (NSValue *dismissButtonValue in [_dismissSegueByButton allKeys])
if (((UIStoryboardSegue *)_dismissSegueByButton[dismissButtonValue]).sourceViewController == viewController) {
dismissButton = [dismissButtonValue nonretainedObjectValue];
NSAssert([self.view.subviews containsObject:dismissButton], @"Missing dismiss button in dictionary.");
}
if (!dismissButton)
return;
NSValue *dismissSegueKey = [NSValue valueWithNonretainedObject:dismissButton];
[_dismissSegueByButton removeObjectForKey:dismissSegueKey];
[UIView animateWithDuration:0.1f animations:^{
dismissButton.alpha = 0;
} completion:^(BOOL finished) {
[dismissButton removeFromSuperview];
}];
}
@end
@implementation MPOverlaySegue
+ (instancetype)dismissViewController:(UIViewController *)viewController {
return [[self alloc] initWithIdentifier:nil source:viewController destination:viewController];
}
- (void)perform {
UIViewController *sourceViewController = self.sourceViewController;
UIViewController *destinationViewController = self.destinationViewController;
MPOverlayViewController *containerViewController = self.sourceViewController;
while (containerViewController && ![(id)containerViewController isKindOfClass:[MPOverlayViewController class]])
containerViewController = (id)containerViewController.parentViewController;
NSAssert( [containerViewController isKindOfClass:[MPOverlayViewController class]],
@"Not an overlay container: %@", containerViewController );
if (!destinationViewController.parentViewController) {
// Winding
[containerViewController addChildViewController:destinationViewController];
UIView *dismissButton = [containerViewController addDismissButtonForSegue:self];
destinationViewController.view.frame = containerViewController.view.bounds;
destinationViewController.view.translatesAutoresizingMaskIntoConstraints = YES;
destinationViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[containerViewController.view addSubview:destinationViewController.view];
[containerViewController setNeedsStatusBarAppearanceUpdate];
CGRectSetY( destinationViewController.view.frame, 100 );
destinationViewController.view.transform = CGAffineTransformMakeScale( 1.2f, 1.2f );
destinationViewController.view.alpha = 0;
[UIView transitionWithView:containerViewController.view duration:0.3f
options:UIViewAnimationOptionAllowAnimatedContent animations:^{
destinationViewController.view.transform = CGAffineTransformIdentity;
CGRectSetY( destinationViewController.view.frame, 0 );
destinationViewController.view.alpha = 1;
dismissButton.alpha = 1;
} completion:^(BOOL finished) {
[destinationViewController didMoveToParentViewController:containerViewController];
[containerViewController setNeedsStatusBarAppearanceUpdate];
}];
}
else {
// Unwinding
[sourceViewController willMoveToParentViewController:nil];
[UIView transitionWithView:containerViewController.view duration:0.2f
options:UIViewAnimationOptionAllowAnimatedContent animations:^{
CGRectSetY( sourceViewController.view.frame, 100 );
sourceViewController.view.transform = CGAffineTransformMakeScale( 0.8f, 0.8f );
sourceViewController.view.alpha = 0;
[containerViewController removeDismissButtonForViewController:sourceViewController];
} completion:^(BOOL finished) {
if (finished) {
[sourceViewController.view removeFromSuperview];
[sourceViewController removeFromParentViewController];
[containerViewController setNeedsStatusBarAppearanceUpdate];
}
}];
}
}
@end

View File

@@ -27,8 +27,9 @@ typedef NS_ENUM ( NSUInteger, MPPasswordCellMode ) {
@interface MPPasswordCell : MPCell <UIScrollViewDelegate, UITextFieldDelegate>
- (void)setElement:(MPElementEntity *)element animated:(BOOL)animated;
- (void)setSite:(MPSiteEntity *)site animated:(BOOL)animated;
- (void)setTransientSite:(NSString *)siteName animated:(BOOL)animated;
- (void)setMode:(MPPasswordCellMode)mode animated:(BOOL)animated;
- (MPSiteEntity *)siteInContext:(NSManagedObjectContext *)context;
@end

View File

@@ -20,6 +20,7 @@
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Store.h"
#import "UIColor+Expanded.h"
#import "MPAppDelegate_InApp.h"
@interface MPPasswordCell()
@@ -31,6 +32,7 @@
@property(nonatomic, strong) IBOutlet UILabel *counterLabel;
@property(nonatomic, strong) IBOutlet UIButton *counterButton;
@property(nonatomic, strong) IBOutlet UIButton *upgradeButton;
@property(nonatomic, strong) IBOutlet UIButton *answersButton;
@property(nonatomic, strong) IBOutlet UIButton *modeButton;
@property(nonatomic, strong) IBOutlet UIButton *editButton;
@property(nonatomic, strong) IBOutlet UIScrollView *modeScrollView;
@@ -44,7 +46,7 @@
@end
@implementation MPPasswordCell {
NSManagedObjectID *_elementOID;
NSManagedObjectID *_siteOID;
}
#pragma mark - Life cycle
@@ -60,11 +62,10 @@
[self setupLayer];
[self observeKeyPath:@"bounds" withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
[self observeKeyPath:@"bounds" withBlock:^(id from, id to, NSKeyValueChange cause, MPPasswordCell *_self) {
if (from && !CGSizeEqualToSize( [from CGRectValue].size, [to CGRectValue].size ))
[_self setupLayer];
}];
[self.contentButton observeKeyPath:@"highlighted"
withBlock:^(id from, id to, NSKeyValueChange cause, UIButton *button) {
[UIView animateWithDuration:.2f delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
@@ -131,7 +132,7 @@
[super prepareForReuse];
_elementOID = nil;
_siteOID = nil;
self.transientSite = nil;
self.mode = MPPasswordCellModePassword;
[self updateAnimated:NO];
@@ -139,6 +140,7 @@
- (void)dealloc {
[self removeKeyPathObservers];
[self.contentButton removeKeyPathObservers];
[self.loginNameButton removeKeyPathObservers];
}
@@ -154,9 +156,9 @@
[self updateAnimated:animated];
}
- (void)setElement:(MPElementEntity *)element animated:(BOOL)animated {
- (void)setSite:(MPSiteEntity *)site animated:(BOOL)animated {
_elementOID = [element objectID];
_siteOID = [site objectID];
[self updateAnimated:animated];
}
@@ -194,10 +196,10 @@
NSString *password = self.passwordField.text;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
TimeToCrack timeToCrack;
MPElementEntity *element = [self elementInContext:context];
id<MPAlgorithm> algorithm = element.algorithm?: MPAlgorithmDefault;
MPSiteEntity *site = [self siteInContext:context];
id<MPAlgorithm> algorithm = site.algorithm?: MPAlgorithmDefault;
MPAttacker attackHardware = [[MPConfig get].siteAttacker unsignedIntegerValue];
if ([algorithm timeToCrack:&timeToCrack passwordOfType:[self elementInContext:context].type byAttacker:attackHardware] ||
if ([algorithm timeToCrack:&timeToCrack passwordOfType:[self siteInContext:context].type byAttacker:attackHardware] ||
[algorithm timeToCrack:&timeToCrack passwordString:password byAttacker:attackHardware])
PearlMainQueue( ^{
self.strengthLabel.text = NSStringFromTimeToCrack( timeToCrack );
@@ -213,23 +215,26 @@
NSString *text = textField.text;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [self elementInContext:context];
if (!element)
MPSiteEntity *site = [self siteInContext:context];
if (!site)
return;
if (textField == self.passwordField) {
if ([element.algorithm savePassword:text toElement:element usingKey:[MPiOSAppDelegate get].key])
if ([site.algorithm savePassword:text toSite:site usingKey:[MPiOSAppDelegate get].key])
[PearlOverlay showTemporaryOverlayWithTitle:@"Password Updated" dismissAfter:2];
}
else if (textField == self.loginNameField &&
((element.loginGenerated && ![text length]) ||
(!element.loginGenerated && ![text isEqualToString:element.loginName]))) {
element.loginName = text;
element.loginGenerated = NO;
if ([text length])
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Saved" dismissAfter:2];
else
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Cleared" dismissAfter:2];
((site.loginGenerated && ![text length]) ||
(!site.loginGenerated && ![text isEqualToString:site.loginName]))) {
if (site.loginGenerated || !([site.loginName isEqualToString:text] || (!text && !site.loginName))) {
site.loginGenerated = NO;
site.loginName = text;
if ([text length])
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Saved" dismissAfter:2];
else
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Cleared" dismissAfter:2];
}
}
[context saveToStore];
@@ -242,17 +247,17 @@
- (IBAction)doDelete:(UIButton *)sender {
MPElementEntity *element = [self elementInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
if (!element)
MPSiteEntity *site = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
if (!site)
return;
[PearlSheet showSheetWithTitle:strf( @"Delete %@?", element.name ) viewStyle:UIActionSheetStyleAutomatic
[PearlSheet showSheetWithTitle:strf( @"Delete %@?", site.name ) viewStyle:UIActionSheetStyleAutomatic
initSheet:nil tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
if (buttonIndex == [sheet cancelButtonIndex])
return;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[context deleteObject:[self elementInContext:context]];
[context deleteObject:[self siteInContext:context]];
[context saveToStore];
}];
} cancelTitle:@"Cancel" destructiveTitle:@"Delete Site" otherTitles:nil];
@@ -264,12 +269,11 @@
[PearlSheet showSheetWithTitle:@"Change Password Type" viewStyle:UIActionSheetStyleAutomatic
initSheet:^(UIActionSheet *sheet) {
MPElementEntity
*mainElement = [self elementInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
for (NSNumber *typeNumber in [MPAlgorithmDefault allTypes]) {
MPElementType type = [typeNumber unsignedIntegerValue];
MPSiteType type = [typeNumber unsignedIntegerValue];
NSString *typeName = [MPAlgorithmDefault nameOfType:type];
if (type == mainElement.type)
if (type == mainSite.type)
[sheet addButtonWithTitle:strf( @"● %@", typeName )];
else
[sheet addButtonWithTitle:typeName];
@@ -278,12 +282,12 @@
if (buttonIndex == [sheet cancelButtonIndex])
return;
MPElementType type = [[MPAlgorithmDefault allTypes][buttonIndex] unsignedIntegerValue]?: MPElementTypeGeneratedLong;
MPSiteType type = [[MPAlgorithmDefault allTypes][buttonIndex] unsignedIntegerValue]?: MPSiteTypeGeneratedLong;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [self elementInContext:context];
element = [[MPiOSAppDelegate get] changeElement:element saveInContext:context toType:type];
[self setElement:element animated:YES];
MPSiteEntity *site = [self siteInContext:context];
site = [[MPiOSAppDelegate get] changeSite:site saveInContext:context toType:type];
[self setSite:site animated:YES];
}];
} cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:nil];
}
@@ -293,7 +297,7 @@
self.loginNameField.enabled = YES;
self.passwordField.enabled = YES;
if ([self elementInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].type & MPElementTypeClassStored)
if ([self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]].type & MPSiteTypeClassStored)
[self.passwordField becomeFirstResponder];
else
[self.loginNameField becomeFirstResponder];
@@ -309,14 +313,12 @@
[self setMode:MPPasswordCellModePassword animated:YES];
break;
}
[self updateAnimated:YES];
}
- (IBAction)doUpgrade:(UIButton *)sender {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
if (![[self elementInContext:context] migrateExplicitly:YES]) {
if (![[self siteInContext:context] tryMigrateExplicitly:YES]) {
[PearlOverlay showTemporaryOverlayWithTitle:@"Couldn't Upgrade Site" dismissAfter:2];
return;
}
@@ -330,11 +332,11 @@
- (IBAction)doIncrementCounter:(UIButton *)sender {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [self elementInContext:context];
if (!element || ![element isKindOfClass:[MPElementGeneratedEntity class]])
MPSiteEntity *site = [self siteInContext:context];
if (!site || ![site isKindOfClass:[MPGeneratedSiteEntity class]])
return;
++((MPElementGeneratedEntity *)element).counter;
++((MPGeneratedSiteEntity *)site).counter;
[context saveToStore];
[PearlOverlay showTemporaryOverlayWithTitle:@"Generating New Password" dismissAfter:2];
@@ -362,11 +364,11 @@
return;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [self elementInContext:context];
if (!element || ![element isKindOfClass:[MPElementGeneratedEntity class]])
MPSiteEntity *site = [self siteInContext:context];
if (!site || ![site isKindOfClass:[MPGeneratedSiteEntity class]])
return;
((MPElementGeneratedEntity *)element).counter = 1;
((MPGeneratedSiteEntity *)site).counter = 1;
[context saveToStore];
[PearlOverlay showTemporaryOverlayWithTitle:@"Counter Reset" dismissAfter:2];
@@ -392,8 +394,8 @@
}
[[MPiOSAppDelegate get]
addElementNamed:self.transientSite completion:^(MPElementEntity *element, NSManagedObjectContext *context) {
[self copyContentOfElement:element saveInContext:context];
addSiteNamed:self.transientSite completion:^(MPSiteEntity *site, NSManagedObjectContext *context) {
[self copyContentOfSite:site saveInContext:context];
PearlMainQueueAfter( .3f, ^{
[UIView animateWithDuration:.2f animations:^{
@@ -406,7 +408,7 @@
}
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[self copyContentOfElement:[self elementInContext:context] saveInContext:context];
[self copyContentOfSite:[self siteInContext:context] saveInContext:context];
PearlMainQueueAfter( .3f, ^{
[UIView animateWithDuration:.2f animations:^{
@@ -423,9 +425,9 @@
}];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [self elementInContext:context];
if (![self copyLoginOfElement:element saveInContext:context]) {
element.loginGenerated = YES;
MPSiteEntity *site = [self siteInContext:context];
if (![self copyLoginOfSite:site saveInContext:context]) {
site.loginGenerated = YES;
[context saveToStore];
[PearlOverlay showTemporaryOverlayWithTitle:@"Login Name Generated" dismissAfter:2];
[self updateAnimated:YES];
@@ -461,15 +463,16 @@
}
[UIView animateWithDuration:animated? .3f: 0 animations:^{
MPElementEntity *mainElement = [self elementInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
// UI
self.upgradeButton.alpha = mainElement.requiresExplicitMigration? 1: 0;
self.upgradeButton.gone = !mainSite.requiresExplicitMigration;
self.answersButton.gone = ![[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateAnswers];
BOOL settingsMode = self.mode == MPPasswordCellModeSettings;
self.loginNameContainer.alpha = settingsMode || mainElement.loginGenerated || [mainElement.loginName length]? 0.7f: 0;
self.loginNameField.textColor = [UIColor colorWithHexString:mainElement.loginGenerated? @"5E636D": @"6D5E63"];
self.loginNameContainer.alpha = settingsMode || mainSite.loginGenerated || [mainSite.loginName length]? 0.7f: 0;
self.loginNameField.textColor = [UIColor colorWithHexString:mainSite.loginGenerated? @"5E636D": @"6D5E63"];
self.modeButton.alpha = self.transientSite? 0: settingsMode? 0.5f: 0.1f;
self.counterLabel.alpha = self.counterButton.alpha = mainElement.type & MPElementTypeClassGenerated? 0.5f: 0;
self.counterLabel.alpha = self.counterButton.alpha = mainSite.type & MPSiteTypeClassGenerated? 0.5f: 0;
self.modeButton.selected = settingsMode;
self.strengthLabel.gone = !settingsMode;
self.modeScrollView.scrollEnabled = !self.transientSite;
@@ -478,36 +481,43 @@
[self.loginNameField resignFirstResponder];
[self.passwordField resignFirstResponder];
}
if ([[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateLogins])
[self.loginNameButton setTitle:@"Tap to generate username or use pencil to save one" forState:UIControlStateNormal];
else
[self.loginNameButton setTitle:@"Tap the pencil to save a username" forState:UIControlStateNormal];
// Site Name
self.siteNameLabel.text = strl( @"%@ - %@", self.transientSite?: mainElement.name,
self.transientSite? @"Tap to create": [mainElement.algorithm shortNameOfType:mainElement.type] );
self.siteNameLabel.text = strl( @"%@ - %@", self.transientSite?: mainSite.name,
self.transientSite? @"Tap to create": [mainSite.algorithm shortNameOfType:mainSite.type] );
// Site Password
self.passwordField.secureTextEntry = [[MPiOSConfig get].hidePasswords boolValue];
self.passwordField.attributedPlaceholder = stra(
mainElement.type & MPElementTypeClassStored? strl( @"No password" ):
mainElement.type & MPElementTypeClassGenerated? strl( @"..." ): @"", @{
mainSite.type & MPSiteTypeClassStored? strl( @"No password" ):
mainSite.type & MPSiteTypeClassGenerated? strl( @"..." ): @"", @{
NSForegroundColorAttributeName : [UIColor whiteColor]
} );
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementEntity *element = [self elementInContext:context];
MPSiteEntity *site = [self siteInContext:context];
MPKey *key = [MPiOSAppDelegate get].key;
NSString *password, *loginName = [element resolveLoginUsingKey:key];
if (!key)
return;
NSString *password, *loginName = [site resolveLoginUsingKey:key];
if (self.transientSite)
password = [MPAlgorithmDefault generatePasswordForSiteNamed:self.transientSite ofType:
[[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPElementTypeGeneratedLong
[[MPiOSAppDelegate get] activeUserInContext:context].defaultType?: MPSiteTypeGeneratedLong
withCounter:1 usingKey:key];
else if (element)
password = [element resolvePasswordUsingKey:key];
else if (site)
password = [site resolvePasswordUsingKey:key];
else
return;
TimeToCrack timeToCrack;
NSString *timeToCrackString = nil;
id<MPAlgorithm> algorithm = element.algorithm?: MPAlgorithmDefault;
id<MPAlgorithm> algorithm = site.algorithm?: MPAlgorithmDefault;
MPAttacker attackHardware = [[MPConfig get].siteAttacker unsignedIntegerValue];
if ([algorithm timeToCrack:&timeToCrack passwordOfType:element.type byAttacker:attackHardware] ||
if ([algorithm timeToCrack:&timeToCrack passwordOfType:site.type byAttacker:attackHardware] ||
[algorithm timeToCrack:&timeToCrack passwordString:password byAttacker:attackHardware])
timeToCrackString = NSStringFromTimeToCrack( timeToCrack );
@@ -533,8 +543,8 @@
}];
// Site Counter
if ([mainElement isKindOfClass:[MPElementGeneratedEntity class]])
self.counterLabel.text = strf( @"%lu", (unsigned long)((MPElementGeneratedEntity *)mainElement).counter );
if ([mainSite isKindOfClass:[MPGeneratedSiteEntity class]])
self.counterLabel.text = strf( @"%lu", (unsigned long)((MPGeneratedSiteEntity *)mainSite).counter );
// Site Login Name
self.loginNameField.enabled = self.passwordField.enabled = //
@@ -544,10 +554,10 @@
}];
}
- (BOOL)copyContentOfElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context {
- (BOOL)copyContentOfSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context {
inf( @"Copying password for: %@", element.name );
NSString *password = [element resolvePasswordUsingKey:[MPAppDelegate_Shared get].key];
inf( @"Copying password for: %@", site.name );
NSString *password = [site resolvePasswordUsingKey:[MPAppDelegate_Shared get].key];
if (![password length])
return NO;
@@ -556,15 +566,15 @@
[UIPasteboard generalPasteboard].string = password;
} );
[element use];
[site use];
[context saveToStore];
return YES;
}
- (BOOL)copyLoginOfElement:(MPElementEntity *)element saveInContext:(NSManagedObjectContext *)context {
- (BOOL)copyLoginOfSite:(MPSiteEntity *)site saveInContext:(NSManagedObjectContext *)context {
inf( @"Copying login for: %@", element.name );
NSString *loginName = [element.algorithm resolveLoginForElement:element usingKey:[MPiOSAppDelegate get].key];
inf( @"Copying login for: %@", site.name );
NSString *loginName = [site.algorithm resolveLoginForSite:site usingKey:[MPiOSAppDelegate get].key];
if (![loginName length])
return NO;
@@ -573,14 +583,14 @@
[UIPasteboard generalPasteboard].string = loginName;
} );
[element use];
[site use];
[context saveToStore];
return YES;
}
- (MPElementEntity *)elementInContext:(NSManagedObjectContext *)context {
- (MPSiteEntity *)siteInContext:(NSManagedObjectContext *)context {
return [MPElementEntity existingObjectWithID:_elementOID inContext:context];
return [MPSiteEntity existingObjectWithID:_siteOID inContext:context];
}
@end

View File

@@ -42,11 +42,9 @@
passwordsVC.active = NO;
UIView *passwordsView = passwordsVC.view;
passwordsView.translatesAutoresizingMaskIntoConstraints = NO;
[combinedVC.view insertSubview:passwordsView belowSubview:combinedVC.usersView];
[combinedVC.view addConstraintsWithVisualFormats:@[ @"H:|[passwordsView]|", @"V:|[passwordsView]|" ]
options:0 metrics:nil
views:NSDictionaryOfVariableBindings( passwordsView )];
passwordsView.frame = combinedVC.view.bounds;
passwordsView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[combinedVC.view insertSubview:passwordsView belowSubview:combinedVC.usersVC.view];
[passwordsVC setActive:YES animated:self.animated completion:^(BOOL finished) {
if (!finished)

View File

@@ -16,7 +16,7 @@
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
@class MPElementEntity;
@class MPSiteEntity;
@class MPCoachmark;
@interface MPPasswordsViewController : UIViewController<UISearchBarDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@@ -27,6 +27,7 @@
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *passwordsToBottomConstraint;
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *navigationBarToTopConstraint;
@property(strong, nonatomic) IBOutlet NSLayoutConstraint *popdownToTopConstraint;
@property(strong, nonatomic) IBOutlet UIView *badNameTipContainer;
@property(strong, nonatomic) IBOutlet UIView *popdownView;
@property(strong, nonatomic) IBOutlet UIView *popdownContainer;

View File

@@ -22,7 +22,11 @@
#import "MPPopdownSegue.h"
#import "MPAppDelegate_Key.h"
#import "MPPasswordCell.h"
#import "UICollectionView+PearlReloadFromArray.h"
#import "MPAnswersViewController.h"
typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
MPPasswordsBadNameTip = 1 << 0,
};
@interface MPPasswordsViewController()<NSFetchedResultsControllerDelegate>
@@ -32,10 +36,6 @@
@end
@implementation MPPasswordsViewController {
__weak id _storeChangingObserver;
__weak id _storeChangedObserver;
__weak id _mocObserver;
NSArray *_notificationObservers;
__weak UITapGestureRecognizer *_passwordsDismissRecognizer;
NSFetchedResultsController *_fetchedResultsController;
UIColor *_backgroundColor;
@@ -43,6 +43,7 @@
__weak UIViewController *_popdownVC;
BOOL _showTransientItem;
NSUInteger _transientItem;
NSCharacterSet *_siteNameAcceptableCharactersSet;
}
#pragma mark - Life
@@ -51,6 +52,11 @@
[super viewDidLoad];
NSMutableCharacterSet *siteNameAcceptableCharactersSet = [[NSCharacterSet alphanumericCharacterSet] mutableCopy];
[siteNameAcceptableCharactersSet formIntersectionWithCharacterSet:[[NSCharacterSet uppercaseLetterCharacterSet] invertedSet]];
[siteNameAcceptableCharactersSet addCharactersInString:@"@.-+~&_;:/"];
_siteNameAcceptableCharactersSet = siteNameAcceptableCharactersSet;
_backgroundColor = self.passwordCollectionView.backgroundColor;
_darkenedBackgroundColor = [_backgroundColor colorWithAlphaComponent:0.6f];
_transientItem = NSNotFound;
@@ -68,23 +74,36 @@
[super viewWillAppear:animated];
[self registerObservers];
[self observeStore];
[self updateConfigKey:nil];
[self updatePasswords];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserInContext:context];
if (![MPAlgorithmDefault tryMigrateUser:activeUser inContext:context])
[PearlOverlay showTemporaryOverlayWithTitle:@"Some Sites Need Upgrade" dismissAfter:2];
[context saveToStore];
}];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self removeObservers];
[self stopObservingStore];
PearlRemoveNotificationObservers();
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"popdown"])
_popdownVC = segue.destinationViewController;
if ([segue.identifier isEqualToString:@"answers"])
((MPAnswersViewController *)segue.destinationViewController).site =
[[MPPasswordCell findAsSuperviewOf:sender] siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
@@ -96,12 +115,6 @@
#pragma mark - UICollectionViewDelegateFlowLayout
- (CGSize) collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
referenceSizeForHeaderInSection:(NSInteger)section {
return CGSizeMake( collectionView.bounds.size.width, CGRectGetBottom( self.passwordsSearchBar.frame ).y );
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
@@ -131,19 +144,13 @@ referenceSizeForHeaderInSection:(NSInteger)section {
MPPasswordCell *cell = [MPPasswordCell dequeueCellFromCollectionView:collectionView indexPath:indexPath];
if (indexPath.item < ((id<NSFetchedResultsSectionInfo>)self.fetchedResultsController.sections[indexPath.section]).numberOfObjects)
[cell setElement:[self.fetchedResultsController objectAtIndexPath:indexPath] animated:NO];
[cell setSite:[self.fetchedResultsController objectAtIndexPath:indexPath] animated:NO];
else
[cell setTransientSite:self.query animated:NO];
return cell;
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath {
return [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"MPPasswordHeader" forIndexPath:indexPath];
}
#pragma mark - UIScrollDelegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
@@ -235,12 +242,32 @@ referenceSizeForHeaderInSection:(NSInteger)section {
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if (searchBar == self.passwordsSearchBar)
if (searchBar == self.passwordsSearchBar) {
if ([self.query length] && [[self.query stringByTrimmingCharactersInSet:_siteNameAcceptableCharactersSet] length])
[self showTips:MPPasswordsBadNameTip];
[self updatePasswords];
}
}
#pragma mark - Private
- (void)showTips:(MPPasswordsTips)showTips {
[UIView animateWithDuration:0.3f animations:^{
if (showTips & MPPasswordsBadNameTip)
self.badNameTipContainer.alpha = 1;
} completion:^(BOOL finished) {
if (finished)
PearlMainQueueAfter( 5, ^{
[UIView animateWithDuration:0.3f animations:^{
if (showTips & MPPasswordsBadNameTip)
self.badNameTipContainer.alpha = 0;
}];
} );
}];
}
- (void)fetchedItemsDidUpdate {
NSString *query = self.query;
@@ -264,91 +291,49 @@ referenceSizeForHeaderInSection:(NSInteger)section {
- (void)registerObservers {
if ([_notificationObservers count])
return;
Weakify( self );
_notificationObservers = @[
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationWillResignActiveNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify( self );
self.passwordSelectionContainer.alpha = 0;
}],
[[NSNotificationCenter defaultCenter]
addObserverForName:MPSignedOutNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify( self );
_fetchedResultsController = nil;
self.passwordsSearchBar.text = nil;
[self.passwordCollectionView reloadData];
}],
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationDidBecomeActiveNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify( self );
[self updatePasswords];
[UIView animateWithDuration:1 animations:^{
self.passwordSelectionContainer.alpha = 1;
}];
}],
[[NSNotificationCenter defaultCenter]
addObserverForName:MPCheckConfigNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self updateConfigKey:note.object];
}],
];
}
- (void)removeObservers {
for (id observer in _notificationObservers)
[[NSNotificationCenter defaultCenter] removeObserver:observer];
_notificationObservers = nil;
}
- (void)observeStore {
Weakify( self );
PearlRemoveNotificationObservers();
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue],
^(MPPasswordsViewController *self, NSNotification *note) {
self.passwordSelectionContainer.alpha = 0;
} );
PearlAddNotificationObserver( UIApplicationWillEnterForegroundNotification, nil, [NSOperationQueue mainQueue],
^(MPPasswordsViewController *self, NSNotification *note) {
[self updatePasswords];
} );
PearlAddNotificationObserver( UIApplicationDidBecomeActiveNotification, nil, [NSOperationQueue mainQueue],
^(MPPasswordsViewController *self, NSNotification *note) {
[UIView animateWithDuration:0.7f animations:^{
self.passwordSelectionContainer.alpha = 1;
}];
} );
PearlAddNotificationObserver( MPSignedOutNotification, nil, [NSOperationQueue mainQueue],
^(MPPasswordsViewController *self, NSNotification *note) {
_fetchedResultsController = nil;
self.passwordsSearchBar.text = nil;
[self.passwordCollectionView reloadData];
} );
PearlAddNotificationObserver( MPCheckConfigNotification, nil, [NSOperationQueue mainQueue],
^(MPPasswordsViewController *self, NSNotification *note) {
[self updateConfigKey:note.object];
} );
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, nil, nil,
^(MPPasswordsViewController *self, NSNotification *note) {
self->_fetchedResultsController = nil;
[self.passwordCollectionView reloadData];
} );
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, nil, nil,
^(MPPasswordsViewController *self, NSNotification *note) {
[self updatePasswords];
[self registerObservers];
} );
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
if (!_mocObserver && mainContext)
_mocObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextDidSaveNotification object:mainContext
queue:nil usingBlock:^(NSNotification *note) {
if (![[MPiOSAppDelegate get] activeUserInContext:mainContext])
if (mainContext)
PearlAddNotificationObserver( NSManagedObjectContextDidSaveNotification, mainContext, nil,
^(MPPasswordsViewController *self, NSNotification *note) {
if (![[MPiOSAppDelegate get] activeUserInContext:note.object])
[[MPiOSAppDelegate get] signOutAnimated:YES];
}];
if (!_storeChangingObserver)
_storeChangingObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:NSPersistentStoreCoordinatorStoresWillChangeNotification object:nil
queue:nil usingBlock:^(NSNotification *note) {
Strongify( self );
self->_fetchedResultsController = nil;
if (self->_mocObserver)
[[NSNotificationCenter defaultCenter] removeObserver:self->_mocObserver];
[self.passwordCollectionView reloadData];
}];
if (!_storeChangedObserver)
_storeChangedObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:NSPersistentStoreCoordinatorStoresDidChangeNotification object:nil
queue:nil usingBlock:^(NSNotification *note) {
Strongify( self );
[self updatePasswords];
}];
}
- (void)stopObservingStore {
if (_mocObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_mocObserver];
if (_storeChangingObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_storeChangingObserver];
if (_storeChangedObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_storeChangedObserver];
} );
}
- (void)updateConfigKey:(NSString *)key {
@@ -384,7 +369,7 @@ referenceSizeForHeaderInSection:(NSInteger)section {
[NSPredicate predicateWithFormat:@"user == %@ AND name BEGINSWITH[cd] %@", activeUserOID, query]:
[NSPredicate predicateWithFormat:@"user == %@", activeUserOID];
if (![self.fetchedResultsController performFetch:&error])
err( @"Couldn't fetch elements: %@", error );
err( @"Couldn't fetch sites: %@", [error fullDescription] );
[self.passwordCollectionView performBatchUpdates:^{
[self fetchedItemsDidUpdate];
@@ -396,10 +381,12 @@ referenceSizeForHeaderInSection:(NSInteger)section {
[self.passwordCollectionView insertSections:[NSIndexSet indexSetWithIndex:section]];
else if (section >= toSections)
[self.passwordCollectionView deleteSections:[NSIndexSet indexSetWithIndex:section]];
else
else if (section < [oldSections count])
[self.passwordCollectionView reloadItemsFromArray:oldSections[section]
toArray:[[self.fetchedResultsController sections][section] objects]
inSection:section];
else
[self.passwordCollectionView reloadSections:[NSIndexSet indexSetWithIndex:section]];
}
} completion:^(BOOL finished) {
if (finished)
@@ -421,7 +408,7 @@ referenceSizeForHeaderInSection:(NSInteger)section {
if (!_fetchedResultsController) {
_showTransientItem = NO;
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
fetchRequest.sortDescriptors = @[
[[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector( lastUsed ) ) ascending:NO]
];
@@ -430,7 +417,7 @@ referenceSizeForHeaderInSection:(NSInteger)section {
initWithFetchRequest:fetchRequest managedObjectContext:mainContext sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
}];
[self observeStore];
[self registerObservers];
}
return _fetchedResultsController;

View File

@@ -1,12 +1,12 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPPopdownSegue.h
@@ -22,6 +22,8 @@
@implementation MPPopdownSegue {
}
static char UnwindingObserverKey;
- (void)perform {
MPPasswordsViewController *passwordsVC;
@@ -35,17 +37,30 @@
[passwordsVC addChildViewController:popdownVC];
[passwordsVC.popdownContainer addSubview:popdownView];
[passwordsVC.popdownContainer addConstraintsWithVisualFormats:@[ @"H:|[popdownView]|", @"V:|[popdownView]|" ] options:0
metrics:nil views:NSDictionaryOfVariableBindings(popdownView)];
metrics:nil views:NSDictionaryOfVariableBindings( popdownView )];
[UIView animateWithDuration:0.3f animations:^{
[[passwordsVC.popdownToTopConstraint updatePriority:1] layoutIfNeeded];
} completion:^(BOOL finished) {
} completion:^(BOOL finished) {
[popdownVC didMoveToParentViewController:passwordsVC];
id<NSObject> observer = [[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
[[[MPPopdownSegue alloc] initWithIdentifier:@"unwind-popdown" source:popdownVC
destination:passwordsVC] perform];
}];
objc_setAssociatedObject( popdownVC, &UnwindingObserverKey, observer, OBJC_ASSOCIATION_RETAIN );
}];
}
else if ([self.destinationViewController isKindOfClass:[MPPasswordsViewController class]]) {
else {
popdownVC = self.sourceViewController;
passwordsVC = self.destinationViewController;
for (passwordsVC = self.sourceViewController; passwordsVC && ![(id)passwordsVC isKindOfClass:[MPPasswordsViewController class]];
passwordsVC = (id)passwordsVC.parentViewController);
NSAssert( passwordsVC, @"Couldn't find passwords VC to pop back to." );
[[NSNotificationCenter defaultCenter] removeObserver:objc_getAssociatedObject( popdownVC, &UnwindingObserverKey )];
objc_setAssociatedObject( popdownVC, &UnwindingObserverKey, nil, OBJC_ASSOCIATION_RETAIN );
[popdownVC willMoveToParentViewController:nil];
[UIView animateWithDuration:0.3f delay:0 options:UIViewAnimationOptionOverrideInheritedDuration animations:^{

View File

@@ -31,6 +31,7 @@
inf( @"Preferences will appear" );
[super viewWillAppear:animated];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"tipped.passwordsPreferences"];
MPUserEntity *activeUser = [[MPiOSAppDelegate get] activeUserForMainThread];
self.generatedTypeControl.selectedSegmentIndex = [self generatedSegmentIndexForType:activeUser.defaultType];
@@ -102,7 +103,7 @@
self.generatedTypeControl.selectedSegmentIndex = -1;
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPElementType defaultType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType = [self typeForSelectedSegment];
MPSiteType defaultType = [[MPiOSAppDelegate get] activeUserInContext:context].defaultType = [self typeForSelectedSegment];
[context saveToStore];
PearlMainQueue( ^{
@@ -179,31 +180,31 @@
return nil;
}
- (enum MPElementType)typeForSelectedSegment {
- (enum MPSiteType)typeForSelectedSegment {
NSInteger selectedGeneratedIndex = self.generatedTypeControl.selectedSegmentIndex;
NSInteger selectedStoredIndex = self.storedTypeControl.selectedSegmentIndex;
switch (selectedGeneratedIndex) {
case 0:
return MPElementTypeGeneratedMaximum;
return MPSiteTypeGeneratedMaximum;
case 1:
return MPElementTypeGeneratedLong;
return MPSiteTypeGeneratedLong;
case 2:
return MPElementTypeGeneratedMedium;
return MPSiteTypeGeneratedMedium;
case 3:
return MPElementTypeGeneratedBasic;
return MPSiteTypeGeneratedBasic;
case 4:
return MPElementTypeGeneratedShort;
return MPSiteTypeGeneratedShort;
case 5:
return MPElementTypeGeneratedPIN;
return MPSiteTypeGeneratedPIN;
default:
switch (selectedStoredIndex) {
case 0:
return MPElementTypeStoredPersonal;
return MPSiteTypeStoredPersonal;
case 1:
return MPElementTypeStoredDevicePrivate;
return MPSiteTypeStoredDevicePrivate;
default:
Throw( @"unsupported selected type index: generated=%ld, stored=%ld", (long)selectedGeneratedIndex,
(long)selectedStoredIndex );
@@ -211,32 +212,32 @@
}
}
- (NSInteger)generatedSegmentIndexForType:(MPElementType)type {
- (NSInteger)generatedSegmentIndexForType:(MPSiteType)type {
switch (type) {
case MPElementTypeGeneratedMaximum:
case MPSiteTypeGeneratedMaximum:
return 0;
case MPElementTypeGeneratedLong:
case MPSiteTypeGeneratedLong:
return 1;
case MPElementTypeGeneratedMedium:
case MPSiteTypeGeneratedMedium:
return 2;
case MPElementTypeGeneratedBasic:
case MPSiteTypeGeneratedBasic:
return 3;
case MPElementTypeGeneratedShort:
case MPSiteTypeGeneratedShort:
return 4;
case MPElementTypeGeneratedPIN:
case MPSiteTypeGeneratedPIN:
return 5;
default:
return -1;
}
}
- (NSInteger)storedSegmentIndexForType:(MPElementType)type {
- (NSInteger)storedSegmentIndexForType:(MPSiteType)type {
switch (type) {
case MPElementTypeStoredPersonal:
case MPSiteTypeStoredPersonal:
return 0;
case MPElementTypeStoredDevicePrivate:
case MPSiteTypeStoredDevicePrivate:
return 1;
default:
return -1;

View File

@@ -9,16 +9,17 @@
*/
//
// MPEmergencySegue.h
// MPEmergencySegue
// MPRootSegue.h
// MPRootSegue
//
// Created by lhunath on 2014-04-09.
// Created by lhunath on 2014-09-26.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface MPEmergencySegue : UIStoryboardSegue
@property(nonatomic) BOOL unwind;
@interface MPRootSegue : UIStoryboardSegue
@end

View File

@@ -0,0 +1,38 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// MPRootSegue.h
// MPRootSegue
//
// Created by lhunath on 2014-09-26.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "MPRootSegue.h"
@implementation MPRootSegue {
}
- (void)perform {
UIViewController *sourceViewController = self.sourceViewController;
UIViewController *destinationViewController = self.destinationViewController;
[sourceViewController addChildViewController:destinationViewController];
destinationViewController.view.frame = sourceViewController.view.bounds;
destinationViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[sourceViewController.view addSubview:destinationViewController.view];
[destinationViewController didMoveToParentViewController:sourceViewController];
[sourceViewController setNeedsStatusBarAppearanceUpdate];
}
@end

View File

@@ -21,6 +21,7 @@
@interface MPSetupViewController : UIViewController
@property(weak, nonatomic) IBOutlet UISwitch *rememberLoginSwitch;
@property(weak, nonatomic) IBOutlet UISwitch *showPasswordsSwitch;
- (IBAction)close:(UIBarButtonItem *)sender;

View File

@@ -28,6 +28,8 @@
if (self.rememberLoginSwitch)
self.rememberLoginSwitch.on = [[MPiOSConfig get].rememberLogin boolValue];
if (self.showPasswordsSwitch)
self.showPasswordsSwitch.on = ![[MPiOSConfig get].hidePasswords boolValue];
}
- (void)viewWillDisappear:(BOOL)animated {
@@ -36,6 +38,8 @@
if (self.rememberLoginSwitch)
[MPiOSConfig get].rememberLogin = @(self.rememberLoginSwitch.on);
if (self.showPasswordsSwitch)
[MPiOSConfig get].hidePasswords = @(!self.showPasswordsSwitch.on);
}
- (IBAction)close:(UIBarButtonItem *)sender {

View File

@@ -0,0 +1,34 @@
//
// MPPreferencesViewController.h
// MasterPassword-iOS
//
// Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import <UIKit/UIKit.h>
@class MPStoreProductCell;
@interface MPStoreViewController : PearlMutableStaticTableViewController
@property(weak, nonatomic) IBOutlet MPStoreProductCell *generateLoginCell;
@property(weak, nonatomic) IBOutlet MPStoreProductCell *generateAnswersCell;
@property(weak, nonatomic) IBOutlet MPStoreProductCell *iOSIntegrationCell;
@property(weak, nonatomic) IBOutlet MPStoreProductCell *touchIDCell;
@property(weak, nonatomic) IBOutlet MPStoreProductCell *fuelCell;
@property(weak, nonatomic) IBOutlet UITableViewCell *loadingCell;
@property(weak, nonatomic) IBOutlet NSLayoutConstraint *fuelMeterConstraint;
@property(weak, nonatomic) IBOutlet UIButton *fuelSpeedButton;
+ (NSString *)latestStoreFeatures;
@end
@interface MPStoreProductCell : UITableViewCell
@property(nonatomic) IBOutlet UILabel *priceLabel;
@property(nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
@property(nonatomic) IBOutlet UIView *purchasedIndicator;
@end

View File

@@ -0,0 +1,313 @@
//
// MPPreferencesViewController.m
// MasterPassword-iOS
//
// Created by Maarten Billemont on 04/06/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPStoreViewController.h"
#import "MPiOSAppDelegate.h"
#import "UIColor+Expanded.h"
#import "MPAppDelegate_InApp.h"
#import "MPPasswordsViewController.h"
PearlEnum( MPDevelopmentFuelConsumption,
MPDevelopmentFuelConsumptionQuarterly, MPDevelopmentFuelConsumptionMonthly, MPDevelopmentFuelWeekly );
@interface MPStoreViewController()<MPInAppDelegate>
@property(nonatomic, strong) NSNumberFormatter *currencyFormatter;
@property(nonatomic, strong) NSArray *products;
@end
@implementation MPStoreViewController
+ (NSString *)latestStoreFeatures {
NSMutableString *features = [NSMutableString string];
NSArray *storeVersions = @[
@"Generated Usernames\nSecurity Question Answers"
];
NSInteger storeVersion = [[NSUserDefaults standardUserDefaults] integerForKey:@"storeVersion"];
for (; storeVersion < [storeVersions count]; ++storeVersion)
[features appendFormat:@"%@\n", storeVersions[storeVersion]];
if (![features length])
return nil;
[[NSUserDefaults standardUserDefaults] setInteger:storeVersion forKey:@"storeVersion"];
return features;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.currencyFormatter = [NSNumberFormatter new];
self.currencyFormatter.numberStyle = NSNumberFormatterCurrencyStyle;
self.tableView.tableHeaderView = [UIView new];
self.tableView.tableFooterView = [UIView new];
self.view.backgroundColor = [UIColor clearColor];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.tableView.contentInset = UIEdgeInsetsMake( 64, 0, 49, 0 );
[self reloadCellsHiding:self.allCellsBySection[0] showing:@[ self.loadingCell ]];
[self.allCellsBySection[0] enumerateObjectsUsingBlock:^(MPStoreProductCell *cell, NSUInteger idx, BOOL *stop) {
if ([cell isKindOfClass:[MPStoreProductCell class]]) {
cell.purchasedIndicator.alpha = 0;
[cell.activityIndicator stopAnimating];
}
}];
PearlAddNotificationObserver( NSUserDefaultsDidChangeNotification, nil, [NSOperationQueue mainQueue],
^(MPStoreViewController *self, NSNotification *note) {
[self updateProducts];
[self updateFuel];
} );
[[MPiOSAppDelegate get] registerProductsObserver:self];
[self updateFuel];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
PearlRemoveNotificationObservers();
}
#pragma mark - UITableViewDelegate
- (MPStoreProductCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MPStoreProductCell *cell = (MPStoreProductCell *)[super tableView:tableView cellForRowAtIndexPath:indexPath];
if (cell.contentView.translatesAutoresizingMaskIntoConstraints) {
cell.contentView.translatesAutoresizingMaskIntoConstraints = NO;
[cell addConstraint:
[NSLayoutConstraint constraintWithItem:cell attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual
toItem:cell.contentView attribute:NSLayoutAttributeWidth multiplier:1 constant:0]];
}
if (indexPath.section == 0)
cell.selectionStyle = [[MPiOSAppDelegate get] isFeatureUnlocked:[self productForCell:cell].productIdentifier]?
UITableViewCellSelectionStyleNone: UITableViewCellSelectionStyleDefault;
if (cell.selectionStyle != UITableViewCellSelectionStyleNone) {
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:cell.bounds];
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithRGBAHex:0x78DDFB33];
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
[cell layoutIfNeeded];
[cell layoutIfNeeded];
dbg_return_tr( cell.contentView.bounds.size.height, @ );
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
MPStoreProductCell *cell = (MPStoreProductCell *)[self tableView:tableView cellForRowAtIndexPath:indexPath];
if (cell.selectionStyle == UITableViewCellSelectionStyleNone)
return;
SKProduct *product = [self productForCell:cell];
if (product && ![[MPAppDelegate_Shared get] isFeatureUnlocked:product.productIdentifier])
[[MPAppDelegate_Shared get] purchaseProductWithIdentifier:product.productIdentifier
quantity:[self quantityForProductIdentifier:product.productIdentifier]];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
#pragma mark - Actions
- (IBAction)toggleFuelConsumption:(id)sender {
NSUInteger fuelConsumption = [[MPiOSConfig get].developmentFuelConsumption unsignedIntegerValue];
[MPiOSConfig get].developmentFuelConsumption = @((fuelConsumption + 1) % MPDevelopmentFuelConsumptionCount);
[self updateProducts];
}
- (IBAction)restorePurchases:(id)sender {
[PearlAlert showAlertWithTitle:@"Restore Previous Purchases" message:
@"This will check with Apple to find and activate any purchases you made from other devices."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
[[MPAppDelegate_Shared get] restoreCompletedTransactions];
} cancelTitle:@"Cancel" otherTitles:@"Find Purchases", nil];
}
- (IBAction)sendThanks:(id)sender {
[[self dismissPopup].navigationController performSegueWithIdentifier:@"web" sender:
[NSURL URLWithString:@"http://thanks.lhunath.com"]];
}
#pragma mark - MPInAppDelegate
- (void)updateWithProducts:(NSArray *)products {
self.products = products;
[self updateProducts];
}
- (void)updateWithTransaction:(SKPaymentTransaction *)transaction {
MPStoreProductCell *cell = [self cellForProductIdentifier:transaction.payment.productIdentifier];
if (!cell)
return;
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
[cell.activityIndicator startAnimating];
break;
case SKPaymentTransactionStatePurchased:
[cell.activityIndicator stopAnimating];
break;
case SKPaymentTransactionStateFailed:
[cell.activityIndicator stopAnimating];
break;
case SKPaymentTransactionStateRestored:
[cell.activityIndicator stopAnimating];
break;
case SKPaymentTransactionStateDeferred:
[cell.activityIndicator startAnimating];
break;
}
}
#pragma mark - Private
- (MPPasswordsViewController *)dismissPopup {
for (UIViewController *vc = self; (vc = vc.parentViewController);)
if ([vc isKindOfClass:[MPPasswordsViewController class]]) {
MPPasswordsViewController *passwordsVC = (MPPasswordsViewController *)vc;
[passwordsVC dismissPopdown:self];
return passwordsVC;
}
return nil;
}
- (SKProduct *)productForCell:(MPStoreProductCell *)cell {
for (SKProduct *product in self.products)
if ([self cellForProductIdentifier:product.productIdentifier] == cell)
return product;
return nil;
}
- (MPStoreProductCell *)cellForProductIdentifier:(NSString *)productIdentifier {
if ([productIdentifier isEqualToString:MPProductGenerateLogins])
return self.generateLoginCell;
if ([productIdentifier isEqualToString:MPProductGenerateAnswers])
return self.generateAnswersCell;
if ([productIdentifier isEqualToString:MPProductOSIntegration])
return self.iOSIntegrationCell;
if ([productIdentifier isEqualToString:MPProductTouchID])
return self.touchIDCell;
if ([productIdentifier isEqualToString:MPProductFuel])
return self.fuelCell;
return nil;
}
- (void)updateProducts {
NSMutableArray *showCells = [NSMutableArray array];
NSMutableArray *hideCells = [NSMutableArray array];
[hideCells addObjectsFromArray:self.allCellsBySection[0]];
[hideCells addObject:self.loadingCell];
for (SKProduct *product in self.products) {
[self showCellForProductWithIdentifier:MPProductGenerateLogins ifProduct:product showingCells:showCells];
[self showCellForProductWithIdentifier:MPProductGenerateAnswers ifProduct:product showingCells:showCells];
[self showCellForProductWithIdentifier:MPProductOSIntegration ifProduct:product showingCells:showCells];
[self showCellForProductWithIdentifier:MPProductTouchID ifProduct:product showingCells:showCells];
[self showCellForProductWithIdentifier:MPProductFuel ifProduct:product showingCells:showCells];
}
[hideCells removeObjectsInArray:showCells];
[self reloadCellsHiding:hideCells showing:showCells];
}
- (void)updateFuel {
CGFloat weeklyFuelConsumption = [self weeklyFuelConsumption]; /* consume x fuel / week */
CGFloat fuel = [[MPiOSConfig get].developmentFuel floatValue]; /* x fuel left */
NSDate *now = [NSDate date];
NSTimeInterval fuelSecondsElapsed = -[[MPiOSConfig get].developmentFuelChecked timeIntervalSinceDate:now];
if (fuelSecondsElapsed > 3600 || ![MPiOSConfig get].developmentFuelChecked) {
NSTimeInterval weeksElapsed = fuelSecondsElapsed / (3600 * 24 * 7 /* 1 week */); /* x weeks elapsed */
fuel -= weeklyFuelConsumption * weeksElapsed;
[MPiOSConfig get].developmentFuelChecked = now;
[MPiOSConfig get].developmentFuel = @(fuel);
}
CGFloat fuelRatio = weeklyFuelConsumption == 0? 0: fuel / weeklyFuelConsumption; /* x weeks worth of fuel left */
[self.fuelMeterConstraint updateConstant:MIN( 0.5f, fuelRatio - 0.5f ) * 160]; /* -80pt = 0 weeks left, 80pt = >=1 week left */
}
- (CGFloat)weeklyFuelConsumption {
switch ((MPDevelopmentFuelConsumption)[[MPiOSConfig get].developmentFuelConsumption unsignedIntegerValue]) {
case MPDevelopmentFuelConsumptionQuarterly:
[self.fuelSpeedButton setTitle:@"1h / quarter" forState:UIControlStateNormal];
return 1.f / 12 /* 12 weeks */;
case MPDevelopmentFuelConsumptionMonthly:
[self.fuelSpeedButton setTitle:@"1h / month" forState:UIControlStateNormal];
return 1.f / 4 /* 4 weeks */;
case MPDevelopmentFuelWeekly:
[self.fuelSpeedButton setTitle:@"1h / week" forState:UIControlStateNormal];
return 1.f;
}
return 0;
}
- (void)showCellForProductWithIdentifier:(NSString *)productIdentifier ifProduct:(SKProduct *)product
showingCells:(NSMutableArray *)showCells {
if (![product.productIdentifier isEqualToString:productIdentifier])
return;
MPStoreProductCell *cell = [self cellForProductIdentifier:productIdentifier];
[showCells addObject:cell];
self.currencyFormatter.locale = product.priceLocale;
BOOL purchased = [[MPiOSAppDelegate get] isFeatureUnlocked:productIdentifier];
NSInteger quantity = [self quantityForProductIdentifier:productIdentifier];
cell.priceLabel.text = purchased? @"": [self.currencyFormatter stringFromNumber:@([product.price floatValue] * quantity)];
cell.purchasedIndicator.alpha = purchased? 1: 0;
}
- (NSInteger)quantityForProductIdentifier:(NSString *)productIdentifier {
if ([productIdentifier isEqualToString:MPProductFuel])
return (NSInteger)(MP_FUEL_HOURLY_RATE * [self weeklyFuelConsumption]);
return 1;
}
@end
@implementation MPStoreProductCell
@end

View File

@@ -13,11 +13,11 @@
@protocol MPTypeDelegate<NSObject>
@required
- (void)didSelectType:(MPElementType)type;
- (MPElementType)selectedType;
- (void)didSelectType:(MPSiteType)type;
- (MPSiteType)selectedType;
@optional
- (MPElementEntity *)selectedElement;
- (MPSiteEntity *)selectedSite;
@end

View File

@@ -12,7 +12,7 @@
@interface MPTypeViewController()
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath;
- (MPSiteType)typeAtIndexPath:(NSIndexPath *)indexPath;
@end
@@ -63,21 +63,21 @@
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
MPElementEntity *selectedElement = nil;
if ([self.delegate respondsToSelector:@selector(selectedElement)])
selectedElement = [self.delegate selectedElement];
MPSiteEntity *selectedSite = nil;
if ([self.delegate respondsToSelector:@selector( selectedSite )])
selectedSite = [self.delegate selectedSite];
MPElementType cellType = [self typeAtIndexPath:indexPath];
MPElementType selectedType = selectedElement? selectedElement.type: [self.delegate selectedType];
MPSiteType cellType = [self typeAtIndexPath:indexPath];
MPSiteType selectedType = selectedSite? selectedSite.type: [self.delegate selectedType];
cell.selected = (selectedType == cellType);
if (cellType != (MPElementType)NSNotFound && cellType & MPElementTypeClassGenerated) {
if (cellType != (MPSiteType)NSNotFound && cellType & MPSiteTypeClassGenerated) {
[(UITextField *)[cell viewWithTag:2] setText:@"..."];
NSString *name = selectedElement.name;
NSString *name = selectedSite.name;
NSUInteger counter = 0;
if ([selectedElement isKindOfClass:[MPElementGeneratedEntity class]])
counter = ((MPElementGeneratedEntity *)selectedElement).counter;
if ([selectedSite isKindOfClass:[MPGeneratedSiteEntity class]])
counter = ((MPGeneratedSiteEntity *)selectedSite).counter;
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ), ^{
NSString *typeContent = [MPAlgorithmDefault generatePasswordForSiteNamed:name ofType:cellType
@@ -96,8 +96,8 @@
NSAssert(self.navigationController.topViewController == self, @"Not the currently active navigation item.");
MPElementType type = [self typeAtIndexPath:indexPath];
if (type == (MPElementType)NSNotFound)
MPSiteType type = [self typeAtIndexPath:indexPath];
if (type == (MPSiteType)NSNotFound)
// Selected a non-type row.
return;
@@ -105,31 +105,31 @@
[self.navigationController popViewControllerAnimated:YES];
}
- (MPElementType)typeAtIndexPath:(NSIndexPath *)indexPath {
- (MPSiteType)typeAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.section) {
case 0: {
// Generated
switch (indexPath.row) {
case 0:
return (MPElementType)NSNotFound;
return (MPSiteType)NSNotFound;
case 1:
return MPElementTypeGeneratedMaximum;
return MPSiteTypeGeneratedMaximum;
case 2:
return MPElementTypeGeneratedLong;
return MPSiteTypeGeneratedLong;
case 3:
return MPElementTypeGeneratedMedium;
return MPSiteTypeGeneratedMedium;
case 4:
return MPElementTypeGeneratedBasic;
return MPSiteTypeGeneratedBasic;
case 5:
return MPElementTypeGeneratedShort;
return MPSiteTypeGeneratedShort;
case 6:
return MPElementTypeGeneratedPIN;
return MPSiteTypeGeneratedPIN;
case 7:
return (MPElementType)NSNotFound;
return (MPSiteType)NSNotFound;
default: {
Throw(@"Unsupported row: %ld, when selecting generated element type.", (long)indexPath.row);
Throw(@"Unsupported row: %ld, when selecting generated site type.", (long)indexPath.row);
}
}
}
@@ -138,22 +138,22 @@
// Stored
switch (indexPath.row) {
case 0:
return (MPElementType)NSNotFound;
return (MPSiteType)NSNotFound;
case 1:
return MPElementTypeStoredPersonal;
return MPSiteTypeStoredPersonal;
case 2:
return MPElementTypeStoredDevicePrivate;
return MPSiteTypeStoredDevicePrivate;
case 3:
return (MPElementType)NSNotFound;
return (MPSiteType)NSNotFound;
default: {
Throw(@"Unsupported row: %ld, when selecting stored element type.", (long)indexPath.row);
Throw(@"Unsupported row: %ld, when selecting stored site type.", (long)indexPath.row);
}
}
}
default:
Throw(@"Unsupported section: %ld, when selecting element type.", (long)indexPath.section);
Throw(@"Unsupported section: %ld, when selecting site type.", (long)indexPath.section);
}
}

View File

@@ -16,25 +16,26 @@
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
@interface MPUsersViewController : UIViewController <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UITextFieldDelegate>
@property(weak, nonatomic) IBOutlet UIView *userSelectionContainer;
@property(weak, nonatomic) IBOutlet UIButton *marqueeButton;
@property(weak, nonatomic) IBOutlet UIView *gitTipTip;
@property(weak, nonatomic) IBOutlet UITextField *entryField;
@property(weak, nonatomic) IBOutlet UILabel *entryLabel;
@property(weak, nonatomic) IBOutlet UILabel *entryTipTitleLabel;
@property(weak, nonatomic) IBOutlet UILabel *entryTipSubtitleLabel;
@property(weak, nonatomic) IBOutlet UIView *entryTipContainer;
@property(weak, nonatomic) IBOutlet UIView *entryContainer;
@property(weak, nonatomic) IBOutlet UIView *footerContainer;
@property(weak, nonatomic) IBOutlet UIActivityIndicatorView *storeLoadingActivity;
@property(weak, nonatomic) IBOutlet UICollectionView *avatarCollectionView;
@property (strong, nonatomic) IBOutlet UIButton *nextAvatarButton;
@property (strong, nonatomic) IBOutlet UIButton *previousAvatarButton;
@property(weak, nonatomic) IBOutlet UIView *avatarTipContainer;
@property(weak, nonatomic) IBOutlet UIView *entryTipContainer;
@property(weak, nonatomic) IBOutlet UIView *preferencesTipContainer;
@property(weak, nonatomic) IBOutlet UIView *thanksTipContainer;
@property(weak, nonatomic) IBOutlet UIButton *nextAvatarButton;
@property(weak, nonatomic) IBOutlet UIButton *previousAvatarButton;
@property(assign, nonatomic) BOOL active;
@property(assign, nonatomic, readonly) BOOL active;
- (void)setActive:(BOOL)active animated:(BOOL)animated;
- (IBAction)changeAvatar:(UIButton *)sender;

View File

@@ -22,9 +22,14 @@
#import "MPiOSAppDelegate.h"
#import "MPAppDelegate_Store.h"
#import "MPAppDelegate_Key.h"
#import "PearlSizedTextView.h"
#import "MPWebViewController.h"
#import "UIView+FontScale.h"
typedef NS_OPTIONS( NSUInteger, MPUsersTips ) {
MPUsersThanksTip = 1 << 0,
MPUsersAvatarTip = 1 << 1,
MPUsersMasterPasswordTip = 1 << 2,
MPUsersPreferencesTip = 1 << 3,
};
typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
/** The users are all inactive */
@@ -51,10 +56,6 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
@end
@implementation MPUsersViewController {
__weak id _storeChangingObserver;
__weak id _storeChangedObserver;
__weak id _mocObserver;
NSArray *_notificationObservers;
NSString *_masterPasswordChoice;
NSOperationQueue *_afterUpdates;
}
@@ -74,6 +75,12 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
self.view.backgroundColor = [UIColor clearColor];
self.avatarCollectionView.allowsMultipleSelection = YES;
[self.entryField addTarget:self action:@selector( textFieldEditingChanged: ) forControlEvents:UIControlEventEditingChanged];
self.preferencesTipContainer.alpha = 0;
[self setActive:YES animated:NO];
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"tipped.thanks"])
[self showTips:MPUsersThanksTip];
}
- (void)viewWillAppear:(BOOL)animated {
@@ -81,15 +88,13 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
[super viewWillAppear:animated];
self.userSelectionContainer.alpha = 0;
[self setActive:YES animated:NO];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self removeObservers];
[self stopObservingStore];
PearlRemoveNotificationObservers();
[self.marqueeTipTimer invalidate];
}
@@ -98,7 +103,6 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
[super viewDidAppear:animated];
[self observeStore];
[self registerObservers];
[self reloadUsers];
@@ -398,34 +402,23 @@ referenceSizeForFooterInSection:(NSInteger)section {
if (buttonIndex == [sheet destructiveButtonIndex]) {
// Delete User
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
if (!user_)
return;
[context deleteObject:user_];
[context saveToStore];
[self reloadUsers]; // I do NOT understand why our ObjectsDidChangeNotification isn't firing on saveToStore.
}];
[PearlAlert showParentalGateWithTitle:@"Deleting User" message:
@"The user and its sites will be deleted.\nPlease confirm by solving:"
completion:^(BOOL continuing) {
if (continuing)
[self deleteUser:userID];
}];
return;
}
if (buttonIndex == [sheet firstOtherButtonIndex])
// Reset Password
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
if (!user_)
return;
[[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{
PearlMainQueue( ^{
NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell];
[self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO
scrollPosition:UICollectionViewScrollPositionNone];
[self collectionView:self.avatarCollectionView didSelectItemAtIndexPath:avatarIndexPath];
} );
}];
}];
[PearlAlert showParentalGateWithTitle:@"Resetting User" message:
@"The user's master password will be reset.\nPlease confirm by solving:"
completion:^(BOOL continuing) {
if (continuing)
[self resetUser:userID avatar:avatarCell];
}];
} cancelTitle:[PearlStrings get].commonButtonCancel
destructiveTitle:@"Delete User" otherTitles:@"Reset Password", nil];
}
@@ -448,6 +441,66 @@ referenceSizeForFooterInSection:(NSInteger)section {
#pragma mark - Private
- (void)deleteUser:(NSManagedObjectID *)userID {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity
*user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
if (!user_)
return;
[context deleteObject:user_];
[context saveToStore];
[self reloadUsers]; // I do NOT understand why our ObjectsDidChangeNotification isn't firing on saveToStore.
}];
}
- (void)resetUser:(NSManagedObjectID *)userID avatar:(MPAvatarCell *)avatarCell {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *user_ = [MPUserEntity existingObjectWithID:userID inContext:context];
if (!user_)
return;
[[MPiOSAppDelegate get] changeMasterPasswordFor:user_ saveInContext:context didResetBlock:^{
PearlMainQueue( ^{
NSIndexPath *avatarIndexPath = [self.avatarCollectionView indexPathForCell:avatarCell];
[self.avatarCollectionView selectItemAtIndexPath:avatarIndexPath animated:NO
scrollPosition:UICollectionViewScrollPositionNone];
[self collectionView:self.avatarCollectionView didSelectItemAtIndexPath:avatarIndexPath];
} );
}];
}];
}
- (void)showTips:(MPUsersTips)showTips {
[UIView animateWithDuration:0.3f animations:^{
if (showTips & MPUsersThanksTip)
self.thanksTipContainer.alpha = 1;
if (showTips & MPUsersAvatarTip)
self.avatarTipContainer.alpha = 1;
if (showTips & MPUsersMasterPasswordTip)
self.entryTipContainer.alpha = 1;
if (showTips & MPUsersPreferencesTip)
self.preferencesTipContainer.alpha = 1;
} completion:^(BOOL finished) {
if (finished)
PearlMainQueueAfter( 5, ^{
[UIView animateWithDuration:0.3f animations:^{
if (showTips & MPUsersThanksTip)
self.thanksTipContainer.alpha = 0;
if (showTips & MPUsersAvatarTip)
self.avatarTipContainer.alpha = 0;
if (showTips & MPUsersMasterPasswordTip)
self.entryTipContainer.alpha = 0;
if (showTips & MPUsersPreferencesTip)
self.preferencesTipContainer.alpha = 0;
}];
} );
}];
}
- (void)showEntryTip:(NSString *)message {
NSUInteger newlineIndex = [message rangeOfString:@"\n"].location;
@@ -455,17 +508,7 @@ referenceSizeForFooterInSection:(NSInteger)section {
NSString *messageSubtitle = newlineIndex == NSNotFound? nil: [message substringFromIndex:newlineIndex];
self.entryTipTitleLabel.text = messageTitle;
self.entryTipSubtitleLabel.text = messageSubtitle;
[UIView animateWithDuration:0.3f animations:^{
self.entryTipContainer.alpha = 1;
} completion:^(BOOL finished) {
if (finished)
PearlMainQueueAfter( 4, ^{
[UIView animateWithDuration:0.3f animations:^{
self.entryTipContainer.alpha = 0;
}];
} );
}];
[self showTips:MPUsersMasterPasswordTip];
}
- (void)firedMarqueeTimer:(NSTimer *)timer {
@@ -474,15 +517,15 @@ referenceSizeForFooterInSection:(NSInteger)section {
if ([nextMarqueeString isEqualToString:[self.marqueeButton titleForState:UIControlStateNormal]])
return;
[UIView animateWithDuration:timer? 0.5: 0 animations:^{
[UIView animateWithDuration:timer? 0.5f: 0 animations:^{
self.marqueeButton.alpha = 0;
} completion:^(BOOL finished) {
if (!finished)
return;
[self.marqueeButton setTitle:nextMarqueeString forState:UIControlStateNormal];
[UIView animateWithDuration:timer? 0.5: 0 animations:^{
self.marqueeButton.alpha = 0.5;
[UIView animateWithDuration:timer? 0.5f: 0 animations:^{
self.marqueeButton.alpha = 0.5f;
}];
}];
}
@@ -585,50 +628,27 @@ referenceSizeForFooterInSection:(NSInteger)section {
- (void)registerObservers {
if ([_notificationObservers count])
return;
Weakify( self );
_notificationObservers = @[
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationWillResignActiveNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify( self );
// [self emergencyCloseAnimated:NO];
self.userSelectionContainer.alpha = 0;
}],
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationDidBecomeActiveNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
Strongify( self );
[self reloadUsers];
[UIView animateWithDuration:1 animations:^{
self.userSelectionContainer.alpha = 1;
}];
}],
];
[self removeKeyPathObservers];
[self observeKeyPath:@"avatarCollectionView.contentOffset" withBlock:
^(id from, id to, NSKeyValueChange cause, MPUsersViewController *_self) {
[_self updateAvatarVisibility];
}];
}
- (void)removeObservers {
for (id observer in _notificationObservers)
[[NSNotificationCenter defaultCenter] removeObserver:observer];
_notificationObservers = nil;
[self removeKeyPathObservers];
}
- (void)observeStore {
Weakify( self );
PearlRemoveNotificationObservers();
PearlAddNotificationObserver( UIApplicationDidEnterBackgroundNotification, nil, [NSOperationQueue mainQueue],
^(MPUsersViewController *self, NSNotification *note) {
self.userSelectionContainer.alpha = 0;
} );
PearlAddNotificationObserver( UIApplicationWillEnterForegroundNotification, nil, [NSOperationQueue mainQueue],
^(MPUsersViewController *self, NSNotification *note) {
[self reloadUsers];
} );
PearlAddNotificationObserver( UIApplicationDidBecomeActiveNotification, nil, [NSOperationQueue mainQueue],
^(MPUsersViewController *self, NSNotification *note) {
[UIView animateWithDuration:0.5f animations:^{
self.userSelectionContainer.alpha = 1;
}];
} );
NSManagedObjectContext *mainContext = [MPiOSAppDelegate managedObjectContextForMainThreadIfReady];
[UIView animateWithDuration:0.3f animations:^{
@@ -639,11 +659,9 @@ referenceSizeForFooterInSection:(NSInteger)section {
if (!mainContext && !self.storeLoadingActivity.isAnimating)
[self.storeLoadingActivity startAnimating];
if (!_mocObserver && mainContext)
_mocObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextObjectsDidChangeNotification object:mainContext
queue:nil usingBlock:^(NSNotification *note) {
Strongify( self );
if (mainContext)
PearlAddNotificationObserver( NSManagedObjectContextObjectsDidChangeNotification, mainContext, [NSOperationQueue mainQueue],
^(MPUsersViewController *self, NSNotification *note) {
NSSet *insertedObjects = note.userInfo[NSInsertedObjectsKey];
NSSet *deletedObjects = note.userInfo[NSDeletedObjectsKey];
if ([[NSSetUnion( insertedObjects, deletedObjects )
@@ -651,39 +669,21 @@ referenceSizeForFooterInSection:(NSInteger)section {
return [evaluatedObject isKindOfClass:[MPUserEntity class]];
}]] count])
[self reloadUsers];
}];
if (!_storeChangingObserver)
_storeChangingObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:NSPersistentStoreCoordinatorStoresWillChangeNotification object:nil
queue:nil usingBlock:^(NSNotification *note) {
Strongify( self );
if (self->_mocObserver)
[[NSNotificationCenter defaultCenter] removeObserver:self->_mocObserver];
self.userIDs = nil;
}];
if (!_storeChangedObserver)
_storeChangedObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:NSPersistentStoreCoordinatorStoresDidChangeNotification object:nil
queue:nil usingBlock:^(NSNotification *note) {
Strongify( self );
[self reloadUsers];
}];
}
- (void)stopObservingStore {
if (_mocObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_mocObserver];
if (_storeChangingObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_storeChangingObserver];
if (_storeChangedObserver)
[[NSNotificationCenter defaultCenter] removeObserver:_storeChangedObserver];
} );
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, nil, [NSOperationQueue mainQueue],
^(MPUsersViewController *self, NSNotification *note) {
self.userIDs = nil;
} );
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresDidChangeNotification, nil, [NSOperationQueue mainQueue],
^(MPUsersViewController *self, NSNotification *note) {
[self registerObservers];
[self reloadUsers];
} );
}
- (void)reloadUsers {
[self afterUpdatesMainQueue:^{
[self observeStore];
if (![MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) {
NSError *error = nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
@@ -692,7 +692,7 @@ referenceSizeForFooterInSection:(NSInteger)section {
];
NSArray *users = [mainContext executeFetchRequest:fetchRequest error:&error];
if (!users) {
err( @"Failed to load users: %@", error );
err( @"Failed to load users: %@", [error fullDescription] );
self.userIDs = nil;
}
@@ -707,11 +707,6 @@ referenceSizeForFooterInSection:(NSInteger)section {
#pragma mark - Properties
- (void)setActive:(BOOL)active {
[self setActive:active animated:NO];
}
- (void)setActive:(BOOL)active animated:(BOOL)animated {
_active = active;
@@ -827,6 +822,29 @@ referenceSizeForFooterInSection:(NSInteger)section {
}
}
// Manage tip visibility.
switch (activeUserState) {
case MPActiveUserStateNone:
case MPActiveUserStateMasterPasswordConfirmation:
case MPActiveUserStateLogin: {
break;
}
case MPActiveUserStateUserName: {
[self showTips:MPUsersAvatarTip];
break;
}
case MPActiveUserStateMasterPasswordChoice: {
[self showEntryTip:strl( @"A short phrase makes a strong, memorable password." )];
break;
}
case MPActiveUserStateMinimized: {
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"tipped.passwordsPreferences"])
[self showTips:MPUsersPreferencesTip];
break;
}
}
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
[_afterUpdates setSuspended:NO];

View File

@@ -50,6 +50,9 @@
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
if ([[request.URL absoluteString] rangeOfString:@"thanks.lhunath.com"].location != NSNotFound)
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"tipped.thanks"];
if ([request.URL isEqual:request.mainDocumentURL]) {
self.webNavigationItem.title = request.URL.host;
self.webNavigationItem.prompt = strl( @"Loading" );

View File

@@ -7,7 +7,6 @@
//
#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#import "MPAppDelegate_Shared.h"

View File

@@ -10,27 +10,27 @@
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#import "IASKSettingsReader.h"
#import "MPStoreViewController.h"
@interface MPiOSAppDelegate()
@interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate>
@property(nonatomic, strong) UIDocumentInteractionController *interactionController;
@property(nonatomic, weak) PearlAlert *handleCloudDisabledAlert;
@property(nonatomic, weak) PearlAlert *handleCloudContentAlert;
@property(nonatomic, weak) PearlAlert *fixCloudContentAlert;
@property(nonatomic, weak) PearlOverlay *storeLoadingOverlay;
@end
@implementation MPiOSAppDelegate
+ (void)initialize {
if ([self class] == [MPiOSAppDelegate class]) {
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
[PearlLogger get].historyLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelTrace: PearlLogLevelInfo;
#ifdef DEBUG
[PearlLogger get].printLevel = PearlLogLevelDebug; //Trace;
#else
[PearlLogger get].printLevel = [[MPiOSConfig get].traceMode boolValue]? PearlLogLevelDebug: PearlLogLevelInfo;
#endif
}
} );
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
@@ -70,18 +70,15 @@
err( @"During Analytics Setup: %@", exception );
}
@try {
[[NSNotificationCenter defaultCenter] addObserverForName:MPCheckConfigNotification object:nil queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
[self updateConfigKey:note.object];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:kIASKAppSettingChanged object:nil queue:nil usingBlock:
^(NSNotification *note) {
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note.object];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification object:nil queue:nil usingBlock:
^(NSNotification *note) {
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
}];
PearlAddNotificationObserver( MPCheckConfigNotification, nil, [NSOperationQueue mainQueue], ^(id self, NSNotification *note) {
[self updateConfigKey:note.object];
} );
PearlAddNotificationObserver( kIASKAppSettingChanged, nil, nil, ^(id self, NSNotification *note) {
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:note.object];
} );
PearlAddNotificationObserver( NSUserDefaultsDidChangeNotification, nil, nil, ^(id self, NSNotification *note) {
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
} );
#ifdef ADHOC
[PearlAlert showAlertWithTitle:@"Welcome, tester!" message:
@@ -107,41 +104,48 @@
@try {
inf( @"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier] );
[[NSNotificationCenter defaultCenter] addObserverForName:MPFoundInconsistenciesNotification object:nil queue:nil usingBlock:
^(NSNotification *note) {
switch ((MPFixableResult)[note.userInfo[MPInconsistenciesFixResultUserKey] unsignedIntegerValue]) {
PearlAddNotificationObserver( MPFoundInconsistenciesNotification, nil, nil, ^(id self, NSNotification *note) {
switch ((MPFixableResult)[note.userInfo[MPInconsistenciesFixResultUserKey] unsignedIntegerValue]) {
case MPFixableResultNoProblems:
break;
case MPFixableResultProblemsFixed:
[PearlAlert showAlertWithTitle:@"Inconsistencies Fixed" message:
@"Some inconsistencies were detected in your sites.\n"
@"All issues were fixed."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
break;
case MPFixableResultProblemsNotFixed:
[PearlAlert showAlertWithTitle:@"Inconsistencies Found" message:
@"Some inconsistencies were detected in your sites.\n"
@"Not all issues could be fixed. Try signing in to each user or checking the logs."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
break;
}
}];
case MPFixableResultNoProblems:
break;
case MPFixableResultProblemsFixed:
[PearlAlert showAlertWithTitle:@"Inconsistencies Fixed" message:
@"Some inconsistencies were detected in your sites.\n"
@"All issues were fixed."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
break;
case MPFixableResultProblemsNotFixed:
[PearlAlert showAlertWithTitle:@"Inconsistencies Found" message:
@"Some inconsistencies were detected in your sites.\n"
@"Not all issues could be fixed. Try signing in to each user or checking the logs."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:nil cancelTitle:[PearlStrings get].commonButtonOkay otherTitles:nil];
break;
}
} );
PearlMainQueue( ^{
if ([[MPiOSConfig get].showSetup boolValue])
[self.navigationController performSegueWithIdentifier:@"setup" sender:self];
} );
NSString *latestFeatures = [MPStoreViewController latestStoreFeatures];
if (latestFeatures)
[PearlAlert showAlertWithTitle:@"New Features" message:
strf( @"The following features are now available in the store:\n\n%@•••\n\n"
@"Find the store from the user pulldown after logging in.", latestFeatures )
viewStyle:UIAlertViewStyleDefault initAlert:nil tappedButtonBlock:nil
cancelTitle:@"Thanks" otherTitles:nil];
MPCheckpoint( MPCheckpointStarted, @{
@"simulator" : PearlStringB( [PearlDeviceUtils isSimulator] ),
@"encrypted" : PearlStringB( [PearlDeviceUtils isAppEncrypted] ),
@"jailbroken" : PearlStringB( [PearlDeviceUtils isJailbroken] ),
@"platform" : [PearlDeviceUtils platform],
#ifdef APPSTORE
@"legal" : PearlStringB([PearlDeviceUtils isAppEncrypted]),
@"legal" : PearlStringB([PearlDeviceUtils isAppEncrypted]),
#else
@"legal" : @"YES",
#endif
@@ -168,88 +172,106 @@
NSData *importedSitesData = [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url]
returningResponse:&response error:&error];
if (error)
err( @"While reading imported sites from %@: %@", url, error );
if (!importedSitesData)
err( @"While reading imported sites from %@: %@", url, [error fullDescription] );
if (!importedSitesData) {
[PearlAlert showError:strf( @"Master Password couldn't read the import sites.\n\n%@", [error localizedDescription]?: error )];
return;
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:@"Importing"];
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
__block NSString *masterPassword = nil;
dispatch_group_t importPasswordGroup = dispatch_group_create();
dispatch_group_enter( importPasswordGroup );
dispatch_async( dispatch_get_main_queue(), ^{
[PearlAlert showAlertWithTitle:@"Import File's Master Password"
message:strf( @"%@'s export was done using a different master password.\n"
@"Enter that master password to unlock the exported data.", userName )
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
@try {
if (buttonIndex_ == [alert_ cancelButtonIndex])
return;
masterPassword = [alert_ textFieldAtIndex:0].text;
}
@finally {
dispatch_group_leave( importPasswordGroup );
}
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock Import", nil];
} );
dispatch_group_wait( importPasswordGroup, DISPATCH_TIME_FOREVER );
return masterPassword;
} askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) {
__block NSString *masterPassword = nil;
dispatch_group_t userPasswordGroup = dispatch_group_create();
dispatch_group_enter( userPasswordGroup );
dispatch_async( dispatch_get_main_queue(), ^{
[PearlAlert showAlertWithTitle:strf( @"Master Password for\n%@", userName )
message:strf( @"Imports %lu sites, overwriting %lu.",
(unsigned long)importCount, (unsigned long)deleteCount )
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
@try {
if (buttonIndex_ == [alert_ cancelButtonIndex])
return;
masterPassword = [alert_ textFieldAtIndex:0].text;
}
@finally {
dispatch_group_leave( userPasswordGroup );
}
}
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;
}
[activityOverlay cancelOverlayAnimated:YES];
NSString *importedSitesString = [[NSString alloc] initWithData:importedSitesData encoding:NSUTF8StringEncoding];
if (!importedSitesString) {
[PearlAlert showError:@"Master Password couldn't understand the import file."];
return;
}
[self importSites:importedSitesString];
} );
return YES;
}
- (void)importSites:(NSString *)importedSitesString {
if ([NSThread isMainThread]) {
PearlNotMainQueue( ^{
[self importSites:importedSitesString];
} );
return;
}
PearlOverlay *activityOverlay = [PearlOverlay showProgressOverlayWithTitle:@"Importing"];
MPImportResult result = [self importSites:importedSitesString askImportPassword:^NSString *(NSString *userName) {
__block NSString *masterPassword = nil;
dispatch_group_t importPasswordGroup = dispatch_group_create();
dispatch_group_enter( importPasswordGroup );
dispatch_async( dispatch_get_main_queue(), ^{
[PearlAlert showAlertWithTitle:@"Import File's Master Password"
message:strf( @"%@'s export was done using a different master password.\n"
@"Enter that master password to unlock the exported data.", userName )
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
@try {
if (buttonIndex_ == [alert_ cancelButtonIndex])
return;
masterPassword = [alert_ textFieldAtIndex:0].text;
}
@finally {
dispatch_group_leave( importPasswordGroup );
}
}
cancelTitle:[PearlStrings get].commonButtonCancel otherTitles:@"Unlock Import", nil];
} );
dispatch_group_wait( importPasswordGroup, DISPATCH_TIME_FOREVER );
return masterPassword;
} askUserPassword:^NSString *(NSString *userName, NSUInteger importCount, NSUInteger deleteCount) {
__block NSString *masterPassword = nil;
dispatch_group_t userPasswordGroup = dispatch_group_create();
dispatch_group_enter( userPasswordGroup );
dispatch_async( dispatch_get_main_queue(), ^{
[PearlAlert showAlertWithTitle:strf( @"Master Password for\n%@", userName )
message:strf( @"Imports %lu sites, overwriting %lu.",
(unsigned long)importCount, (unsigned long)deleteCount )
viewStyle:UIAlertViewStyleSecureTextInput
initAlert:nil tappedButtonBlock:^(UIAlertView *alert_, NSInteger buttonIndex_) {
@try {
if (buttonIndex_ == [alert_ cancelButtonIndex])
return;
masterPassword = [alert_ textFieldAtIndex:0].text;
}
@finally {
dispatch_group_leave( userPasswordGroup );
}
}
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;
}
[activityOverlay cancelOverlayAnimated:YES];
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
inf( @"Received memory warning." );
@@ -257,13 +279,13 @@
[super applicationDidReceiveMemoryWarning:application];
}
- (void)applicationWillResignActive:(UIApplication *)application {
- (void)applicationDidEnterBackground:(UIApplication *)application {
inf( @"Will deactivate" );
inf( @"Will background" );
if (![[MPiOSConfig get].rememberLogin boolValue])
[self signOutAnimated:NO];
[super applicationWillResignActive:application];
[super applicationDidEnterBackground:application];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
@@ -271,6 +293,21 @@
inf( @"Re-activated" );
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
NSString *importHeader = @"# Master Password site export";
NSString *importedSitesString = [UIPasteboard generalPasteboard].string;
if ([importedSitesString length] > [importHeader length] &&
[[importedSitesString substringToIndex:[importHeader length]] isEqualToString:importHeader])
[PearlAlert showAlertWithTitle:@"Import Sites?" message:
@"We've detected Master Password import sites on your pasteboard, would you like to import them?"
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
[self importSites:importedSitesString];
[UIPasteboard generalPasteboard].string = nil;
} cancelTitle:@"No" otherTitles:@"Import Sites", nil];
[super applicationDidBecomeActive:application];
}
@@ -339,6 +376,26 @@
showComposerForVC:viewController];
}
- (void)handleCoordinatorError:(NSError *)error {
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
[PearlAlert showAlertWithTitle:@"Failed To Load Sites" message:
@"Master Password was unable to open your sites history.\n"
@"This may be due to corruption. You can either reset Master Password and "
@"recreate your user, or E-Mail us your logs and leave your corrupt store as-is for now."
viewStyle:UIAlertViewStyleDefault initAlert:nil
tappedButtonBlock:^(UIAlertView *alert, NSInteger buttonIndex) {
if (buttonIndex == [alert cancelButtonIndex])
return;
if (buttonIndex == [alert firstOtherButtonIndex])
[self openFeedbackWithLogs:YES forVC:nil];
if (buttonIndex == [alert firstOtherButtonIndex] + 1)
[self deleteAndResetStore];
} cancelTitle:@"Ignore" otherTitles:@"E-Mail Logs", @"Reset", nil];
} );
}
- (void)showExportForVC:(UIViewController *)viewController {
[PearlAlert showAlertWithTitle:@"Exporting Your Sites"
@@ -409,12 +466,38 @@
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
attachments:[[PearlEMailAttachment alloc] initWithContent:[exportedSites dataUsingEncoding:NSUTF8StringEncoding]
mimeType:@"text/plain" fileName:
strf( @"%@ (%@).mpsites", [self activeUserForMainThread].name,
[exportDateFormatter stringFromDate:[NSDate date]] )],
nil];
NSString *exportFileName = strf( @"%@ (%@).mpsites",
[self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
[PearlSheet showSheetWithTitle:@"Export Destination" viewStyle:UIActionSheetStyleBlackTranslucent initSheet:nil
tappedButtonBlock:^(UIActionSheet *sheet, NSInteger buttonIndex) {
if (buttonIndex == [sheet cancelButtonIndex])
return;
if (buttonIndex == [sheet firstOtherButtonIndex]) {
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
attachments:[[PearlEMailAttachment alloc]
initWithContent:[exportedSites dataUsingEncoding:NSUTF8StringEncoding]
mimeType:@"text/plain" fileName:exportFileName],
nil];
return;
}
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask] lastObject];
NSURL *exportURL = [[applicationSupportURL
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
URLByAppendingPathComponent:exportFileName isDirectory:NO];
NSError *error = nil;
if (![[exportedSites dataUsingEncoding:NSUTF8StringEncoding]
writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&error])
err( @"Failed to write export data to URL %@: %@", exportURL, [error fullDescription] );
else {
self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
self.interactionController.delegate = self;
[self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
}
} cancelTitle:@"Cancel" destructiveTitle:nil otherTitles:@"Send As E-Mail", @"Share / Airdrop", nil];
}
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset {
@@ -446,6 +529,13 @@
otherTitles:[PearlStrings get].commonButtonContinue, nil];
}
#pragma mark - UIDocumentInteractionControllerDelegate
- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application {
// self.interactionController = nil;
}
#pragma mark - PearlConfigDelegate
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)value {

View File

@@ -18,5 +18,8 @@
@property(nonatomic, retain) NSNumber *loginNameTipShown;
@property(nonatomic, retain) NSNumber *traceMode;
@property(nonatomic, retain) NSNumber *dictationSearch;
@property(nonatomic, retain) NSNumber *developmentFuel;
@property(nonatomic, retain) NSNumber *developmentFuelConsumption;
@property(nonatomic, retain) NSDate *developmentFuelChecked;
@end

View File

@@ -9,6 +9,7 @@
@implementation MPiOSConfig
@dynamic helpHidden, siteInfoHidden, showSetup, actionsTipShown, typeTipShown, loginNameTipShown, traceMode, dictationSearch;
@dynamic developmentFuel, developmentFuelConsumption, developmentFuelChecked;
- (id)init {
@@ -16,15 +17,15 @@
return self;
[self.defaults registerDefaults:@{
NSStringFromSelector( @selector(helpHidden) ) : @NO,
NSStringFromSelector( @selector(siteInfoHidden) ) : @YES,
NSStringFromSelector( @selector(showSetup) ) : @YES,
NSStringFromSelector( @selector(iTunesID) ) : @"510296984",
NSStringFromSelector( @selector(actionsTipShown) ) : @(!self.firstRun),
NSStringFromSelector( @selector(typeTipShown) ) : @(!self.firstRun),
NSStringFromSelector( @selector(loginNameTipShown) ) : @NO,
NSStringFromSelector( @selector(traceMode) ) : @NO,
NSStringFromSelector( @selector(dictationSearch) ) : @NO
NSStringFromSelector( @selector( helpHidden ) ) : @NO,
NSStringFromSelector( @selector( siteInfoHidden ) ) : @YES,
NSStringFromSelector( @selector( showSetup ) ) : @YES,
NSStringFromSelector( @selector( iTunesID ) ) : @"510296984",
NSStringFromSelector( @selector( actionsTipShown ) ) : @(!self.firstRun),
NSStringFromSelector( @selector( typeTipShown ) ) : @(!self.firstRun),
NSStringFromSelector( @selector( loginNameTipShown ) ) : @NO,
NSStringFromSelector( @selector( traceMode ) ) : @NO,
NSStringFromSelector( @selector( dictationSearch ) ) : @NO,
}];
return self;

View File

@@ -21,7 +21,7 @@
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.lyndir.lhunath.MasterPassword.sites</string>
<string>com.lyndir.masterpassword.sites</string>
</array>
</dict>
</array>
@@ -39,19 +39,6 @@
<string>[auto]</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.lyndir.lhunath.MasterPassword</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.lyndir.lhunath.MasterPassword</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>[auto]</string>
<key>LSRequiresIPhoneOS</key>
@@ -109,10 +96,14 @@
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.utf8-plain-text</string>
</array>
<key>UTTypeDescription</key>
<string>Master Password sites</string>
<key>UTTypeIdentifier</key>
<string>com.lyndir.lhunath.MasterPassword.sites</string>
<string>com.lyndir.masterpassword.sites</string>
<key>UTTypeSize320IconFile</key>
<string></string>
<key>UTTypeSize64IconFile</key>

View File

@@ -1,17 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword</string>
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.shared</string>
</array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)com.lyndir.lhunath.MasterPassword.shared</string>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.lyndir.lhunath.MasterPassword</string>
</array>
</dict>
<dict/>
</plist>

View File

@@ -0,0 +1,25 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// NSString(MPMarkDown).h
// NSString(MPMarkDown)
//
// Created by lhunath on 2014-09-28.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSString(MPMarkDown)
- (NSAttributedString *)attributedMarkdownStringWithFontSize:(CGFloat)fontSize;
@end

View File

@@ -0,0 +1,56 @@
/**
* Copyright Maarten Billemont (http://www.lhunath.com, lhunath@lyndir.com)
*
* See the enclosed file LICENSE for license information (LGPLv3). If you did
* not receive this file, see http://www.gnu.org/licenses/lgpl-3.0.txt
*
* @author Maarten Billemont <lhunath@lyndir.com>
* @license http://www.gnu.org/licenses/lgpl-3.0.txt
*/
//
// NSString(MPMarkDown).h
// NSString(MPMarkDown)
//
// Created by lhunath on 2014-09-28.
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import "NSString+MPMarkDown.h"
#import "markdown_lib.h"
#import "markdown_peg.h"
@implementation NSString(MPMarkDown)
- (NSAttributedString *)attributedMarkdownStringWithFontSize:(CGFloat)fontSize {
NSMutableAttributedString *attributedString = markdown_to_attr_string( self, 0, @{
@(EMPH) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Bold" size:fontSize] },
@(STRONG) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-ExtraBold" size:fontSize] },
@(EMPH | STRONG) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-ExtraBold" size:fontSize] },
@(PLAIN) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Regular" size:fontSize] },
@(H1) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * 2.f] },
@(H2) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * 1.5f] },
@(H3) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * 1.17f] },
@(H4) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * 1.f] },
@(H5) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * .83f] },
@(H6) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * .75f] },
@(BLOCKQUOTE) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * 1.17f] },
@(CODE) : @{ NSFontAttributeName : [UIFont fontWithName:@"SourceCodePro-Regular" size:fontSize] },
@(VERBATIM) : @{ NSFontAttributeName : [UIFont fontWithName:@"SourceCodePro-Regular" size:fontSize] },
@(NOTE) : @{ NSFontAttributeName : [UIFont fontWithName:@"Exo2.0-Thin" size:fontSize * 1.17f] },
} );
// Trim trailing newlines.
NSCharacterSet *trimSet = [NSCharacterSet newlineCharacterSet];
while (YES) {
NSRange range = [attributedString.string rangeOfCharacterFromSet:trimSet options:NSBackwardsSearch];
if (!range.length || NSMaxRange( range ) != attributedString.length)
break;
[attributedString replaceCharactersInRange:range withString:@""];
}
return attributedString;
}
@end

Some files were not shown because too many files have changed in this diff Show More