2
0

Improved signin/signout state logic.

This commit is contained in:
Maarten Billemont
2012-06-08 00:40:30 +02:00
parent f796888901
commit 09d5e64c55
16 changed files with 259 additions and 181 deletions

View File

@@ -10,13 +10,11 @@
@interface MPAppDelegate_Shared (Key)
- (void)loadSavedKey;
- (IBAction)signOut:(id)sender;
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password;
- (void)signOut;
- (BOOL)tryMasterPassword:(NSString *)tryPassword forUser:(MPUserEntity *)user;
- (void)storeSavedKey;
- (void)forgetSavedKey;
- (void)unsetKey;
- (void)storeSavedKeyFor:(MPUserEntity *)user;
- (void)forgetSavedKeyFor:(MPUserEntity *)user;
- (NSData *)keyWithLength:(NSUInteger)keyLength;

View File

@@ -12,7 +12,7 @@
@implementation MPAppDelegate_Shared (Key)
static NSDictionary *keyQuery(MPUserEntity *user) {
return [PearlKeyChain createQueryForClass:kSecClassGenericPassword
attributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"Saved Master Password", (__bridge id)kSecAttrService,
@@ -21,113 +21,131 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
matches:nil];
}
- (void)forgetSavedKey {
if ([PearlKeyChain deleteItemForQuery:keyQuery(self.activeUser)] != errSecItemNotFound) {
inf(@"Removed key from keychain.");
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPForgotten];
#endif
- (NSData *)loadSavedKeyFor:(MPUserEntity *)user {
NSData *key = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
if (key)
inf(@"Found key (for: %@) in keychain.", user.name);
else {
user.saveKey = NO;
inf(@"No key found (for: %@) in keychain.", user.name);
}
return key;
}
- (IBAction)signOut:(id)sender {
- (void)storeSavedKeyFor:(MPUserEntity *)user {
[self forgetSavedKey];
[self unsetKey];
}
if (user.saveKey) {
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(user)];
- (void)loadSavedKey {
if (self.activeUser.saveKey) {
// Key should be saved in keychain. Load it.
self.key = [PearlKeyChain dataOfItemForQuery:keyQuery(self.activeUser)];
inf(@"Looking for key in keychain: %@.", self.key? @"found": @"missing");
if (self.key)
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeySet object:self];
} else {
// Key should not be stored in keychain. Delete it.
if ([PearlKeyChain deleteItemForQuery:keyQuery(self.activeUser)] != errSecItemNotFound)
inf(@"Removed key from keychain.");
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPUnstored];
if (![existingKey isEqualToData:self.key]) {
inf(@"Updating key in keychain.");
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(user)
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.key, (__bridge id) kSecValueData,
#if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id) kSecAttrAccessible,
#endif
}
}
- (BOOL)tryMasterPassword:(NSString *)tryPassword forUser:(MPUserEntity *)user {
if (![tryPassword length])
return NO;
NSData *tryKey = keyForPassword(tryPassword, user.name);
NSData *tryKeyID = keyIDForKey(tryKey);
inf(@"Key ID was known? %@.", user.keyID? @"YES": @"NO");
if (user.keyID) {
// A key ID is known -> a master password is set.
// Make sure the user's entered master password matches it.
if (![user.keyID isEqual:tryKeyID]) {
wrn(@"Key ID mismatch. Expected: %@, answer: %@.", [user.keyID encodeHex], [tryKeyID encodeHex]);
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
#endif
return NO;
nil]];
}
} else {
// A key ID is not known -> recording a new master password.
user.keyID = tryKeyID;
[[MPAppDelegate_Shared get] saveContext];
}
user.lastUsed = [NSDate date];
}
- (void)forgetSavedKeyFor:(MPUserEntity *)user {
OSStatus result = [PearlKeyChain deleteItemForQuery:keyQuery(user)];
if (result == noErr || result == errSecItemNotFound) {
user.saveKey = NO;
if (result == noErr) {
inf(@"Removed key (for: %@) from keychain.", user.name);
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyForgotten object:self];
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointForgetSavedKey];
#endif
}
}
}
- (void)signOut {
self.key = nil;
self.activeUser = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedOut object:self];
}
- (BOOL)signInAsUser:(MPUserEntity *)user usingMasterPassword:(NSString *)password {
NSData *tryKey = nil;
// Method 1: When the user has no keyID set, set a new key from the given master password.
if (!user.keyID) {
if ([password length])
if ((tryKey = keyForPassword(password, user.name))) {
user.keyID = keyIDForKey(tryKey);
[[MPAppDelegate_Shared get] saveContext];
}
}
// Method 2: Depending on the user's saveKey, load or remove the key from the keychain.
if (!user.saveKey)
// Key should not be stored in keychain. Delete it.
[self forgetSavedKeyFor:user];
else if (!tryKey) {
// Key should be saved in keychain. Load it.
if ((tryKey = [self loadSavedKeyFor:user]))
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
// Loaded password doesn't match user's keyID. Forget saved password: it is incorrect.
tryKey = nil;
[self forgetSavedKeyFor:user];
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPEntered];
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
#endif
if (self.key != tryKey) {
self.key = tryKey;
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeySet object:self];
}
}
// Method 3: Check the given master password string.
if (!tryKey) {
if ([password length])
if ((tryKey = keyForPassword(password, user.name)))
if (![user.keyID isEqual:keyIDForKey(tryKey)]) {
tryKey = nil;
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointMPMismatch];
#endif
}
}
// No more methods left, fail if key still not known.
if (!tryKey)
return NO;
if (![self.key isEqualToData:tryKey]) {
self.key = tryKey;
[self storeSavedKeyFor:user];
}
user.lastUsed = [NSDate date];
self.activeUser = user;
[[MPAppDelegate_Shared get] saveContext];
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationSignedIn object:self];
#ifdef TESTFLIGHT_SDK_VERSION
[TestFlight passCheckpoint:MPTestFlightCheckpointSetKey];
[TestFlight passCheckpoint:MPTestFlightCheckpointSignedIn];
#endif
return YES;
}
- (void)storeSavedKey {
if (self.activeUser.saveKey) {
NSData *existingKey = [PearlKeyChain dataOfItemForQuery:keyQuery(self.activeUser)];
if (![existingKey isEqualToData:self.key]) {
inf(@"Updating key in keychain.");
[PearlKeyChain addOrUpdateItemForQuery:keyQuery(self.activeUser)
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
self.key, (__bridge id)kSecValueData,
#if TARGET_OS_IPHONE
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, (__bridge id)kSecAttrAccessible,
#endif
nil]];
}
}
}
- (void)unsetKey {
self.key = nil;
self.activeUser = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:MPNotificationKeyUnset object:self];
}
- (NSData *)keyWithLength:(NSUInteger)keyLength {
return [self.key subdataWithRange:NSMakeRange(0, MIN(keyLength, self.key.length))];
}

