| /* |
| * Copyright (C) 2015-2016 Savoir-faire Linux Inc. |
| * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| #import "AccSecurityVC.h" |
| |
| #import <QUrl> |
| #import <certificate.h> |
| #import <tlsmethodmodel.h> |
| #import <qitemselectionmodel.h> |
| #import <ciphermodel.h> |
| #import <accountmodel.h> |
| |
| #import "QNSTreeController.h" |
| #import "CertificateWC.h" |
| |
| #define COLUMNID_NAME @"CipherNameColumn" |
| #define COLUMNID_STATE @"CipherStateColumn" |
| |
| @interface AccSecurityVC () { |
| __unsafe_unretained IBOutlet NSOutlineView *cipherListView; |
| __unsafe_unretained IBOutlet NSButton *useTLS; |
| __unsafe_unretained IBOutlet NSView *tlsContainer; |
| |
| __unsafe_unretained IBOutlet NSView *pvkContainer; |
| __unsafe_unretained IBOutlet NSImageView *pvkPasswordValidation; |
| |
| __unsafe_unretained IBOutlet NSButton *showUserCertButton; |
| __unsafe_unretained IBOutlet NSButton *showCAButton; |
| __unsafe_unretained IBOutlet NSSecureTextField *pvkPasswordField; |
| __unsafe_unretained IBOutlet NSTextField *outgoingTlsServerName; |
| __unsafe_unretained IBOutlet NSTextField *tlsNegotiationTimeout; |
| __unsafe_unretained IBOutlet NSStepper *tlsNegotiationTimeoutStepper; |
| __unsafe_unretained IBOutlet NSPathControl *caListPathControl; |
| __unsafe_unretained IBOutlet NSPathControl *certificatePathControl; |
| __unsafe_unretained IBOutlet NSPathControl *pvkPathControl; |
| __unsafe_unretained IBOutlet NSPopUpButton *tlsMethodList; |
| __unsafe_unretained IBOutlet NSButton *srtpRTPFallback; |
| __unsafe_unretained IBOutlet NSButton *useSRTP; |
| |
| __unsafe_unretained IBOutlet NSButton *verifyCertAsClientButton; |
| __unsafe_unretained IBOutlet NSButton *verifyCertAsServerButton; |
| __unsafe_unretained IBOutlet NSButton *requireCertButton; |
| } |
| |
| @property QNSTreeController *treeController; |
| @property CertificateWC* certificateWC; |
| |
| @end |
| |
| @implementation AccSecurityVC |
| @synthesize treeController; |
| @synthesize certificateWC; |
| |
| // Tags for views |
| NS_ENUM(NSInteger, TagViews) { |
| PVK_PASSWORD = 0, |
| OUTGOING_TLS_SRV_NAME = 1, |
| TLS_NEGOTIATION = 2 |
| }; |
| |
| - (void)awakeFromNib |
| { |
| NSLog(@"INIT Security VC"); |
| [pvkPasswordField setTag:TagViews::PVK_PASSWORD]; |
| [outgoingTlsServerName setTag:TagViews::OUTGOING_TLS_SRV_NAME]; |
| [tlsNegotiationTimeoutStepper setTag:TagViews::TLS_NEGOTIATION]; |
| [tlsNegotiationTimeout setTag:TagViews::TLS_NEGOTIATION]; |
| |
| QObject::connect(AccountModel::instance().selectionModel(), |
| &QItemSelectionModel::currentChanged, |
| [=](const QModelIndex ¤t, const QModelIndex &previous) { |
| if(!current.isValid()) |
| return; |
| [self loadAccount]; |
| }); |
| } |
| |
| - (Account*) currentAccount |
| { |
| auto accIdx = AccountModel::instance().selectionModel()->currentIndex(); |
| return AccountModel::instance().getAccountByModelIndex(accIdx); |
| } |
| |
| - (void)loadAccount |
| { |
| auto account = [self currentAccount]; |
| |
| [self updateControlsWithTag:TagViews::PVK_PASSWORD]; |
| [self updateControlsWithTag:OUTGOING_TLS_SRV_NAME]; |
| [self updateControlsWithTag:TagViews::TLS_NEGOTIATION]; |
| |
| QModelIndex qTlsMethodIdx = account->tlsMethodModel()->selectionModel()->currentIndex(); |
| [tlsMethodList removeAllItems]; |
| [tlsMethodList addItemWithTitle:qTlsMethodIdx.data(Qt::DisplayRole).toString().toNSString()]; |
| |
| treeController = [[QNSTreeController alloc] initWithQModel:account->cipherModel()]; |
| [treeController setAvoidsEmptySelection:NO]; |
| [treeController setAlwaysUsesMultipleValuesMarker:YES]; |
| [treeController setChildrenKeyPath:@"children"]; |
| |
| [cipherListView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil]; |
| [cipherListView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil]; |
| [cipherListView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil]; |
| |
| [useTLS setState:account->isTlsEnabled()]; |
| [tlsContainer setHidden:!account->isTlsEnabled()]; |
| |
| [useSRTP setState:account->isSrtpEnabled()]; |
| [srtpRTPFallback setState:account->isSrtpRtpFallback()]; |
| [srtpRTPFallback setEnabled:useSRTP.state]; |
| |
| if(account->tlsCaListCertificate() != nil) { |
| [caListPathControl setURL:[NSURL fileURLWithPath:account->tlsCaListCertificate()->path().toNSString()]]; |
| } else { |
| [caListPathControl setURL:nil]; |
| } |
| |
| auto tlsCert = account->tlsCertificate(); |
| |
| if(tlsCert != nil) { |
| [certificatePathControl setURL:[NSURL fileURLWithPath:tlsCert->path().toNSString()]]; |
| if(tlsCert->requirePrivateKey()) { |
| [pvkContainer setHidden:NO]; |
| if(!account->tlsPrivateKey().isEmpty()) { |
| [pvkPathControl setURL:[NSURL fileURLWithPath:account->tlsPrivateKey().toNSString()]]; |
| if (tlsCert->requirePrivateKeyPassword()) { |
| [pvkPasswordField setHidden:NO]; |
| } else |
| [pvkPasswordField setHidden:YES]; |
| } else { |
| [pvkPathControl setURL:nil]; |
| } |
| } else { |
| [pvkContainer setHidden:YES]; |
| } |
| } else { |
| [certificatePathControl setURL:nil]; |
| } |
| |
| if (account->tlsCaListCertificate()) |
| [showCAButton setHidden:!(account->tlsCaListCertificate()->isValid() == Certificate::CheckValues::PASSED)]; |
| else |
| [showCAButton setHidden:YES]; |
| |
| [verifyCertAsServerButton setState:account->isTlsVerifyServer()]; |
| [verifyCertAsClientButton setState:account->isTlsVerifyClient()]; |
| [requireCertButton setState:account->isTlsRequireClientCertificate()]; |
| } |
| |
| - (IBAction)chooseTlsMethod:(id)sender { |
| int index = [sender indexOfSelectedItem]; |
| QModelIndex qIdx = [self currentAccount]->tlsMethodModel()->index(index, 0); |
| [self currentAccount]->tlsMethodModel()->selectionModel()->setCurrentIndex(qIdx, QItemSelectionModel::ClearAndSelect); |
| } |
| |
| - (IBAction)toggleUseTLS:(id)sender { |
| [self currentAccount]->setTlsEnabled([sender state]); |
| [tlsContainer setHidden:![sender state]]; |
| } |
| |
| - (IBAction)toggleUseSRTP:(id)sender { |
| [self currentAccount]->setSrtpEnabled([sender state]); |
| [srtpRTPFallback setEnabled:[sender state]]; |
| } |
| - (IBAction)toggleRTPFallback:(id)sender { |
| [self currentAccount]->setSrtpRtpFallback([sender state]); |
| } |
| |
| - (IBAction)toggleVerifyCertAsClient:(id)sender { |
| [self currentAccount]->setTlsVerifyClient([sender state]); |
| } |
| |
| - (IBAction)toggleVerifyCertServer:(id)sender { |
| [self currentAccount]->setTlsVerifyServer([sender state]); |
| } |
| |
| - (IBAction)toggleRequireCert:(id)sender { |
| [self currentAccount]->setTlsRequireClientCertificate([sender state]); |
| } |
| |
| - (IBAction)toggleCipher:(id)sender { |
| NSInteger row = [sender clickedRow]; |
| NSTableColumn *col = [sender tableColumnWithIdentifier:COLUMNID_STATE]; |
| NSButtonCell *cell = [col dataCellForRow:row]; |
| [self currentAccount]->cipherModel()->setData([self currentAccount]->cipherModel()->index(row, 0, QModelIndex()), |
| cell.state == NSOnState ? Qt::Unchecked : Qt::Checked, Qt::CheckStateRole); |
| } |
| |
| - (void) updateControlsWithTag:(NSInteger) tag |
| { |
| switch (tag) { |
| case TagViews::PVK_PASSWORD: { |
| [pvkPasswordField setStringValue:[self currentAccount]->tlsPassword().toNSString()]; |
| BOOL passMatch = [self currentAccount]->tlsCertificate() && |
| [self currentAccount]->tlsCertificate()->privateKeyMatch() == Certificate::CheckValues::PASSED; |
| [pvkPasswordValidation setImage:[NSImage imageNamed:passMatch?@"ic_action_accept":@"ic_action_cancel"]]; |
| } |
| break; |
| case TagViews::OUTGOING_TLS_SRV_NAME: |
| [outgoingTlsServerName setStringValue:[self currentAccount]->tlsServerName().toNSString()]; |
| break; |
| case TagViews::TLS_NEGOTIATION: |
| [tlsNegotiationTimeout setIntegerValue:[self currentAccount]->tlsNegotiationTimeoutSec()]; |
| [tlsNegotiationTimeoutStepper setIntegerValue:[self currentAccount]->tlsNegotiationTimeoutSec()]; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| #pragma mark - NSTextFieldDelegate methods |
| |
| -(void)controlTextDidChange:(NSNotification *)notif |
| { |
| NSTextField *textField = [notif object]; |
| NSRange test = [[textField currentEditor] selectedRange]; |
| |
| [self valueDidChange:textField]; |
| //FIXME: saving account lose focus because in NSTreeController we remove and reinsert row so View selction change |
| [textField.window makeFirstResponder:textField]; |
| [[textField currentEditor] setSelectedRange:test]; |
| } |
| |
| - (IBAction) valueDidChange: (id) sender |
| { |
| switch ([sender tag]) { |
| case TagViews::PVK_PASSWORD: |
| [self currentAccount]->setTlsPassword([[sender stringValue] UTF8String]); |
| break; |
| case TagViews::OUTGOING_TLS_SRV_NAME: |
| [self currentAccount]->setTlsServerName([[sender stringValue] UTF8String]); |
| break; |
| case TagViews::TLS_NEGOTIATION: |
| [self currentAccount]->setTlsNegotiationTimeoutSec([sender integerValue]); |
| break; |
| default: |
| break; |
| } |
| [self updateControlsWithTag:[sender tag]]; |
| } |
| |
| #pragma mark - NSPathControl delegate methods |
| |
| - (IBAction)caListPathControlSingleClick:(id)sender |
| { |
| NSURL* fileURL; |
| if ([sender isKindOfClass:[NSMenuItem class]]) { |
| fileURL = nil; |
| } else { |
| fileURL = [[sender clickedPathComponentCell] URL]; |
| } |
| [self->caListPathControl setURL:fileURL]; |
| [self currentAccount]->setTlsCaListCertificate([[fileURL path] UTF8String]); |
| |
| if ([self currentAccount]->tlsCaListCertificate()->isValid() == Certificate::CheckValues::PASSED) { |
| [showCAButton setHidden:NO]; |
| } else |
| [showCAButton setHidden:YES]; |
| } |
| |
| - (IBAction)certificatePathControlSingleClick:(id)sender |
| { |
| NSURL* fileURL; |
| if ([sender isKindOfClass:[NSMenuItem class]]) { |
| fileURL = nil; |
| } else { |
| fileURL = [[sender clickedPathComponentCell] URL]; |
| } |
| [self->certificatePathControl setURL:fileURL]; |
| [self currentAccount]->setTlsCertificate([[fileURL path] UTF8String]); |
| |
| auto cert = [self currentAccount]->tlsCertificate(); |
| |
| if (cert) { |
| [showUserCertButton setHidden:!(cert->isValid() == Certificate::CheckValues::PASSED)]; |
| [pvkContainer setHidden:!cert->requirePrivateKey()]; |
| } else { |
| [showUserCertButton setHidden:YES]; |
| [pvkContainer setHidden:YES]; |
| } |
| |
| } |
| |
| - (IBAction)pvkFilePathControlSingleClick:(id)sender |
| { |
| NSURL* fileURL; |
| if ([sender isKindOfClass:[NSMenuItem class]]) { |
| fileURL = nil; |
| } else { |
| fileURL = [[sender clickedPathComponentCell] URL]; |
| } |
| [self currentAccount]->setTlsPrivateKey([[fileURL path] UTF8String]); |
| if([self currentAccount]->tlsCertificate()->requirePrivateKeyPassword()) { |
| [pvkPasswordField setHidden:NO]; |
| } else { |
| [pvkPasswordField setHidden:YES]; |
| } |
| } |
| |
| - (IBAction)showCA:(id)sender |
| { |
| certificateWC = [[CertificateWC alloc] initWithWindowNibName:@"CertificateWindow"]; |
| [certificateWC setCertificate:[self currentAccount]->tlsCaListCertificate()]; |
| #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_9 |
| [self.view.window beginSheet:certificateWC.window completionHandler:nil]; |
| #else |
| [NSApp beginSheet: certificateWC.window |
| modalForWindow: self.view.window |
| modalDelegate: self |
| didEndSelector: nil |
| contextInfo: nil]; |
| #endif |
| } |
| |
| - (IBAction)showEndpointCertificate:(id)sender |
| { |
| certificateWC = [[CertificateWC alloc] initWithWindowNibName:@"CertificateWindow"]; |
| [certificateWC setCertificate:[self currentAccount]->tlsCertificate()]; |
| #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_9 |
| [self.view.window beginSheet:certificateWC.window completionHandler:nil]; |
| #else |
| [NSApp beginSheet: certificateWC.window |
| modalForWindow: self.view.window |
| modalDelegate: self |
| didEndSelector: nil |
| contextInfo: nil]; |
| #endif |
| } |
| |
| #pragma mark - NSPathControlDelegate methods |
| |
| - (void)pathControl:(NSPathControl *)pathControl willDisplayOpenPanel:(NSOpenPanel *)openPanel |
| { |
| NSLog(@"willDisplayOpenPanel"); |
| [openPanel setAllowsMultipleSelection:NO]; |
| [openPanel setCanChooseDirectories:NO]; |
| [openPanel setCanChooseFiles:YES]; |
| [openPanel setResolvesAliases:YES]; |
| |
| if(pathControl == caListPathControl) { |
| [openPanel setTitle:NSLocalizedString(@"Choose a CA list", @"Open panel title")]; |
| } else if (pathControl == certificatePathControl) { |
| [openPanel setTitle:NSLocalizedString(@"Choose a certificate", @"Open panel title")]; |
| } else { |
| [openPanel setTitle:NSLocalizedString(@"Choose a private key file", @"Open panel title")]; |
| } |
| |
| [openPanel setPrompt:NSLocalizedString(@"Choose CA", @"Open panel prompt for 'Choose a file'")]; |
| [openPanel setDelegate:self]; |
| } |
| |
| - (void)pathControl:(NSPathControl *)pathControl willPopUpMenu:(NSMenu *)menu |
| { |
| NSMenuItem *item; |
| if(pathControl == caListPathControl) { |
| item = [menu addItemWithTitle:NSLocalizedString(@"Remove value", @"Contextual menu entry") |
| action:@selector(caListPathControlSingleClick:) keyEquivalent:@""]; |
| } else if (pathControl == certificatePathControl) { |
| item = [menu addItemWithTitle:NSLocalizedString(@"Remove value", @"Contextual menu entry") |
| action:@selector(certificatePathControlSingleClick:) keyEquivalent:@""]; |
| } else { |
| item = [menu addItemWithTitle:NSLocalizedString(@"Remove value", @"Contextual menu entry") |
| action:@selector(pvkFilePathControlSingleClick:) keyEquivalent:@""]; |
| } |
| [item setTarget:self]; // or whatever target you want |
| } |
| |
| #pragma mark - NSOpenSavePanelDelegate delegate methods |
| |
| - (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError **)outError |
| { |
| return YES; |
| } |
| |
| #pragma mark - NSMenuDelegate methods |
| |
| - (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel |
| { |
| auto qIdx = [self currentAccount]->tlsMethodModel()->index(index); |
| [item setTitle:qIdx.data(Qt::DisplayRole).toString().toNSString()]; |
| return YES; |
| } |
| |
| - (NSInteger)numberOfItemsInMenu:(NSMenu *)menu |
| { |
| return [self currentAccount]->tlsMethodModel()->rowCount(); |
| } |
| |
| #pragma mark - NSOutlineViewDelegate methods |
| |
| - (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item; |
| { |
| return YES; |
| } |
| |
| - (NSCell *)outlineView:(NSOutlineView *)outlineView dataCellForTableColumn:(NSTableColumn *)tableColumn item:(id)item |
| { |
| NSCell *returnCell = [tableColumn dataCell]; |
| return returnCell; |
| } |
| |
| - (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor |
| { |
| if ([[fieldEditor string] length] == 0) { |
| // don't allow empty node names |
| return NO; |
| } else { |
| return YES; |
| } |
| } |
| |
| - (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item |
| { |
| return NO; |
| } |
| |
| - (void)outlineView:(NSOutlineView *)olv willDisplayCell:(NSCell*)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item |
| { |
| QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)]; |
| if(!qIdx.isValid()) |
| return; |
| |
| if ([[tableColumn identifier] isEqualToString:COLUMNID_NAME]) { |
| cell.title = qIdx.data(Qt::DisplayRole).toString().toNSString(); |
| } |
| } |
| |
| @end |