2
0
MasterPassword/MasterPassword/ObjC/Mac/MPPasswordWindowController.m

349 lines
14 KiB
Mathematica
Raw Normal View History

//
// MPPasswordWindowController.m
// MasterPassword-Mac
//
// Created by Maarten Billemont on 04/03/12.
// Copyright (c) 2012 Lyndir. All rights reserved.
//
#import "MPPasswordWindowController.h"
#import "MPMacAppDelegate.h"
2012-05-13 08:24:19 +00:00
#import "MPAppDelegate_Key.h"
#import "MPAppDelegate_Store.h"
#define MPAlertUnlockMP @"MPAlertUnlockMP"
#define MPAlertIncorrectMP @"MPAlertIncorrectMP"
2013-04-20 18:11:19 +00:00
@interface MPPasswordWindowController()
2013-04-20 18:11:19 +00:00
@property(nonatomic) BOOL inProgress;
@property(nonatomic) BOOL siteFieldPreventCompletion;
@end
@implementation MPPasswordWindowController {
NSManagedObjectID *_activeElementOID;
}
- (void)windowDidLoad {
2012-06-08 21:46:13 +00:00
if ([[MPMacConfig get].dialogStyleHUD boolValue])
self.window.styleMask = NSHUDWindowMask | NSTitledWindowMask | NSUtilityWindowMask | NSClosableWindowMask;
else
self.window.styleMask = NSTexturedBackgroundWindowMask | NSResizableWindowMask | NSTitledWindowMask | NSClosableWindowMask;
[self setContent:@""];
[self.tipField setStringValue:@""];
2012-06-08 21:46:13 +00:00
[self.userLabel setStringValue:PearlString( @"%@'s password for:", [[MPMacAppDelegate get] activeUserForThread].name )];
// [[MPMacAppDelegate get] addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) {
// [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
// if (![MPAlgorithmDefault migrateUser:[[MPMacAppDelegate get] activeUserInContext:moc]])
// [NSAlert alertWithMessageText:@"Migration Needed" defaultButton:@"OK" alternateButton:nil otherButton:nil
// informativeTextWithFormat:@"Certain sites require explicit migration to get updated to the latest version of the "
// @"Master Password algorithm. For these sites, a migration button will appear. Migrating these sites will cause "
// @"their passwords to change. You'll need to update your profile for that site with the new password."];
// [moc saveToStore];
// }];
// } forKeyPath:@"key" options:NSKeyValueObservingOptionInitial context:nil];
2013-04-20 18:11:19 +00:00
[[NSNotificationCenter defaultCenter]
addObserverForName:NSWindowDidBecomeKeyNotification object:self.window queue:nil usingBlock:^(NSNotification *note) {
if (!self.inProgress)
[self unlock];
[self.siteField selectText:self];
}];
[[NSNotificationCenter defaultCenter]
addObserverForName:NSWindowWillCloseNotification object:self.window queue:nil usingBlock:^(NSNotification *note) {
[[NSApplication sharedApplication] hide:self];
}];
[[NSNotificationCenter defaultCenter]
addObserverForName:MPSignedOutNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
[self.window close];
}];
2012-06-08 21:46:13 +00:00
[super windowDidLoad];
}
- (void)unlock {
2012-06-08 21:46:13 +00:00
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserForThread];
2013-04-21 21:05:59 +00:00
if (!activeUser)
2013-04-20 18:11:19 +00:00
// No user to sign in with.
return;
if ([MPMacAppDelegate get].key)
2013-04-20 18:11:19 +00:00
// Already logged in.
return;
if ([[MPMacAppDelegate get] signInAsUser:activeUser usingMasterPassword:nil])
2013-04-20 18:11:19 +00:00
// Load the key from the keychain.
return;
2012-06-08 21:46:13 +00:00
if (![MPMacAppDelegate get].key)
2013-04-20 18:11:19 +00:00
// Ask the user to set the key through his master password.
dispatch_async( dispatch_get_main_queue(), ^{
if ([MPMacAppDelegate get].key)
return;
2012-06-08 21:46:13 +00:00
self.content = @"";
[self.siteField setStringValue:@""];
[self.tipField setStringValue:@""];
2013-04-20 18:11:19 +00:00
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked."
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Cancel"
informativeTextWithFormat:@"The master password is required to unlock the application for:\n\n%@",
2013-04-21 21:05:59 +00:00
activeUser.name];
2013-04-20 18:11:19 +00:00
NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert setAccessoryView:passwordField];
[alert layout];
[passwordField becomeFirstResponder];
[alert beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertUnlockMP];
2013-04-20 18:11:19 +00:00
} );
}
2012-06-08 21:46:13 +00:00
- (void)alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
if (contextInfo == MPAlertIncorrectMP) {
[self.window close];
return;
}
if (contextInfo == MPAlertUnlockMP) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserForThread];
switch (returnCode) {
case NSAlertAlternateReturn:
// "Change" button.
{
NSInteger returnCode_ = [[NSAlert
alertWithMessageText:@"Changing Master Password" defaultButton:nil
alternateButton:[PearlStrings get].commonButtonCancel otherButton:nil informativeTextWithFormat:
@"This will allow you to log in with a different master password.\n\n"
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
@"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again."]
runModal];
if (returnCode_ == NSAlertDefaultReturn) {
2013-04-21 21:05:59 +00:00
activeUser.keyID = nil;
[[MPMacAppDelegate get] forgetSavedKeyFor:activeUser];
[[MPMacAppDelegate get] signOutAnimated:YES];
}
}
break;
case NSAlertOtherReturn:
// "Cancel" button.
[self.window close];
return;
case NSAlertDefaultReturn: {
// "Unlock" button.
self.contentContainer.alphaValue = 0;
[self.progressView startAnimation:nil];
self.inProgress = YES;
2013-04-20 18:11:19 +00:00
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0 ), ^{
BOOL success = [[MPMacAppDelegate get] signInAsUser:activeUser
usingMasterPassword:[(NSSecureTextField *)alert.accessoryView stringValue]];
self.inProgress = NO;
2013-04-20 18:11:19 +00:00
dispatch_async( dispatch_get_main_queue(), ^{
[self.progressView stopAnimation:nil];
if (success)
self.contentContainer.alphaValue = 1;
else {
[[NSAlert alertWithError:[NSError errorWithDomain:MPErrorDomain code:0 userInfo:@{
2013-04-20 18:11:19 +00:00
NSLocalizedDescriptionKey : PearlString( @"Incorrect master password for user %@",
2013-04-21 21:05:59 +00:00
activeUser.name )
}]] beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertIncorrectMP];
}
2013-04-20 18:11:19 +00:00
} );
} );
}
default:
break;
}
return;
}
}
2012-06-08 21:46:13 +00:00
- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words
forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index {
NSString *query = [[control stringValue] substringWithRange:charRange];
if (![query length] || ![MPMacAppDelegate get].key)
return nil;
2012-06-08 21:46:13 +00:00
__block NSMutableArray *mutableResults = [NSMutableArray array];
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPElementEntity class] )];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(name BEGINSWITH[cd] %@) AND user == %@",
query, [[MPMacAppDelegate get] activeUserInContext:context]];
NSError *error = nil;
NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error];
if (error)
err(@"While fetching elements for completion: %@", error);
if (siteResults) {
for (MPElementEntity *element in siteResults)
[mutableResults addObject:element.name];
//[mutableResults addObject:query]; // For when the app should be able to create new sites.
}
}];
2012-06-08 21:46:13 +00:00
if ([mutableResults count] == 1) {
//[textView setString:[(MPElementEntity *)[siteResults objectAtIndex:0] name]];
//[textView setSelectedRange:NSMakeRange( [query length], [[textView string] length] - [query length] )];
[self trySiteAndCopyContent:NO];
}
return mutableResults;
}
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector {
2012-06-08 21:46:13 +00:00
if (commandSelector == @selector(cancel:)) {
[self.window close];
return YES;
}
2013-04-20 18:11:19 +00:00
if ((self.siteFieldPreventCompletion = [NSStringFromSelector( commandSelector ) hasPrefix:@"delete"]))
return NO;
if (commandSelector == @selector(insertNewline:)) {
[self trySiteAndCopyContent:YES];
return YES;
}
2012-06-08 21:46:13 +00:00
return NO;
}
- (void)copyContents {
}
- (void)controlTextDidEndEditing:(NSNotification *)note {
if (note.object != self.siteField)
return;
[self trySiteAndCopyContent:NO];
}
- (void)controlTextDidChange:(NSNotification *)note {
if (note.object != self.siteField)
return;
// Update the site content as the site name changes.
BOOL enterPressed = [[NSApp currentEvent] type] == NSKeyDown &&
[[[NSApp currentEvent] charactersIgnoringModifiers] isEqualToString:@"\r"];
[self trySiteAndCopyContent:enterPressed];
if (enterPressed)
return;
if (self.siteFieldPreventCompletion) {
self.siteFieldPreventCompletion = NO;
return;
}
2012-06-08 21:46:13 +00:00
self.siteFieldPreventCompletion = YES;
[(NSText *)[note.userInfo objectForKey:@"NSFieldEditor"] complete:self];
self.siteFieldPreventCompletion = NO;
}
- (MPElementEntity *)activeElementForThread {
return [self activeElementInContext:[MPMacAppDelegate managedObjectContextForThreadIfReady]];
}
- (MPElementEntity *)activeElementInContext:(NSManagedObjectContext *)moc {
if (!_activeElementOID)
return nil;
NSError *error;
MPElementEntity *activeElement = (MPElementEntity *)[moc existingObjectWithID:_activeElementOID error:&error];
if (!activeElement)
err(@"Couldn't retrieve active element: %@", error);
2012-06-08 21:46:13 +00:00
return activeElement;
}
- (void)setContent:(NSString *)content {
2012-06-08 21:46:13 +00:00
NSMutableParagraphStyle *paragraph = [NSMutableParagraphStyle new];
paragraph.alignment = NSCenterTextAlignment;
2012-06-08 21:46:13 +00:00
[self.contentField setAttributedStringValue:[[NSAttributedString alloc] initWithString:content attributes:@{
NSParagraphStyleAttributeName : paragraph
}]];
}
- (void)trySiteAndCopyContent:(BOOL)copyContent {
2012-06-08 21:46:13 +00:00
[self setContent:@""];
[self.tipField setStringValue:@"Generating..."];
2012-06-08 21:46:13 +00:00
2013-04-20 18:11:19 +00:00
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 ), ^{
NSString *content = [[self activeElementForThread].content description];
if (!content)
content = @"";
if (copyContent) {
[[NSPasteboard generalPasteboard] declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
if (![[NSPasteboard generalPasteboard] setString:content forType:NSPasteboardTypeString]) {
wrn(@"Couldn't copy password to pasteboard.");
return;
}
NSManagedObjectContext *moc = [MPMacAppDelegate managedObjectContextForThreadIfReady];
MPElementEntity *activeElement = [self activeElementInContext:moc];
[activeElement use];
[moc saveToStore];
}
2012-06-08 21:46:13 +00:00
2013-04-20 18:11:19 +00:00
dispatch_async( dispatch_get_main_queue(), ^{
[self setContent:content];
self.tipField.alphaValue = 1;
if (!copyContent)
[self.tipField setStringValue:@"Hit ⌤ (ENTER) to copy the password."];
else {
[self.tipField setStringValue:@"Copied! Hit ⎋ (ESC) to close window."];
dispatch_time_t popTime = dispatch_time( DISPATCH_TIME_NOW, (int64_t)(5.0f * NSEC_PER_SEC) );
dispatch_after( popTime, dispatch_get_main_queue(), ^{
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.2f];
[self.tipField.animator setAlphaValue:0];
[NSAnimationContext endGrouping];
} );
}
2013-04-20 18:11:19 +00:00
} );
} );
2012-06-08 21:46:13 +00:00
// For when the app should be able to create new sites.
/*
else
[[MPMacAppDelegate get].managedObjectContext performBlock:^{
MPElementEntity *element = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([MPElementGeneratedEntity class])
inManagedObjectContext:[MPMacAppDelegate get].managedObjectContext];
assert([element isKindOfClass:ClassFromMPElementType(element.type)]);
assert([MPMacAppDelegate get].keyID);
element.name = siteName;
element.keyID = [MPMacAppDelegate get].keyID;
NSString *description = [element.content description];
[element use];
dispatch_async(dispatch_get_main_queue(), ^{
[self setContent:description];
});
}];
*/
}
@end