View File

@@ -59,22 +59,21 @@ typedef enum {
#define MPTestFlightCheckpointDeactivated @"MPTestFlightCheckpointDeactivated"
#define MPTestFlightCheckpointTerminated @"MPTestFlightCheckpointTerminated"
#define MPTestFlightCheckpointShowGuide @"MPTestFlightCheckpointShowGuide"
#define MPTestFlightCheckpointMPForgotten @"MPTestFlightCheckpointMPForgotten"
#define MPTestFlightCheckpointMPChanged @"MPTestFlightCheckpointMPChanged"
#define MPTestFlightCheckpointMPUnstored @"MPTestFlightCheckpointMPUnstored"
#define MPTestFlightCheckpointForgetSavedKey @"MPTestFlightCheckpointForgetSavedKey"
#define MPTestFlightCheckpointChangeMP @"MPTestFlightCheckpointChangeMP"
#define MPTestFlightCheckpointMPMismatch @"MPTestFlightCheckpointMPMismatch"
#define MPTestFlightCheckpointMPEntered @"MPTestFlightCheckpointMPEntered"
#define MPTestFlightCheckpointMPValid @"MPTestFlightCheckpointMPValid"
#define MPTestFlightCheckpointLocalStoreIncompatible @"MPTestFlightCheckpointLocalStoreIncompatible"
#define MPTestFlightCheckpointCloudStoreIncompatible @"MPTestFlightCheckpointCloudStoreIncompatible"
#define MPTestFlightCheckpointSetKey @"MPTestFlightCheckpointSetKey"
#define MPTestFlightCheckpointSignedIn @"MPTestFlightCheckpointSetKey"
#define MPTestFlightCheckpointCloudEnabled @"MPTestFlightCheckpointCloudEnabled"
#define MPTestFlightCheckpointCloudDisabled @"MPTestFlightCheckpointCloudDisabled"
#define MPTestFlightCheckpointSitesImported @"MPTestFlightCheckpointSitesImported"
#define MPTestFlightCheckpointSitesExported @"MPTestFlightCheckpointSitesExported"
#define MPNotificationStoreUpdated @"MPNotificationStoreUpdated"
#define MPNotificationKeySet @"MPNotificationKeySet"
#define MPNotificationKeyUnset @"MPNotificationKeyUnset"
#define MPNotificationSignedIn @"MPNotificationKeySet"
#define MPNotificationSignedOut @"MPNotificationKeyUnset"
#define MPNotificationKeyForgotten @"MPNotificationKeyForgotten"
#define MPNotificationElementUsed @"MPNotificationElementUsed"

View File

@@ -3,7 +3,7 @@
<entity name="MPElementEntity" representedClassName="MPElementEntity" isAbstract="YES" syncable="YES">
<attribute name="content" optional="YES" transient="YES" attributeType="Transformable" syncable="YES"/>
<attribute name="lastUsed" attributeType="Date" syncable="YES"/>
<attribute name="name" attributeType="String" minValueString="1" indexed="YES" syncable="YES" isSyncIdentityProperty="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"/>
@@ -18,8 +18,8 @@
<attribute name="avatar_" attributeType="Integer 16" defaultValueString="0" 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" isSyncIdentityProperty="YES"/>
<attribute name="saveKey_" attributeType="Boolean" defaultValueString="NO" 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"/>
</entity>
<elements>

View File

@@ -14,8 +14,8 @@
+ (MPAppDelegate *)get;
- (void)checkConfig;
- (void)showGuide;
- (void)loadKey:(BOOL)animated;
- (void)export;
- (void)changeMP;

View File

@@ -50,6 +50,12 @@
return (MPAppDelegate *)[super get];
}
- (void)checkConfig {
if ([[MPConfig get].iCloud boolValue] != [self.storeManager iCloudEnabled])
[self.storeManager useiCloudStore:[[MPConfig get].iCloud boolValue] alertUser:YES];
}
- (void)showGuide {
[self.navigationController performSegueWithIdentifier:@"MP_Guide" sender:self];
@@ -57,21 +63,6 @@
[TestFlight passCheckpoint:MPTestFlightCheckpointShowGuide];
}
- (void)loadKey:(BOOL)animated {
if (!self.key)
// Try and load the key from the keychain.
[self loadSavedKey];
if (!self.key)
// Ask the user to set the key through his master password.
PearlMainThread(^{
[self.navigationController presentViewController:
[self.navigationController.storyboard instantiateViewControllerWithIdentifier:@"MPUnlockViewController"]
animated:animated completion:nil];
});
}
- (void)export {
[PearlAlert showNotice:
@@ -131,10 +122,10 @@
if (buttonIndex == [alert cancelButtonIndex])
return;
[[MPAppDelegate get] forgetSavedKey];
[[MPAppDelegate get] loadKey:YES];
self.activeUser.keyID = nil;
[self signOut];
[TestFlight passCheckpoint:MPTestFlightCheckpointMPChanged];
[TestFlight passCheckpoint:MPTestFlightCheckpointChangeMP];
}
cancelTitle:[PearlStrings get].commonButtonAbort
otherTitles:[PearlStrings get].commonButtonContinue, nil];
@@ -147,12 +138,6 @@
[self checkConfig];
}
- (void)checkConfig {
if ([[MPConfig get].iCloud boolValue] != [self.storeManager iCloudEnabled])
[self.storeManager useiCloudStore:[[MPConfig get].iCloud boolValue] alertUser:YES];
}
#pragma mark - UIApplicationDelegate
@@ -287,7 +272,11 @@
[[UISegmentedControl appearance] setDividerImage:segmentUnselectedUnselected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setDividerImage:segmentSelectedUnselected forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UISegmentedControl appearance] setDividerImage:segUnselectedSelected forLeftSegmentState:UIControlStateNormal rightSegmentState:UIControlStateSelected barMetrics:UIBarMetricsDefault];*/
[[NSNotificationCenter defaultCenter] addObserverForName:MPNotificationSignedOut object:nil queue:nil
usingBlock:^(NSNotification *note) {
[self.navigationController performSegueWithIdentifier:@"MP_Unlock" sender:nil];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:kIASKAppSettingChanged object:nil queue:nil
usingBlock:^(NSNotification *note) {
[self checkConfig];
@@ -379,11 +368,7 @@
if ([[MPiOSConfig get].showQuickStart boolValue])
[self showGuide];
else {
[self loadKey:NO];
[self checkConfig];
}
[TestFlight passCheckpoint:MPTestFlightCheckpointActivated];
[super applicationDidBecomeActive:application];
@@ -421,11 +406,9 @@
[self saveContext];
if (![[MPiOSConfig get].rememberLogin boolValue]) {
[self unsetKey];
[self loadKey:NO];
}
if (![[MPiOSConfig get].rememberLogin boolValue])
[self signOut];
[TestFlight passCheckpoint:MPTestFlightCheckpointDeactivated];
}

View File

@@ -32,14 +32,6 @@
[MPiOSConfig get].showQuickStart = [NSNumber numberWithBool:NO];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[[MPAppDelegate get] loadKey:animated];
}
- (void)viewDidUnload {
[self setScrollView:nil];

View File

@@ -71,6 +71,8 @@
[super viewWillAppear:animated];
if (![MPAppDelegate get].activeUser)
[self.navigationController performSegueWithIdentifier:@"MP_Unlock" sender:self];
if (self.activeElement.user != [MPAppDelegate get].activeUser)
self.activeElement = nil;
self.searchDisplayController.searchBar.text = nil;
@@ -103,6 +105,8 @@
}
}];
[[MPAppDelegate get] checkConfig];
[super viewDidAppear:animated];
}
@@ -220,7 +224,7 @@
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSString *error = [self.helpView stringByEvaluatingJavaScriptFromString:
PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))];
PearlString(@"setClass('%@');", ClassNameFromMPElementType(self.activeElement.type))];
if (error.length)
err(@"helpView.setClass: %@", error);
}
@@ -419,8 +423,8 @@
case 5:
#endif
{
[[MPAppDelegate get] signOut:self];
[[MPAppDelegate get] loadKey:YES];
[[MPAppDelegate get] forgetSavedKeyFor:[MPAppDelegate get].activeUser];
[[MPAppDelegate get] signOut];
break;
}
}

