Fixed queuing of password generation logic.
[IMPROVED] Removed password logic out of MPEntities so that it can be backgrounded without relying on the persistence layer staying up. [IMPROVED] Some workload removed from the main thread.
This commit is contained in:
parent
36386c3213
commit
f0dcc4c34c
@ -9,6 +9,7 @@
|
|||||||
<inspection_tool class="OCNotLocalizedStringInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="OCNotLocalizedStringInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="OCUnusedMacroInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="OCUnusedMacroInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="OCUnusedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="OCUnusedMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="UnavailableInDeploymentTarget" enabled="true" level="INFO" enabled_by_default="true" />
|
||||||
<inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="UnusedLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="UnusedParameter" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="UnusedParameter" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
</profile>
|
</profile>
|
||||||
|
2
External/UbiquityStoreManager
vendored
2
External/UbiquityStoreManager
vendored
@ -1 +1 @@
|
|||||||
Subproject commit a2ce82ea58d8a02237f33fdc4393259b09cb4967
|
Subproject commit 0202de6cf5b1f2847f4726af0788f6fce9683b32
|
@ -16,6 +16,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "MPKey.h"
|
#import "MPKey.h"
|
||||||
|
#import "MPElementStoredEntity.h"
|
||||||
#import "MPElementGeneratedEntity.h"
|
#import "MPElementGeneratedEntity.h"
|
||||||
|
|
||||||
#define MPAlgorithmDefaultVersion 1
|
#define MPAlgorithmDefaultVersion 1
|
||||||
@ -37,8 +38,19 @@
|
|||||||
- (NSString *)classNameOfType:(MPElementType)type;
|
- (NSString *)classNameOfType:(MPElementType)type;
|
||||||
- (Class)classOfType:(MPElementType)type;
|
- (Class)classOfType:(MPElementType)type;
|
||||||
|
|
||||||
- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key;
|
|
||||||
- (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key;
|
- (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key;
|
||||||
|
- (NSString *)storedContentForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key;
|
||||||
|
|
||||||
|
- (void)saveContent:(NSString *)clearContent toElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
||||||
|
- (NSString *)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
||||||
|
- (void)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey
|
||||||
|
result:(void (^)(NSString *result))resultBlock;
|
||||||
|
|
||||||
|
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
|
||||||
|
intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
||||||
|
- (void)importClearTextContent:(NSString *)clearContent intoElement:(MPElementEntity *)element
|
||||||
|
usingKey:(MPKey *)elementKey;
|
||||||
|
- (NSString *)exportContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -203,27 +203,6 @@
|
|||||||
Throw(@"Type not supported: %d", type);
|
Throw(@"Type not supported: %d", type);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)generateContentForElement:(MPElementGeneratedEntity *)element usingKey:(MPKey *)key {
|
|
||||||
|
|
||||||
if (!element)
|
|
||||||
return nil;
|
|
||||||
|
|
||||||
if (!(element.type & MPElementTypeClassGenerated)) {
|
|
||||||
err(@"Incorrect type (is not MPElementTypeClassGenerated): %@, for: %@", [self nameOfType:element.type], element.name);
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
if (!element.name.length) {
|
|
||||||
err(@"Missing name.");
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
if (!key.keyData.length) {
|
|
||||||
err(@"Missing key.");
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [self generateContentNamed:element.name ofType:element.type withCounter:element.counter usingKey:key];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key {
|
- (NSString *)generateContentNamed:(NSString *)name ofType:(MPElementType)type withCounter:(NSUInteger)counter usingKey:(MPKey *)key {
|
||||||
|
|
||||||
static NSDictionary *MPTypes_ciphers = nil;
|
static NSDictionary *MPTypes_ciphers = nil;
|
||||||
@ -247,14 +226,14 @@
|
|||||||
const char *seedBytes = seed.bytes;
|
const char *seedBytes = seed.bytes;
|
||||||
|
|
||||||
// Determine the cipher from the first seed byte.
|
// Determine the cipher from the first seed byte.
|
||||||
assert([seed length]);
|
NSAssert([seed length], @"Missing seed.");
|
||||||
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:type]]
|
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:type]]
|
||||||
valueForKey:[self nameOfType:type]];
|
valueForKey:[self nameOfType:type]];
|
||||||
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
|
NSString *cipher = [typeCiphers objectAtIndex:htons(seedBytes[0]) % [typeCiphers count]];
|
||||||
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher);
|
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher);
|
||||||
|
|
||||||
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||||
assert([seed length] >= [cipher length] + 1);
|
NSAssert([seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher.");
|
||||||
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||||
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||||
uint16_t keyByte = htons(seedBytes[c + 1]);
|
uint16_t keyByte = htons(seedBytes[c + 1]);
|
||||||
@ -270,4 +249,240 @@
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSString *)storedContentForElement:(MPElementStoredEntity *)element usingKey:(MPKey *)key {
|
||||||
|
|
||||||
|
return [self decryptContent:element.contentObject usingKey:key];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)saveContent:(NSString *)clearContent toElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
||||||
|
|
||||||
|
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: {
|
||||||
|
NSAssert(NO, @"Cannot save content to element with generated type %d.", element.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MPElementTypeStoredPersonal: {
|
||||||
|
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||||
|
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
|
||||||
|
|
||||||
|
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
|
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||||
|
((MPElementStoredEntity *)element).contentObject = encryptedContent;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MPElementTypeStoredDevicePrivate: {
|
||||||
|
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||||
|
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
|
||||||
|
|
||||||
|
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
|
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||||
|
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
|
||||||
|
if (!encryptedContent)
|
||||||
|
[PearlKeyChain deleteItemForQuery:elementQuery];
|
||||||
|
else
|
||||||
|
[PearlKeyChain addOrUpdateItemForQuery:elementQuery withAttributes:@{
|
||||||
|
(__bridge id)kSecValueData : encryptedContent,
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
|
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
||||||
|
#endif
|
||||||
|
}];
|
||||||
|
((MPElementStoredEntity *)element).contentObject = nil;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
||||||
|
|
||||||
|
dispatch_group_t group = dispatch_group_create();
|
||||||
|
dispatch_group_enter( group );
|
||||||
|
__block NSString *result = nil;
|
||||||
|
[self resolveContentForElement:element usingKey:elementKey result:^(NSString *result_) {
|
||||||
|
result = result_;
|
||||||
|
dispatch_group_leave( group );
|
||||||
|
}];
|
||||||
|
dispatch_group_wait( group, DISPATCH_TIME_FOREVER );
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)resolveContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey 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: {
|
||||||
|
NSAssert([element isKindOfClass:[MPElementGeneratedEntity class]],
|
||||||
|
@"Element with generated type %d is not an MPElementGeneratedEntity, but a %@.", element.type, [element class]);
|
||||||
|
|
||||||
|
NSString *name = element.name;
|
||||||
|
MPElementType type = element.type;
|
||||||
|
NSUInteger counter = ((MPElementGeneratedEntity *)element).counter;
|
||||||
|
id<MPAlgorithm> algorithm = nil;
|
||||||
|
if (!element.name.length)
|
||||||
|
err(@"Missing name.");
|
||||||
|
else if (!elementKey.keyData.length)
|
||||||
|
err(@"Missing key.");
|
||||||
|
else
|
||||||
|
algorithm = element.algorithm;
|
||||||
|
|
||||||
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||||
|
NSString *result = [algorithm generateContentNamed:name ofType:type withCounter:counter usingKey:elementKey];
|
||||||
|
resultBlock( result );
|
||||||
|
} );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MPElementTypeStoredPersonal: {
|
||||||
|
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||||
|
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
|
||||||
|
|
||||||
|
NSData *encryptedContent = ((MPElementStoredEntity *)element).contentObject;
|
||||||
|
|
||||||
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||||
|
NSString *result = [self decryptContent:encryptedContent usingKey:elementKey];
|
||||||
|
resultBlock( result );
|
||||||
|
} );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MPElementTypeStoredDevicePrivate: {
|
||||||
|
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||||
|
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
|
||||||
|
|
||||||
|
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
|
||||||
|
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:elementQuery];
|
||||||
|
|
||||||
|
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
|
||||||
|
NSString *result = [self decryptContent:encryptedContent usingKey:elementKey];
|
||||||
|
resultBlock( result );
|
||||||
|
} );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)importKey
|
||||||
|
intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
||||||
|
|
||||||
|
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:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MPElementTypeStoredPersonal: {
|
||||||
|
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||||
|
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
|
||||||
|
if ([importKey.keyID isEqualToData:elementKey.keyID])
|
||||||
|
((MPElementStoredEntity *)element).contentObject = [protectedContent decodeBase64];
|
||||||
|
|
||||||
|
else {
|
||||||
|
NSString *clearContent = [self decryptContent:[protectedContent decodeBase64] usingKey:importKey];
|
||||||
|
[self importClearTextContent:clearContent intoElement:element usingKey:elementKey];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MPElementTypeStoredDevicePrivate:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)importClearTextContent:(NSString *)clearContent intoElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
||||||
|
|
||||||
|
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:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MPElementTypeStoredPersonal: {
|
||||||
|
[self saveContent:clearContent toElement:element usingKey:elementKey];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MPElementTypeStoredDevicePrivate:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)exportContentForElement:(MPElementEntity *)element usingKey:(MPKey *)elementKey {
|
||||||
|
|
||||||
|
NSAssert([elementKey.keyID isEqualToData:element.user.keyID], @"Element does not belong to current user.");
|
||||||
|
if (!(element.type & MPElementFeatureExportContent))
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
NSString *result = nil;
|
||||||
|
switch (element.type) {
|
||||||
|
case MPElementTypeGeneratedMaximum:
|
||||||
|
case MPElementTypeGeneratedLong:
|
||||||
|
case MPElementTypeGeneratedMedium:
|
||||||
|
case MPElementTypeGeneratedBasic:
|
||||||
|
case MPElementTypeGeneratedShort:
|
||||||
|
case MPElementTypeGeneratedPIN: {
|
||||||
|
result = nil;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MPElementTypeStoredPersonal: {
|
||||||
|
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
|
||||||
|
@"Element with stored type %d is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
|
||||||
|
result = [((MPElementStoredEntity *)element).contentObject encodeBase64];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MPElementTypeStoredDevicePrivate: {
|
||||||
|
result = nil;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)migrateExplicitly:(BOOL)explicit {
|
||||||
|
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
|
||||||
|
|
||||||
|
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
||||||
|
attributes:@{
|
||||||
|
(__bridge id)kSecAttrService : @"DevicePrivate",
|
||||||
|
(__bridge id)kSecAttrAccount : name
|
||||||
|
}
|
||||||
|
matches:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)decryptContent:(NSData *)encryptedContent usingKey:(MPKey *)key {
|
||||||
|
|
||||||
|
NSData *decryptedContent = nil;
|
||||||
|
if ([encryptedContent length])
|
||||||
|
decryptedContent = [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
||||||
|
if (!decryptedContent)
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -68,13 +68,13 @@
|
|||||||
const unsigned char *seedBytes = seed.bytes;
|
const unsigned char *seedBytes = seed.bytes;
|
||||||
|
|
||||||
// Determine the cipher from the first seed byte.
|
// Determine the cipher from the first seed byte.
|
||||||
assert([seed length]);
|
NSAssert([seed length], @"Missing seed.");
|
||||||
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:type]] valueForKey:[self nameOfType:type]];
|
NSArray *typeCiphers = [[MPTypes_ciphers valueForKey:[self classNameOfType:type]] valueForKey:[self nameOfType:type]];
|
||||||
NSString *cipher = [typeCiphers objectAtIndex:seedBytes[0] % [typeCiphers count]];
|
NSString *cipher = [typeCiphers objectAtIndex:seedBytes[0] % [typeCiphers count]];
|
||||||
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher);
|
trc(@"type %@, ciphers: %@, selected: %@", [self nameOfType:type], typeCiphers, cipher);
|
||||||
|
|
||||||
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
// Encode the content, character by character, using subsequent seed bytes and the cipher.
|
||||||
assert([seed length] >= [cipher length] + 1);
|
NSAssert([seed length] >= [cipher length] + 1, @"Insufficient seed bytes to encode cipher.");
|
||||||
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
NSMutableString *content = [NSMutableString stringWithCapacity:[cipher length]];
|
||||||
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
for (NSUInteger c = 0; c < [cipher length]; ++c) {
|
||||||
uint16_t keyByte = seedBytes[c + 1];
|
uint16_t keyByte = seedBytes[c + 1];
|
||||||
|
@ -171,8 +171,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
|||||||
|
|
||||||
for (MPElementEntity *element in user.elements) {
|
for (MPElementEntity *element in user.elements) {
|
||||||
if (element.type & MPElementTypeClassStored) {
|
if (element.type & MPElementTypeClassStored) {
|
||||||
id content = nil;
|
NSString *content;
|
||||||
while (!(content = [element contentUsingKey:recoverKey])) {
|
while (!(content = [element.algorithm storedContentForElement:(MPElementStoredEntity *)element usingKey:recoverKey])) {
|
||||||
// Failed to decrypt element with the current recoveryKey. Ask user for a new one to use.
|
// Failed to decrypt element with the current recoveryKey. Ask user for a new one to use.
|
||||||
__block NSString *masterPassword = nil;
|
__block NSString *masterPassword = nil;
|
||||||
|
|
||||||
@ -209,7 +209,7 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
if (![recoverKey isEqualToKey:newKey])
|
if (![recoverKey isEqualToKey:newKey])
|
||||||
[element setContent:content usingKey:newKey];
|
[element.algorithm saveContent:content toElement:element usingKey:newKey];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,7 +336,9 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
|||||||
|
|
||||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager willLoadStoreIsCloud:(BOOL)isCloudStore {
|
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager willLoadStoreIsCloud:(BOOL)isCloudStore {
|
||||||
|
|
||||||
|
// [manager setValue:@"C61DCF27-BD25-4CF1-AE8F-8B613DF8AB47" forKey:@"storeUUID"];
|
||||||
NSManagedObjectContext *moc = [self mainManagedObjectContextIfReady];
|
NSManagedObjectContext *moc = [self mainManagedObjectContextIfReady];
|
||||||
|
NSLog( @"willLoadStoreIsCloud:%d mainMoc:%@", isCloudStore, moc);
|
||||||
[moc performBlockAndWait:^{
|
[moc performBlockAndWait:^{
|
||||||
[moc saveToStore];
|
[moc saveToStore];
|
||||||
|
|
||||||
@ -350,6 +352,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
|||||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didLoadStoreForCoordinator:(NSPersistentStoreCoordinator *)coordinator
|
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didLoadStoreForCoordinator:(NSPersistentStoreCoordinator *)coordinator
|
||||||
isCloud:(BOOL)isCloudStore {
|
isCloud:(BOOL)isCloudStore {
|
||||||
|
|
||||||
|
NSLog( @"didLoadStoreForCoordinatorStores:%d isCloud:%d", [[coordinator persistentStores] count], isCloudStore );
|
||||||
inf(@"Using iCloud? %@", @(isCloudStore));
|
inf(@"Using iCloud? %@", @(isCloudStore));
|
||||||
MPCheckpoint( MPCheckpointCloud, @{
|
MPCheckpoint( MPCheckpointCloud, @{
|
||||||
@"enabled" : @(isCloudStore)
|
@"enabled" : @(isCloudStore)
|
||||||
@ -382,16 +385,17 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
|||||||
|
|
||||||
self.privateManagedObjectContext = privateManagedObjectContext;
|
self.privateManagedObjectContext = privateManagedObjectContext;
|
||||||
self.mainManagedObjectContext = mainManagedObjectContext;
|
self.mainManagedObjectContext = mainManagedObjectContext;
|
||||||
|
NSLog( @"set mainMoc:%@", mainManagedObjectContext );
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreErrorCause)cause
|
- (void)ubiquityStoreManager:(UbiquityStoreManager *)manager didEncounterError:(NSError *)error cause:(UbiquityStoreErrorCause)cause
|
||||||
context:(id)context {
|
context:(id)context {
|
||||||
|
|
||||||
err(@"[StoreManager] ERROR: cause=%d, context=%@, error=%@", cause, context, error);
|
err(@"[StoreManager] ERROR: cause=%@, context=%@, error=%@", NSStringFromUSMCause( cause ), context, error);
|
||||||
MPCheckpoint( MPCheckpointMPErrorUbiquity, @{
|
MPCheckpoint( MPCheckpointMPErrorUbiquity, @{
|
||||||
@"cause" : @(cause),
|
@"cause" : @(cause),
|
||||||
@"error.domain" : NilToNSNull(error.domain),
|
@"error.code" : @(error.code),
|
||||||
@"error.code" : @(error.code)
|
@"error.domain" : NilToNSNull(error.domain)
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,7 +410,9 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
|||||||
|
|
||||||
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
|
||||||
MPUserEntity *activeUser = [self activeUserInContext:context];
|
MPUserEntity *activeUser = [self activeUserInContext:context];
|
||||||
assert(activeUser);
|
NSAssert(activeUser, @"Missing user.");
|
||||||
|
if (!activeUser)
|
||||||
|
return;
|
||||||
|
|
||||||
MPElementType type = activeUser.defaultType;
|
MPElementType type = activeUser.defaultType;
|
||||||
if (!type)
|
if (!type)
|
||||||
@ -673,14 +679,14 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
|||||||
element.version = version;
|
element.version = version;
|
||||||
if ([exportContent length]) {
|
if ([exportContent length]) {
|
||||||
if (clearText)
|
if (clearText)
|
||||||
[element importClearTextContent:exportContent usingKey:userKey];
|
[element.algorithm importClearTextContent:exportContent intoElement:element usingKey:userKey];
|
||||||
else {
|
else {
|
||||||
if (!importKey)
|
if (!importKey)
|
||||||
importKey = [importAlgorithm keyForPassword:importPassword( user.name ) ofUserNamed:user.name];
|
importKey = [importAlgorithm keyForPassword:importPassword( user.name ) ofUserNamed:user.name];
|
||||||
if (![importKey.keyID isEqualToData:importKeyID])
|
if (![importKey.keyID isEqualToData:importKeyID])
|
||||||
return MPImportResultInvalidPassword;
|
return MPImportResultInvalidPassword;
|
||||||
|
|
||||||
[element importProtectedContent:exportContent protectedByKey:importKey usingKey:userKey];
|
[element.algorithm importProtectedContent:exportContent protectedByKey:importKey intoElement:element usingKey:userKey];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -735,9 +741,9 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
|
|||||||
// Determine the content to export.
|
// Determine the content to export.
|
||||||
if (!(type & MPElementFeatureDevicePrivate)) {
|
if (!(type & MPElementFeatureDevicePrivate)) {
|
||||||
if (showPasswords)
|
if (showPasswords)
|
||||||
content = element.content;
|
content = [element.algorithm resolveContentForElement:element usingKey:self.key];
|
||||||
else if (type & MPElementFeatureExportContent)
|
else if (type & MPElementFeatureExportContent)
|
||||||
content = element.exportContent;
|
content = [element.algorithm exportContentForElement:element usingKey:self.key];
|
||||||
}
|
}
|
||||||
|
|
||||||
[export appendFormat:@"%@ %8ld %8s %20s\t%@\n",
|
[export appendFormat:@"%@ %8ld %8s %20s\t%@\n",
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
|
|
||||||
@interface MPElementEntity : NSManagedObject
|
@interface MPElementEntity : NSManagedObject
|
||||||
|
|
||||||
@property(nonatomic, retain) id content;
|
|
||||||
@property(nonatomic, retain) NSDate *lastUsed;
|
@property(nonatomic, retain) NSDate *lastUsed;
|
||||||
@property(nonatomic, retain) NSString *loginName;
|
@property(nonatomic, retain) NSString *loginName;
|
||||||
@property(nonatomic, retain) NSString *name;
|
@property(nonatomic, retain) NSString *name;
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
@implementation MPElementEntity
|
@implementation MPElementEntity
|
||||||
|
|
||||||
@dynamic content;
|
|
||||||
@dynamic lastUsed;
|
@dynamic lastUsed;
|
||||||
@dynamic loginName;
|
@dynamic loginName;
|
||||||
@dynamic name;
|
@dynamic name;
|
||||||
|
@ -33,13 +33,7 @@
|
|||||||
@property(assign) BOOL requiresExplicitMigration;
|
@property(assign) BOOL requiresExplicitMigration;
|
||||||
@property(readonly) id<MPAlgorithm> algorithm;
|
@property(readonly) id<MPAlgorithm> algorithm;
|
||||||
|
|
||||||
- (id)contentUsingKey:(MPKey *)key;
|
|
||||||
- (void)setContent:(id)content usingKey:(MPKey *)key;
|
|
||||||
|
|
||||||
- (NSUInteger)use;
|
- (NSUInteger)use;
|
||||||
- (NSString *)exportContent;
|
|
||||||
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)contentProtectionKey usingKey:(MPKey *)key2;
|
|
||||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key;
|
|
||||||
- (BOOL)migrateExplicitly:(BOOL)explicit;
|
- (BOOL)migrateExplicitly:(BOOL)explicit;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -13,14 +13,21 @@
|
|||||||
|
|
||||||
- (BOOL)saveToStore {
|
- (BOOL)saveToStore {
|
||||||
|
|
||||||
__block BOOL success = NO;
|
__block BOOL success = YES;
|
||||||
|
if ([self hasChanges])
|
||||||
[self performBlockAndWait:^{
|
[self performBlockAndWait:^{
|
||||||
|
@try {
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
if (!(success = [self save:&error]))
|
if (!(success = [self save:&error]))
|
||||||
err(@"While saving: %@", error);
|
err(@"While saving: %@", error);
|
||||||
|
}
|
||||||
|
@catch (NSException *exception) {
|
||||||
|
success = NO;
|
||||||
|
err(@"While saving: %@", exception);
|
||||||
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
return !self.parentContext || [self.parentContext saveToStore];
|
return success && (!self.parentContext || [self.parentContext saveToStore]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@ -111,47 +118,6 @@
|
|||||||
return ++self.uses;
|
return ++self.uses;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)content {
|
|
||||||
|
|
||||||
MPKey *key = [MPAppDelegate_Shared get].key;
|
|
||||||
if (!key)
|
|
||||||
return nil;
|
|
||||||
|
|
||||||
assert([key.keyID isEqualToData:self.user.keyID]);
|
|
||||||
return [self contentUsingKey:key];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setContent:(id)content {
|
|
||||||
|
|
||||||
MPKey *key = [MPAppDelegate_Shared get].key;
|
|
||||||
if (!key)
|
|
||||||
return;
|
|
||||||
|
|
||||||
assert([key.keyID isEqualToData:self.user.keyID]);
|
|
||||||
[self setContent:content usingKey:key];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (id)contentUsingKey:(MPKey *)key {
|
|
||||||
|
|
||||||
Throw(@"Content retrieval implementation missing for: %@", [self class]);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setContent:(id)content usingKey:(MPKey *)key {
|
|
||||||
|
|
||||||
Throw(@"Content assignment implementation missing for: %@", [self class]);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)exportContent {
|
|
||||||
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)contentProtectionKey usingKey:(MPKey *)key {
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key {
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)description {
|
- (NSString *)description {
|
||||||
|
|
||||||
return PearlString( @"%@:%@", [self class], [self name] );
|
return PearlString( @"%@:%@", [self class], [self name] );
|
||||||
@ -191,107 +157,10 @@
|
|||||||
self.counter_ = @(aCounter);
|
self.counter_ = @(aCounter);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)contentUsingKey:(MPKey *)key {
|
|
||||||
|
|
||||||
assert(self.type & MPElementTypeClassGenerated);
|
|
||||||
|
|
||||||
if (![self.name length])
|
|
||||||
return nil;
|
|
||||||
if (!key)
|
|
||||||
return nil;
|
|
||||||
|
|
||||||
return [self.algorithm generateContentForElement:self usingKey:key];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MPElementStoredEntity(MP)
|
@implementation MPElementStoredEntity(MP)
|
||||||
|
|
||||||
+ (NSDictionary *)queryForDevicePrivateElementNamed:(NSString *)name {
|
|
||||||
|
|
||||||
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
|
|
||||||
attributes:@{
|
|
||||||
(__bridge id)kSecAttrService : @"DevicePrivate",
|
|
||||||
(__bridge id)kSecAttrAccount : name
|
|
||||||
}
|
|
||||||
matches:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (id)contentUsingKey:(MPKey *)key {
|
|
||||||
|
|
||||||
assert(self.type & MPElementTypeClassStored);
|
|
||||||
|
|
||||||
if (!key)
|
|
||||||
return nil;
|
|
||||||
|
|
||||||
NSData *encryptedContent;
|
|
||||||
if (self.type & MPElementFeatureDevicePrivate)
|
|
||||||
encryptedContent = [PearlKeyChain dataOfItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]];
|
|
||||||
else
|
|
||||||
encryptedContent = self.contentObject;
|
|
||||||
|
|
||||||
NSData *decryptedContent = nil;
|
|
||||||
if ([encryptedContent length])
|
|
||||||
decryptedContent = [self decryptContent:encryptedContent usingKey:key];
|
|
||||||
|
|
||||||
if (!decryptedContent)
|
|
||||||
return nil;
|
|
||||||
|
|
||||||
return [[NSString alloc] initWithBytes:decryptedContent.bytes length:decryptedContent.length encoding:NSUTF8StringEncoding];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSData *)decryptContent:(NSData *)encryptedContent usingKey:(MPKey *)key {
|
|
||||||
|
|
||||||
return [encryptedContent decryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setContent:(id)content usingKey:(MPKey *)key {
|
|
||||||
|
|
||||||
assert(self.type & MPElementTypeClassStored);
|
|
||||||
assert([key.keyID isEqualToData:self.user.keyID]);
|
|
||||||
|
|
||||||
NSData *encryptedContent = [[[content description] dataUsingEncoding:NSUTF8StringEncoding]
|
|
||||||
encryptWithSymmetricKey:[key subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
|
|
||||||
|
|
||||||
if (self.type & MPElementFeatureDevicePrivate) {
|
|
||||||
[PearlKeyChain addOrUpdateItemForQuery:[MPElementStoredEntity queryForDevicePrivateElementNamed:self.name]
|
|
||||||
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
|
|
||||||
encryptedContent, (__bridge id)kSecValueData,
|
|
||||||
#if TARGET_OS_IPHONE
|
|
||||||
(__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
||||||
(__bridge id)kSecAttrAccessible,
|
|
||||||
#endif
|
|
||||||
nil]];
|
|
||||||
self.contentObject = nil;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
self.contentObject = encryptedContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)exportContent {
|
|
||||||
|
|
||||||
return [self.contentObject encodeBase64];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)importProtectedContent:(NSString *)protectedContent protectedByKey:(MPKey *)contentProtectionKey usingKey:(MPKey *)key {
|
|
||||||
|
|
||||||
if ([contentProtectionKey.keyID isEqualToData:key.keyID])
|
|
||||||
self.contentObject = [protectedContent decodeBase64];
|
|
||||||
|
|
||||||
else {
|
|
||||||
NSString *clearContent = [[NSString alloc] initWithData:[self decryptContent:[protectedContent decodeBase64]
|
|
||||||
usingKey:contentProtectionKey]
|
|
||||||
encoding:NSUTF8StringEncoding];
|
|
||||||
|
|
||||||
[self importClearTextContent:clearContent usingKey:key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)importClearTextContent:(NSString *)clearContent usingKey:(MPKey *)key {
|
|
||||||
|
|
||||||
[self setContent:clearContent usingKey:key];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MPUserEntity(MP)
|
@implementation MPUserEntity(MP)
|
||||||
|
@ -99,11 +99,12 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MPKey *key = [MPAppDelegate_Shared get].key;
|
||||||
NSMutableDictionary *elementChanges = [NSMutableDictionary dictionaryWithCapacity:[elements count]];
|
NSMutableDictionary *elementChanges = [NSMutableDictionary dictionaryWithCapacity:[elements count]];
|
||||||
for (MPElementEntity *element in elements) {
|
for (MPElementEntity *element in elements) {
|
||||||
id oldContent = [element content];
|
id oldContent = [element.algorithm resolveContentForElement:element usingKey:key];
|
||||||
[element migrateExplicitly:YES];
|
[element migrateExplicitly:YES];
|
||||||
id newContent = [element content];
|
id newContent = [element.algorithm resolveContentForElement:element usingKey:key];
|
||||||
|
|
||||||
if (!(element.type & MPElementFeatureDevicePrivate) && (!oldContent || ![oldContent isEqual:newContent]))
|
if (!(element.type & MPElementFeatureDevicePrivate) && (!oldContent || ![oldContent isEqual:newContent]))
|
||||||
[elementChanges setObject:@{
|
[elementChanges setObject:@{
|
||||||
|
@ -85,7 +85,8 @@
|
|||||||
queue:[NSOperationQueue mainQueue] usingBlock:
|
queue:[NSOperationQueue mainQueue] usingBlock:
|
||||||
^(NSNotification *note) {
|
^(NSNotification *note) {
|
||||||
MPElementEntity *activeElement = [self activeElementForMainThread];
|
MPElementEntity *activeElement = [self activeElementForMainThread];
|
||||||
if (activeElement.type & MPElementTypeClassStored && ![[activeElement.content description] length])
|
if (activeElement.type & MPElementTypeClassStored &&
|
||||||
|
![[activeElement.algorithm resolveContentForElement:activeElement usingKey:[MPAppDelegate_Shared get].key] length])
|
||||||
[self showToolTip:@"Tap to set a password." withIcon:self.toolTipEditIcon];
|
[self showToolTip:@"Tap to set a password." withIcon:self.toolTipEditIcon];
|
||||||
if (activeElement.requiresExplicitMigration)
|
if (activeElement.requiresExplicitMigration)
|
||||||
[self showToolTip:@"Password outdated. Tap to upgrade it." withIcon:nil];
|
[self showToolTip:@"Password outdated. Tap to upgrade it." withIcon:nil];
|
||||||
@ -246,13 +247,11 @@
|
|||||||
self.contentField.enabled = NO;
|
self.contentField.enabled = NO;
|
||||||
self.contentField.text = @"";
|
self.contentField.text = @"";
|
||||||
if (activeElement.name && ![activeElement isDeleted])
|
if (activeElement.name && ![activeElement isDeleted])
|
||||||
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ), ^{
|
[activeElement.algorithm resolveContentForElement:activeElement usingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
|
||||||
NSString *description = [activeElement.content description];
|
|
||||||
|
|
||||||
dispatch_async( dispatch_get_main_queue(), ^{
|
dispatch_async( dispatch_get_main_queue(), ^{
|
||||||
self.contentField.text = description;
|
self.contentField.text = result;
|
||||||
} );
|
|
||||||
} );
|
} );
|
||||||
|
}];
|
||||||
|
|
||||||
self.loginNameField.enabled = NO;
|
self.loginNameField.enabled = NO;
|
||||||
self.loginNameField.text = activeElement.loginName;
|
self.loginNameField.text = activeElement.loginName;
|
||||||
@ -474,21 +473,21 @@
|
|||||||
- (IBAction)copyContent {
|
- (IBAction)copyContent {
|
||||||
|
|
||||||
MPElementEntity *activeElement = [self activeElementForMainThread];
|
MPElementEntity *activeElement = [self activeElementForMainThread];
|
||||||
id content = activeElement.content;
|
|
||||||
if (!content)
|
|
||||||
// Nothing to copy.
|
|
||||||
return;
|
|
||||||
|
|
||||||
inf(@"Copying password for: %@", activeElement.name);
|
inf(@"Copying password for: %@", activeElement.name);
|
||||||
[UIPasteboard generalPasteboard].string = [content description];
|
|
||||||
|
|
||||||
[self showContentTip:@"Copied!" withIcon:nil];
|
|
||||||
|
|
||||||
MPCheckpoint( MPCheckpointCopyToPasteboard, @{
|
MPCheckpoint( MPCheckpointCopyToPasteboard, @{
|
||||||
@"type" : NilToNSNull(activeElement.typeName),
|
@"type" : NilToNSNull(activeElement.typeName),
|
||||||
@"version" : @(activeElement.version),
|
@"version" : @(activeElement.version),
|
||||||
@"emergency" : @NO
|
@"emergency" : @NO
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
[activeElement.algorithm resolveContentForElement:activeElement usingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {
|
||||||
|
if (!result)
|
||||||
|
// Nothing to copy.
|
||||||
|
return;
|
||||||
|
|
||||||
|
[UIPasteboard generalPasteboard].string = result;
|
||||||
|
[self showContentTip:@"Copied!" withIcon:nil];
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)copyLoginName:(UITapGestureRecognizer *)sender {
|
- (IBAction)copyLoginName:(UITapGestureRecognizer *)sender {
|
||||||
@ -604,12 +603,13 @@
|
|||||||
if (!activeElement)
|
if (!activeElement)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
NSString *oldPassword = [activeElement.content description];
|
MPKey *key = [MPAppDelegate_Shared get].key;
|
||||||
|
NSString *oldPassword = [activeElement.algorithm resolveContentForElement:activeElement usingKey:key];
|
||||||
if (!task( activeElement, context ))
|
if (!task( activeElement, context ))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
activeElement = [self activeElementInContext:context];
|
activeElement = [self activeElementInContext:context];
|
||||||
NSString *newPassword = [activeElement.content description];
|
NSString *newPassword = [activeElement.algorithm resolveContentForElement:activeElement usingKey:key];
|
||||||
|
|
||||||
// Save.
|
// Save.
|
||||||
[context saveToStore];
|
[context saveToStore];
|
||||||
@ -865,17 +865,18 @@
|
|||||||
if (textField == self.contentField) {
|
if (textField == self.contentField) {
|
||||||
self.contentField.enabled = NO;
|
self.contentField.enabled = NO;
|
||||||
MPElementEntity *activeElement = [self activeElementForMainThread];
|
MPElementEntity *activeElement = [self activeElementForMainThread];
|
||||||
|
MPKey *key = [MPAppDelegate_Shared get].key;
|
||||||
if (![activeElement isKindOfClass:[MPElementStoredEntity class]]) {
|
if (![activeElement isKindOfClass:[MPElementStoredEntity class]]) {
|
||||||
// Not of a type whose content can be edited.
|
// Not of a type whose content can be edited.
|
||||||
err(@"Cannot update element content: Element is not stored: %@", activeElement.name);
|
err(@"Cannot update element content: Element is not stored: %@", activeElement.name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if ([((MPElementStoredEntity *)activeElement).content isEqual:self.contentField.text])
|
else if ([[activeElement.algorithm resolveContentForElement:activeElement usingKey:key] isEqual:self.contentField.text])
|
||||||
// Content hasn't changed.
|
// Content hasn't changed.
|
||||||
return;
|
return;
|
||||||
|
|
||||||
[self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement_, NSManagedObjectContext *context) {
|
[self changeActiveElementWithoutWarningDo:^BOOL(MPElementEntity *activeElement_, NSManagedObjectContext *context) {
|
||||||
((MPElementStoredEntity *)activeElement_).content = self.contentField.text;
|
[activeElement_.algorithm saveContent:self.contentField.text toElement:activeElement_ usingKey:key];
|
||||||
return YES;
|
return YES;
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@
|
|||||||
|
|
||||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
|
|
||||||
assert(self.navigationController.topViewController == self);
|
NSAssert(self.navigationController.topViewController == self, @"Not the currently active navigation item.");
|
||||||
|
|
||||||
MPElementType type = [self typeAtIndexPath:indexPath];
|
MPElementType type = [self typeAtIndexPath:indexPath];
|
||||||
if (type == (MPElementType)NSNotFound)
|
if (type == (MPElementType)NSNotFound)
|
||||||
|
Loading…
Reference in New Issue
Block a user