Source code for src.gui.main_window

# -*- coding: utf-8 -*-
# Copyright 2018 Ross Jacobs All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Main Window is the controlling class for the GUI."""
import sys

from PyQt5.QtWidgets import QMainWindow

from src.gui.modal_dialogs import show_error_dialog, vpn_status_dialog
from src.dashboard_browser.dashboard_browser import DashboardBrowser
from src.modules.vpn_connection import VpnConnection
from src.modules.vpn_tests import TroubleshootVpn
from src.modules.os_utils import is_duplicate_application
from src.gui.menu_bars import MenuBars
import src.gui.gui_setup as gui_setup
from src.gui.systray import SystrayIcon
from src.gui.tshoot_failed_vpn_dialog import tshoot_failed_vpn_dialog

DEBUG = True


[docs]class MainWindow(QMainWindow): """Main Window is the controlling class for the GUI. Attributes: browser (DashboardBrowser): Browser used to store user credentials menu_widget (MenuBars): Used to tie the menu bars to the MainWindow tray_icon (SystrayIcon): Used to tie the tray icon to the MainWindow """ # Telling PyCharm linter not to (incorrectly) inspect PyQt function args # noinspection PyArgumentList def __init__(self): super(MainWindow, self).__init__() # If there's a duplicate instance, close this one if is_duplicate_application('merlink'): show_error_dialog('ERROR: You already have a running merlink ' 'instance!\nThis application will now close.') self.close() # In lieu of sys.exit(app.exec_()) self.browser = DashboardBrowser() # Tie the menu bars, tray_icon, and main window UI to this object. self.menu_widget = MenuBars(self.menuBar()) self.menu_widget.generate_menu_bars() self.tray_icon = SystrayIcon(self) gui_setup.main_window_widget_setup(self) gui_setup.main_window_set_layout(self) self.show()
[docs] def show_main_menu(self): """Show the main menu GUI Is called on main_window instantiation. Creates the scaffolding for the main menu GUI and generates all GUI elements that will be later used by other class methods. Has 2 radio option Qt signals/slots: * Admin user radio option toggled -> Set admin user/pass view * Guest user radio option toggled -> Set guest user/pass view Has 3 program-driving Qt signals/slots: * Organization dropdown changed -> Update model and view * Network dropdown changed -> Update model and view * "Connect" button clicked -> Initiate VPN connection """ # Set up radio button for dashboard/admin user gui_setup.main_window_set_admin_layout(self) self.radio_admin_user.toggled.connect(self.set_admin_layout) self.radio_guest_user.toggled.connect(self.set_guest_layout) org_list = self.browser.get_org_names() self.org_dropdown.addItems(org_list) # Get the data we need and remove the cruft we don't current_org = org_list[self.browser.get_active_org_index()] print('main window orgs', org_list) self.status.showMessage("Status: Fetching networks in " + current_org + "...") self.connect_btn.setEnabled(False) self.vpn_name_textfield.setEnabled(False) # Remove all elements from the network UI dropdown self.network_dropdown.clear() self.refresh_network_dropdown() # All of the major MainWindow slots that signals target self.org_dropdown.currentIndexChanged.connect(self.change_organization) self.network_dropdown.activated.connect(self.change_network) self.connect_btn.clicked.connect(self.setup_vpn)
[docs] def change_organization(self): """Change the org by getting required data and showing it to user * If we don't have data for this org, get info; otherwise don't. * If the user has not selected an organization, this fn will do nothing. """ # -1 due to having a 'select' option. selected_org_index = self.org_dropdown.currentIndex() - 1 self.connect_btn.setEnabled(False) self.vpn_name_textfield.setEnabled(False) if selected_org_index == -1: self.status.showMessage("Status: Select an Organization") self.network_dropdown.setEnabled(False) else: self.network_dropdown.setEnabled(True) self.status.showMessage("Status: Fetching organizations...") # Change primary organization self.browser.set_active_org_index(selected_org_index) print("In change_organization and this is the network list " + str(self.browser.get_active_org_networks())) self.refresh_network_dropdown() self.status.showMessage("Status: In org " + self.browser.get_active_org_name())
[docs] def change_network(self): """Change the network to new value for both model and view This will have been triggered by a network dropdown change. Get network info for this network and let user know. """ # Off by 1 due to Select option current_network_index = self.network_dropdown.currentIndex() - 1 if current_network_index == -1: self.status.showMessage("Status: Select a Network") self.connect_btn.setEnabled(False) self.vpn_name_textfield.setEnabled(False) else: network_list = self.browser.get_active_org_networks() print('main window network list', network_list) current_network = network_list[current_network_index] self.status.showMessage("Status: Fetching network data for " + current_network + "...") self.browser.set_active_network_index(current_network_index) self.browser.get_client_vpn_data() if not self.browser.client_vpn_checks(): self.connect_btn.setEnabled(False) self.vpn_name_textfield.setEnabled(False) error_message = "ERROR: Client VPN is not enabled on " + \ current_network + ".\n\nPlease enable it and"\ + " try again." show_error_dialog(error_message) self.status.showMessage("Status: Client VPN is not enabled on " "'" + current_network + "'. Please " "enable it and try again.") else: self.connect_btn.setEnabled(True) self.status.showMessage("Status: Ready to connect to " + current_network + ".") vpn_name = current_network.replace('- appliance', '') + '- VPN' self.vpn_name_textfield.setText(vpn_name) self.vpn_name_textfield.setEnabled(True)
[docs] def refresh_network_dropdown(self): """Remove old values of the network dropdown and add new ones. Remove previous contents of Networks QComboBox and add new ones according to chosen organization """ self.network_dropdown.clear() self.network_dropdown.addItem('-- Select a Network --') current_org_network_list = self.browser.get_active_org_networks() print('current_org_network_list', current_org_network_list) self.network_dropdown.addItems(current_org_network_list)
[docs] def setup_vpn(self): """Setup VPN vars and start OS-dependent connection scripts Passes vpn vars that are required for an L2TP connection as list vpn_data. Passes OS-specific parameters as list <OS>_options. """ if DEBUG: print("entering attempt_connection function") # If they have not selected organization or network if 'Select' in self.org_dropdown.currentText() or \ 'Select' in self.network_dropdown.currentText(): # They haven't selected an item in one of the message boxes self.show_error_dialog('You must select BOTH an organization ' 'AND network before connecting!') else: # Get current network from dropdown network_name = self.network_dropdown.currentText() # Set VPN name to the network name +/- cosmetic things # If the user is logging in as a guest user if self.radio_admin_user.isChecked() == 0: username = self.radio_username_textfield.text() password = self.radio_password_textfield.text() else: username = self.browser.username password = self.browser.password # Change status to reflect we're connecting. # For fast connections, you might not see this message self.status.showMessage('Status: Connecting...') # Send a list to attempt_connection containing data # from all the textboxes and spinboxes # Create VPN connection vpn_data = [ self.vpn_name_textfield.text(), *self.browser.get_psk_and_address(), username, password ] connection = VpnConnection(vpn_data) if sys.platform == 'win32': windows_options = [ self.dns_suffix_txtbox.text(), self.idle_disconnect_spinbox.value(), self.split_tunneled_chkbox.checkState(), self.remember_credential_chkbox.checkState(), self.use_winlogon_chkbox.checkState(), DEBUG, ] return_code = \ connection.attempt_windows_vpn(windows_options) elif sys.platform == 'darwin': macos_options = [] return_code = \ connection.attempt_macos_vpn(macos_options) # linux, linux2 are valid for linux distros elif sys.platform.startswith('linux'): linux_options = [] return_code = \ connection.attempt_linux_vpn(linux_options) else: return_code = False successful_connection = (return_code == 0) if successful_connection: self.communicate_vpn_success() else: self.communicate_vpn_failure()
[docs] def communicate_vpn_success(self): """Let the user know that they are connected.""" self.status.showMessage('Status: Connected') vpn_status_dialog("Connection Success", "Successfully Connected!") # Show this when connected self.tray_icon.set_vpn_success() self.hide()
[docs] def communicate_vpn_failure(self): """Let the user know that the VPN connection failed.""" self.status.showMessage('Status: Connection Failed') show_error_dialog("Connection Failure") self.tray_icon.set_vpn_failure() self.troubleshoot_connection() # Start troubleshooting self.status.showMessage("Status: Verifying configuration for " + self.current_network + "...") self.tshoot_vpn_fail_gui()
[docs] def tshoot_vpn_fail_gui(self): """Troubleshoot VPN failure and then show the user the results.""" result = TroubleshootVpn(self.fw_status_text, self.client_vpn_text, self.current_ddns, self.current_primary_ip) tshoot_failed_vpn_dialog(result.get_test_results()) self.status.showMessage("Status: Ready to connect to " + self.current_network + ".")
[docs] def is_vpn_connected(self): """Determines whether the VPN is connected. TODO: Implement this function Returns: vpn_status (bool): Whether or not there is an active VPN connection """ vpn_status = False return vpn_status
[docs] def set_admin_layout(self): """Set the dashboard admin layout.""" gui_setup.main_window_set_admin_layout(self)
[docs] def set_guest_layout(self): """Set the guest user layout.""" gui_setup.main_window_set_guest_layout(self)