View File

@@ -9,6 +9,7 @@
#import <QuartzCore/QuartzCore.h>
#import "MPPreferencesViewController.h"
#import "MPAppDelegate.h"
#import "MPAppDelegate_Key.h"
@interface MPPreferencesViewController ()
@@ -114,7 +115,10 @@
- (IBAction)didToggleSwitch:(UISwitch *)sender {
[MPAppDelegate get].activeUser.saveKey = sender.on;
if (([MPAppDelegate get].activeUser.saveKey = sender.on))
[[MPAppDelegate get] storeSavedKeyFor:[MPAppDelegate get].activeUser];
else
[[MPAppDelegate get] forgetSavedKeyFor:[MPAppDelegate get].activeUser];
}
@end

View File

@@ -119,12 +119,6 @@
[self.avatarsView autoSizeContentIgnoreHidden:YES ignoreInvisible:YES limitPadding:NO ignoreSubviews:nil];
[self updateLayoutAnimated:YES allowScroll:YES completion:nil];
self.deleteTip.alpha = 0;
if ([users count] > 1)
[UIView animateWithDuration:0.5f animations:^{
self.deleteTip.alpha = 1;
}];
}
- (UIButton *)setupAvatar:(UIButton *)avatar forUser:(MPUserEntity *)user {
@@ -169,7 +163,11 @@
if (!self.selectedUser)
[self.passwordField resignFirstResponder];
else if ([[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:nil]) {
[self dismissModalViewControllerAnimated:YES];
return;
}
[self updateLayoutAnimated:YES allowScroll:YES completion:^(BOOL finished) {
if (finished)
if (self.selectedUser)
@@ -230,6 +228,7 @@
self.nameLabel.backgroundColor = [UIColor blackColor];
self.oldNameLabel.center = self.nameLabel.center;
self.avatarShadowColor = [UIColor whiteColor];
self.deleteTip.alpha = 0;
} else if (!self.selectedUser && self.passwordView.alpha == 1) {
self.passwordView.alpha = 0;
self.avatarsView.center = CGPointMake(160, 240);
@@ -238,6 +237,7 @@
self.nameLabel.backgroundColor = [UIColor clearColor];
self.oldNameLabel.center = self.nameLabel.center;
self.avatarShadowColor = [UIColor lightGrayColor];
self.deleteTip.alpha = [self.avatarToUser count] > 2? 1: 0;
}
MPUserEntity *targetedUser = self.selectedUser;
@@ -294,14 +294,14 @@
[self setSpinnerActive:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL unlocked = [[MPAppDelegate get] tryMasterPassword:self.passwordField.text forUser:self.selectedUser];
BOOL unlocked = [[MPAppDelegate get] signInAsUser:self.selectedUser usingMasterPassword:self.passwordField.text];
dispatch_async(dispatch_get_main_queue(), ^{
if (unlocked) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (long) (NSEC_PER_SEC * 0.5f)), dispatch_get_main_queue(), ^{
[self dismissModalViewControllerAnimated:YES];
});
} else
} else if (self.passwordField.text.length)
[self setPasswordTip:@"Incorrect password."];
[self setSpinnerActive:NO];

View File

@@ -925,6 +925,7 @@ L4m3P4sSw0rD</string>
<connections>
<segue destination="PQa-Xl-A3x" kind="relationship" relationship="rootViewController" id="LUg-eF-JQd"/>
<segue destination="qz3-eG-aEi" kind="modal" identifier="MP_Guide" id="vyG-wN-8hU"/>
<segue destination="Nbn-Rv-sP1" kind="modal" identifier="MP_Unlock" id="6s2-3H-q5S"/>
</connections>
</navigationController>
</objects>