summaryrefslogtreecommitdiff
path: root/src/leap/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/gui')
-rw-r--r--src/leap/gui/__init__.py0
-rw-r--r--src/leap/gui/mainwindow.py600
-rw-r--r--src/leap/gui/ui/mainwindow.ui377
-rw-r--r--src/leap/gui/ui/wizard.ui800
-rw-r--r--src/leap/gui/wizard.py403
-rw-r--r--src/leap/gui/wizardpage.py39
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>&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">
+ <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="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>&lt;b&gt;User:&lt;/b&gt;</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>&amp;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&amp;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>&amp;Sign 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;LEAP</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>
+ </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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;New 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 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>&lt;b&gt;Enrollment policy:&lt;/b&gt;</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>&lt;b&gt;User:&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_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="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)