summaryrefslogtreecommitdiff
path: root/src/leap/bitmask/gui
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2013-08-12 16:25:50 -0300
committerTomás Touceda <chiiph@leap.se>2013-08-12 16:25:50 -0300
commit75a1b6e96b789a8d3d4b9b22bbf62e30ffe62751 (patch)
treecc39f23e95bdbff7495cc866e2f51c1c4f54bc32 /src/leap/bitmask/gui
parent733fd79e1da439604bd45587417fe466a6af9d92 (diff)
parent3c7981e61d3b48f9a000d08056ff79e993c71ce1 (diff)
Merge remote-tracking branch 'kali/feature/create_bitmask_namespace' into develop
Diffstat (limited to 'src/leap/bitmask/gui')
-rw-r--r--src/leap/bitmask/gui/__init__.py21
-rw-r--r--src/leap/bitmask/gui/loggerwindow.py139
-rw-r--r--src/leap/bitmask/gui/login.py245
-rw-r--r--src/leap/bitmask/gui/mainwindow.py1542
-rw-r--r--src/leap/bitmask/gui/statuspanel.py460
-rw-r--r--src/leap/bitmask/gui/twisted_main.py60
-rw-r--r--src/leap/bitmask/gui/ui/loggerwindow.ui155
-rw-r--r--src/leap/bitmask/gui/ui/login.ui132
-rw-r--r--src/leap/bitmask/gui/ui/mainwindow.ui315
-rw-r--r--src/leap/bitmask/gui/ui/statuspanel.ui289
-rw-r--r--src/leap/bitmask/gui/ui/wizard.ui846
-rw-r--r--src/leap/bitmask/gui/wizard.py628
-rw-r--r--src/leap/bitmask/gui/wizardpage.py40
13 files changed, 4872 insertions, 0 deletions
diff --git a/src/leap/bitmask/gui/__init__.py b/src/leap/bitmask/gui/__init__.py
new file mode 100644
index 00000000..4b289442
--- /dev/null
+++ b/src/leap/bitmask/gui/__init__.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# __init__.py
+# Copyright (C) 2013 LEAP
+#
+# 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, see <http://www.gnu.org/licenses/>.
+"""
+init file for leap.gui
+"""
+app = __import__("app", globals(), locals(), [], 2)
+__all__ = [app]
diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py
new file mode 100644
index 00000000..981bf65d
--- /dev/null
+++ b/src/leap/bitmask/gui/loggerwindow.py
@@ -0,0 +1,139 @@
+# -*- coding: utf-8 -*-
+# loggerwindow.py
+# Copyright (C) 2013 LEAP
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+"""
+History log window
+"""
+import logging
+
+from PySide import QtGui
+
+from ui_loggerwindow import Ui_LoggerWindow
+
+from leap.bitmask.util.leap_log_handler import LeapLogHandler
+from leap.common.check import leap_assert, leap_assert_type
+
+logger = logging.getLogger(__name__)
+
+
+class LoggerWindow(QtGui.QDialog):
+ """
+ Window that displays a history of the logged messages in the app.
+ """
+ def __init__(self, handler):
+ """
+ Initialize the widget with the custom handler.
+
+ :param handler: Custom handler that supports history and signal.
+ :type handler: LeapLogHandler.
+ """
+ QtGui.QDialog.__init__(self)
+ leap_assert(handler, "We need a handler for the logger window")
+ leap_assert_type(handler, LeapLogHandler)
+
+ # Load UI
+ self.ui = Ui_LoggerWindow()
+ self.ui.setupUi(self)
+
+ # Make connections
+ self.ui.btnSave.clicked.connect(self._save_log_to_file)
+ self.ui.btnDebug.toggled.connect(self._load_history),
+ self.ui.btnInfo.toggled.connect(self._load_history),
+ self.ui.btnWarning.toggled.connect(self._load_history),
+ self.ui.btnError.toggled.connect(self._load_history),
+ self.ui.btnCritical.toggled.connect(self._load_history)
+
+ # Load logging history and connect logger with the widget
+ self._logging_handler = handler
+ self._connect_to_handler()
+ self._load_history()
+
+ def _connect_to_handler(self):
+ """
+ This method connects the loggerwindow with the handler through a
+ signal communicate the logger events.
+ """
+ self._logging_handler.new_log.connect(self._add_log_line)
+
+ def _add_log_line(self, log):
+ """
+ Adds a line to the history, only if it's in the desired levels to show.
+
+ :param log: a log record to be inserted in the widget
+ :type log: a dict with RECORD_KEY and MESSAGE_KEY.
+ the record contains the LogRecord of the logging module,
+ the message contains the formatted message for the log.
+ """
+ html_style = {
+ logging.DEBUG: "background: #CDFFFF;",
+ logging.INFO: "background: white;",
+ logging.WARNING: "background: #FFFF66;",
+ logging.ERROR: "background: red; color: white;",
+ logging.CRITICAL: "background: red; color: white; font: bold;"
+ }
+ level = log[LeapLogHandler.RECORD_KEY].levelno
+ message = log[LeapLogHandler.MESSAGE_KEY]
+ message = message.replace('\n', '<br>\n')
+
+ if self._logs_to_display[level]:
+ open_tag = "<tr style='" + html_style[level] + "'>"
+ open_tag += "<td width='100%' style='padding: 5px;'>"
+ close_tag = "</td></tr>"
+ message = open_tag + message + close_tag
+
+ self.ui.txtLogHistory.append(message)
+
+ def _load_history(self):
+ """
+ Load the previous logged messages in the widget.
+ They are stored in the custom handler.
+ """
+ self._set_logs_to_display()
+ self.ui.txtLogHistory.clear()
+ history = self._logging_handler.log_history
+ for line in history:
+ self._add_log_line(line)
+
+ def _set_logs_to_display(self):
+ """
+ Sets the logs_to_display dict getting the toggled options from the ui
+ """
+ self._logs_to_display = {
+ logging.DEBUG: self.ui.btnDebug.isChecked(),
+ logging.INFO: self.ui.btnInfo.isChecked(),
+ logging.WARNING: self.ui.btnWarning.isChecked(),
+ logging.ERROR: self.ui.btnError.isChecked(),
+ logging.CRITICAL: self.ui.btnCritical.isChecked()
+ }
+
+ def _save_log_to_file(self):
+ """
+ Lets the user save the current log to a file
+ """
+ fileName, filtr = QtGui.QFileDialog.getSaveFileName(
+ self, self.tr("Save As"))
+
+ if fileName:
+ try:
+ with open(fileName, 'w') as output:
+ output.write(self.ui.txtLogHistory.toPlainText())
+ output.write('\n')
+ logger.debug('Log saved in %s' % (fileName, ))
+ except IOError, e:
+ logger.error("Error saving log file: %r" % (e, ))
+ else:
+ logger.debug('Log not saved!')
diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py
new file mode 100644
index 00000000..db7b8e2a
--- /dev/null
+++ b/src/leap/bitmask/gui/login.py
@@ -0,0 +1,245 @@
+# -*- coding: utf-8 -*-
+# login.py
+# Copyright (C) 2013 LEAP
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Login widget implementation
+"""
+import logging
+
+from PySide import QtCore, QtGui
+from ui_login import Ui_LoginWidget
+
+from leap.bitmask.util.keyring_helpers import has_keyring
+
+logger = logging.getLogger(__name__)
+
+
+class LoginWidget(QtGui.QWidget):
+ """
+ Login widget that emits signals to display the wizard or to
+ perform login.
+ """
+
+ # Emitted when the login button is clicked
+ login = QtCore.Signal()
+ cancel_login = QtCore.Signal()
+
+ # Emitted when the user selects "Other..." in the provider
+ # combobox or click "Create Account"
+ show_wizard = QtCore.Signal()
+
+ MAX_STATUS_WIDTH = 40
+
+ BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$"
+
+ def __init__(self, settings, parent=None):
+ """
+ Constructs the LoginWidget.
+
+ :param settings: client wide settings
+ :type settings: LeapSettings
+ :param parent: The parent widget for this widget
+ :type parent: QWidget or None
+ """
+ QtGui.QWidget.__init__(self, parent)
+
+ self._settings = settings
+ self._selected_provider_index = -1
+
+ self.ui = Ui_LoginWidget()
+ self.ui.setupUi(self)
+
+ self.ui.chkRemember.stateChanged.connect(
+ self._remember_state_changed)
+ self.ui.chkRemember.setEnabled(has_keyring())
+
+ self.ui.lnPassword.setEchoMode(QtGui.QLineEdit.Password)
+
+ self.ui.btnLogin.clicked.connect(self.login)
+ self.ui.lnPassword.returnPressed.connect(self.login)
+
+ self.ui.lnUser.returnPressed.connect(self._focus_password)
+
+ self.ui.cmbProviders.currentIndexChanged.connect(
+ self._current_provider_changed)
+ self.ui.btnCreateAccount.clicked.connect(
+ self.show_wizard)
+
+ username_re = QtCore.QRegExp(self.BARE_USERNAME_REGEX)
+ self.ui.lnUser.setValidator(
+ QtGui.QRegExpValidator(username_re, self))
+
+ def _remember_state_changed(self, state):
+ """
+ Saves the remember state in the LeapSettings
+
+ :param state: possible stats can be Checked, Unchecked and
+ PartiallyChecked
+ :type state: QtCore.Qt.CheckState
+ """
+ enable = True if state == QtCore.Qt.Checked else False
+ self._settings.set_remember(enable)
+
+ def set_providers(self, provider_list):
+ """
+ Set the provider list to provider_list plus an "Other..." item
+ that triggers the wizard
+
+ :param provider_list: list of providers
+ :type provider_list: list of str
+ """
+ self.ui.cmbProviders.blockSignals(True)
+ self.ui.cmbProviders.clear()
+ self.ui.cmbProviders.addItems(provider_list + [self.tr("Other...")])
+ self.ui.cmbProviders.blockSignals(False)
+
+ def select_provider_by_name(self, name):
+ """
+ Given a provider name/domain, it selects it in the combobox
+
+ :param name: name or domain for the provider
+ :type name: str
+ """
+ provider_index = self.ui.cmbProviders.findText(name)
+ self.ui.cmbProviders.setCurrentIndex(provider_index)
+
+ def get_selected_provider(self):
+ """
+ Returns the selected provider in the combobox
+ """
+ return self.ui.cmbProviders.currentText()
+
+ def set_remember(self, value):
+ """
+ Checks the remember user and password checkbox
+
+ :param value: True to mark it checked, False otherwise
+ :type value: bool
+ """
+ self.ui.chkRemember.setChecked(value)
+
+ def get_remember(self):
+ """
+ Returns the remember checkbox state
+
+ :rtype: bool
+ """
+ return self.ui.chkRemember.isChecked()
+
+ def set_user(self, user):
+ """
+ Sets the user and focuses on the next field, password.
+
+ :param user: user to set the field to
+ :type user: str
+ """
+ self.ui.lnUser.setText(user)
+ self._focus_password()
+
+ def get_user(self):
+ """
+ Returns the user that appears in the widget.
+
+ :rtype: str
+ """
+ return self.ui.lnUser.text()
+
+ def set_password(self, password):
+ """
+ Sets the password for the widget
+
+ :param password: password to set
+ :type password: str
+ """
+ self.ui.lnPassword.setText(password)
+
+ def get_password(self):
+ """
+ Returns the password that appears in the widget
+
+ :rtype: str
+ """
+ return self.ui.lnPassword.text()
+
+ def set_status(self, status, error=True):
+ """
+ Sets the status label at the login stage to status
+
+ :param status: status message
+ :type status: str
+ """
+ if len(status) > self.MAX_STATUS_WIDTH:
+ status = status[:self.MAX_STATUS_WIDTH] + "..."
+ if error:
+ status = "<font color='red'><b>%s</b></font>" % (status,)
+ self.ui.lblStatus.setText(status)
+
+ def set_enabled(self, enabled=False):
+ """
+ Enables or disables all the login widgets
+
+ :param enabled: wether they should be enabled or not
+ :type enabled: bool
+ """
+ self.ui.lnUser.setEnabled(enabled)
+ self.ui.lnPassword.setEnabled(enabled)
+ self.ui.chkRemember.setEnabled(enabled)
+ self.ui.cmbProviders.setEnabled(enabled)
+
+ self._set_cancel(not enabled)
+
+ def _set_cancel(self, enabled=False):
+ """
+ Enables or disables the cancel action in the "log in" process.
+
+ :param enabled: wether it should be enabled or not
+ :type enabled: bool
+ """
+ text = self.tr("Cancel")
+ login_or_cancel = self.cancel_login
+
+ if not enabled:
+ text = self.tr("Log In")
+ login_or_cancel = self.login
+
+ self.ui.btnLogin.setText(text)
+
+ self.ui.btnLogin.clicked.disconnect()
+ self.ui.btnLogin.clicked.connect(login_or_cancel)
+
+ def _focus_password(self):
+ """
+ Focuses in the password lineedit
+ """
+ self.ui.lnPassword.setFocus()
+
+ def _current_provider_changed(self, param):
+ """
+ SLOT
+ TRIGGERS: self.ui.cmbProviders.currentIndexChanged
+ """
+ if param == (self.ui.cmbProviders.count() - 1):
+ self.show_wizard.emit()
+ # Leave the previously selected provider in the combobox
+ prev_provider = 0
+ if self._selected_provider_index != -1:
+ prev_provider = self._selected_provider_index
+ self.ui.cmbProviders.blockSignals(True)
+ self.ui.cmbProviders.setCurrentIndex(prev_provider)
+ self.ui.cmbProviders.blockSignals(False)
+ else:
+ self._selected_provider_index = param
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
new file mode 100644
index 00000000..6dd28f04
--- /dev/null
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -0,0 +1,1542 @@
+# -*- coding: utf-8 -*-
+# mainwindow.py
+# Copyright (C) 2013 LEAP
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Main window for the leap client
+"""
+import logging
+import os
+import platform
+import tempfile
+from functools import partial
+
+import keyring
+
+from PySide import QtCore, QtGui
+from twisted.internet import threads
+
+from leap.bitmask.config.leapsettings import LeapSettings
+from leap.bitmask.config.providerconfig import ProviderConfig
+from leap.bitmask.crypto.srpauth import SRPAuth
+from leap.bitmask.gui.loggerwindow import LoggerWindow
+from leap.bitmask.gui.wizard import Wizard
+from leap.bitmask.gui.login import LoginWidget
+from leap.bitmask.gui.statuspanel import StatusPanelWidget
+from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper
+from leap.bitmask.services.eip.eipconfig import EIPConfig
+from leap.bitmask.services.eip.providerbootstrapper import ProviderBootstrapper
+# XXX: Soledad might not work out of the box in Windows, issue #2932
+from leap.bitmask.services.soledad.soledadbootstrapper import \
+ SoledadBootstrapper
+from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper
+from leap.bitmask.services.mail import imap
+from leap.bitmask.platform_init import IS_WIN, IS_MAC
+from leap.bitmask.platform_init.initializers import init_platform
+
+from leap.bitmask.services.eip.vpnprocess import VPN
+from leap.bitmask.services.eip.vpnprocess import OpenVPNAlreadyRunning
+from leap.bitmask.services.eip.vpnprocess import AlienOpenVPNAlreadyRunning
+
+from leap.bitmask.services.eip.vpnlaunchers import VPNLauncherException
+from leap.bitmask.services.eip.vpnlaunchers import OpenVPNNotFoundException
+from leap.bitmask.services.eip.vpnlaunchers import EIPNoPkexecAvailable
+from leap.bitmask.services.eip.vpnlaunchers import \
+ EIPNoPolkitAuthAgentAvailable
+from leap.bitmask.services.eip.vpnlaunchers import EIPNoTunKextLoaded
+
+from leap.bitmask.util import __version__ as VERSION
+from leap.bitmask.util.keyring_helpers import has_keyring
+
+from leap.bitmask.services.mail.smtpconfig import SMTPConfig
+
+if IS_WIN:
+ from leap.bitmask.platform_init.locks import WindowsLock
+ from leap.bitmask.platform_init.locks import raise_window_ack
+
+from leap.common.check import leap_assert
+from leap.common.events import register
+from leap.common.events import events_pb2 as proto
+
+from ui_mainwindow import Ui_MainWindow
+
+logger = logging.getLogger(__name__)
+
+
+class MainWindow(QtGui.QMainWindow):
+ """
+ Main window for login and presenting status updates to the user
+ """
+
+ # StackedWidget indexes
+ LOGIN_INDEX = 0
+ EIP_STATUS_INDEX = 1
+
+ # Keyring
+ KEYRING_KEY = "bitmask"
+
+ # SMTP
+ PORT_KEY = "port"
+ IP_KEY = "ip_address"
+
+ OPENVPN_SERVICE = "openvpn"
+ MX_SERVICE = "mx"
+
+ # Signals
+ new_updates = QtCore.Signal(object)
+ raise_window = QtCore.Signal([])
+ soledad_ready = QtCore.Signal([])
+
+ # We use this flag to detect abnormal terminations
+ user_stopped_eip = False
+
+ def __init__(self, quit_callback,
+ standalone=False,
+ openvpn_verb=1,
+ bypass_checks=False):
+ """
+ Constructor for the client main window
+
+ :param quit_callback: Function to be called when closing
+ the application.
+ :type quit_callback: callable
+
+ :param standalone: Set to true if the app should use configs
+ inside its pwd
+ :type standalone: bool
+
+ :param bypass_checks: Set to true if the app should bypass
+ first round of checks for CA
+ certificates at bootstrap
+ :type bypass_checks: bool
+ """
+ QtGui.QMainWindow.__init__(self)
+
+ # register leap events
+ register(signal=proto.UPDATER_NEW_UPDATES,
+ callback=self._new_updates_available,
+ reqcbk=lambda req, resp: None) # make rpc call async
+ register(signal=proto.RAISE_WINDOW,
+ callback=self._on_raise_window_event,
+ reqcbk=lambda req, resp: None) # make rpc call async
+
+ self._quit_callback = quit_callback
+
+ self._updates_content = ""
+
+ self.ui = Ui_MainWindow()
+ self.ui.setupUi(self)
+
+ self._settings = LeapSettings(standalone)
+
+ self._login_widget = LoginWidget(
+ self._settings,
+ self.ui.stackedWidget.widget(self.LOGIN_INDEX))
+ self.ui.loginLayout.addWidget(self._login_widget)
+
+ # Signals
+ # TODO separate logic from ui signals.
+
+ self._login_widget.login.connect(self._login)
+ self._login_widget.cancel_login.connect(self._cancel_login)
+ self._login_widget.show_wizard.connect(
+ self._launch_wizard)
+
+ self.ui.btnShowLog.clicked.connect(self._show_logger_window)
+
+ self._status_panel = StatusPanelWidget(
+ self.ui.stackedWidget.widget(self.EIP_STATUS_INDEX))
+ self.ui.statusLayout.addWidget(self._status_panel)
+
+ self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX)
+
+ self._status_panel.start_eip.connect(self._start_eip)
+ self._status_panel.stop_eip.connect(self._stop_eip)
+
+ # This is loaded only once, there's a bug when doing that more
+ # than once
+ ProviderConfig.standalone = standalone
+ EIPConfig.standalone = standalone
+ self._standalone = standalone
+ self._provider_config = ProviderConfig()
+ # Used for automatic start of EIP
+ self._provisional_provider_config = ProviderConfig()
+ self._eip_config = EIPConfig()
+
+ self._already_started_eip = False
+
+ # This is created once we have a valid provider config
+ self._srp_auth = None
+ self._logged_user = None
+
+ # This thread is always running, although it's quite
+ # lightweight when it's done setting up provider
+ # configuration and certificate.
+ self._provider_bootstrapper = ProviderBootstrapper(bypass_checks)
+
+ # Intermediate stages, only do something if there was an error
+ self._provider_bootstrapper.name_resolution.connect(
+ self._intermediate_stage)
+ self._provider_bootstrapper.https_connection.connect(
+ self._intermediate_stage)
+ self._provider_bootstrapper.download_ca_cert.connect(
+ self._intermediate_stage)
+
+ # Important stages, loads the provider config and checks
+ # certificates
+ self._provider_bootstrapper.download_provider_info.connect(
+ self._load_provider_config)
+ self._provider_bootstrapper.check_api_certificate.connect(
+ self._provider_config_loaded)
+
+ # This thread is similar to the provider bootstrapper
+ self._eip_bootstrapper = EIPBootstrapper()
+
+ self._eip_bootstrapper.download_config.connect(
+ self._eip_intermediate_stage)
+ self._eip_bootstrapper.download_client_certificate.connect(
+ self._finish_eip_bootstrap)
+
+ self._soledad_bootstrapper = SoledadBootstrapper()
+ self._soledad_bootstrapper.download_config.connect(
+ self._soledad_intermediate_stage)
+ self._soledad_bootstrapper.gen_key.connect(
+ self._soledad_bootstrapped_stage)
+
+ self._smtp_bootstrapper = SMTPBootstrapper()
+ self._smtp_bootstrapper.download_config.connect(
+ self._smtp_bootstrapped_stage)
+
+ self._vpn = VPN(openvpn_verb=openvpn_verb)
+ self._vpn.qtsigs.state_changed.connect(
+ self._status_panel.update_vpn_state)
+ self._vpn.qtsigs.status_changed.connect(
+ self._status_panel.update_vpn_status)
+ self._vpn.qtsigs.process_finished.connect(
+ self._eip_finished)
+
+ self.ui.action_log_out.setEnabled(False)
+ self.ui.action_log_out.triggered.connect(self._logout)
+ self.ui.action_about_leap.triggered.connect(self._about)
+ self.ui.action_quit.triggered.connect(self.quit)
+ self.ui.action_wizard.triggered.connect(self._launch_wizard)
+ self.ui.action_show_logs.triggered.connect(self._show_logger_window)
+ self.raise_window.connect(self._do_raise_mainwindow)
+
+ # Used to differentiate between real quits and close to tray
+ self._really_quit = False
+
+ self._systray = None
+
+ self._action_eip_provider = QtGui.QAction(
+ self.tr("No default provider"), self)
+ self._action_eip_provider.setEnabled(False)
+ self._action_eip_status = QtGui.QAction(
+ self.tr("Encrypted internet is OFF"),
+ self)
+ self._action_eip_status.setEnabled(False)
+
+ self._status_panel.set_action_eip_status(
+ self._action_eip_status)
+
+ self._action_eip_startstop = QtGui.QAction(
+ self.tr("Turn OFF"), self)
+ self._action_eip_startstop.triggered.connect(
+ self._stop_eip)
+ self._action_eip_startstop.setEnabled(False)
+ self._status_panel.set_action_eip_startstop(
+ self._action_eip_startstop)
+
+ self._action_visible = QtGui.QAction(self.tr("Hide Main Window"), self)
+ self._action_visible.triggered.connect(self._toggle_visible)
+
+ self._enabled_services = []
+
+ self._center_window()
+
+ self.ui.lblNewUpdates.setVisible(False)
+ self.ui.btnMore.setVisible(False)
+ self.ui.btnMore.clicked.connect(self._updates_details)
+
+ self.new_updates.connect(self._react_to_new_updates)
+ self.soledad_ready.connect(self._start_imap_service)
+
+ init_platform()
+
+ self._wizard = None
+ self._wizard_firstrun = False
+
+ self._logger_window = None
+
+ self._bypass_checks = bypass_checks
+
+ self._soledad = None
+ self._keymanager = None
+ self._imap_service = None
+
+ self._login_defer = None
+ self._download_provider_defer = None
+
+ self._smtp_config = SMTPConfig()
+
+ if self._first_run():
+ self._wizard_firstrun = True
+ self._wizard = Wizard(standalone=standalone,
+ bypass_checks=bypass_checks)
+ # Give this window time to finish init and then show the wizard
+ QtCore.QTimer.singleShot(1, self._launch_wizard)
+ self._wizard.accepted.connect(self._finish_init)
+ self._wizard.rejected.connect(self._rejected_wizard)
+ else:
+ self._finish_init()
+
+ def _rejected_wizard(self):
+ """
+ SLOT
+ TRIGGERS: self._wizard.rejected
+
+ Called if the wizard has been cancelled or closed before
+ finishing.
+ """
+ if self._wizard_firstrun:
+ self._settings.set_properprovider(False)
+ self.quit()
+ else:
+ self._finish_init()
+
+ def _launch_wizard(self):
+ """
+ SLOT
+ TRIGGERS:
+ self._login_widget.show_wizard
+ self.ui.action_wizard.triggered
+
+ Also called in first run.
+
+ Launches the wizard, creating the object itself if not already
+ there.
+ """
+ if self._wizard is None:
+ self._wizard = Wizard(bypass_checks=self._bypass_checks)
+ self._wizard.accepted.connect(self._finish_init)
+ self._wizard.rejected.connect(self._wizard.close)
+
+ self.setVisible(False)
+ # Do NOT use exec_, it will use a child event loop!
+ # Refer to http://www.themacaque.com/?p=1067 for funny details.
+ self._wizard.show()
+ if IS_MAC:
+ self._wizard.raise_()
+ self._wizard.finished.connect(self._wizard_finished)
+
+ def _wizard_finished(self):
+ """
+ SLOT
+ TRIGGERS
+ self._wizard.finished
+
+ Called when the wizard has finished.
+ """
+ self.setVisible(True)
+
+ def _get_leap_logging_handler(self):
+ """
+ Gets the leap handler from the top level logger
+
+ :return: a logging handler or None
+ :rtype: LeapLogHandler or None
+ """
+ from leap.util.leap_log_handler import LeapLogHandler
+ leap_logger = logging.getLogger('leap')
+ for h in leap_logger.handlers:
+ if isinstance(h, LeapLogHandler):
+ return h
+ return None
+
+ def _show_logger_window(self):
+ """
+ SLOT
+ TRIGGERS:
+ self.ui.action_show_logs.triggered
+ self.ui.btnShowLog.clicked
+
+ Displays the window with the history of messages logged until now
+ and displays the new ones on arrival.
+ """
+ if self._logger_window is None:
+ leap_log_handler = self._get_leap_logging_handler()
+ if leap_log_handler is None:
+ logger.error('Leap logger handler not found')
+ else:
+ self._logger_window = LoggerWindow(handler=leap_log_handler)
+ self._logger_window.setVisible(
+ not self._logger_window.isVisible())
+ self.ui.btnShowLog.setChecked(self._logger_window.isVisible())
+ else:
+ self._logger_window.setVisible(not self._logger_window.isVisible())
+ self.ui.btnShowLog.setChecked(self._logger_window.isVisible())
+
+ self._logger_window.finished.connect(self._uncheck_logger_button)
+
+ def _uncheck_logger_button(self):
+ """
+ SLOT
+ Sets the checked state of the loggerwindow button to false.
+ """
+ self.ui.btnShowLog.setChecked(False)
+
+ def _new_updates_available(self, req):
+ """
+ Callback for the new updates event
+
+ :param req: Request type
+ :type req: leap.common.events.events_pb2.SignalRequest
+ """
+ self.new_updates.emit(req)
+
+ def _react_to_new_updates(self, req):
+ """
+ SLOT
+ TRIGGER: self._new_updates_available
+
+ Displays the new updates label and sets the updates_content
+ """
+ self.moveToThread(QtCore.QCoreApplication.instance().thread())
+ self.ui.lblNewUpdates.setVisible(True)
+ self.ui.btnMore.setVisible(True)
+ self._updates_content = req.content
+
+ def _updates_details(self):
+ """
+ SLOT
+ TRIGGER: self.ui.btnMore.clicked
+
+ Parses and displays the updates details
+ """
+ msg = self.tr("The Bitmask app is ready to update, please"
+ " restart the application.")
+
+ # We assume that if there is nothing in the contents, then
+ # the Bitmask bundle is what needs updating.
+ if len(self._updates_content) > 0:
+ files = self._updates_content.split(", ")
+ files_str = ""
+ for f in files:
+ final_name = f.replace("/data/", "")
+ final_name = final_name.replace(".thp", "")
+ files_str += final_name
+ files_str += "\n"
+ msg += self.tr(" The following components will be updated:\n%s") \
+ % (files_str,)
+
+ QtGui.QMessageBox.information(self,
+ self.tr("Updates available"),
+ msg)
+
+ def _finish_init(self):
+ """
+ SLOT
+ TRIGGERS:
+ self._wizard.accepted
+
+ Also called at the end of the constructor if not first run,
+ and after _rejected_wizard if not first run.
+
+ Implements the behavior after either constructing the
+ mainwindow object, loading the saved user/password, or after
+ the wizard has been executed.
+ """
+ # XXX: May be this can be divided into two methods?
+
+ self._login_widget.set_providers(self._configured_providers())
+ self._show_systray()
+ self.show()
+ if IS_MAC:
+ self.raise_()
+
+ if self._wizard:
+ possible_username = self._wizard.get_username()
+ possible_password = self._wizard.get_password()
+
+ # select the configured provider in the combo box
+ domain = self._wizard.get_domain()
+ self._login_widget.select_provider_by_name(domain)
+
+ self._login_widget.set_remember(self._wizard.get_remember())
+ self._enabled_services = list(self._wizard.get_services())
+ self._settings.set_enabled_services(
+ self._login_widget.get_selected_provider(),
+ self._enabled_services)
+ if possible_username is not None:
+ self._login_widget.set_user(possible_username)
+ if possible_password is not None:
+ self._login_widget.set_password(possible_password)
+ self._login()
+ self._wizard = None
+ self._settings.set_properprovider(True)
+ else:
+ self._try_autostart_eip()
+ if not self._settings.get_remember():
+ # nothing to do here
+ return
+
+ saved_user = self._settings.get_user()
+
+ try:
+ username, domain = saved_user.split('@')
+ except (ValueError, AttributeError) as e:
+ # if the saved_user does not contain an '@' or its None
+ logger.error('Username@provider malformed. %r' % (e, ))
+ saved_user = None
+
+ if saved_user is not None and has_keyring():
+ # fill the username
+ self._login_widget.set_user(username)
+
+ # select the configured provider in the combo box
+ self._login_widget.select_provider_by_name(domain)
+
+ self._login_widget.set_remember(True)
+
+ saved_password = None
+ try:
+ saved_password = keyring.get_password(self.KEYRING_KEY,
+ saved_user
+ .encode("utf8"))
+ except ValueError, e:
+ logger.debug("Incorrect Password. %r." % (e,))
+
+ if saved_password is not None:
+ self._login_widget.set_password(
+ saved_password.decode("utf8"))
+ self._login()
+
+ def _try_autostart_eip(self):
+ """
+ Tries to autostart EIP
+ """
+ default_provider = self._settings.get_defaultprovider()
+
+ if default_provider is None:
+ logger.info("Cannot autostart Encrypted Internet because there is "
+ "no default provider configured")
+ return
+
+ self._action_eip_provider.setText(default_provider)
+
+ self._enabled_services = self._settings.get_enabled_services(
+ default_provider)
+
+ if self._provisional_provider_config.load(
+ os.path.join("leap",
+ "providers",
+ default_provider,
+ "provider.json")):
+ self._download_eip_config()
+ else:
+ # XXX: Display a proper message to the user
+ logger.error("Unable to load %s config, cannot autostart." %
+ (default_provider,))
+
+ def _show_systray(self):
+ """
+ Sets up the systray icon
+ """
+ if self._systray is not None:
+ self._systray.setVisible(True)
+ return
+
+ # Placeholder actions
+ # They are temporary to display the tray as designed
+ preferences_action = QtGui.QAction(self.tr("Preferences"), self)
+ preferences_action.setEnabled(False)
+ help_action = QtGui.QAction(self.tr("Help"), self)
+ help_action.setEnabled(False)
+
+ systrayMenu = QtGui.QMenu(self)
+ systrayMenu.addAction(self._action_visible)
+ systrayMenu.addSeparator()
+ systrayMenu.addAction(self._action_eip_provider)
+ systrayMenu.addAction(self._action_eip_status)
+ systrayMenu.addAction(self._action_eip_startstop)
+ systrayMenu.addSeparator()
+ systrayMenu.addAction(preferences_action)
+ systrayMenu.addAction(help_action)
+ systrayMenu.addSeparator()
+ systrayMenu.addAction(self.ui.action_log_out)
+ systrayMenu.addAction(self.ui.action_quit)
+ self._systray = QtGui.QSystemTrayIcon(self)
+ self._systray.setContextMenu(systrayMenu)
+ self._systray.setIcon(self._status_panel.ERROR_ICON_TRAY)
+ self._systray.setVisible(True)
+ self._systray.activated.connect(self._tray_activated)
+
+ self._status_panel.set_systray(self._systray)
+
+ def _tray_activated(self, reason=None):
+ """
+ SLOT
+ TRIGGER: self._systray.activated
+
+ Displays the context menu from the tray icon
+ """
+ self._update_hideshow_menu()
+
+ context_menu = self._systray.contextMenu()
+ if not IS_MAC:
+ # for some reason, context_menu.show()
+ # is failing in a way beyond my understanding.
+ # (not working the first time it's clicked).
+ # this works however.
+ context_menu.exec_(self._systray.geometry().center())
+
+ def _update_hideshow_menu(self):
+ """
+ Updates the Hide/Show main window menu text based on the
+ visibility of the window.
+ """
+ get_action = lambda visible: (
+ self.tr("Show Main Window"),
+ self.tr("Hide Main Window"))[int(visible)]
+
+ # set labels
+ visible = self.isVisible() and self.isActiveWindow()
+ self._action_visible.setText(get_action(visible))
+
+ def _toggle_visible(self):
+ """
+ SLOT
+ TRIGGER: self._action_visible.triggered
+
+ Toggles the window visibility
+ """
+ visible = self.isVisible() and self.isActiveWindow()
+ if not visible:
+ self.show()
+ self.activateWindow()
+ self.raise_()
+ else:
+ self.hide()
+
+ self._update_hideshow_menu()
+
+ def _center_window(self):
+ """
+ Centers the mainwindow based on the desktop geometry
+ """
+ geometry = self._settings.get_geometry()
+ state = self._settings.get_windowstate()
+
+ if geometry is None:
+ app = QtGui.QApplication.instance()
+ width = app.desktop().width()
+ height = app.desktop().height()
+ window_width = self.size().width()
+ window_height = self.size().height()
+ x = (width / 2.0) - (window_width / 2.0)
+ y = (height / 2.0) - (window_height / 2.0)
+ self.move(x, y)
+ else:
+ self.restoreGeometry(geometry)
+
+ if state is not None:
+ self.restoreState(state)
+
+ def _about(self):
+ """
+ SLOT
+ TRIGGERS: self.ui.action_about_leap.triggered
+
+ Display the About Bitmask dialog
+ """
+ QtGui.QMessageBox.about(
+ self, self.tr("About Bitmask - %s") % (VERSION,),
+ self.tr("Version: <b>%s</b><br>"
+ "<br>"
+ "Bitmask is the Desktop client application for "
+ "the LEAP platform, supporting encrypted internet "
+ "proxy, secure email, and secure chat (coming soon).<br>"
+ "<br>"
+ "LEAP is a non-profit dedicated to giving "
+ "all internet users access to secure "
+ "communication. Our focus is on adapting "
+ "encryption technology to make it easy to use "
+ "and widely available. <br>"
+ "<br>"
+ "<a href='https://leap.se'>More about LEAP"
+ "</a>") % (VERSION,))
+
+ def changeEvent(self, e):
+ """
+ Reimplements the changeEvent method to minimize to tray
+ """
+ if QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \
+ e.type() == QtCore.QEvent.WindowStateChange and \
+ self.isMinimized():
+ self._toggle_visible()
+ e.accept()
+ return
+ QtGui.QMainWindow.changeEvent(self, e)
+
+ def closeEvent(self, e):
+ """
+ Reimplementation of closeEvent to close to tray
+ """
+ if QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \
+ not self._really_quit:
+ self._toggle_visible()
+ e.ignore()
+ return
+
+ self._settings.set_geometry(self.saveGeometry())
+ self._settings.set_windowstate(self.saveState())
+
+ QtGui.QMainWindow.closeEvent(self, e)
+
+ def _configured_providers(self):
+ """
+ Returns the available providers based on the file structure
+
+ :rtype: list
+ """
+
+ # TODO: check which providers have a valid certificate among
+ # other things, not just the directories
+ providers = []
+ try:
+ providers = os.listdir(
+ os.path.join(self._provider_config.get_path_prefix(),
+ "leap",
+ "providers"))
+ except Exception as e:
+ logger.debug("Error listing providers, assume there are none. %r"
+ % (e,))
+
+ return providers
+
+ def _first_run(self):
+ """
+ Returns True if there are no configured providers. False otherwise
+
+ :rtype: bool
+ """
+ has_provider_on_disk = len(self._configured_providers()) != 0
+ is_proper_provider = self._settings.get_properprovider()
+ return not (has_provider_on_disk and is_proper_provider)
+
+ def _download_provider_config(self):
+ """
+ Starts the bootstrapping sequence. It will download the
+ provider configuration if it's not present, otherwise will
+ emit the corresponding signals inmediately
+ """
+ provider = self._login_widget.get_selected_provider()
+
+ pb = self._provider_bootstrapper
+ d = pb.run_provider_select_checks(provider, download_if_needed=True)
+ self._download_provider_defer = d
+
+ def _load_provider_config(self, data):
+ """
+ SLOT
+ TRIGGER: self._provider_bootstrapper.download_provider_info
+
+ Once the provider config has been downloaded, this loads the
+ self._provider_config instance with it and starts the second
+ part of the bootstrapping sequence
+
+ :param data: result from the last stage of the
+ run_provider_select_checks
+ :type data: dict
+ """
+ if data[self._provider_bootstrapper.PASSED_KEY]:
+ provider = self._login_widget.get_selected_provider()
+
+ # If there's no loaded provider or
+ # we want to connect to other provider...
+ if (not self._provider_config.loaded() or
+ self._provider_config.get_domain() != provider):
+ self._provider_config.load(
+ os.path.join("leap", "providers",
+ provider, "provider.json"))
+
+ if self._provider_config.loaded():
+ self._provider_bootstrapper.run_provider_setup_checks(
+ self._provider_config,
+ download_if_needed=True)
+ else:
+ self._login_widget.set_status(
+ self.tr("Unable to login: Problem with provider"))
+ logger.error("Could not load provider configuration.")
+ self._login_widget.set_enabled(True)
+ else:
+ self._login_widget.set_status(
+ self.tr("Unable to login: Problem with provider"))
+ logger.error(data[self._provider_bootstrapper.ERROR_KEY])
+ self._login_widget.set_enabled(True)
+
+ def _login(self):
+ """
+ SLOT
+ TRIGGERS:
+ self._login_widget.login
+
+ Starts the login sequence. Which involves bootstrapping the
+ selected provider if the selection is valid (not empty), then
+ start the SRP authentication, and as the last step
+ bootstrapping the EIP service
+ """
+ leap_assert(self._provider_config, "We need a provider config")
+
+ username = self._login_widget.get_user()
+ password = self._login_widget.get_password()
+ provider = self._login_widget.get_selected_provider()
+
+ self._enabled_services = self._settings.get_enabled_services(
+ self._login_widget.get_selected_provider())
+
+ if len(provider) == 0:
+ self._login_widget.set_status(
+ self.tr("Please select a valid provider"))
+ return
+
+ if len(username) == 0:
+ self._login_widget.set_status(
+ self.tr("Please provide a valid username"))
+ return
+
+ if len(password) == 0:
+ self._login_widget.set_status(
+ self.tr("Please provide a valid Password"))
+ return
+
+ self._login_widget.set_status(self.tr("Logging in..."), error=False)
+ self._login_widget.set_enabled(False)
+
+ if self._login_widget.get_remember() and has_keyring():
+ # in the keyring and in the settings
+ # we store the value 'usename@provider'
+ username_domain = (username + '@' + provider).encode("utf8")
+ try:
+ keyring.set_password(self.KEYRING_KEY,
+ username_domain,
+ password.encode("utf8"))
+ # Only save the username if it was saved correctly in
+ # the keyring
+ self._settings.set_user(username_domain)
+ except Exception as e:
+ logger.error("Problem saving data to keyring. %r"
+ % (e,))
+
+ self._download_provider_config()
+
+ def _cancel_login(self):
+ """
+ SLOT
+ TRIGGERS:
+ self._login_widget.cancel_login
+
+ Stops the login sequence.
+ """
+ logger.debug("Cancelling log in.")
+
+ if self._download_provider_defer:
+ logger.debug("Cancelling download provider defer.")
+ self._download_provider_defer.cancel()
+
+ if self._login_defer:
+ logger.debug("Cancelling login defer.")
+ self._login_defer.cancel()
+
+ def _provider_config_loaded(self, data):
+ """
+ SLOT
+ TRIGGER: self._provider_bootstrapper.check_api_certificate
+
+ Once the provider configuration is loaded, this starts the SRP
+ authentication
+ """
+ leap_assert(self._provider_config, "We need a provider config!")
+
+ if data[self._provider_bootstrapper.PASSED_KEY]:
+ username = self._login_widget.get_user().encode("utf8")
+ password = self._login_widget.get_password().encode("utf8")
+
+ if self._srp_auth is None:
+ self._srp_auth = SRPAuth(self._provider_config)
+ self._srp_auth.authentication_finished.connect(
+ self._authentication_finished)
+ self._srp_auth.logout_finished.connect(
+ self._done_logging_out)
+
+ # TODO: Add errback!
+ self._login_defer = self._srp_auth.authenticate(username, password)
+ else:
+ self._login_widget.set_status(
+ "Unable to login: Problem with provider")
+ logger.error(data[self._provider_bootstrapper.ERROR_KEY])
+ self._login_widget.set_enabled(True)
+
+ def _authentication_finished(self, ok, message):
+ """
+ SLOT
+ TRIGGER: self._srp_auth.authentication_finished
+
+ Once the user is properly authenticated, try starting the EIP
+ service
+ """
+
+ # In general we want to "filter" likely complicated error
+ # messages, but in this case, the messages make more sense as
+ # they come. Since they are "Unknown user" or "Unknown
+ # password"
+ self._login_widget.set_status(message, error=not ok)
+
+ if ok:
+ self._logged_user = self._login_widget.get_user()
+ self.ui.action_log_out.setEnabled(True)
+ # We leave a bit of room for the user to see the
+ # "Succeeded" message and then we switch to the EIP status
+ # panel
+ QtCore.QTimer.singleShot(1000, self._switch_to_status)
+ self._login_defer = None
+ else:
+ self._login_widget.set_enabled(True)
+
+ def _switch_to_status(self):
+ """
+ Changes the stackedWidget index to the EIP status one and
+ triggers the eip bootstrapping
+ """
+ if not self._already_started_eip:
+ self._status_panel.set_provider(
+ "%s@%s" % (self._login_widget.get_user(),
+ self._get_best_provider_config().get_domain()))
+
+ self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX)
+
+ self._soledad_bootstrapper.run_soledad_setup_checks(
+ self._provider_config,
+ self._login_widget.get_user(),
+ self._login_widget.get_password(),
+ download_if_needed=True,
+ standalone=self._standalone)
+
+ self._download_eip_config()
+
+ def _soledad_intermediate_stage(self, data):
+ """
+ SLOT
+ TRIGGERS:
+ self._soledad_bootstrapper.download_config
+
+ If there was a problem, displays it, otherwise it does nothing.
+ This is used for intermediate bootstrapping stages, in case
+ they fail.
+ """
+ passed = data[self._soledad_bootstrapper.PASSED_KEY]
+ if not passed:
+ # TODO: display in the GUI:
+ # should pass signal to a slot in status_panel
+ # that sets the global status
+ logger.warning("Soledad failed to start: %s" %
+ (data[self._soledad_bootstrapper.ERROR_KEY],))
+
+ def _soledad_bootstrapped_stage(self, data):
+ """
+ SLOT
+ TRIGGERS:
+ self._soledad_bootstrapper.gen_key
+
+ If there was a problem, displays it, otherwise it does nothing.
+ This is used for intermediate bootstrapping stages, in case
+ they fail.
+
+ :param data: result from the bootstrapping stage for Soledad
+ :type data: dict
+ """
+ passed = data[self._soledad_bootstrapper.PASSED_KEY]
+ if not passed:
+ logger.error(data[self._soledad_bootstrapper.ERROR_KEY])
+ return
+
+ logger.debug("Done bootstrapping Soledad")
+
+ self._soledad = self._soledad_bootstrapper.soledad
+ self._keymanager = self._soledad_bootstrapper.keymanager
+
+ # Ok, now soledad is ready, so we can allow other things that
+ # depend on soledad to start.
+
+ # this will trigger start_imap_service
+ self.soledad_ready.emit()
+
+ # TODO connect all these activations to the soledad_ready
+ # signal so the logic is clearer to follow.
+
+ if self._provider_config.provides_mx() and \
+ self._enabled_services.count(self.MX_SERVICE) > 0:
+ self._smtp_bootstrapper.run_smtp_setup_checks(
+ self._provider_config,
+ self._smtp_config,
+ True)
+ else:
+ if self._enabled_services.count(self.MX_SERVICE) > 0:
+ pass # TODO: show MX status
+ #self._status_panel.set_eip_status(
+ # self.tr("%s does not support MX") %
+ # (self._provider_config.get_domain(),),
+ # error=True)
+ else:
+ pass # TODO: show MX status
+ #self._status_panel.set_eip_status(
+ # self.tr("MX is disabled"))
+
+ # Service control methods: smtp
+
+ def _smtp_bootstrapped_stage(self, data):
+ """
+ SLOT
+ TRIGGERS:
+ self._smtp_bootstrapper.download_config
+
+ If there was a problem, displays it, otherwise it does nothing.
+ This is used for intermediate bootstrapping stages, in case
+ they fail.
+
+ :param data: result from the bootstrapping stage for Soledad
+ :type data: dict
+ """
+ passed = data[self._smtp_bootstrapper.PASSED_KEY]
+ if not passed:
+ logger.error(data[self._smtp_bootstrapper.ERROR_KEY])
+ return
+ logger.debug("Done bootstrapping SMTP")
+
+ hosts = self._smtp_config.get_hosts()
+ # TODO: handle more than one host and define how to choose
+ if len(hosts) > 0:
+ hostname = hosts.keys()[0]
+ logger.debug("Using hostname %s for SMTP" % (hostname,))
+ host = hosts[hostname][self.IP_KEY].encode("utf-8")
+ port = hosts[hostname][self.PORT_KEY]
+ # TODO: pick local smtp port in a better way
+ # TODO: Make the encrypted_only configurable
+
+ from leap.mail.smtp import setup_smtp_relay
+ client_cert = self._eip_config.get_client_cert_path(
+ self._provider_config)
+ setup_smtp_relay(port=2013,
+ keymanager=self._keymanager,
+ smtp_host=host,
+ smtp_port=port,
+ smtp_cert=client_cert,
+ smtp_key=client_cert,
+ encrypted_only=False)
+
+ def _start_imap_service(self):
+ """
+ SLOT
+ TRIGGERS:
+ soledad_ready
+ """
+ logger.debug('Starting imap service')
+
+ self._imap_service = imap.start_imap_service(
+ self._soledad,
+ self._keymanager)
+
+ def _get_socket_host(self):
+ """
+ Returns the socket and port to be used for VPN
+
+ :rtype: tuple (str, str) (host, port)
+ """
+
+ # TODO: make this properly multiplatform
+
+ if platform.system() == "Windows":
+ host = "localhost"
+ port = "9876"
+ else:
+ host = os.path.join(tempfile.mkdtemp(prefix="leap-tmp"),
+ 'openvpn.socket')
+ port = "unix"
+
+ return host, port
+
+ def _start_eip(self):
+ """
+ SLOT
+ TRIGGERS:
+ self._status_panel.start_eip
+ self._action_eip_startstop.triggered
+ or called from _finish_eip_bootstrap
+
+ Starts EIP
+ """
+ self._status_panel.eip_pre_up()
+ self.user_stopped_eip = False
+ provider_config = self._get_best_provider_config()
+
+ try:
+ host, port = self._get_socket_host()
+ self._vpn.start(eipconfig=self._eip_config,
+ providerconfig=provider_config,
+ socket_host=host,
+ socket_port=port)
+
+ self._settings.set_defaultprovider(
+ provider_config.get_domain())
+
+ provider = provider_config.get_domain()
+ if self._logged_user is not None:
+ provider = "%s@%s" % (self._logged_user, provider)
+
+ self._status_panel.set_provider(provider)
+
+ self._action_eip_provider.setText(provider_config.get_domain())
+
+ self._status_panel.eip_started()
+
+ # XXX refactor into status_panel method?
+ self._action_eip_startstop.setText(self.tr("Turn OFF"))
+ self._action_eip_startstop.disconnect(self)
+ self._action_eip_startstop.triggered.connect(
+ self._stop_eip)
+ except EIPNoPolkitAuthAgentAvailable:
+ self._status_panel.set_global_status(
+ # XXX this should change to polkit-kde where
+ # applicable.
+ self.tr("We could not find any "
+ "authentication "
+ "agent in your system.<br/>"
+ "Make sure you have "
+ "<b>polkit-gnome-authentication-"
+ "agent-1</b> "
+ "running and try again."),
+ error=True)
+ self._set_eipstatus_off()
+ except EIPNoTunKextLoaded:
+ self._status_panel.set_global_status(
+ self.tr("Encrypted Internet cannot be started because "
+ "the tuntap extension is not installed properly "
+ "in your system."))
+ self._set_eipstatus_off()
+ except EIPNoPkexecAvailable:
+ self._status_panel.set_global_status(
+ self.tr("We could not find <b>pkexec</b> "
+ "in your system."),
+ error=True)
+ self._set_eipstatus_off()
+ except OpenVPNNotFoundException:
+ self._status_panel.set_global_status(
+ self.tr("We could not find openvpn binary."),
+ error=True)
+ self._set_eipstatus_off()
+ except OpenVPNAlreadyRunning as e:
+ self._status_panel.set_global_status(
+ self.tr("Another openvpn instance is already running, and "
+ "could not be stopped."),
+ error=True)
+ self._set_eipstatus_off()
+ except AlienOpenVPNAlreadyRunning as e:
+ self._status_panel.set_global_status(
+ self.tr("Another openvpn instance is already running, and "
+ "could not be stopped because it was not launched by "
+ "Bitmask. Please stop it and try again."),
+ error=True)
+ self._set_eipstatus_off()
+ except VPNLauncherException as e:
+ # XXX We should implement again translatable exceptions so
+ # we can pass a translatable string to the panel (usermessage attr)
+ self._status_panel.set_global_status("%s" % (e,), error=True)
+ self._set_eipstatus_off()
+ else:
+ self._already_started_eip = True
+
+ def _set_eipstatus_off(self):
+ """
+ Sets eip status to off
+ """
+ self._status_panel.set_eip_status(self.tr("OFF"), error=True)
+ self._status_panel.set_eip_status_icon("error")
+ self._status_panel.set_startstop_enabled(True)
+ self._status_panel.eip_stopped()
+
+ self._set_action_eipstart_off()
+
+ def _set_action_eipstart_off(self):
+ """
+ Sets eip startstop action to OFF status.
+ """
+ self._action_eip_startstop.setText(self.tr("Turn ON"))
+ self._action_eip_startstop.disconnect(self)
+ self._action_eip_startstop.triggered.connect(
+ self._start_eip)
+
+ def _stop_eip(self, abnormal=False):
+ """
+ SLOT
+ TRIGGERS:
+ self._status_panel.stop_eip
+ self._action_eip_startstop.triggered
+ or called from _eip_finished
+
+ Stops vpn process and makes gui adjustments to reflect
+ the change of state.
+
+ :param abnormal: whether this was an abnormal termination.
+ :type abnormal: bool
+ """
+ if abnormal:
+ logger.warning("Abnormal EIP termination.")
+
+ self.user_stopped_eip = True
+ self._vpn.terminate()
+
+ self._set_eipstatus_off()
+
+ self._already_started_eip = False
+ self._settings.set_defaultprovider(None)
+ if self._logged_user:
+ self._status_panel.set_provider(
+ "%s@%s" % (self._logged_user,
+ self._get_best_provider_config().get_domain()))
+
+ def _get_best_provider_config(self):
+ """
+ Returns the best ProviderConfig to use at a moment. We may
+ have to use self._provider_config or
+ self._provisional_provider_config depending on the start
+ status.
+
+ :rtype: ProviderConfig
+ """
+ leap_assert(self._provider_config is not None or
+ self._provisional_provider_config is not None,
+ "We need a provider config")
+
+ provider_config = None
+ if self._provider_config.loaded():
+ provider_config = self._provider_config
+ elif self._provisional_provider_config.loaded():
+ provider_config = self._provisional_provider_config
+ else:
+ leap_assert(False, "We could not find any usable ProviderConfig.")
+
+ return provider_config
+
+ def _download_eip_config(self):
+ """
+ Starts the EIP bootstrapping sequence
+ """
+ leap_assert(self._eip_bootstrapper, "We need an eip bootstrapper!")
+
+ provider_config = self._get_best_provider_config()
+
+ if provider_config.provides_eip() and \
+ self._enabled_services.count(self.OPENVPN_SERVICE) > 0 and \
+ not self._already_started_eip:
+
+ self._status_panel.set_eip_status(
+ self.tr("Starting..."))
+ self._eip_bootstrapper.run_eip_setup_checks(
+ provider_config,
+ download_if_needed=True)
+ self._already_started_eip = True
+ elif not self._already_started_eip:
+ if self._enabled_services.count(self.OPENVPN_SERVICE) > 0:
+ self._status_panel.set_eip_status(
+ self.tr("Not supported"),
+ error=True)
+ else:
+ self._status_panel.set_eip_status(self.tr("Disabled"))
+ self._status_panel.set_startstop_enabled(False)
+
+ def _finish_eip_bootstrap(self, data):
+ """
+ SLOT
+ TRIGGER: self._eip_bootstrapper.download_client_certificate
+
+ Starts the VPN thread if the eip configuration is properly
+ loaded
+ """
+ leap_assert(self._eip_config, "We need an eip config!")
+ passed = data[self._eip_bootstrapper.PASSED_KEY]
+
+ if not passed:
+ error_msg = self.tr("There was a problem with the provider")
+ self._status_panel.set_eip_status(error_msg, error=True)
+ logger.error(data[self._eip_bootstrapper.ERROR_KEY])
+ self._already_started_eip = False
+ return
+
+ provider_config = self._get_best_provider_config()
+
+ domain = provider_config.get_domain()
+
+ loaded = self._eip_config.loaded()
+ if not loaded:
+ eip_config_path = os.path.join("leap", "providers",
+ domain, "eip-service.json")
+ api_version = provider_config.get_api_version()
+ self._eip_config.set_api_version(api_version)
+ loaded = self._eip_config.load(eip_config_path)
+
+ if loaded:
+ self._start_eip()
+ else:
+ self._status_panel.set_eip_status(
+ self.tr("Could not load Encrypted Internet "
+ "Configuration."),
+ error=True)
+
+ def _logout(self):
+ """
+ SLOT
+ TRIGGER: self.ui.action_log_out.triggered
+
+ Starts the logout sequence
+ """
+ # XXX: If other defers are doing authenticated stuff, this
+ # might conflict with those. CHECK!
+ threads.deferToThread(self._srp_auth.logout)
+
+ def _done_logging_out(self, ok, message):
+ """
+ SLOT
+ TRIGGER: self._srp_auth.logout_finished
+
+ Switches the stackedWidget back to the login stage after
+ logging out
+ """
+ self._logged_user = None
+ self.ui.action_log_out.setEnabled(False)
+ self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX)
+ self._login_widget.set_password("")
+ self._login_widget.set_enabled(True)
+ self._login_widget.set_status("")
+
+ def _intermediate_stage(self, data):
+ """
+ SLOT
+ TRIGGERS:
+ self._provider_bootstrapper.name_resolution
+ self._provider_bootstrapper.https_connection
+ self._provider_bootstrapper.download_ca_cert
+ self._eip_bootstrapper.download_config
+
+ If there was a problem, displays it, otherwise it does nothing.
+ This is used for intermediate bootstrapping stages, in case
+ they fail.
+ """
+ passed = data[self._provider_bootstrapper.PASSED_KEY]
+ if not passed:
+ self._login_widget.set_enabled(True)
+ self._login_widget.set_status(
+ self.tr("Unable to connect: Problem with provider"))
+ logger.error(data[self._provider_bootstrapper.ERROR_KEY])
+
+ def _eip_intermediate_stage(self, data):
+ """
+ SLOT
+ TRIGGERS:
+ self._eip_bootstrapper.download_config
+
+ If there was a problem, displays it, otherwise it does nothing.
+ This is used for intermediate bootstrapping stages, in case
+ they fail.
+ """
+ passed = data[self._provider_bootstrapper.PASSED_KEY]
+ if not passed:
+ self._login_widget.set_status(
+ self.tr("Unable to connect: Problem with provider"))
+ logger.error(data[self._provider_bootstrapper.ERROR_KEY])
+ self._already_started_eip = False
+
+ def _eip_finished(self, exitCode):
+ """
+ SLOT
+ TRIGGERS:
+ self._vpn.process_finished
+
+ Triggered when the EIP/VPN process finishes to set the UI
+ accordingly.
+ """
+ logger.info("VPN process finished with exitCode %s..."
+ % (exitCode,))
+
+ # Ideally we would have the right exit code here,
+ # but the use of different wrappers (pkexec, cocoasudo) swallows
+ # the openvpn exit code so we get zero exit in some cases where we
+ # shouldn't. As a workaround we just use a flag to indicate
+ # a purposeful switch off, and mark everything else as unexpected.
+
+ # In the near future we should trigger a native notification from here,
+ # since the user really really wants to know she is unprotected asap.
+ # And the right thing to do will be to fail-close.
+
+ # TODO we should have a way of parsing the latest lines in the vpn
+ # log buffer so we can have a more precise idea of which type
+ # of error did we have (server side, local problem, etc)
+ abnormal = True
+
+ # XXX check if these exitCodes are pkexec/cocoasudo specific
+ if exitCode in (126, 127):
+ self._status_panel.set_global_status(
+ self.tr("Encrypted Internet could not be launched "
+ "because you did not authenticate properly."),
+ error=True)
+ self._vpn.killit()
+ elif exitCode != 0 or not self.user_stopped_eip:
+ self._status_panel.set_global_status(
+ self.tr("Encrypted Internet finished in an "
+ "unexpected manner!"), error=True)
+ else:
+ abnormal = False
+ if exitCode == 0 and IS_MAC:
+ # XXX remove this warning after I fix cocoasudo.
+ logger.warning("The above exit code MIGHT BE WRONG.")
+ self._stop_eip(abnormal)
+
+ def _on_raise_window_event(self, req):
+ """
+ Callback for the raise window event
+ """
+ if IS_WIN:
+ raise_window_ack()
+ self.raise_window.emit()
+
+ def _do_raise_mainwindow(self):
+ """
+ SLOT
+ TRIGGERS:
+ self._on_raise_window_event
+
+ Triggered when we receive a RAISE_WINDOW event.
+ """
+ TOPFLAG = QtCore.Qt.WindowStaysOnTopHint
+ self.setWindowFlags(self.windowFlags() | TOPFLAG)
+ self.show()
+ self.setWindowFlags(self.windowFlags() & ~TOPFLAG)
+ self.show()
+ if IS_MAC:
+ self.raise_()
+
+ def _cleanup_pidfiles(self):
+ """
+ Removes lockfiles on a clean shutdown.
+
+ Triggered after aboutToQuit signal.
+ """
+ if IS_WIN:
+ WindowsLock.release_all_locks()
+
+ def _cleanup_and_quit(self):
+ """
+ Call all the cleanup actions in a serialized way.
+ Should be called from the quit function.
+ """
+ logger.debug('About to quit, doing cleanup...')
+
+ if self._imap_service is not None:
+ self._imap_service.stop()
+
+ if self._srp_auth is not None:
+ if self._srp_auth.get_session_id() is not None or \
+ self._srp_auth.get_token() is not None:
+ # XXX this can timeout after loong time: See #3368
+ self._srp_auth.logout()
+
+ if self._soledad:
+ logger.debug("Closing soledad...")
+ self._soledad.close()
+ else:
+ logger.error("No instance of soledad was found.")
+
+ logger.debug('Terminating vpn')
+ self._vpn.terminate(shutdown=True)
+
+ if self._login_defer:
+ logger.debug("Cancelling login defer.")
+ self._login_defer.cancel()
+
+ if self._download_provider_defer:
+ logger.debug("Cancelling download provider defer.")
+ self._download_provider_defer.cancel()
+
+ # TODO missing any more cancels?
+
+ logger.debug('Cleaning pidfiles')
+ self._cleanup_pidfiles()
+
+ def quit(self):
+ """
+ Cleanup and tidely close the main window before quitting.
+ """
+ # TODO: separate the shutting down of services from the
+ # UI stuff.
+ self._cleanup_and_quit()
+
+ self._really_quit = True
+
+ if self._wizard:
+ self._wizard.close()
+
+ if self._logger_window:
+ self._logger_window.close()
+
+ self.close()
+
+ if self._quit_callback:
+ self._quit_callback()
+
+ logger.debug('Bye.')
+
+
+if __name__ == "__main__":
+ import signal
+
+ def sigint_handler(*args, **kwargs):
+ logger.debug('SIGINT catched. shutting down...')
+ mainwindow = args[0]
+ mainwindow.quit()
+
+ import sys
+
+ logger = logging.getLogger(name='leap')
+ logger.setLevel(logging.DEBUG)
+ console = logging.StreamHandler()
+ console.setLevel(logging.DEBUG)
+ formatter = logging.Formatter(
+ '%(asctime)s '
+ '- %(name)s - %(levelname)s - %(message)s')
+ console.setFormatter(formatter)
+ logger.addHandler(console)
+
+ app = QtGui.QApplication(sys.argv)
+ mainwindow = MainWindow()
+ mainwindow.show()
+
+ timer = QtCore.QTimer()
+ timer.start(500)
+ timer.timeout.connect(lambda: None)
+
+ sigint = partial(sigint_handler, mainwindow)
+ signal.signal(signal.SIGINT, sigint)
+
+ sys.exit(app.exec_())
diff --git a/src/leap/bitmask/gui/statuspanel.py b/src/leap/bitmask/gui/statuspanel.py
new file mode 100644
index 00000000..8f5427ad
--- /dev/null
+++ b/src/leap/bitmask/gui/statuspanel.py
@@ -0,0 +1,460 @@
+# -*- coding: utf-8 -*-
+# statuspanel.py
+# Copyright (C) 2013 LEAP
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+"""
+Status Panel widget implementation
+"""
+import logging
+
+from datetime import datetime
+from functools import partial
+
+from PySide import QtCore, QtGui
+
+from leap.bitmask.services.eip.vpnprocess import VPNManager
+from leap.bitmask.platform_init import IS_WIN, IS_LINUX
+from leap.bitmask.util import first
+from leap.common.check import leap_assert_type
+
+from ui_statuspanel import Ui_StatusPanel
+
+logger = logging.getLogger(__name__)
+
+
+class RateMovingAverage(object):
+ """
+ Moving window average for calculating
+ upload and download rates.
+ """
+ SAMPLE_SIZE = 5
+
+ def __init__(self):
+ """
+ Initializes an empty array of fixed size
+ """
+ self.reset()
+
+ def reset(self):
+ self._data = [None for i in xrange(self.SAMPLE_SIZE)]
+
+ def append(self, x):
+ """
+ Appends a new data point to the collection.
+
+ :param x: A tuple containing timestamp and traffic points
+ in the form (timestamp, traffic)
+ :type x: tuple
+ """
+ self._data.pop(0)
+ self._data.append(x)
+
+ def get(self):
+ """
+ Gets the collection.
+ """
+ return self._data
+
+ def get_average(self):
+ """
+ Gets the moving average.
+ """
+ data = filter(None, self.get())
+ traff = [traffic for (ts, traffic) in data]
+ times = [ts for (ts, traffic) in data]
+
+ try:
+ deltatraffic = traff[-1] - first(traff)
+ deltat = (times[-1] - first(times)).seconds
+ except IndexError:
+ deltatraffic = 0
+ deltat = 0
+
+ try:
+ rate = float(deltatraffic) / float(deltat) / 1024
+ except ZeroDivisionError:
+ rate = 0
+
+ # In some cases we get negative rates
+ if rate < 0:
+ rate = 0
+
+ return rate
+
+ def get_total(self):
+ """
+ Gets the total accumulated throughput.
+ """
+ try:
+ return self._data[-1][1] / 1024
+ except TypeError:
+ return 0
+
+
+class StatusPanelWidget(QtGui.QWidget):
+ """
+ Status widget that displays the current state of the LEAP services
+ """
+
+ start_eip = QtCore.Signal()
+ stop_eip = QtCore.Signal()
+
+ DISPLAY_TRAFFIC_RATES = True
+ RATE_STR = "%14.2f KB/s"
+ TOTAL_STR = "%14.2f Kb"
+
+ def __init__(self, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+
+ self._systray = None
+ self._action_eip_status = None
+
+ self.ui = Ui_StatusPanel()
+ self.ui.setupUi(self)
+
+ self.ui.btnEipStartStop.setEnabled(False)
+ self.ui.btnEipStartStop.clicked.connect(
+ self.start_eip)
+
+ self.hide_status_box()
+
+ # Set the EIP status icons
+ self.CONNECTING_ICON = None
+ self.CONNECTED_ICON = None
+ self.ERROR_ICON = None
+ self.CONNECTING_ICON_TRAY = None
+ self.CONNECTED_ICON_TRAY = None
+ self.ERROR_ICON_TRAY = None
+ self._set_eip_icons()
+
+ self._set_traffic_rates()
+ self._make_status_clickable()
+
+ def _make_status_clickable(self):
+ """
+ Makes upload and download figures clickable.
+ """
+ onclicked = self._on_VPN_status_clicked
+ self.ui.btnUpload.clicked.connect(onclicked)
+ self.ui.btnDownload.clicked.connect(onclicked)
+
+ def _on_VPN_status_clicked(self):
+ """
+ SLOT
+ TRIGGER: self.ui.btnUpload.clicked
+ self.ui.btnDownload.clicked
+
+ Toggles between rate and total throughput display for vpn
+ status figures.
+ """
+ self.DISPLAY_TRAFFIC_RATES = not self.DISPLAY_TRAFFIC_RATES
+ self.update_vpn_status(None) # refresh
+
+ def _set_traffic_rates(self):
+ """
+ Initializes up and download rates.
+ """
+ self._up_rate = RateMovingAverage()
+ self._down_rate = RateMovingAverage()
+
+ self.ui.btnUpload.setText(self.RATE_STR % (0,))
+ self.ui.btnDownload.setText(self.RATE_STR % (0,))
+
+ def _reset_traffic_rates(self):
+ """
+ Resets up and download rates, and cleans up the labels.
+ """
+ self._up_rate.reset()
+ self._down_rate.reset()
+ self.update_vpn_status(None)
+
+ def _update_traffic_rates(self, up, down):
+ """
+ Updates up and download rates.
+
+ :param up: upload total.
+ :type up: int
+ :param down: download total.
+ :type down: int
+ """
+ ts = datetime.now()
+ self._up_rate.append((ts, up))
+ self._down_rate.append((ts, down))
+
+ def _get_traffic_rates(self):
+ """
+ Gets the traffic rates (in KB/s).
+
+ :returns: a tuple with the (up, down) rates
+ :rtype: tuple
+ """
+ up = self._up_rate
+ down = self._down_rate
+
+ return (up.get_average(), down.get_average())
+
+ def _get_traffic_totals(self):
+ """
+ Gets the traffic total throughput (in Kb).
+
+ :returns: a tuple with the (up, down) totals
+ :rtype: tuple
+ """
+ up = self._up_rate
+ down = self._down_rate
+
+ return (up.get_total(), down.get_total())
+
+ def _set_eip_icons(self):
+ """
+ Sets the EIP status icons for the main window and for the tray
+
+ MAC : dark icons
+ LINUX : dark icons in window, light icons in tray
+ WIN : light icons
+ """
+ EIP_ICONS = EIP_ICONS_TRAY = (
+ ":/images/conn_connecting-light.png",
+ ":/images/conn_connected-light.png",
+ ":/images/conn_error-light.png")
+
+ if IS_LINUX:
+ EIP_ICONS_TRAY = (
+ ":/images/conn_connecting.png",
+ ":/images/conn_connected.png",
+ ":/images/conn_error.png")
+ elif IS_WIN:
+ EIP_ICONS = EIP_ICONS_TRAY = (
+ ":/images/conn_connecting.png",
+ ":/images/conn_connected.png",
+ ":/images/conn_error.png")
+
+ self.CONNECTING_ICON = QtGui.QPixmap(EIP_ICONS[0])
+ self.CONNECTED_ICON = QtGui.QPixmap(EIP_ICONS[1])
+ self.ERROR_ICON = QtGui.QPixmap(EIP_ICONS[2])
+
+ self.CONNECTING_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[0])
+ self.CONNECTED_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[1])
+ self.ERROR_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[2])
+
+ def set_systray(self, systray):
+ """
+ Sets the systray object to use.
+
+ :param systray: Systray object
+ :type systray: QtGui.QSystemTrayIcon
+ """
+ leap_assert_type(systray, QtGui.QSystemTrayIcon)
+ self._systray = systray
+
+ def set_action_eip_startstop(self, action_eip_startstop):
+ """
+ Sets the action_eip_startstop to use.
+
+ :param action_eip_startstop: action_eip_status to be used
+ :type action_eip_startstop: QtGui.QAction
+ """
+ self._action_eip_startstop = action_eip_startstop
+
+ def set_action_eip_status(self, action_eip_status):
+ """
+ Sets the action_eip_status to use.
+
+ :param action_eip_status: action_eip_status to be used
+ :type action_eip_status: QtGui.QAction
+ """
+ leap_assert_type(action_eip_status, QtGui.QAction)
+ self._action_eip_status = action_eip_status
+
+ def set_global_status(self, status, error=False):
+ """
+ Sets the global status label.
+
+ :param status: status message
+ :type status: str or unicode
+ :param error: if the status is an erroneous one, then set this
+ to True
+ :type error: bool
+ """
+ leap_assert_type(error, bool)
+ if error:
+ status = "<font color='red'><b>%s</b></font>" % (status,)
+ self.ui.lblGlobalStatus.setText(status)
+ self.ui.globalStatusBox.show()
+
+ def hide_status_box(self):
+ """
+ Hide global status box.
+ """
+ self.ui.globalStatusBox.hide()
+
+ def set_eip_status(self, status, error=False):
+ """
+ Sets the status label at the VPN stage to status
+
+ :param status: status message
+ :type status: str or unicode
+ :param error: if the status is an erroneous one, then set this
+ to True
+ :type error: bool
+ """
+ leap_assert_type(error, bool)
+
+ self._systray.setToolTip(status)
+ if error:
+ status = "<font color='red'>%s</font>" % (status,)
+ self.ui.lblEIPStatus.setText(status)
+
+ def set_startstop_enabled(self, value):
+ """
+ Enable or disable btnEipStartStop and _action_eip_startstop
+ based on value
+
+ :param value: True for enabled, False otherwise
+ :type value: bool
+ """
+ leap_assert_type(value, bool)
+ self.ui.btnEipStartStop.setEnabled(value)
+ self._action_eip_startstop.setEnabled(value)
+
+ def eip_pre_up(self):
+ """
+ Triggered when the app activates eip.
+ Hides the status box and disables the start/stop button.
+ """
+ self.hide_status_box()
+ self.set_startstop_enabled(False)
+
+ def eip_started(self):
+ """
+ Sets the state of the widget to how it should look after EIP
+ has started
+ """
+ self.ui.btnEipStartStop.setText(self.tr("Turn OFF"))
+ self.ui.btnEipStartStop.disconnect(self)
+ self.ui.btnEipStartStop.clicked.connect(
+ self.stop_eip)
+
+ def eip_stopped(self):
+ """
+ Sets the state of the widget to how it should look after EIP
+ has stopped
+ """
+ self._reset_traffic_rates()
+ self.ui.btnEipStartStop.setText(self.tr("Turn ON"))
+ self.ui.btnEipStartStop.disconnect(self)
+ self.ui.btnEipStartStop.clicked.connect(
+ self.start_eip)
+
+ def set_icon(self, icon):
+ """
+ Sets the icon to display for EIP
+
+ :param icon: icon to display
+ :type icon: QPixmap
+ """
+ self.ui.lblVPNStatusIcon.setPixmap(icon)
+
+ def update_vpn_status(self, data):
+ """
+ SLOT
+ TRIGGER: VPN.status_changed
+
+ Updates the download/upload labels based on the data provided
+ by the VPN thread.
+
+ :param data: a dictionary with the tcp/udp write and read totals.
+ If data is None, we just will refresh the display based
+ on the previous data.
+ :type data: dict
+ """
+ if data:
+ upload = float(data[VPNManager.TCPUDP_WRITE_KEY] or "0")
+ download = float(data[VPNManager.TCPUDP_READ_KEY] or "0")
+ self._update_traffic_rates(upload, download)
+
+ if self.DISPLAY_TRAFFIC_RATES:
+ uprate, downrate = self._get_traffic_rates()
+ upload_str = self.RATE_STR % (uprate,)
+ download_str = self.RATE_STR % (downrate,)
+
+ else: # display total throughput
+ uptotal, downtotal = self._get_traffic_totals()
+ upload_str = self.TOTAL_STR % (uptotal,)
+ download_str = self.TOTAL_STR % (downtotal,)
+
+ self.ui.btnUpload.setText(upload_str)
+ self.ui.btnDownload.setText(download_str)
+
+ def update_vpn_state(self, data):
+ """
+ SLOT
+ TRIGGER: VPN.state_changed
+
+ Updates the displayed VPN state based on the data provided by
+ the VPN thread
+ """
+ status = data[VPNManager.STATUS_STEP_KEY]
+ self.set_eip_status_icon(status)
+ if status == "CONNECTED":
+ self.set_eip_status(self.tr("ON"))
+ # Only now we can properly enable the button.
+ self.set_startstop_enabled(True)
+ elif status == "AUTH":
+ self.set_eip_status(self.tr("Authenticating..."))
+ elif status == "GET_CONFIG":
+ self.set_eip_status(self.tr("Retrieving configuration..."))
+ elif status == "WAIT":
+ self.set_eip_status(self.tr("Waiting to start..."))
+ elif status == "ASSIGN_IP":
+ self.set_eip_status(self.tr("Assigning IP"))
+ elif status == "ALREADYRUNNING":
+ # Put the following calls in Qt's event queue, otherwise
+ # the UI won't update properly
+ QtCore.QTimer.singleShot(0, self.stop_eip)
+ QtCore.QTimer.singleShot(0, partial(self.set_global_status,
+ self.tr("Unable to start VPN, "
+ "it's already "
+ "running.")))
+ else:
+ self.set_eip_status(status)
+
+ def set_eip_status_icon(self, status):
+ """
+ Given a status step from the VPN thread, set the icon properly
+
+ :param status: status step
+ :type status: str
+ """
+ selected_pixmap = self.ERROR_ICON
+ selected_pixmap_tray = self.ERROR_ICON_TRAY
+ tray_message = self.tr("Encryption is OFF")
+ if status in ("WAIT", "AUTH", "GET_CONFIG",
+ "RECONNECTING", "ASSIGN_IP"):
+ selected_pixmap = self.CONNECTING_ICON
+ selected_pixmap_tray = self.CONNECTING_ICON_TRAY
+ tray_message = self.tr("Turning ON")
+ elif status in ("CONNECTED"):
+ tray_message = self.tr("Encryption is ON")
+ selected_pixmap = self.CONNECTED_ICON
+ selected_pixmap_tray = self.CONNECTED_ICON_TRAY
+
+ self.set_icon(selected_pixmap)
+ self._systray.setIcon(QtGui.QIcon(selected_pixmap_tray))
+ self._action_eip_status.setText(tray_message)
+
+ def set_provider(self, provider):
+ self.ui.lblProvider.setText(provider)
diff --git a/src/leap/bitmask/gui/twisted_main.py b/src/leap/bitmask/gui/twisted_main.py
new file mode 100644
index 00000000..c7add3ee
--- /dev/null
+++ b/src/leap/bitmask/gui/twisted_main.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+# twisted_main.py
+# Copyright (C) 2013 LEAP
+#
+# 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, see <http://www.gnu.org/licenses/>.
+"""
+Main functions for integration of twisted reactor
+"""
+import logging
+
+from twisted.internet import error
+
+# Resist the temptation of putting the import reactor here,
+# it will raise an "reactor already imported" error.
+
+logger = logging.getLogger(__name__)
+
+
+def start(app):
+ """
+ Start the mainloop.
+
+ :param app: the main qt QApplication instance.
+ :type app: QtCore.QApplication
+ """
+ from twisted.internet import reactor
+ logger.debug('starting twisted reactor')
+
+ # this seems to be troublesome under some
+ # unidentified settings.
+ #reactor.run()
+
+ reactor.runReturn()
+ app.exec_()
+
+
+def quit(app):
+ """
+ Stop the mainloop.
+
+ :param app: the main qt QApplication instance.
+ :type app: QtCore.QApplication
+ """
+ from twisted.internet import reactor
+ logger.debug('stopping twisted reactor')
+ try:
+ reactor.stop()
+ except error.ReactorNotRunning:
+ logger.debug('reactor not running')
diff --git a/src/leap/bitmask/gui/ui/loggerwindow.ui b/src/leap/bitmask/gui/ui/loggerwindow.ui
new file mode 100644
index 00000000..b08428a9
--- /dev/null
+++ b/src/leap/bitmask/gui/ui/loggerwindow.ui
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>LoggerWindow</class>
+ <widget class="QWidget" name="LoggerWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>648</width>
+ <height>469</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Logs</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../../../../data/resources/mainwindow.qrc">
+ <normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="2" column="0" colspan="2">
+ <widget class="QTextBrowser" name="txtLogHistory"/>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QPushButton" name="btnDebug">
+ <property name="text">
+ <string>Debug</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../../../../data/resources/loggerwindow.qrc">
+ <normaloff>:/images/oxygen-icons/script-error.png</normaloff>:/images/oxygen-icons/script-error.png</iconset>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnInfo">
+ <property name="text">
+ <string>Info</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../../../../data/resources/loggerwindow.qrc">
+ <normaloff>:/images/oxygen-icons/dialog-information.png</normaloff>:/images/oxygen-icons/dialog-information.png</iconset>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnWarning">
+ <property name="text">
+ <string>Warning</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../../../../data/resources/loggerwindow.qrc">
+ <normaloff>:/images/oxygen-icons/dialog-warning.png</normaloff>:/images/oxygen-icons/dialog-warning.png</iconset>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnError">
+ <property name="text">
+ <string>Error</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../../../../data/resources/loggerwindow.qrc">
+ <normaloff>:/images/oxygen-icons/dialog-error.png</normaloff>:/images/oxygen-icons/dialog-error.png</iconset>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnCritical">
+ <property name="text">
+ <string>Critical</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../../../../data/resources/loggerwindow.qrc">
+ <normaloff>:/images/oxygen-icons/edit-bomb.png</normaloff>:/images/oxygen-icons/edit-bomb.png</iconset>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnSave">
+ <property name="text">
+ <string>Save to file</string>
+ </property>
+ <property name="icon">
+ <iconset resource="../../../../data/resources/loggerwindow.qrc">
+ <normaloff>:/images/oxygen-icons/document-save-as.png</normaloff>:/images/oxygen-icons/document-save-as.png</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>btnDebug</tabstop>
+ <tabstop>btnInfo</tabstop>
+ <tabstop>btnWarning</tabstop>
+ <tabstop>btnError</tabstop>
+ <tabstop>btnCritical</tabstop>
+ <tabstop>btnSave</tabstop>
+ <tabstop>txtLogHistory</tabstop>
+ </tabstops>
+ <resources>
+ <include location="../../../../data/resources/loggerwindow.qrc"/>
+ <include location="../../../../data/resources/mainwindow.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/leap/bitmask/gui/ui/login.ui b/src/leap/bitmask/gui/ui/login.ui
new file mode 100644
index 00000000..42a6897a
--- /dev/null
+++ b/src/leap/bitmask/gui/ui/login.ui
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>LoginWidget</class>
+ <widget class="QWidget" name="LoginWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>356</width>
+ <height>223</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="5" column="2">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="1" colspan="2">
+ <widget class="QComboBox" name="cmbProviders"/>
+ </item>
+ <item row="5" column="0">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="6" column="1">
+ <widget class="QPushButton" name="btnCreateAccount">
+ <property name="text">
+ <string>Create a new account</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>&lt;b&gt;Provider:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1" colspan="2">
+ <widget class="QLineEdit" name="lnPassword">
+ <property name="inputMask">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" colspan="2">
+ <widget class="QLineEdit" name="lnUser"/>
+ </item>
+ <item row="4" column="1" colspan="2">
+ <widget class="QCheckBox" name="chkRemember">
+ <property name="text">
+ <string>Remember username and password</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>&lt;b&gt;Username:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>&lt;b&gt;Password:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QPushButton" name="btnLogin">
+ <property name="text">
+ <string>Log In</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="3">
+ <widget class="QLabel" name="lblStatus">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>cmbProviders</tabstop>
+ <tabstop>lnUser</tabstop>
+ <tabstop>lnPassword</tabstop>
+ <tabstop>chkRemember</tabstop>
+ <tabstop>btnLogin</tabstop>
+ <tabstop>btnCreateAccount</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui
new file mode 100644
index 00000000..ecd3cbe9
--- /dev/null
+++ b/src/leap/bitmask/gui/ui/mainwindow.ui
@@ -0,0 +1,315 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>429</width>
+ <height>579</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Bitmask</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../../../../data/resources/mainwindow.qrc">
+ <normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset>
+ </property>
+ <property name="inputMethodHints">
+ <set>Qt::ImhHiddenText</set>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>128</width>
+ <height>128</height>
+ </size>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0" colspan="5">
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="2" column="0">
+ <spacer name="horizontalSpacer_8">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="1">
+ <spacer name="horizontalSpacer_7">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="lblNewUpdates">
+ <property name="text">
+ <string>There are new updates available, please restart.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QPushButton" name="btnMore">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>More...</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <spacer name="horizontalSpacer_9">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="6" column="2">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="10" column="0" colspan="5">
+ <widget class="QStackedWidget" name="stackedWidget">
+ <property name="currentIndex">
+ <number>1</number>
+ </property>
+ <widget class="QWidget" name="loginPage">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="loginLayout"/>
+ </item>
+ <item row="0" column="2">
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="0">
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_2">
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <layout class="QVBoxLayout" name="statusLayout"/>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="7" column="2">
+ <widget class="QLabel" name="label">
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/mask-launcher.png</pixmap>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="3" colspan="2">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="17" column="2">
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="7" column="0" colspan="2">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="18" column="2">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <spacer name="horizontalSpacer_10">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnShowLog">
+ <property name="text">
+ <string>Show Log</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>429</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menuSession">
+ <property name="title">
+ <string>&amp;Session</string>
+ </property>
+ <addaction name="action_log_out"/>
+ <addaction name="separator"/>
+ <addaction name="action_quit"/>
+ </widget>
+ <widget class="QMenu" name="menuHelp">
+ <property name="title">
+ <string>Help</string>
+ </property>
+ <addaction name="action_help"/>
+ <addaction name="separator"/>
+ <addaction name="action_about_leap"/>
+ </widget>
+ <addaction name="menuSession"/>
+ <addaction name="menuHelp"/>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ <action name="action_log_out">
+ <property name="text">
+ <string>Log &amp;out</string>
+ </property>
+ </action>
+ <action name="action_quit">
+ <property name="text">
+ <string>&amp;Quit</string>
+ </property>
+ </action>
+ <action name="action_about_leap">
+ <property name="text">
+ <string>About &amp;Bitmask</string>
+ </property>
+ </action>
+ <action name="action_help">
+ <property name="text">
+ <string>&amp;Help</string>
+ </property>
+ </action>
+ <action name="action_wizard">
+ <property name="text">
+ <string>&amp;Wizard</string>
+ </property>
+ </action>
+ <action name="action_show_logs">
+ <property name="text">
+ <string>Show &amp;logs</string>
+ </property>
+ </action>
+ </widget>
+ <resources>
+ <include location="../../../../data/resources/mainwindow.qrc"/>
+ <include location="../../../../data/resources/locale.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/leap/bitmask/gui/ui/statuspanel.ui b/src/leap/bitmask/gui/ui/statuspanel.ui
new file mode 100644
index 00000000..3482ac7c
--- /dev/null
+++ b/src/leap/bitmask/gui/ui/statuspanel.ui
@@ -0,0 +1,289 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>StatusPanel</class>
+ <widget class="QWidget" name="StatusPanel">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>384</width>
+ <height>477</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="lblProvider">
+ <property name="styleSheet">
+ <string notr="true">font: bold;</string>
+ </property>
+ <property name="text">
+ <string>user@domain.org</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="status_rows" native="true">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="eip_controls">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Encrypted Internet: </string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="lblEIPStatus">
+ <property name="styleSheet">
+ <string notr="true">font: bold;</string>
+ </property>
+ <property name="text">
+ <string>Off</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::AutoText</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnEipStartStop">
+ <property name="text">
+ <string>Turn On</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0">
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Preferred</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>11</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="0" rowspan="2">
+ <widget class="QLabel" name="lblVPNStatusIcon">
+ <property name="maximumSize">
+ <size>
+ <width>64</width>
+ <height>64</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../data/resources/icons.qrc">:/images/light/64/network-eip-down.png</pixmap>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <layout class="QHBoxLayout" name="eip_bandwidth">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../data/resources/icons.qrc">:/images/light/16/down-arrow.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnDownload">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>120</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="cursor">
+ <cursorShape>PointingHandCursor</cursorShape>
+ </property>
+ <property name="text">
+ <string>0.0 KB/s</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../data/resources/icons.qrc">:/images/light/16/up-arrow.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignLeft">
+ <widget class="QPushButton" name="btnUpload">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>120</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="cursor">
+ <cursorShape>PointingHandCursor</cursorShape>
+ </property>
+ <property name="text">
+ <string>0.0 KB/s</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QGroupBox" name="globalStatusBox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="lblGlobalStatus">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="../../../../data/resources/icons.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui
new file mode 100644
index 00000000..5e0108dc
--- /dev/null
+++ b/src/leap/bitmask/gui/ui/wizard.ui
@@ -0,0 +1,846 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Wizard</class>
+ <widget class="QWizard" name="Wizard">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>536</width>
+ <height>452</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Bitmask first run</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../../../../data/resources/mainwindow.qrc">
+ <normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <property name="wizardStyle">
+ <enum>QWizard::ModernStyle</enum>
+ </property>
+ <property name="options">
+ <set>QWizard::IndependentPages</set>
+ </property>
+ <widget class="QWizardPage" name="introduction_page">
+ <property name="title">
+ <string>Welcome</string>
+ </property>
+ <property name="subTitle">
+ <string>This is the Bitmask first run wizard</string>
+ </property>
+ <attribute name="pageId">
+ <string notr="true">0</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="3" column="0">
+ <widget class="QRadioButton" name="rdoLogin">
+ <property name="text">
+ <string>Log In with my credentials</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Now we will guide you through some configuration that is needed before you can connect for the first time.&lt;/p&gt;&lt;p&gt;If you ever need to modify these options again, you can find the wizard in the &lt;span style=&quot; font-style:italic;&quot;&gt;'Settings'&lt;/span&gt; menu from the main window.&lt;/p&gt;&lt;p&gt;Do you want to &lt;span style=&quot; font-weight:600;&quot;&gt;sign up&lt;/span&gt; for a new account, or &lt;span style=&quot; font-weight:600;&quot;&gt;log in&lt;/span&gt; with an already existing username?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QRadioButton" name="rdoRegister">
+ <property name="text">
+ <string>Sign up for a new account</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <spacer name="verticalSpacer_11">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="4" column="0">
+ <spacer name="verticalSpacer_12">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="WizardPage" name="select_provider_page">
+ <property name="title">
+ <string>Provider selection</string>
+ </property>
+ <property name="subTitle">
+ <string>Please enter the domain of the provider you want to use for your connection</string>
+ </property>
+ <attribute name="pageId">
+ <string notr="true">1</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="1">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>60</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="lnProvider"/>
+ </item>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="btnCheck">
+ <property name="text">
+ <string>Check</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>https://</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" colspan="3">
+ <widget class="QGroupBox" name="grpCheckProvider">
+ <property name="title">
+ <string>Checking for a valid provider</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Getting provider information</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Can we stablish a secure connection?</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="lblProviderInfo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="lblHTTPS">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="lblNameResolution">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Can we reach this provider?</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="1" colspan="2">
+ <widget class="QLabel" name="lblProviderSelectStatus">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWizardPage" name="provider_info_page">
+ <property name="title">
+ <string>Provider Information</string>
+ </property>
+ <property name="subTitle">
+ <string>Description of services offered by this provider</string>
+ </property>
+ <attribute name="pageId">
+ <string notr="true">2</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="1" column="0" colspan="2">
+ <widget class="QLabel" name="lblProviderName">
+ <property name="text">
+ <string>Name</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <spacer name="verticalSpacer_15">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="3" column="1" colspan="2">
+ <widget class="QLabel" name="lblProviderDesc">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>200</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Desc</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="lblServ">
+ <property name="text">
+ <string>&lt;b&gt;Services offered:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLabel" name="lblServicesOffered">
+ <property name="text">
+ <string>services</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <spacer name="horizontalSpacer_6">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="label_12">
+ <property name="text">
+ <string>&lt;b&gt;Enrollment policy:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QLabel" name="lblProviderPolicy">
+ <property name="text">
+ <string>policy</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <spacer name="verticalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>&lt;b&gt;URL:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" colspan="2">
+ <widget class="QLabel" name="lblProviderURL">
+ <property name="text">
+ <string>URL</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>&lt;b&gt;Description:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="WizardPage" name="setup_provider_page">
+ <property name="title">
+ <string>Provider setup</string>
+ </property>
+ <property name="subTitle">
+ <string>Gathering configuration options for this provider</string>
+ </property>
+ <attribute name="pageId">
+ <string notr="true">3</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>60</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="lblSetupProviderExpl">
+ <property name="text">
+ <string>We are downloading some bits that we need to establish a secure connection with the provider for the first time.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_6">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Setting up provider</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_6">
+ <item row="2" column="1">
+ <widget class="QLabel" name="lblCheckCaFpr">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="lblDownloadCaCert">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>Getting info from the Certificate Authority</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_10">
+ <property name="text">
+ <string>Do we trust this Certificate Authority?</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_11">
+ <property name="text">
+ <string>Establishing a trust relationship with this provider</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="lblCheckApiCert">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>24</width>
+ <height>24</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Emblem-question.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_8">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="WizardPage" name="register_user_page">
+ <property name="title">
+ <string>Register new user</string>
+ </property>
+ <property name="subTitle">
+ <string>Register a new user with provider</string>
+ </property>
+ <attribute name="pageId">
+ <string notr="true">4</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_7">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <property name="leftMargin">
+ <number>4</number>
+ </property>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_16">
+ <property name="text">
+ <string>&lt;b&gt;Password:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1" colspan="2">
+ <widget class="QLineEdit" name="lblPassword"/>
+ </item>
+ <item row="4" column="1" colspan="2">
+ <widget class="QLineEdit" name="lblPassword2"/>
+ </item>
+ <item row="2" column="1" colspan="2">
+ <widget class="QLineEdit" name="lblUser"/>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_17">
+ <property name="text">
+ <string>&lt;b&gt;Re-enter password:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QPushButton" name="btnRegister">
+ <property name="text">
+ <string>Register</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <spacer name="verticalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>60</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="7" column="1">
+ <spacer name="verticalSpacer_7">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_15">
+ <property name="text">
+ <string>&lt;b&gt;Username:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1" colspan="2">
+ <widget class="QCheckBox" name="chkRemember">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Remember my username and password</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" colspan="2">
+ <widget class="QLabel" name="lblRegisterStatus">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::AutoText</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWizardPage" name="service_selection">
+ <property name="title">
+ <string>Service selection</string>
+ </property>
+ <property name="subTitle">
+ <string>Please select the services you would like to have</string>
+ </property>
+ <attribute name="pageId">
+ <string notr="true">5</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_8">
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="grpServices">
+ <property name="title">
+ <string notr="true">Services by PROVIDER</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_9">
+ <item row="0" column="0">
+ <layout class="QVBoxLayout" name="serviceListLayout"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWizardPage" name="finish_page">
+ <property name="title">
+ <string>Congratulations!</string>
+ </property>
+ <property name="subTitle">
+ <string>You have successfully configured Bitmask.</string>
+ </property>
+ <attribute name="pageId">
+ <string notr="true">6</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_10">
+ <item row="1" column="0">
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="1">
+ <spacer name="verticalSpacer_9">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="label_23">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/mask-icon.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="label_25">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Globe.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <spacer name="verticalSpacer_10">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="3">
+ <spacer name="horizontalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>WizardPage</class>
+ <extends>QWizardPage</extends>
+ <header>wizardpage.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>lblUser</tabstop>
+ <tabstop>lblPassword</tabstop>
+ <tabstop>lblPassword2</tabstop>
+ <tabstop>btnRegister</tabstop>
+ <tabstop>rdoRegister</tabstop>
+ <tabstop>rdoLogin</tabstop>
+ <tabstop>lnProvider</tabstop>
+ <tabstop>btnCheck</tabstop>
+ </tabstops>
+ <resources>
+ <include location="../../../../data/resources/mainwindow.qrc"/>
+ <include location="../../../../data/resources/locale.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
new file mode 100644
index 00000000..ed6c1da0
--- /dev/null
+++ b/src/leap/bitmask/gui/wizard.py
@@ -0,0 +1,628 @@
+# -*- coding: utf-8 -*-
+# wizard.py
+# Copyright (C) 2013 LEAP
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+"""
+First run wizard
+"""
+import os
+import logging
+import json
+
+from functools import partial
+
+from PySide import QtCore, QtGui
+from twisted.internet import threads
+
+from leap.bitmask.config.providerconfig import ProviderConfig
+from leap.bitmask.crypto.srpregister import SRPRegister
+from leap.bitmask.util.privilege_policies import is_missing_policy_permissions
+from leap.bitmask.util.request_helpers import get_content
+from leap.bitmask.util.keyring_helpers import has_keyring
+from leap.bitmask.services.eip.providerbootstrapper import ProviderBootstrapper
+from leap.bitmask.services import get_supported
+
+from ui_wizard import Ui_Wizard
+
+logger = logging.getLogger(__name__)
+
+
+class Wizard(QtGui.QWizard):
+ """
+ First run wizard to register a user and setup a provider
+ """
+
+ INTRO_PAGE = 0
+ SELECT_PROVIDER_PAGE = 1
+ PRESENT_PROVIDER_PAGE = 2
+ SETUP_PROVIDER_PAGE = 3
+ REGISTER_USER_PAGE = 4
+ SERVICES_PAGE = 5
+ FINISH_PAGE = 6
+
+ WEAK_PASSWORDS = ("123456", "qweasd", "qwerty",
+ "password")
+
+ BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$"
+
+ def __init__(self, standalone=False, bypass_checks=False):
+ """
+ Constructor for the main Wizard.
+
+ :param standalone: If True, the application is running as standalone
+ and the wizard should display some messages according to this.
+ :type standalone: bool
+ :param bypass_checks: Set to true if the app should bypass
+ first round of checks for CA certificates at bootstrap
+ :type bypass_checks: bool
+ """
+ QtGui.QWizard.__init__(self)
+
+ self.standalone = standalone
+
+ self.ui = Ui_Wizard()
+ self.ui.setupUi(self)
+
+ self.setPixmap(QtGui.QWizard.LogoPixmap,
+ QtGui.QPixmap(":/images/mask-icon.png"))
+
+ self.QUESTION_ICON = QtGui.QPixmap(":/images/Emblem-question.png")
+ self.ERROR_ICON = QtGui.QPixmap(":/images/Dialog-error.png")
+ self.OK_ICON = QtGui.QPixmap(":/images/Dialog-accept.png")
+
+ # Correspondence for services and their name to display
+ EIP_LABEL = self.tr("Encrypted Internet")
+ MX_LABEL = self.tr("Encrypted Mail")
+
+ if self._is_need_eip_password_warning():
+ EIP_LABEL += " " + self.tr(
+ "(will need admin password to start)")
+
+ self.SERVICE_DISPLAY = [
+ EIP_LABEL,
+ MX_LABEL
+ ]
+ self.SERVICE_CONFIG = [
+ "openvpn",
+ "mx"
+ ]
+
+ self._selected_services = set()
+ self._shown_services = set()
+
+ self._show_register = False
+
+ self.ui.grpCheckProvider.setVisible(False)
+ self.ui.btnCheck.clicked.connect(self._check_provider)
+ self.ui.lnProvider.returnPressed.connect(self._check_provider)
+
+ self._provider_bootstrapper = ProviderBootstrapper(bypass_checks)
+ self._provider_bootstrapper.name_resolution.connect(
+ self._name_resolution)
+ self._provider_bootstrapper.https_connection.connect(
+ self._https_connection)
+ self._provider_bootstrapper.download_provider_info.connect(
+ self._download_provider_info)
+
+ self._provider_bootstrapper.download_ca_cert.connect(
+ self._download_ca_cert)
+ self._provider_bootstrapper.check_ca_fingerprint.connect(
+ self._check_ca_fingerprint)
+ self._provider_bootstrapper.check_api_certificate.connect(
+ self._check_api_certificate)
+
+ self._domain = None
+ self._provider_config = ProviderConfig()
+
+ # We will store a reference to the defers for eventual use
+ # (eg, to cancel them) but not doing anything with them right now.
+ self._provider_select_defer = None
+ self._provider_setup_defer = None
+
+ self.currentIdChanged.connect(self._current_id_changed)
+
+ self.ui.lblPassword.setEchoMode(QtGui.QLineEdit.Password)
+ self.ui.lblPassword2.setEchoMode(QtGui.QLineEdit.Password)
+
+ self.ui.lnProvider.textChanged.connect(
+ self._enable_check)
+
+ self.ui.lblUser.returnPressed.connect(
+ self._focus_password)
+ self.ui.lblPassword.returnPressed.connect(
+ self._focus_second_password)
+ self.ui.lblPassword2.returnPressed.connect(
+ self._register)
+ self.ui.btnRegister.clicked.connect(
+ self._register)
+
+ usernameRe = QtCore.QRegExp(self.BARE_USERNAME_REGEX)
+ self.ui.lblUser.setValidator(
+ QtGui.QRegExpValidator(usernameRe, self))
+
+ self.page(self.REGISTER_USER_PAGE).setCommitPage(True)
+
+ self._username = None
+ self._password = None
+
+ self.page(self.REGISTER_USER_PAGE).setButtonText(
+ QtGui.QWizard.CommitButton, self.tr("&Next >"))
+ self.page(self.FINISH_PAGE).setButtonText(
+ QtGui.QWizard.FinishButton, self.tr("Connect"))
+
+ # XXX: Temporary removal for enrollment policy
+ # https://leap.se/code/issues/2922
+ self.ui.label_12.setVisible(False)
+ self.ui.lblProviderPolicy.setVisible(False)
+
+ def get_domain(self):
+ return self._domain
+
+ def get_username(self):
+ return self._username
+
+ def get_password(self):
+ return self._password
+
+ def get_remember(self):
+ return has_keyring() and self.ui.chkRemember.isChecked()
+
+ def get_services(self):
+ return self._selected_services
+
+ def _enable_check(self, text):
+ self.ui.btnCheck.setEnabled(len(self.ui.lnProvider.text()) != 0)
+ self._reset_provider_check()
+
+ def _focus_password(self):
+ """
+ Focuses at the password lineedit for the registration page
+ """
+ self.ui.lblPassword.setFocus()
+
+ def _focus_second_password(self):
+ """
+ Focuses at the second password lineedit for the registration page
+ """
+ self.ui.lblPassword2.setFocus()
+
+ def _basic_password_checks(self, username, password, password2):
+ """
+ Performs basic password checks to avoid really easy passwords.
+
+ :param username: username provided at the registrarion form
+ :type username: str
+ :param password: password from the registration form
+ :type password: str
+ :param password2: second password from the registration form
+ :type password: str
+
+ :return: returns True if all the checks pass, False otherwise
+ :rtype: bool
+ """
+ message = None
+
+ if message is None and password != password2:
+ message = self.tr("Passwords don't match")
+
+ if message is None and len(password) < 6:
+ message = self.tr("Password too short")
+
+ if message is None and password in self.WEAK_PASSWORDS:
+ message = self.tr("Password too easy")
+
+ if message is None and username == password:
+ message = self.tr("Password equal to username")
+
+ if message is not None:
+ self._set_register_status(message, error=True)
+ self._focus_password()
+ return False
+
+ return True
+
+ def _register(self):
+ """
+ Performs the registration based on the values provided in the form
+ """
+ self.ui.btnRegister.setEnabled(False)
+
+ username = self.ui.lblUser.text()
+ password = self.ui.lblPassword.text()
+ password2 = self.ui.lblPassword2.text()
+
+ if self._basic_password_checks(username, password, password2):
+ register = SRPRegister(provider_config=self._provider_config)
+ register.registration_finished.connect(
+ self._registration_finished)
+
+ threads.deferToThread(
+ partial(register.register_user,
+ username.encode("utf8"),
+ password.encode("utf8")))
+
+ self._username = username
+ self._password = password
+ self._set_register_status(self.tr("Starting registration..."))
+ else:
+ self.ui.btnRegister.setEnabled(True)
+
+ def _set_registration_fields_visibility(self, visible):
+ """
+ This method hides the username and password labels and inputboxes.
+
+ :param visible: sets the visibility of the widgets
+ True: widgets are visible or False: are not
+ :type visible: bool
+ """
+ # username and password inputs
+ self.ui.lblUser.setVisible(visible)
+ self.ui.lblPassword.setVisible(visible)
+ self.ui.lblPassword2.setVisible(visible)
+
+ # username and password labels
+ self.ui.label_15.setVisible(visible)
+ self.ui.label_16.setVisible(visible)
+ self.ui.label_17.setVisible(visible)
+
+ # register button
+ self.ui.btnRegister.setVisible(visible)
+
+ def _registration_finished(self, ok, req):
+ if ok:
+ user_domain = self._username + "@" + self._domain
+ message = "<font color='green'><h3>"
+ message += self.tr("User %s successfully registered.") % (
+ user_domain, )
+ message += "</h3></font>"
+ self._set_register_status(message)
+
+ self.ui.lblPassword2.clearFocus()
+ self._set_registration_fields_visibility(False)
+
+ # Allow the user to remember his password
+ if has_keyring():
+ self.ui.chkRemember.setVisible(True)
+ self.ui.chkRemember.setEnabled(True)
+
+ self.page(self.REGISTER_USER_PAGE).set_completed()
+ self.button(QtGui.QWizard.BackButton).setEnabled(False)
+ else:
+ old_username = self._username
+ self._username = None
+ self._password = None
+ error_msg = self.tr("Unknown error")
+ try:
+ content, _ = get_content(req)
+ json_content = json.loads(content)
+ error_msg = json_content.get("errors").get("login")[0]
+ if not error_msg.istitle():
+ error_msg = "%s %s" % (old_username, error_msg)
+ except Exception as e:
+ logger.error("Unknown error: %r" % (e,))
+
+ self._set_register_status(error_msg, error=True)
+ self.ui.btnRegister.setEnabled(True)
+
+ def _set_register_status(self, status, error=False):
+ """
+ Sets the status label in the registration page to status
+
+ :param status: status message to display, can be HTML
+ :type status: str
+ """
+ if error:
+ status = "<font color='red'><b>%s</b></font>" % (status,)
+ self.ui.lblRegisterStatus.setText(status)
+
+ def _reset_provider_check(self):
+ """
+ Resets the UI for checking a provider. Also resets the domain
+ in this object.
+ """
+ self.ui.lblNameResolution.setPixmap(None)
+ self.ui.lblHTTPS.setPixmap(None)
+ self.ui.lblProviderInfo.setPixmap(None)
+ self.ui.lblProviderSelectStatus.setText("")
+ self._domain = None
+ self.button(QtGui.QWizard.NextButton).setEnabled(False)
+ self.page(self.SELECT_PROVIDER_PAGE).set_completed(False)
+
+ def _reset_provider_setup(self):
+ """
+ Resets the UI for setting up a provider.
+ """
+ self.ui.lblDownloadCaCert.setPixmap(None)
+ self.ui.lblCheckCaFpr.setPixmap(None)
+ self.ui.lblCheckApiCert.setPixmap(None)
+
+ def _check_provider(self):
+ """
+ SLOT
+ TRIGGERS:
+ self.ui.btnCheck.clicked
+ self.ui.lnProvider.returnPressed
+
+ Starts the checks for a given provider
+ """
+ if len(self.ui.lnProvider.text()) == 0:
+ return
+
+ self.ui.grpCheckProvider.setVisible(True)
+ self.ui.btnCheck.setEnabled(False)
+ self.ui.lnProvider.setEnabled(False)
+ self.button(QtGui.QWizard.BackButton).clearFocus()
+ self._domain = self.ui.lnProvider.text()
+
+ self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON)
+ self._provider_select_defer = self._provider_bootstrapper.\
+ run_provider_select_checks(self._domain)
+
+ def _complete_task(self, data, label, complete=False, complete_page=-1):
+ """
+ Checks a task and completes a page if specified
+
+ :param data: data as it comes from the bootstrapper thread for
+ a specific check
+ :type data: dict
+ :param label: label that displays the status icon for a
+ specific check that corresponds to the data
+ :type label: QtGui.QLabel
+ :param complete: if True, it completes the page specified,
+ which must be of type WizardPage
+ :type complete: bool
+ :param complete_page: page id to complete
+ :type complete_page: int
+ """
+ passed = data[self._provider_bootstrapper.PASSED_KEY]
+ error = data[self._provider_bootstrapper.ERROR_KEY]
+ if passed:
+ label.setPixmap(self.OK_ICON)
+ if complete:
+ self.page(complete_page).set_completed()
+ self.button(QtGui.QWizard.NextButton).setFocus()
+ else:
+ label.setPixmap(self.ERROR_ICON)
+ logger.error(error)
+
+ def _name_resolution(self, data):
+ """
+ SLOT
+ TRIGGER: self._provider_bootstrapper.name_resolution
+
+ Sets the status for the name resolution check
+ """
+ self._complete_task(data, self.ui.lblNameResolution)
+ status = ""
+ passed = data[self._provider_bootstrapper.PASSED_KEY]
+ if not passed:
+ status = self.tr("<font color='red'><b>Non-existent "
+ "provider</b></font>")
+ else:
+ self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON)
+ self.ui.lblProviderSelectStatus.setText(status)
+ self.ui.btnCheck.setEnabled(not passed)
+ self.ui.lnProvider.setEnabled(not passed)
+
+ def _https_connection(self, data):
+ """
+ SLOT
+ TRIGGER: self._provider_bootstrapper.https_connection
+
+ Sets the status for the https connection check
+ """
+ self._complete_task(data, self.ui.lblHTTPS)
+ status = ""
+ passed = data[self._provider_bootstrapper.PASSED_KEY]
+ if not passed:
+ status = self.tr("<font color='red'><b>%s</b></font>") \
+ % (data[self._provider_bootstrapper.ERROR_KEY])
+ self.ui.lblProviderSelectStatus.setText(status)
+ else:
+ self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON)
+ self.ui.btnCheck.setEnabled(not passed)
+ self.ui.lnProvider.setEnabled(not passed)
+
+ def _download_provider_info(self, data):
+ """
+ SLOT
+ TRIGGER: self._provider_bootstrapper.download_provider_info
+
+ Sets the status for the provider information download
+ check. Since this check is the last of this set, it also
+ completes the page if passed
+ """
+ if self._provider_config.load(os.path.join("leap",
+ "providers",
+ self._domain,
+ "provider.json")):
+ self._complete_task(data, self.ui.lblProviderInfo,
+ True, self.SELECT_PROVIDER_PAGE)
+ else:
+ new_data = {
+ self._provider_bootstrapper.PASSED_KEY: False,
+ self._provider_bootstrapper.ERROR_KEY:
+ self.tr("Unable to load provider configuration")
+ }
+ self._complete_task(new_data, self.ui.lblProviderInfo)
+
+ status = ""
+ if not data[self._provider_bootstrapper.PASSED_KEY]:
+ status = self.tr("<font color='red'><b>Not a valid provider"
+ "</b></font>")
+ self.ui.lblProviderSelectStatus.setText(status)
+ self.ui.btnCheck.setEnabled(True)
+ self.ui.lnProvider.setEnabled(True)
+
+ def _download_ca_cert(self, data):
+ """
+ SLOT
+ TRIGGER: self._provider_bootstrapper.download_ca_cert
+
+ Sets the status for the download of the CA certificate check
+ """
+ self._complete_task(data, self.ui.lblDownloadCaCert)
+ passed = data[self._provider_bootstrapper.PASSED_KEY]
+ if passed:
+ self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON)
+
+ def _check_ca_fingerprint(self, data):
+ """
+ SLOT
+ TRIGGER: self._provider_bootstrapper.check_ca_fingerprint
+
+ Sets the status for the CA fingerprint check
+ """
+ self._complete_task(data, self.ui.lblCheckCaFpr)
+ passed = data[self._provider_bootstrapper.PASSED_KEY]
+ if passed:
+ self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON)
+
+ def _check_api_certificate(self, data):
+ """
+ SLOT
+ TRIGGER: self._provider_bootstrapper.check_api_certificate
+
+ Sets the status for the API certificate check. Also finishes
+ the provider bootstrapper thread since it's not needed anymore
+ from this point on, unless the whole check chain is restarted
+ """
+ self._complete_task(data, self.ui.lblCheckApiCert,
+ True, self.SETUP_PROVIDER_PAGE)
+
+ def _service_selection_changed(self, service, state):
+ """
+ SLOT
+ TRIGGER: service_checkbox.stateChanged
+ Adds the service to the state if the state is checked, removes
+ it otherwise
+
+ :param service: service to handle
+ :type service: str
+ :param state: state of the checkbox
+ :type state: int
+ """
+ if state == QtCore.Qt.Checked:
+ self._selected_services = \
+ self._selected_services.union(set([service]))
+ else:
+ self._selected_services = \
+ self._selected_services.difference(set([service]))
+
+ def _populate_services(self):
+ """
+ Loads the services that the provider provides into the UI for
+ the user to enable or disable.
+ """
+ self.ui.grpServices.setTitle(
+ self.tr("Services by %s") %
+ (self._provider_config.get_name(),))
+
+ services = get_supported(
+ self._provider_config.get_services())
+
+ for service in services:
+ try:
+ if service not in self._shown_services:
+ checkbox = QtGui.QCheckBox(self)
+ service_index = self.SERVICE_CONFIG.index(service)
+ checkbox.setText(self.SERVICE_DISPLAY[service_index])
+ self.ui.serviceListLayout.addWidget(checkbox)
+ checkbox.stateChanged.connect(
+ partial(self._service_selection_changed, service))
+ checkbox.setChecked(True)
+ self._shown_services.add(service)
+ except ValueError:
+ logger.error(
+ self.tr("Something went wrong while trying to "
+ "load service %s" % (service,)))
+
+ def _current_id_changed(self, pageId):
+ """
+ SLOT
+ TRIGGER: self.currentIdChanged
+
+ Prepares the pages when they appear
+ """
+ if pageId == self.SELECT_PROVIDER_PAGE:
+ self._reset_provider_check()
+ self._enable_check("")
+
+ if pageId == self.SETUP_PROVIDER_PAGE:
+ self._reset_provider_setup()
+ self.page(pageId).setSubTitle(self.tr("Gathering configuration "
+ "options for %s") %
+ (self._provider_config
+ .get_name(),))
+ self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON)
+ self._provider_setup_defer = self._provider_bootstrapper.\
+ run_provider_setup_checks(self._provider_config)
+
+ if pageId == self.PRESENT_PROVIDER_PAGE:
+ self.page(pageId).setSubTitle(self.tr("Description of services "
+ "offered by %s") %
+ (self._provider_config
+ .get_name(),))
+
+ lang = QtCore.QLocale.system().name()
+ self.ui.lblProviderName.setText(
+ "<b>%s</b>" %
+ (self._provider_config.get_name(lang=lang),))
+ self.ui.lblProviderURL.setText(
+ "https://%s" % (self._provider_config.get_domain(),))
+ self.ui.lblProviderDesc.setText(
+ "<i>%s</i>" %
+ (self._provider_config.get_description(lang=lang),))
+
+ self.ui.lblServicesOffered.setText(self._provider_config
+ .get_services_string())
+ self.ui.lblProviderPolicy.setText(self._provider_config
+ .get_enrollment_policy())
+
+ if pageId == self.REGISTER_USER_PAGE:
+ self.page(pageId).setSubTitle(self.tr("Register a new user with "
+ "%s") %
+ (self._provider_config
+ .get_name(),))
+ self.ui.chkRemember.setVisible(False)
+
+ if pageId == self.SERVICES_PAGE:
+ self._populate_services()
+
+ def _is_need_eip_password_warning(self):
+ """
+ Returns True if we need to add a warning about eip needing
+ administrative permissions to start. That can be either
+ because we are running in standalone mode, or because we could
+ not find the needed privilege escalation mechanisms being operative.
+ """
+ return self.standalone or is_missing_policy_permissions()
+
+ def nextId(self):
+ """
+ Sets the next page id for the wizard based on wether the user
+ wants to register a new identity or uses an existing one
+ """
+ if self.currentPage() == self.page(self.INTRO_PAGE):
+ self._show_register = self.ui.rdoRegister.isChecked()
+
+ if self.currentPage() == self.page(self.SETUP_PROVIDER_PAGE):
+ if self._show_register:
+ return self.REGISTER_USER_PAGE
+ else:
+ return self.SERVICES_PAGE
+
+ return QtGui.QWizard.nextId(self)
diff --git a/src/leap/bitmask/gui/wizardpage.py b/src/leap/bitmask/gui/wizardpage.py
new file mode 100644
index 00000000..b2a00028
--- /dev/null
+++ b/src/leap/bitmask/gui/wizardpage.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# wizardpage.py
+# Copyright (C) 2013 LEAP
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+from PySide import QtGui
+
+
+class WizardPage(QtGui.QWizardPage):
+ """
+ Simple wizard page helper
+ """
+
+ def __init__(self):
+ QtGui.QWizardPage.__init__(self)
+ self._completed = False
+
+ def set_completed(self, val=True):
+ self._completed = val
+ if val:
+ self.completeChanged.emit()
+
+ def isComplete(self):
+ return self._completed
+
+ def cleanupPage(self):
+ self._completed = False
+ QtGui.QWizardPage.cleanupPage(self)