diff options
Diffstat (limited to 'src/leap/gui')
-rw-r--r-- | src/leap/gui/__init__.py | 0 | ||||
-rw-r--r-- | src/leap/gui/mainwindow.py | 600 | ||||
-rw-r--r-- | src/leap/gui/ui/mainwindow.ui | 377 | ||||
-rw-r--r-- | src/leap/gui/ui/wizard.ui | 800 | ||||
-rw-r--r-- | src/leap/gui/wizard.py | 403 | ||||
-rw-r--r-- | src/leap/gui/wizardpage.py | 39 |
6 files changed, 2219 insertions, 0 deletions
diff --git a/src/leap/gui/__init__.py b/src/leap/gui/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/leap/gui/__init__.py diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py new file mode 100644 index 00000000..1821e4a6 --- /dev/null +++ b/src/leap/gui/mainwindow.py @@ -0,0 +1,600 @@ +# -*- 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 os +import logging + +from PySide import QtCore, QtGui + +from ui_mainwindow import Ui_MainWindow +from leap.config.providerconfig import ProviderConfig +from leap.crypto.srpauth import SRPAuth +from leap.services.eip.vpn import VPN +from leap.services.eip.providerbootstrapper import ProviderBootstrapper +from leap.services.eip.eipbootstrapper import EIPBootstrapper +from leap.services.eip.eipconfig import EIPConfig +from leap.gui.wizard import Wizard + +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 + + def __init__(self): + QtGui.QMainWindow.__init__(self) + + self.CONNECTING_ICON = QtGui.QPixmap(":/images/conn_connecting.png") + self.CONNECTED_ICON = QtGui.QPixmap(":/images/conn_connected.png") + self.ERROR_ICON = QtGui.QPixmap(":/images/conn_error.png") + + self.ui = Ui_MainWindow() + self.ui.setupUi(self) + + self.ui.lnPassword.setEchoMode(QtGui.QLineEdit.Password) + + self.ui.btnLogin.clicked.connect(self._login) + self.ui.lnUser.returnPressed.connect(self._focus_password) + self.ui.lnPassword.returnPressed.connect(self._login) + + self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) + + # This is loaded only once, there's a bug when doing that more + # than once + self._provider_config = ProviderConfig() + self._eip_config = EIPConfig() + # This is created once we have a valid provider config + self._srp_auth = 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() + + # TODO: add sigint handler + + # 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._intermediate_stage) + self._eip_bootstrapper.download_client_certificate.connect( + self._start_eip) + + self._vpn = VPN() + self._vpn.state_changed.connect(self._update_vpn_state) + self._vpn.status_changed.connect(self._update_vpn_status) + + QtCore.QCoreApplication.instance().connect( + QtCore.QCoreApplication.instance(), + QtCore.SIGNAL("aboutToQuit()"), + self._vpn.set_should_quit) + QtCore.QCoreApplication.instance().connect( + QtCore.QCoreApplication.instance(), + QtCore.SIGNAL("aboutToQuit()"), + self._provider_bootstrapper.set_should_quit) + QtCore.QCoreApplication.instance().connect( + QtCore.QCoreApplication.instance(), + QtCore.SIGNAL("aboutToQuit()"), + self._eip_bootstrapper.set_should_quit) + + self.ui.action_sign_out.setEnabled(False) + self.ui.action_sign_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) + + # Used to differentiate between real quits and close to tray + self._really_quit = False + + self._systray = None + self._action_visible = QtGui.QAction("Hide", self) + self._action_visible.triggered.connect(self._toggle_visible) + + self._center_window() + self._wizard = None + if self._first_run(): + self._wizard = Wizard() + # Give this window time to finish init and then show the wizard + QtCore.QTimer.singleShot(1, self._launch_wizard) + self._wizard.finished.connect(self._finish_init) + else: + self._finish_init() + + def _launch_wizard(self): + if self._wizard is None: + self._wizard = Wizard() + self._wizard.exec_() + + def _finish_init(self): + self.ui.cmbProviders.addItems(self._configured_providers()) + self._show_systray() + self.show() + if self._wizard: + possible_username = self._wizard.get_username() + if possible_username is not None: + self.ui.lnUser.setText(possible_username) + self._focus_password() + self._wizard = None + + def _show_systray(self): + """ + Sets up the systray icon + """ + systrayMenu = QtGui.QMenu(self) + systrayMenu.addAction(self._action_visible) + systrayMenu.addAction(self.ui.action_sign_out) + systrayMenu.addSeparator() + systrayMenu.addAction(self.ui.action_quit) + self._systray = QtGui.QSystemTrayIcon(self) + self._systray.setContextMenu(systrayMenu) + self._systray.setIcon(QtGui.QIcon(self.ERROR_ICON)) + self._systray.setVisible(True) + self._systray.activated.connect(self._toggle_visible) + + def _toggle_visible(self): + """ + SLOT + TRIGGER: self._systray.activated + + Toggles the window visibility + """ + self.setVisible(not self.isVisible()) + action_visible_text = "Hide" + if not self.isVisible(): + action_visible_text = "Show" + self._action_visible.setText(action_visible_text) + + def _center_window(self): + """ + Centers the mainwindow based on the desktop geometry + """ + 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) + + def _about(self): + """ + Display the About LEAP dialog + """ + QtGui.QMessageBox.about(self, "About LEAP", + "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. " + "<a href=\"https://leap.se\">More about LEAP" + "</a>") + + def quit(self): + self._really_quit = True + if self._wizard: + self._wizard.accept() + self.close() + + 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 + QtGui.QMainWindow.closeEvent(self, e) + + def _configured_providers(self): + """ + Returns the available providers based on the file structure + + @rtype: list + """ + providers = os.listdir( + os.path.join(self._provider_config.get_path_prefix(), + "leap", + "providers")) + return providers + + def _first_run(self): + """ + Returns True if there are no configured providers. False otherwise + + @rtype: bool + """ + return len(self._configured_providers()) == 0 + + def _focus_password(self): + """ + Focuses in the password lineedit + """ + self.ui.lnPassword.setFocus() + + def _set_status(self, status): + """ + Sets the status label at the login stage to status + + @param status: status message + @type status: str + """ + self.ui.lblStatus.setText(status) + + def _set_eip_status(self, status): + """ + Sets the status label at the VPN stage to status + + @param status: status message + @type status: str + """ + self.ui.lblEIPStatus.setText(status) + + def _login_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.btnLogin.setEnabled(enabled) + self.ui.chkRemember.setEnabled(enabled) + self.ui.cmbProviders.setEnabled(enabled) + + 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.ui.cmbProviders.currentText() + + self._provider_bootstrapper.start() + self._provider_bootstrapper.run_provider_select_checks( + provider, + download_if_needed=True) + + 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.ui.cmbProviders.currentText() + if self._provider_config.loaded() or \ + self._provider_config.load(os.path.join("leap", + "providers", + provider, + "provider.json")): + self._provider_bootstrapper.run_provider_setup_checks( + self._provider_config, + download_if_needed=True) + else: + self._set_status("Could not load provider configuration") + self._login_set_enabled(True) + else: + self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) + self._login_set_enabled(True) + + def _login(self): + """ + SLOT + TRIGGERS: + self.ui.btnLogin.clicked + self.ui.lnPassword.returnPressed + + 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 + """ + assert self._provider_config, "We need a provider config" + + username = self.ui.lnUser.text() + password = self.ui.lnPassword.text() + provider = self.ui.cmbProviders.currentText() + + if len(provider) == 0: + self._set_status("Please select a valid provider") + return + + if len(username) == 0: + self._set_status("Please provide a valid username") + return + + if len(password) == 0: + self._set_status("Please provide a valid Password") + return + + self._set_status("Logging in...") + self._login_set_enabled(False) + + self._download_provider_config() + + 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 + """ + assert self._provider_config, "We need a provider config!" + + self._provider_bootstrapper.set_should_quit() + + if data[self._provider_bootstrapper.PASSED_KEY]: + username = self.ui.lnUser.text() + password = self.ui.lnPassword.text() + + 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) + + self._srp_auth.authenticate(username, password) + else: + self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) + self._login_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 + """ + self._set_status(message) + if ok: + self.ui.action_sign_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) + else: + self._login_set_enabled(True) + + def _switch_to_status(self): + """ + Changes the stackedWidget index to the EIP status one and + triggers the eip bootstrapping + """ + self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX) + self._download_eip_config() + + def _download_eip_config(self): + """ + Starts the EIP bootstrapping sequence + """ + assert self._eip_bootstrapper, "We need an eip bootstrapper!" + assert self._provider_config, "We need a provider config" + + self._set_eip_status("Checking configuration, please wait...") + + if self._provider_config.provides_eip(): + self._eip_bootstrapper.start() + self._eip_bootstrapper.run_eip_setup_checks( + self._provider_config, + download_if_needed=True) + else: + self._set_eip_status("%s does not support EIP" % + (self._provider_config.get_domain(),)) + + 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 + if status in ("AUTH", "GET_CONFIG"): + selected_pixmap = self.CONNECTING_ICON + elif status in ("CONNECTED"): + selected_pixmap = self.CONNECTED_ICON + + self.ui.lblVPNStatusIcon.setPixmap(selected_pixmap) + self._systray.setIcon(QtGui.QIcon(selected_pixmap)) + + def _update_vpn_state(self, data): + """ + SLOT + TRIGGER: self._vpn.state_changed + + Updates the displayed VPN state based on the data provided by + the VPN thread + """ + status = data[self._vpn.STATUS_STEP_KEY] + self._set_eip_status_icon(status) + if status == "AUTH": + self._set_eip_status("VPN: Authenticating...") + elif status == "GET_CONFIG": + self._set_eip_status("VPN: Retrieving configuration...") + elif status == "CONNECTED": + self._set_eip_status("VPN: Connected!") + else: + self._set_eip_status(status) + + def _update_vpn_status(self, data): + """ + SLOT + TRIGGER: self._vpn.status_changed + + Updates the download/upload labels based on the data provided + by the VPN thread + """ + upload = float(data[self._vpn.TUNTAP_WRITE_KEY]) + upload = upload / 1000.0 + self.ui.lblUpload.setText("%s Kb" % (upload,)) + download = float(data[self._vpn.TUNTAP_READ_KEY]) + download = download / 1000.0 + self.ui.lblDownload.setText("%s Kb" % (download,)) + + def _start_eip(self, data): + """ + SLOT + TRIGGER: self._eip_bootstrapper.download_client_certificate + + Starts the VPN thread if the eip configuration is properly + loaded + """ + assert self._eip_config, "We need an eip config!" + assert self._provider_config, "We need a provider config!" + + self._eip_bootstrapper.set_should_quit() + if self._eip_config.loaded() or \ + self._eip_config.load(os.path.join("leap", + "providers", + self._provider_config + .get_domain(), + "eip-service.json")): + self._vpn.start(eipconfig=self._eip_config, + providerconfig=self._provider_config, + socket_host="/home/chiiph/vpnsock", + socket_port="unix") + # TODO: display a message if the EIP configuration cannot be + # loaded + + def _logout(self): + """ + SLOT + TRIGGER: self.ui.action_sign_out.triggered + + Starts the logout sequence + """ + self._set_eip_status_icon("error") + self._set_eip_status("Signing out...") + 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._set_status(message) + self.ui.action_sign_out.setEnabled(False) + self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) + self.ui.lnPassword.setText("") + self._login_set_enabled(True) + self._set_status("") + self._vpn.set_should_quit() + + 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_set_enabled(True) + self._set_status(data[self._provider_bootstrapper.ERROR_KEY]) + +if __name__ == "__main__": + import signal + from functools import partial + + 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/gui/ui/mainwindow.ui b/src/leap/gui/ui/mainwindow.ui new file mode 100644 index 00000000..a527eaf6 --- /dev/null +++ b/src/leap/gui/ui/mainwindow.ui @@ -0,0 +1,377 @@ +<?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>405</width> + <height>579</height> + </rect> + </property> + <property name="windowTitle"> + <string>LEAP</string> + </property> + <property name="windowIcon"> + <iconset resource="../../../../data/resources/mainwindow.qrc"> + <normaloff>:/images/leap-color-big.png</normaloff>:/images/leap-color-big.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="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="1" 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="9" 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="2" column="0" colspan="5"> + <widget class="QStackedWidget" name="stackedWidget"> + <property name="currentIndex"> + <number>1</number> + </property> + <widget class="QWidget" name="page"> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="2" column="2" colspan="2"> + <widget class="QLineEdit" name="lnUser"/> + </item> + <item row="4" column="2"> + <widget class="QCheckBox" name="chkRemember"> + <property name="text"> + <string>Remember</string> + </property> + </widget> + </item> + <item row="5" column="2"> + <widget class="QPushButton" name="btnLogin"> + <property name="text"> + <string>Login</string> + </property> + </widget> + </item> + <item row="2" column="4"> + <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="2" 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> + <item row="1" column="1"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string><b>Provider:</b></string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string><b>Password:</b></string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="2" colspan="2"> + <widget class="QComboBox" name="cmbProviders"/> + </item> + <item row="3" column="2" colspan="2"> + <widget class="QLineEdit" name="lnPassword"> + <property name="inputMask"> + <string/> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string><b>User:</b></string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="0" colspan="5"> + <widget class="QLabel" name="lblStatus"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="page_2"> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="1" column="0" colspan="6"> + <widget class="QLabel" name="lblEIPStatus"> + <property name="text"> + <string>Disconnected</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="0" colspan="6"> + <widget class="QLabel" name="lblVPNStatusIcon"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/conn_error.png</pixmap> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="2" column="0"> + <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> + <item row="2" column="2"> + <widget class="QLabel" name="lblUpload"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>0.0 Kb</string> + </property> + </widget> + </item> + <item row="2" column="4"> + <widget class="QLabel" name="lblDownload"> + <property name="minimumSize"> + <size> + <width>70</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>0.0 Kb</string> + </property> + </widget> + </item> + <item row="2" column="5"> + <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="2" column="3"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Arrow-Up-32.png</pixmap> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../data/resources/mainwindow.qrc">:/images/Arrow-Down-32.png</pixmap> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + <item row="1" 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="1" 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/leap-color-big.png</pixmap> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>405</width> + <height>25</height> + </rect> + </property> + <widget class="QMenu" name="menuSession"> + <property name="title"> + <string>&Session</string> + </property> + <addaction name="action_sign_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> + <widget class="QMenu" name="menuSettings"> + <property name="title"> + <string>S&ettings</string> + </property> + <addaction name="action_wizard"/> + </widget> + <addaction name="menuSession"/> + <addaction name="menuSettings"/> + <addaction name="menuHelp"/> + </widget> + <widget class="QStatusBar" name="statusbar"/> + <action name="action_sign_out"> + <property name="text"> + <string>&Sign out</string> + </property> + </action> + <action name="action_quit"> + <property name="text"> + <string>&Quit</string> + </property> + </action> + <action name="action_about_leap"> + <property name="text"> + <string>About &LEAP</string> + </property> + </action> + <action name="action_help"> + <property name="text"> + <string>&Help</string> + </property> + </action> + <action name="action_wizard"> + <property name="text"> + <string>&Wizard</string> + </property> + </action> + </widget> + <tabstops> + <tabstop>lnUser</tabstop> + <tabstop>lnPassword</tabstop> + <tabstop>chkRemember</tabstop> + <tabstop>btnLogin</tabstop> + <tabstop>cmbProviders</tabstop> + </tabstops> + <resources> + <include location="../../../../data/resources/mainwindow.qrc"/> + </resources> + <connections/> +</ui> diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui new file mode 100644 index 00000000..86f8d458 --- /dev/null +++ b/src/leap/gui/ui/wizard.ui @@ -0,0 +1,800 @@ +<?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>LEAP First run</string> + </property> + <property name="windowIcon"> + <iconset resource="../../../../data/resources/mainwindow.qrc"> + <normaloff>:/images/leap-color-big.png</normaloff>:/images/leap-color-big.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 LEAP Client 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><html><head/><body><p>New we will guide you through some configuration that is needed before you can connect for the first time.</p><p>If you ever need to modify these options again, you can find the wizard in the <span style=" font-style:italic;">'Settings'</span> menu from the main window.</p><p>Do you want to <span style=" font-weight:600;">sign up</span> for a new account, or <span style=" font-weight:600;">log in</span> with an already existing username?</p></body></html></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 user 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="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="1" column="1"> + <widget class="QLineEdit" name="lnProvider"/> + </item> + <item row="3" column="0" colspan="3"> + <widget class="QGroupBox" name="grpCheckProvider"> + <property name="title"> + <string>Checking provider</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="3" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Download provider information</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>HTTPS 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="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="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="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>Name resolution</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="1" column="2"> + <widget class="QPushButton" name="btnCheck"> + <property name="text"> + <string>Check</string> + </property> + </widget> + </item> + <item row="2" 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> + </layout> + </widget> + <widget class="QWizardPage" name="provider_info_page"> + <property name="title"> + <string>Provider Information</string> + </property> + <property name="subTitle"> + <string>Services offered by this provider</string> + </property> + <attribute name="pageId"> + <string notr="true">2</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="1" colspan="2"> + <spacer name="horizontalSpacer_6"> + <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="5" column="0" colspan="2"> + <widget class="QLabel" name="label_12"> + <property name="text"> + <string><b>Enrollment policy:</b></string> + </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="2" column="0"> + <widget class="QLabel" name="lblProviderName"> + <property name="text"> + <string>Name</string> + </property> + </widget> + </item> + <item row="4" column="2"> + <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="1" column="1"> + <spacer name="verticalSpacer_13"> + <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="5" column="2"> + <widget class="QLabel" name="lblProviderPolicy"> + <property name="text"> + <string>policy</string> + </property> + </widget> + </item> + <item row="3" column="0" colspan="3"> + <widget class="QLabel" name="lblProviderDesc"> + <property name="text"> + <string>Desc</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="6" column="0"> + <spacer name="verticalSpacer_14"> + <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="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="QGridLayout" name="gridLayout_5"> + <item row="1" column="0"> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Checking 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="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="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>Download CA Certificate</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>Check CA Certificate Fingerprint</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Check API Certificate</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="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 row="0" column="0"> + <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 row="2" column="0"> + <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"> + <item row="1" column="0" colspan="3"> + <widget class="QLabel" name="lblRegisterStatus"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="6" 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><b>User:</b></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_16"> + <property name="text"> + <string><b>Password:</b></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><b>Re-enter password:</b></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="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> + </layout> + </widget> + <widget class="WizardPage" name="eip_setup_page"> + <property name="title"> + <string>EIP Setup</string> + </property> + <property name="subTitle"> + <string>Setting up Encrypted Internet</string> + </property> + <attribute name="pageId"> + <string notr="true">5</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_8"> + <item row="0" column="0"> + <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="1" column="0"> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Checking EIP</string> + </property> + <layout class="QGridLayout" name="gridLayout_9"> + <item row="1" column="0"> + <widget class="QLabel" name="label_19"> + <property name="text"> + <string>Download EIP configuration</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_20"> + <property name="text"> + <string>Download client certificate</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="lblDownloadEIPConfig"> + <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/Emblem-question.png</pixmap> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="lblDownloadClientCert"> + <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/Emblem-question.png</pixmap> + </property> + </widget> + </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>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="2" column="0"> + <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> + </layout> + </widget> + <widget class="QWizardPage" name="finish_page"> + <property name="title"> + <string>Congratulations!</string> + </property> + <property name="subTitle"> + <string>You have successfully configured the LEAP client.</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/leap-color-big.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"/> + </resources> + <connections/> +</ui> diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py new file mode 100644 index 00000000..7dcc8dd6 --- /dev/null +++ b/src/leap/gui/wizard.py @@ -0,0 +1,403 @@ +# -*- 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 + +from PySide import QtCore, QtGui + +from ui_wizard import Ui_Wizard +from leap.config.providerconfig import ProviderConfig +from leap.crypto.srpregister import SRPRegister +from leap.services.eip.providerbootstrapper import ProviderBootstrapper +from leap.services.eip.eipbootstrapper import EIPBootstrapper + +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 + SETUP_EIP_PAGE = 5 + FINISH_PATH = 6 + + WEAK_PASSWORDS = ("1234", "12345", "123456", + "password") + + def __init__(self): + QtGui.QWizard.__init__(self) + + self.ui = Ui_Wizard() + self.ui.setupUi(self) + + 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") + + 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() + 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._eip_bootstrapper = EIPBootstrapper() + + self._eip_bootstrapper.download_config.connect( + self._download_eip_config) + self._eip_bootstrapper.download_client_certificate.connect( + self._download_client_certificate) + + self._domain = None + self._provider_config = ProviderConfig() + + self.currentIdChanged.connect(self._current_id_changed) + + self.ui.lblPassword.setEchoMode(QtGui.QLineEdit.Password) + self.ui.lblPassword2.setEchoMode(QtGui.QLineEdit.Password) + + 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) + + self._username = None + + def __del__(self): + self._provider_bootstrapper.set_should_quit() + self._eip_bootstrapper.set_should_quit() + self._provider_bootstrapper.wait() + self._eip_bootstrapper.wait() + + def get_username(self): + return self._username + + 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 + + try: + username.encode("ascii") + password.encode("ascii") + except: + message = u"Refrain from using non ASCII áñ characters" + + if message is not None and password != password2: + message = "Passwords don't match" + + if message is not None and len(password) < 4: + message = "Password too short" + + if message is not None and password in self.WEAK_PASSWORDS: + message = "Password too easy" + + if message is not None and username == password: + message = "Password equal to username" + + if message is not None: + self._set_register_status(message) + 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) + # See the disabled button + while QtGui.QApplication.instance().hasPendingEvents(): + QtGui.QApplication.instance().processEvents() + self.button(QtGui.QWizard.NextButton).setFocus() + + 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) + ok, req = register.register_user(username, password) + if ok: + self._set_register_status("<b>User registration OK</b>") + self._username = username + self.ui.lblPassword2.clearFocus() + # Detach this call to allow UI updates briefly + QtCore.QTimer.singleShot(1, + self.page(self.REGISTER_USER_PAGE) + .set_completed) + else: + print req.content + error_msg = "Unknown error" + try: + error_msg = req.json().get("errors").get("login")[0] + except: + logger.error("Unknown error: %r" % (req.content,)) + self._set_register_status(error_msg) + self.ui.btnRegister.setEnabled(True) + else: + self.ui.btnRegister.setEnabled(True) + + def _set_register_status(self, status): + """ + Sets the status label in the registration page to status + + @param status: status message to display, can be HTML + @type status: str + """ + self.ui.lblRegisterStatus.setText(status) + + def _check_provider(self): + """ + SLOT + TRIGGERS: + self.ui.btnCheck.clicked + self.ui.lnProvider.returnPressed + + Starts the checks for a given provider + """ + self.ui.grpCheckProvider.setVisible(True) + self.ui.btnCheck.setEnabled(False) + self._domain = self.ui.lnProvider.text() + + self._provider_bootstrapper.start() + 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) + + 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) + + 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: + "Unable to load provider configuration" + } + self._complete_task(new_data, self.ui.lblProviderInfo) + + self.ui.btnCheck.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) + + 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) + + 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) + self._provider_bootstrapper.set_should_quit() + + def _download_eip_config(self, data): + """ + SLOT + TRIGGER: self._eip_bootstrapper.download_config + + Sets the status for the EIP config downloading check + """ + self._complete_task(data, self.ui.lblDownloadEIPConfig) + + def _download_client_certificate(self, data): + """ + SLOT + TRIGGER: self._provider_bootstrapper.download_client_certificate + + Sets the status for the download client certificate check and + completes the page if passed. Also stops the eip bootstrapper + thread since it's not needed from this point on unless the + check chain is restarted + """ + self._complete_task(data, self.ui.lblDownloadClientCert, + True, self.SETUP_EIP_PAGE) + self._eip_bootstrapper.set_should_quit() + + def _current_id_changed(self, pageId): + """ + SLOT + TRIGGER: self.currentIdChanged + + Prepares the pages when they appear + """ + if pageId == self.SELECT_PROVIDER_PAGE: + self.ui.grpCheckProvider.setVisible(False) + self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) + self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON) + self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON) + + if pageId == self.SETUP_PROVIDER_PAGE: + self._provider_bootstrapper.\ + run_provider_setup_checks(self._provider_config) + + if pageId == self.SETUP_EIP_PAGE: + self._eip_bootstrapper.start() + self._eip_bootstrapper.run_eip_setup_checks(self._provider_config) + + if pageId == self.PRESENT_PROVIDER_PAGE: + # TODO: get the right lang for these + self.ui.lblProviderName.setText( + "<b>%s</b>" % + (self._provider_config.get_name(),)) + self.ui.lblProviderURL.setText(self._provider_config.get_domain()) + self.ui.lblProviderDesc.setText( + "<i>%s</i>" % + (self._provider_config.get_description(),)) + self.ui.lblProviderPolicy.setText(self._provider_config + .get_enrollment_policy()) + + 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.SETUP_EIP_PAGE + + return QtGui.QWizard.nextId(self) diff --git a/src/leap/gui/wizardpage.py b/src/leap/gui/wizardpage.py new file mode 100644 index 00000000..2138ac7b --- /dev/null +++ b/src/leap/gui/wizardpage.py @@ -0,0 +1,39 @@ +# -*- 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): + self._completed = True + self.completeChanged.emit() + + def isComplete(self): + return self._completed + + def cleanupPage(self): + self._completed = False + QtGui.QWizardPage.cleanupPage(self) |