summaryrefslogtreecommitdiff
path: root/src/leap/bitmask/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/bitmask/gui')
-rw-r--r--src/leap/bitmask/gui/advanced_key_management.py247
-rw-r--r--src/leap/bitmask/gui/eip_status.py313
-rw-r--r--src/leap/bitmask/gui/loggerwindow.py2
-rw-r--r--src/leap/bitmask/gui/login.py5
-rw-r--r--src/leap/bitmask/gui/mail_status.py2
-rw-r--r--src/leap/bitmask/gui/mainwindow.py1001
-rw-r--r--src/leap/bitmask/gui/preferenceswindow.py209
-rw-r--r--src/leap/bitmask/gui/statemachines.py17
-rw-r--r--src/leap/bitmask/gui/ui/eip_status.ui40
-rw-r--r--src/leap/bitmask/gui/wizard.py102
10 files changed, 977 insertions, 961 deletions
diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py
index be6b4410..b3a4ed8e 100644
--- a/src/leap/bitmask/gui/advanced_key_management.py
+++ b/src/leap/bitmask/gui/advanced_key_management.py
@@ -19,11 +19,8 @@ Advanced Key Management
"""
import logging
-from PySide import QtGui
-from zope.proxy import sameProxiedObjects
+from PySide import QtCore, QtGui
-from leap.keymanager import openpgp
-from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch
from leap.bitmask.services import get_service_display_name, MX_SERVICE
from ui_advanced_key_management import Ui_AdvancedKeyManagement
@@ -34,7 +31,7 @@ class AdvancedKeyManagement(QtGui.QDialog):
"""
Advanced Key Management
"""
- def __init__(self, parent, has_mx, user, keymanager, soledad):
+ def __init__(self, parent, has_mx, user, backend, soledad_started):
"""
:param parent: parent object of AdvancedKeyManagement.
:parent type: QWidget
@@ -43,10 +40,10 @@ class AdvancedKeyManagement(QtGui.QDialog):
:type has_mx: bool
:param user: the current logged in user.
:type user: unicode
- :param keymanager: the existing keymanager instance
- :type keymanager: KeyManager
- :param soledad: a loaded instance of Soledad
- :type soledad: Soledad
+ :param backend: Backend being used
+ :type backend: Backend
+ :param soledad_started: whether soledad has started or not
+ :type soledad_started: bool
"""
QtGui.QDialog.__init__(self, parent)
@@ -56,7 +53,6 @@ class AdvancedKeyManagement(QtGui.QDialog):
# XXX: Temporarily disable the key import.
self.ui.pbImportKeys.setVisible(False)
- # if Soledad is not started yet
if not has_mx:
msg = self.tr("The provider that you are using "
"does not support {0}.")
@@ -64,8 +60,7 @@ class AdvancedKeyManagement(QtGui.QDialog):
self._disable_ui(msg)
return
- # if Soledad is not started yet
- if sameProxiedObjects(soledad, None):
+ if not soledad_started:
msg = self.tr("To use this, you need to enable/start {0}.")
msg = msg.format(get_service_display_name(MX_SERVICE))
self._disable_ui(msg)
@@ -78,17 +73,12 @@ class AdvancedKeyManagement(QtGui.QDialog):
# "existing e-mails.")
# self.ui.lblStatus.setText(msg)
- self._keymanager = keymanager
- self._soledad = soledad
-
- self._key = keymanager.get_key(user, openpgp.OpenPGPKey)
- self._key_priv = keymanager.get_key(
- user, openpgp.OpenPGPKey, private=True)
+ self._user = user
+ self._backend = backend
+ self._backend_connect()
# show current key information
self.ui.leUser.setText(user)
- self.ui.leKeyID.setText(self._key.key_id)
- self.ui.leFingerprint.setText(self._key.fingerprint)
# set up connections
self.ui.pbImportKeys.clicked.connect(self._import_keys)
@@ -98,7 +88,15 @@ class AdvancedKeyManagement(QtGui.QDialog):
self.ui.twPublicKeys.horizontalHeader().setResizeMode(
0, QtGui.QHeaderView.Stretch)
- self._list_keys()
+ self._backend.keymanager_get_key_details(user)
+ self._backend.keymanager_list_keys()
+
+ def _keymanager_key_details(self, details):
+ """
+ Set the current user's key details into the gui.
+ """
+ self.ui.leKeyID.setText(details[0])
+ self.ui.leFingerprint.setText(details[1])
def _disable_ui(self, msg):
"""
@@ -117,53 +115,11 @@ class AdvancedKeyManagement(QtGui.QDialog):
Imports the user's key pair.
Those keys need to be ascii armored.
"""
- fileName, filtr = QtGui.QFileDialog.getOpenFileName(
+ file_name, filtr = QtGui.QFileDialog.getOpenFileName(
self, self.tr("Open keys file"),
options=QtGui.QFileDialog.DontUseNativeDialog)
- if fileName:
- new_key = ''
- try:
- with open(fileName, 'r') as keys_file:
- new_key = keys_file.read()
- except IOError as e:
- logger.error("IOError importing key. {0!r}".format(e))
- QtGui.QMessageBox.critical(
- self, self.tr("Input/Output error"),
- self.tr("There was an error accessing the file.\n"
- "Import canceled."))
- return
-
- keymanager = self._keymanager
- try:
- public_key, private_key = keymanager.parse_openpgp_ascii_key(
- new_key)
- except (KeyAddressMismatch, KeyFingerprintMismatch) as e:
- logger.error(repr(e))
- QtGui.QMessageBox.warning(
- self, self.tr("Data mismatch"),
- self.tr("The public and private key should have the "
- "same address and fingerprint.\n"
- "Import canceled."))
- return
-
- if public_key is None or private_key is None:
- QtGui.QMessageBox.warning(
- self, self.tr("Missing key"),
- self.tr("You need to provide the public AND private "
- "key in the same file.\n"
- "Import canceled."))
- return
-
- if public_key.address != self._key.address:
- logger.error("The key does not match the ID")
- QtGui.QMessageBox.warning(
- self, self.tr("Address mismatch"),
- self.tr("The identity for the key needs to be the same "
- "as your user address.\n"
- "Import canceled."))
- return
-
+ if file_name:
question = self.tr("Are you sure that you want to replace "
"the current key pair whith the imported?")
res = QtGui.QMessageBox.question(
@@ -171,61 +127,152 @@ class AdvancedKeyManagement(QtGui.QDialog):
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
QtGui.QMessageBox.No) # default No
- if res == QtGui.QMessageBox.No:
- return
+ if res == QtGui.QMessageBox.Yes:
+ self._backend.keymanager_import_keys(self._user, file_name)
+ else:
+ logger.debug('Import canceled by the user.')
- keymanager.delete_key(self._key)
- keymanager.delete_key(self._key_priv)
- keymanager.put_key(public_key)
- keymanager.put_key(private_key)
- keymanager.send_key(openpgp.OpenPGPKey)
+ @QtCore.Slot()
+ def _keymanager_import_ok(self):
+ """
+ TRIGGERS:
+ Signaler.keymanager_import_ok
- logger.debug('Import ok')
+ Notify the user that the key import went OK.
+ """
+ QtGui.QMessageBox.information(
+ self, self.tr("Import Successful"),
+ self.tr("The key pair was imported successfully."))
- QtGui.QMessageBox.information(
- self, self.tr("Import Successful"),
- self.tr("The key pair was imported successfully."))
- else:
- logger.debug('Import canceled by the user.')
+ @QtCore.Slot()
+ def _import_ioerror(self):
+ """
+ TRIGGERS:
+ Signaler.keymanager_import_ioerror
+
+ Notify the user that the key import had an IOError problem.
+ """
+ QtGui.QMessageBox.critical(
+ self, self.tr("Input/Output error"),
+ self.tr("There was an error accessing the file.\n"
+ "Import canceled."))
+
+ @QtCore.Slot()
+ def _import_datamismatch(self):
+ """
+ TRIGGERS:
+ Signaler.keymanager_import_datamismatch
+
+ Notify the user that the key import had an data mismatch problem.
+ """
+ QtGui.QMessageBox.warning(
+ self, self.tr("Data mismatch"),
+ self.tr("The public and private key should have the "
+ "same address and fingerprint.\n"
+ "Import canceled."))
+
+ @QtCore.Slot()
+ def _import_missingkey(self):
+ """
+ TRIGGERS:
+ Signaler.keymanager_import_missingkey
+
+ Notify the user that the key import failed due a missing key.
+ """
+ QtGui.QMessageBox.warning(
+ self, self.tr("Missing key"),
+ self.tr("You need to provide the public AND private "
+ "key in the same file.\n"
+ "Import canceled."))
+
+ @QtCore.Slot()
+ def _import_addressmismatch(self):
+ """
+ TRIGGERS:
+ Signaler.keymanager_import_addressmismatch
+
+ Notify the user that the key import failed due an address mismatch.
+ """
+ QtGui.QMessageBox.warning(
+ self, self.tr("Address mismatch"),
+ self.tr("The identity for the key needs to be the same "
+ "as your user address.\n"
+ "Import canceled."))
def _export_keys(self):
"""
Exports the user's key pair.
"""
- fileName, filtr = QtGui.QFileDialog.getSaveFileName(
+ file_name, filtr = QtGui.QFileDialog.getSaveFileName(
self, self.tr("Save keys file"),
options=QtGui.QFileDialog.DontUseNativeDialog)
- if fileName:
- try:
- with open(fileName, 'w') as keys_file:
- keys_file.write(self._key.key_data)
- keys_file.write(self._key_priv.key_data)
-
- logger.debug('Export ok')
- QtGui.QMessageBox.information(
- self, self.tr("Export Successful"),
- self.tr("The key pair was exported successfully.\n"
- "Please, store your private key in a safe place."))
- except IOError as e:
- logger.error("IOError exporting key. {0!r}".format(e))
- QtGui.QMessageBox.critical(
- self, self.tr("Input/Output error"),
- self.tr("There was an error accessing the file.\n"
- "Export canceled."))
- return
+ if file_name:
+ self._backend.keymanager_export_keys(self._user, file_name)
else:
logger.debug('Export canceled by the user.')
- def _list_keys(self):
+ @QtCore.Slot()
+ def _keymanager_export_ok(self):
+ """
+ TRIGGERS:
+ Signaler.keymanager_export_ok
+
+ Notify the user that the key export went OK.
"""
- Loads all the public keys stored in the local db to the keys table.
+ QtGui.QMessageBox.information(
+ self, self.tr("Export Successful"),
+ self.tr("The key pair was exported successfully.\n"
+ "Please, store your private key in a safe place."))
+
+ @QtCore.Slot()
+ def _keymanager_export_error(self):
+ """
+ TRIGGERS:
+ Signaler.keymanager_export_error
+
+ Notify the user that the key export didn't go well.
+ """
+ QtGui.QMessageBox.critical(
+ self, self.tr("Input/Output error"),
+ self.tr("There was an error accessing the file.\n"
+ "Export canceled."))
+
+ @QtCore.Slot()
+ def _keymanager_keys_list(self, keys):
"""
- keys = self._keymanager.get_all_keys_in_local_db()
+ TRIGGERS:
+ Signaler.keymanager_keys_list
+ Load the keys given as parameter in the table.
+
+ :param keys: the list of keys to load.
+ :type keys: list
+ """
keys_table = self.ui.twPublicKeys
+
for key in keys:
row = keys_table.rowCount()
keys_table.insertRow(row)
keys_table.setItem(row, 0, QtGui.QTableWidgetItem(key.address))
keys_table.setItem(row, 1, QtGui.QTableWidgetItem(key.key_id))
+
+ def _backend_connect(self):
+ """
+ Connect to backend signals.
+ """
+ sig = self._backend.signaler
+
+ sig.keymanager_export_ok.connect(self._keymanager_export_ok)
+ sig.keymanager_export_error.connect(self._keymanager_export_error)
+ sig.keymanager_keys_list.connect(self._keymanager_keys_list)
+
+ sig.keymanager_key_details.connect(self._keymanager_key_details)
+
+ sig.keymanager_import_ok.connect(self._keymanager_import_ok)
+
+ sig.keymanager_import_ioerror.connect(self._import_ioerror)
+ sig.keymanager_import_datamismatch.connect(self._import_datamismatch)
+ sig.keymanager_import_missingkey.connect(self._import_missingkey)
+ sig.keymanager_import_addressmismatch.connect(
+ self._import_addressmismatch)
diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py
index ca28b8bf..8b9f2d44 100644
--- a/src/leap/bitmask/gui/eip_status.py
+++ b/src/leap/bitmask/gui/eip_status.py
@@ -24,7 +24,7 @@ from functools import partial
from PySide import QtCore, QtGui
-from leap.bitmask.services.eip.connection import EIPConnection
+from leap.bitmask.config import flags
from leap.bitmask.services import get_service_display_name, EIP_SERVICE
from leap.bitmask.platform_init import IS_LINUX
from leap.bitmask.util.averages import RateMovingAverage
@@ -32,6 +32,7 @@ from leap.common.check import leap_assert_type
from ui_eip_status import Ui_EIPStatus
+QtDelayedCall = QtCore.QTimer.singleShot
logger = logging.getLogger(__name__)
@@ -43,9 +44,14 @@ class EIPStatusWidget(QtGui.QWidget):
RATE_STR = "%1.2f KB/s"
TOTAL_STR = "%1.2f Kb"
- eip_connection_connected = QtCore.Signal()
+ def __init__(self, parent=None, eip_conductor=None):
+ """
+ :param parent: the parent of the widget.
+ :type parent: QObject
- def __init__(self, parent=None):
+ :param eip_conductor: an EIPConductor object.
+ :type eip_conductor: EIPConductor
+ """
QtGui.QWidget.__init__(self, parent)
self._systray = None
@@ -54,13 +60,17 @@ class EIPStatusWidget(QtGui.QWidget):
self.ui = Ui_EIPStatus()
self.ui.setupUi(self)
- self.eipconnection = EIPConnection()
+ self.eip_conductor = eip_conductor
+ self.eipconnection = eip_conductor.eip_connection
# set systray tooltip status
self._eip_status = ""
self._service_name = get_service_display_name(EIP_SERVICE)
self.ui.eip_bandwidth.hide()
+ self.hide_fw_down_button()
+ self.ui.btnFwDown.clicked.connect(
+ self._on_fw_down_button_clicked)
# Set the EIP status icons
self.CONNECTING_ICON = None
@@ -75,11 +85,43 @@ class EIPStatusWidget(QtGui.QWidget):
self._make_status_clickable()
self._provider = ""
+ self.is_restart = False
+ self.is_cold_start = True
# Action for the systray
self._eip_disabled_action = QtGui.QAction(
"{0} is {1}".format(self._service_name, self.tr("disabled")), self)
+ def connect_backend_signals(self):
+ """
+ Connect backend signals.
+ """
+ signaler = self.eip_conductor._backend.signaler
+
+ signaler.eip_openvpn_already_running.connect(
+ self._on_eip_openvpn_already_running)
+ signaler.eip_alien_openvpn_already_running.connect(
+ self._on_eip_alien_openvpn_already_running)
+ signaler.eip_openvpn_not_found_error.connect(
+ self._on_eip_openvpn_not_found_error)
+ signaler.eip_vpn_launcher_exception.connect(
+ self._on_eip_vpn_launcher_exception)
+ signaler.eip_no_polkit_agent_error.connect(
+ self._on_eip_no_polkit_agent_error)
+ signaler.eip_connection_aborted.connect(
+ self._on_eip_connection_aborted)
+ signaler.eip_no_pkexec_error.connect(self._on_eip_no_pkexec_error)
+ signaler.eip_no_tun_kext_error.connect(self._on_eip_no_tun_kext_error)
+
+ signaler.eip_state_changed.connect(self.update_vpn_state)
+ signaler.eip_status_changed.connect(self.update_vpn_status)
+
+ # XXX we cannot connect this signal now because
+ # it interferes with the proper notifications during restarts
+ # without available network.
+ #signaler.eip_network_unreachable.connect(
+ #self._on_eip_network_unreachable)
+
def _make_status_clickable(self):
"""
Makes upload and download figures clickable.
@@ -208,7 +250,7 @@ class EIPStatusWidget(QtGui.QWidget):
def set_action_eip_startstop(self, action_eip_startstop):
"""
- Sets the action_eip_startstop to use.
+ Set the action_eip_startstop to use.
:param action_eip_startstop: action_eip_status to be used
:type action_eip_startstop: QtGui.QAction
@@ -238,9 +280,11 @@ class EIPStatusWidget(QtGui.QWidget):
def eip_pre_up(self):
"""
Triggered when the app activates eip.
- Hides the status box and disables the start/stop button.
+ Disables the start/stop button.
"""
self.set_startstop_enabled(False)
+ msg = self.tr("Encrypted Internet is starting")
+ self.set_eip_message(msg)
@QtCore.Slot()
def disable_eip_start(self):
@@ -248,7 +292,7 @@ class EIPStatusWidget(QtGui.QWidget):
Triggered when a default provider_config has not been found.
Disables the start button and adds instructions to the user.
"""
- #logger.debug('Hiding EIP start button')
+ logger.debug('Hiding EIP start button')
# you might be tempted to change this for a .setEnabled(False).
# it won't work. it's under the claws of the state machine.
# probably the best thing would be to make a conditional
@@ -282,10 +326,19 @@ class EIPStatusWidget(QtGui.QWidget):
if self.isVisible():
self._eip_status_menu.menuAction().setVisible(True)
- # XXX disable (later) --------------------------
+ def set_eip_message(self, message):
+ """
+ Set the EIP Widget main message.
+
+ :param message: the message to set in the widget
+ :type message: str or unicode
+ """
+ self.ui.lblEIPMessage.setText(message)
+ self.ui.lblEIPMessage.show()
+
def set_eip_status(self, status, error=False):
"""
- Sets the status label at the VPN stage to status
+ Set the status label at the VPN stage to status.
:param status: status message
:type status: str or unicode
@@ -326,29 +379,80 @@ class EIPStatusWidget(QtGui.QWidget):
Sets the state of the widget to how it should look after EIP
has started
"""
- self.ui.btnEipStartStop.setText(self.tr("Turn OFF"))
self.ui.btnEipStartStop.disconnect(self)
self.ui.btnEipStartStop.clicked.connect(
self.eipconnection.qtsigs.do_connect_signal)
- # XXX disable -----------------------------
- def eip_stopped(self):
+ def hide_fw_down_button(self):
+ """
+ Hide firewall-down button.
+ """
+ self.ui.btnFwDown.hide()
+
+ def show_fw_down_button(self):
+ """
+ Enable firewall-down button.
"""
+ retry_msg = self.tr("Retry")
+ self.ui.btnEipStartStop.setText(retry_msg)
+ self._action_eip_startstop.setText(retry_msg)
+ self.ui.btnFwDown.show()
+
+ def _on_fw_down_button_clicked(self):
+ """
+ Raise a signal for tearing down the firewall, and hide the button
+ afterwards.
+ """
+ self.eip_conductor._backend.tear_fw_down()
+ QtDelayedCall(50, self.hide_fw_down_button)
+
+ # XXX do actual check
+ msg = "Traffic is being routed in the clear."
+ self.ui.btnEipStartStop.setText(self.tr("Turn ON"))
+ self.set_eip_message(msg)
+ self.set_eip_status("")
+
+ @QtCore.Slot(dict)
+ def eip_stopped(self, restart=False, failed=False):
+ """
+ TRIGGERS:
+ EIPConductor.qtsigs.disconnected_signal
+
Sets the state of the widget to how it should look after EIP
has stopped
"""
- # XXX should connect this to EIPConnection.disconnected_signal
+ self.set_country_code("")
self._reset_traffic_rates()
- # XXX disable -----------------------------
- self.ui.btnEipStartStop.setText(self.tr("Turn ON"))
- self.ui.btnEipStartStop.disconnect(self)
- self.ui.btnEipStartStop.clicked.connect(
- self.eipconnection.qtsigs.do_disconnect_signal)
-
self.ui.eip_bandwidth.hide()
- self.ui.lblEIPMessage.setText(
- self.tr("Traffic is being routed in the clear"))
+
+ # This is assuming the firewall works correctly, but we should test fw
+ # status positively.
+ # Or better call it from the conductor...
+
+ clear_traffic = self.tr("Traffic is being routed in the clear.")
+ unreachable_net = self.tr("Network is unreachable.")
+ failed_msg = self.tr("Error connecting")
+
+ if restart:
+ msg = unreachable_net
+ elif failed:
+ msg = failed_msg
+ else:
+ msg = clear_traffic
+ self.set_eip_message(msg)
+ self.ui.lblEIPStatus.show()
+ self.show()
+
+ def eip_failed_to_connect(self):
+ """
+ Update EIP messages with error during (re)connection.
+ """
+ msg = self.tr("Error connecting.")
+ self.ui.lblEIPMessage.setText(msg)
self.ui.lblEIPStatus.show()
+ self.set_eip_status(self.tr("Bitmask is blocking "
+ "unencrypted traffic."))
+ self.show_fw_down_button()
@QtCore.Slot(dict)
def update_vpn_status(self, data=None):
@@ -407,11 +511,20 @@ class EIPStatusWidget(QtGui.QWidget):
self.ui.lblEIPStatus.hide()
# XXX should be handled by the state machine too.
- self.eip_connection_connected.emit()
+ # --- is this currently being sent?
+ self.eipconnection.qtsigs.connected_signal.emit()
+ self._on_eip_connected()
+ self.is_cold_start = False
# XXX should lookup vpn_state map in EIPConnection
elif vpn_state == "AUTH":
self.set_eip_status(self.tr("Authenticating..."))
+ # we wipe up any previous error info in the EIP message
+ # when we detect vpn authentication is happening
+ msg = self.tr("Encrypted Internet is starting")
+ self.set_eip_message(msg)
+ # on the first-run path, we hadn't showed the button yet.
+ self.eip_button.show()
elif vpn_state == "GET_CONFIG":
self.set_eip_status(self.tr("Retrieving configuration..."))
elif vpn_state == "WAIT":
@@ -423,10 +536,11 @@ class EIPStatusWidget(QtGui.QWidget):
elif vpn_state == "ALREADYRUNNING":
# Put the following calls in Qt's event queue, otherwise
# the UI won't update properly
- QtCore.QTimer.singleShot(
- 0, self.eipconnection.qtsigs.do_disconnect_signal)
+ #self.send_disconnect_signal()
+ QtDelayedCall(
+ 0, self.eipconnection.qtsigns.do_disconnect_signal.emit)
msg = self.tr("Unable to start VPN, it's already running.")
- QtCore.QTimer.singleShot(0, partial(self.set_eip_status, msg))
+ QtDelayedCall(0, partial(self.set_eip_status, msg))
else:
self.set_eip_status(vpn_state)
@@ -468,5 +582,152 @@ class EIPStatusWidget(QtGui.QWidget):
def set_provider(self, provider):
self._provider = provider
+
self.ui.lblEIPMessage.setText(
- self.tr("Route traffic through: {0}").format(self._provider))
+ self.tr("Routing traffic through: <b>{0}</b>").format(
+ provider))
+
+ ccode = flags.CURRENT_VPN_COUNTRY
+ if ccode is not None:
+ self.set_country_code(ccode)
+
+ def set_country_code(self, code):
+ """
+ Set the pixmap of the given country code
+
+ :param code: the country code
+ :type code: str
+ """
+ if code is not None and len(code) == 2:
+ img = ":/images/countries/%s.png" % (code.lower(),)
+ else:
+ img = None
+ cc = self.ui.lblGatewayCountryCode
+ cc.setPixmap(QtGui.QPixmap(img))
+ cc.setToolTip(code)
+
+ def aborted(self):
+ """
+ Notify the state machine that EIP was aborted for some reason.
+ """
+ # signal connection_aborted to state machine:
+ qtsigs = self.eipconnection.qtsigs
+ qtsigs.connection_aborted_signal.emit()
+
+ #
+ # Slots for signals
+ #
+
+ @QtCore.Slot()
+ def _on_eip_connection_aborted(self):
+ """
+ TRIGGERS:
+ Signaler.eip_connection_aborted
+ """
+ # TODO this name is very misleading, since there's a generic signal
+ # that's called connection_aborted / connection_died...
+ # should rename to something more specific about missing config.
+ logger.error("Tried to start EIP but cannot find any "
+ "available provider!")
+
+ eip_status_label = self.tr("Could not load {0} configuration.")
+ eip_status_label = eip_status_label.format(
+ self.eip_conductor.eip_name)
+ self.set_eip_status(eip_status_label, error=True)
+
+ self.aborted()
+
+ def _on_eip_openvpn_already_running(self):
+ self.set_eip_status(
+ self.tr("Another openvpn instance is already running, and "
+ "could not be stopped."),
+ error=True)
+ self.set_eipstatus_off()
+
+ self.aborted()
+
+ def _on_eip_alien_openvpn_already_running(self):
+ self.set_eip_status(
+ self.tr("Another openvpn instance is already running, and "
+ "could not be stopped because it was not launched by "
+ "Bitmask. Please stop it and try again."),
+ error=True)
+ self.set_eipstatus_off()
+
+ self.aborted()
+
+ def _on_eip_openvpn_not_found_error(self):
+ self.set_eip_status(
+ self.tr("We could not find openvpn binary."),
+ error=True)
+ self.set_eipstatus_off()
+
+ self.aborted()
+
+ def _on_eip_vpn_launcher_exception(self):
+ # XXX We should implement again translatable exceptions so
+ # we can pass a translatable string to the panel (usermessage attr)
+ self.set_eip_status("VPN Launcher error.", error=True)
+ self.set_eipstatus_off()
+
+ self.aborted()
+
+ def _on_eip_no_polkit_agent_error(self):
+ self.set_eip_status(
+ # XXX this should change to polkit-kde where
+ # applicable.
+ self.tr("We could not find any authentication agent in your "
+ "system.<br/>Make sure you have"
+ "<b>polkit-gnome-authentication-agent-1</b> running and"
+ "try again."),
+ error=True)
+ self.set_eipstatus_off()
+
+ self.aborted()
+
+ def _on_eip_no_pkexec_error(self):
+ self.set_eip_status(
+ self.tr("We could not find <b>pkexec</b> in your system."),
+ error=True)
+ self.set_eipstatus_off()
+
+ self.aborted()
+
+ def _on_eip_no_tun_kext_error(self):
+ self.set_eip_status(
+ self.tr("{0} cannot be started because the tuntap extension is "
+ "not installed properly in your "
+ "system.").format(self.eip_conductor.eip_name))
+ self.set_eipstatus_off()
+
+ self.aborted()
+
+ def _on_eip_connected(self):
+ """
+ Reconnect the disconnecting signal when we are just connected,
+ so that we restore the disconnecting -> stop behaviour.
+ """
+ self.eip_conductor.reconnect_stop_signal()
+
+ @QtCore.Slot()
+ def _on_eip_network_unreachable(self):
+ """
+ TRIGGERS:
+ self._eip_connection.qtsigs.network_unreachable
+
+ Displays a "network unreachable" error in the EIP status panel.
+ """
+ self.set_eip_status(self.tr("Network is unreachable"),
+ error=True)
+ self.set_eip_status_icon("error")
+
+ def set_eipstatus_off(self, error=True):
+ # XXX this should be handled by the state machine.
+ """
+ Sets eip status to off
+ """
+ self.set_eip_status("", error=error)
+ self.set_eip_status_icon("error")
+
+import eipstatus_rc
+assert(eipstatus_rc)
diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py
index f19b172f..3a8354b1 100644
--- a/src/leap/bitmask/gui/loggerwindow.py
+++ b/src/leap/bitmask/gui/loggerwindow.py
@@ -27,7 +27,7 @@ from twisted.internet import threads
from ui_loggerwindow import Ui_LoggerWindow
from leap.bitmask.util.constants import PASTEBIN_API_DEV_KEY
-from leap.bitmask.util.leap_log_handler import LeapLogHandler
+from leap.bitmask.logs.leap_log_handler import LeapLogHandler
from leap.bitmask.util import pastebin
from leap.common.check import leap_assert, leap_assert_type
diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py
index ac7ad878..f66e71d9 100644
--- a/src/leap/bitmask/gui/login.py
+++ b/src/leap/bitmask/gui/login.py
@@ -24,6 +24,7 @@ from ui_login import Ui_LoginWidget
from leap.bitmask.config import flags
from leap.bitmask.util import make_address
+from leap.bitmask.util.credentials import USERNAME_REGEX
from leap.bitmask.util.keyring_helpers import has_keyring
from leap.bitmask.util.keyring_helpers import get_keyring
from leap.common.check import leap_assert_type
@@ -48,8 +49,6 @@ class LoginWidget(QtGui.QWidget):
MAX_STATUS_WIDTH = 40
- BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$"
-
# Keyring
KEYRING_KEY = "bitmask"
@@ -87,7 +86,7 @@ class LoginWidget(QtGui.QWidget):
self.ui.btnLogout.clicked.connect(
self.logout)
- username_re = QtCore.QRegExp(self.BARE_USERNAME_REGEX)
+ username_re = QtCore.QRegExp(USERNAME_REGEX)
self.ui.lnUser.setValidator(
QtGui.QRegExpValidator(username_re, self))
diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py
index d3346780..5caef745 100644
--- a/src/leap/bitmask/gui/mail_status.py
+++ b/src/leap/bitmask/gui/mail_status.py
@@ -188,7 +188,7 @@ class MailStatusWidget(QtGui.QWidget):
def set_soledad_failed(self):
"""
TRIGGERS:
- SoledadBootstrapper.soledad_failed
+ Signaler.soledad_bootstrap_failed
This method is called whenever soledad has a failure.
"""
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index e3848c46..3ef994b1 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -19,22 +19,17 @@ Main window for Bitmask.
"""
import logging
import socket
-import time
-from threading import Condition
from datetime import datetime
from PySide import QtCore, QtGui
-from zope.proxy import ProxyBase, setProxiedObject
from twisted.internet import reactor, threads
from leap.bitmask import __version__ as VERSION
from leap.bitmask import __version_hash__ as VERSION_HASH
from leap.bitmask.config import flags
from leap.bitmask.config.leapsettings import LeapSettings
-from leap.bitmask.config.providerconfig import ProviderConfig
-from leap.bitmask.gui import statemachines
from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement
from leap.bitmask.gui.eip_preferenceswindow import EIPPreferencesWindow
from leap.bitmask.gui.eip_status import EIPStatusWidget
@@ -45,30 +40,24 @@ from leap.bitmask.gui.preferenceswindow import PreferencesWindow
from leap.bitmask.gui.systray import SysTray
from leap.bitmask.gui.wizard import Wizard
-from leap.bitmask import provider
from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX
from leap.bitmask.platform_init.initializers import init_platform
from leap.bitmask import backend
-from leap.bitmask.services import get_service_display_name
-
+from leap.bitmask.services.eip import conductor as eip_conductor
from leap.bitmask.services.mail import conductor as mail_conductor
from leap.bitmask.services import EIP_SERVICE, MX_SERVICE
-from leap.bitmask.services.eip.connection import EIPConnection
-from leap.bitmask.services.soledad.soledadbootstrapper import \
- SoledadBootstrapper
from leap.bitmask.util import make_address
from leap.bitmask.util.keyring_helpers import has_keyring
-from leap.bitmask.util.leap_log_handler import LeapLogHandler
+from leap.bitmask.logs.leap_log_handler import LeapLogHandler
if IS_WIN:
from leap.bitmask.platform_init.locks import WindowsLock
from leap.bitmask.platform_init.locks import raise_window_ack
-from leap.common.check import leap_assert
from leap.common.events import register
from leap.common.events import events_pb2 as proto
@@ -76,6 +65,7 @@ from leap.mail.imap.service.imap import IMAP_PORT
from ui_mainwindow import Ui_MainWindow
+QtDelayedCall = QtCore.QTimer.singleShot
logger = logging.getLogger(__name__)
@@ -89,17 +79,17 @@ class MainWindow(QtGui.QMainWindow):
new_updates = QtCore.Signal(object)
raise_window = QtCore.Signal([])
soledad_ready = QtCore.Signal([])
- mail_client_logged_in = QtCore.Signal([])
logout = QtCore.Signal([])
+ all_services_stopped = QtCore.Signal()
# We use this flag to detect abnormal terminations
user_stopped_eip = False
# We give EIP some time to come up before starting soledad anyway
- EIP_TIMEOUT = 60000 # in milliseconds
+ EIP_START_TIMEOUT = 60000 # in milliseconds
- # We give each service some time to come to a halt before forcing quit
- SERVICE_STOP_TIMEOUT = 20
+ # We give the services some time to a halt before forcing quit.
+ SERVICES_STOP_TIMEOUT = 20
def __init__(self, quit_callback, bypass_checks=False, start_hidden=False):
"""
@@ -125,9 +115,6 @@ class MainWindow(QtGui.QMainWindow):
register(signal=proto.RAISE_WINDOW,
callback=self._on_raise_window_event,
reqcbk=lambda req, resp: None) # make rpc call async
- register(signal=proto.IMAP_CLIENT_LOGIN,
- callback=self._on_mail_client_logged_in,
- reqcbk=lambda req, resp: None) # make rpc call async
# end register leap events ####################################
self._quit_callback = quit_callback
@@ -142,11 +129,16 @@ class MainWindow(QtGui.QMainWindow):
self._settings = LeapSettings()
+ # Login Widget
self._login_widget = LoginWidget(
self._settings,
self)
self.ui.loginLayout.addWidget(self._login_widget)
+ # Mail Widget
+ self._mail_status = MailStatusWidget(self)
+ self.ui.mailLayout.addWidget(self._mail_status)
+
# Qt Signal Connections #####################################
# TODO separate logic from ui signals.
@@ -155,67 +147,45 @@ class MainWindow(QtGui.QMainWindow):
self._login_widget.show_wizard.connect(self._launch_wizard)
self._login_widget.logout.connect(self._logout)
- self._eip_status = EIPStatusWidget(self)
- self.ui.eipLayout.addWidget(self._eip_status)
- self._login_widget.logged_in_signal.connect(
- self._eip_status.enable_eip_start)
- self._login_widget.logged_in_signal.connect(
- self._enable_eip_start_action)
+ # EIP Control redux #########################################
+ self._eip_conductor = eip_conductor.EIPConductor(
+ self._settings, self._backend)
+ self._eip_status = EIPStatusWidget(self, self._eip_conductor)
- self._mail_status = MailStatusWidget(self)
- self.ui.mailLayout.addWidget(self._mail_status)
-
- self._eip_connection = EIPConnection()
-
- # XXX this should be handled by EIP Conductor
- self._eip_connection.qtsigs.connecting_signal.connect(
- self._start_EIP)
- self._eip_connection.qtsigs.disconnecting_signal.connect(
- self._stop_eip)
+ self.ui.eipLayout.addWidget(self._eip_status)
+ self._eip_conductor.add_eip_widget(self._eip_status)
- self._eip_status.eip_connection_connected.connect(
+ self._eip_conductor.connect_signals()
+ self._eip_conductor.qtsigs.connected_signal.connect(
self._on_eip_connection_connected)
- self._eip_status.eip_connection_connected.connect(
+ self._eip_conductor.qtsigs.connected_signal.connect(
self._maybe_run_soledad_setup_checks)
+
self.offline_mode_bypass_login.connect(
self._maybe_run_soledad_setup_checks)
self.eip_needs_login.connect(self._eip_status.disable_eip_start)
self.eip_needs_login.connect(self._disable_eip_start_action)
+ self._already_started_eip = False
self._trying_to_start_eip = False
- # This is loaded only once, there's a bug when doing that more
- # than once
- # XXX HACK!! But we need it as long as we are using
- # provider_config in here
- self._provider_config = self._backend.get_provider_config()
-
- # Used for automatic start of EIP
- self._provisional_provider_config = ProviderConfig()
-
self._already_started_eip = False
- self._already_started_soledad = False
+ self._soledad_started = False
# This is created once we have a valid provider config
self._srp_auth = None
self._logged_user = None
self._logged_in_offline = False
- self._backend_connected_signals = {}
- self._backend_connect()
+ # Set used to track the services being stopped and need wait.
+ self._services_being_stopped = {}
- self._soledad_bootstrapper = SoledadBootstrapper()
- self._soledad_bootstrapper.download_config.connect(
- self._soledad_intermediate_stage)
- self._soledad_bootstrapper.gen_key.connect(
- self._soledad_bootstrapped_stage)
- self._soledad_bootstrapper.local_only_ready.connect(
- self._soledad_bootstrapped_stage)
- self._soledad_bootstrapper.soledad_invalid_auth_token.connect(
- self._mail_status.set_soledad_invalid_auth_token)
- self._soledad_bootstrapper.soledad_failed.connect(
- self._mail_status.set_soledad_failed)
+ # timeout object used to trigger quit
+ self._quit_timeout_callater = None
+
+ self._backend_connected_signals = []
+ self._backend_connect()
self.ui.action_preferences.triggered.connect(self._show_preferences)
self.ui.action_eip_preferences.triggered.connect(
@@ -241,8 +211,7 @@ class MainWindow(QtGui.QMainWindow):
self._systray = None
- # XXX separate actions into a different
- # module.
+ # XXX separate actions into a different module.
self._action_mail_status = QtGui.QAction(self.tr("Mail is OFF"), self)
self._mail_status.set_action_mail_status(self._action_mail_status)
@@ -260,6 +229,8 @@ class MainWindow(QtGui.QMainWindow):
self._ui_mx_visible = True
self._ui_eip_visible = True
+ self._provider_details = None
+
# last minute UI manipulations
self._center_window()
@@ -280,10 +251,6 @@ class MainWindow(QtGui.QMainWindow):
# XXX should connect to mail_conductor.start_mail_service instead
self.soledad_ready.connect(self._start_smtp_bootstrapping)
self.soledad_ready.connect(self._start_imap_service)
- self.mail_client_logged_in.connect(self._fetch_incoming_mail)
- self.logout.connect(self._stop_imap_service)
- self.logout.connect(self._stop_smtp_service)
-
################################# end Qt Signals connection ########
init_platform()
@@ -296,18 +263,11 @@ class MainWindow(QtGui.QMainWindow):
self._bypass_checks = bypass_checks
self._start_hidden = start_hidden
- # We initialize Soledad and Keymanager instances as
- # transparent proxies, so we can pass the reference freely
- # around.
- self._soledad = ProxyBase(None)
- self._keymanager = ProxyBase(None)
-
- self._soledad_defer = None
-
- self._mail_conductor = mail_conductor.MailConductor(
- self._soledad, self._keymanager)
+ self._mail_conductor = mail_conductor.MailConductor(self._backend)
self._mail_conductor.connect_mail_signals(self._mail_status)
+ self.logout.connect(self._mail_conductor.stop_mail_services)
+
# Eip machine is a public attribute where the state machine for
# the eip connection will be available to the different components.
# Remember that this will not live in the +1600LOC mainwindow for
@@ -315,20 +275,19 @@ class MainWindow(QtGui.QMainWindow):
# the EIPConductor or some other clever component that we will
# instantiate from here.
- self.eip_machine = None
# start event machines
- self.start_eip_machine()
+ # TODO should encapsulate all actions into one object
+ self._eip_conductor.start_eip_machine(
+ action=self._action_eip_startstop)
self._mail_conductor.start_mail_machine()
- self._eip_name = get_service_display_name(EIP_SERVICE)
-
if self._first_run():
self._wizard_firstrun = True
self._disconnect_and_untrack()
self._wizard = Wizard(backend=self._backend,
bypass_checks=bypass_checks)
# Give this window time to finish init and then show the wizard
- QtCore.QTimer.singleShot(1, self._launch_wizard)
+ QtDelayedCall(1, self._launch_wizard)
self._wizard.accepted.connect(self._finish_init)
self._wizard.rejected.connect(self._rejected_wizard)
else:
@@ -357,7 +316,7 @@ class MainWindow(QtGui.QMainWindow):
:param method: the method to call when the signal is triggered.
:type method: callable, Slot or Signal
"""
- self._backend_connected_signals[signal] = method
+ self._backend_connected_signals.append((signal, method))
signal.connect(method)
def _backend_bad_call(self, data):
@@ -370,97 +329,102 @@ class MainWindow(QtGui.QMainWindow):
logger.error("Bad call to the backend:")
logger.error(data)
- def _backend_connect(self):
+ def _backend_connect(self, only_tracked=False):
"""
- Helper to connect to backend signals
- """
- sig = self._backend.signaler
+ Connect to backend signals.
- sig.backend_bad_call.connect(self._backend_bad_call)
+ We track some signals in order to disconnect them on demand.
+ For instance, in the wizard we need to connect to some signals that are
+ already connected in the mainwindow, so to avoid conflicts we do:
+ - disconnect signals needed in wizard (`_disconnect_and_untrack`)
+ - use wizard
+ - reconnect disconnected signals (we use the `only_tracked` param)
- self._connect_and_track(sig.prov_name_resolution,
- self._intermediate_stage)
- self._connect_and_track(sig.prov_https_connection,
- self._intermediate_stage)
- self._connect_and_track(sig.prov_download_ca_cert,
- self._intermediate_stage)
+ :param only_tracked: whether or not we should connect only the signals
+ that we are tracking to disconnect later.
+ :type only_tracked: bool
+ """
+ sig = self._backend.signaler
+ conntrack = self._connect_and_track
+ auth_err = self._authentication_error
- self._connect_and_track(sig.prov_download_provider_info,
- self._load_provider_config)
- self._connect_and_track(sig.prov_check_api_certificate,
- self._provider_config_loaded)
+ conntrack(sig.prov_name_resolution, self._intermediate_stage)
+ conntrack(sig.prov_https_connection, self._intermediate_stage)
+ conntrack(sig.prov_download_ca_cert, self._intermediate_stage)
+ conntrack(sig.prov_download_provider_info, self._load_provider_config)
+ conntrack(sig.prov_check_api_certificate, self._provider_config_loaded)
+ conntrack(sig.prov_check_api_certificate, self._get_provider_details)
- self._connect_and_track(sig.prov_problem_with_provider,
- self._login_problem_provider)
+ conntrack(sig.prov_problem_with_provider, self._login_problem_provider)
+ conntrack(sig.prov_cancelled_setup, self._set_login_cancelled)
- self._connect_and_track(sig.prov_cancelled_setup,
- self._set_login_cancelled)
+ conntrack(sig.prov_get_details, self._provider_get_details)
# Login signals
- self._connect_and_track(sig.srp_auth_ok, self._authentication_finished)
+ conntrack(sig.srp_auth_ok, self._authentication_finished)
- auth_error = (
- lambda: self._authentication_error(self.tr("Unknown error.")))
- self._connect_and_track(sig.srp_auth_error, auth_error)
+ auth_error = lambda: auth_err(self.tr("Unknown error."))
+ conntrack(sig.srp_auth_error, auth_error)
- auth_server_error = (
- lambda: self._authentication_error(
- self.tr("There was a server problem with authentication.")))
- self._connect_and_track(sig.srp_auth_server_error, auth_server_error)
+ auth_server_error = lambda: auth_err(self.tr(
+ "There was a server problem with authentication."))
+ conntrack(sig.srp_auth_server_error, auth_server_error)
- auth_connection_error = (
- lambda: self._authentication_error(
- self.tr("Could not establish a connection.")))
- self._connect_and_track(sig.srp_auth_connection_error,
- auth_connection_error)
+ auth_connection_error = lambda: auth_err(self.tr(
+ "Could not establish a connection."))
+ conntrack(sig.srp_auth_connection_error, auth_connection_error)
- auth_bad_user_or_password = (
- lambda: self._authentication_error(
- self.tr("Invalid username or password.")))
- self._connect_and_track(sig.srp_auth_bad_user_or_password,
- auth_bad_user_or_password)
+ auth_bad_user_or_password = lambda: auth_err(self.tr(
+ "Invalid username or password."))
+ conntrack(sig.srp_auth_bad_user_or_password, auth_bad_user_or_password)
# Logout signals
- self._connect_and_track(sig.srp_logout_ok, self._logout_ok)
- self._connect_and_track(sig.srp_logout_error, self._logout_error)
-
- self._connect_and_track(sig.srp_not_logged_in_error,
- self._not_logged_in_error)
+ conntrack(sig.srp_logout_ok, self._logout_ok)
+ conntrack(sig.srp_logout_error, self._logout_error)
+ conntrack(sig.srp_not_logged_in_error, self._not_logged_in_error)
# EIP bootstrap signals
- self._connect_and_track(sig.eip_config_ready,
- self._eip_intermediate_stage)
- self._connect_and_track(sig.eip_client_certificate_ready,
- self._finish_eip_bootstrap)
+ conntrack(sig.eip_config_ready, self._eip_intermediate_stage)
+ conntrack(sig.eip_client_certificate_ready, self._finish_eip_bootstrap)
+
+ ###################################################
+ # Add tracked signals above this, untracked below!
+ ###################################################
+ if only_tracked:
+ return
# We don't want to disconnect some signals so don't track them:
+
+ sig.backend_bad_call.connect(self._backend_bad_call)
+
sig.prov_unsupported_client.connect(self._needs_update)
sig.prov_unsupported_api.connect(self._incompatible_api)
+ sig.prov_get_all_services.connect(self._provider_get_all_services)
- # EIP start signals
- sig.eip_openvpn_already_running.connect(
- self._on_eip_openvpn_already_running)
- sig.eip_alien_openvpn_already_running.connect(
- self._on_eip_alien_openvpn_already_running)
- sig.eip_openvpn_not_found_error.connect(
- self._on_eip_openvpn_not_found_error)
- sig.eip_vpn_launcher_exception.connect(
- self._on_eip_vpn_launcher_exception)
- sig.eip_no_polkit_agent_error.connect(
- self._on_eip_no_polkit_agent_error)
- sig.eip_no_pkexec_error.connect(self._on_eip_no_pkexec_error)
- sig.eip_no_tun_kext_error.connect(self._on_eip_no_tun_kext_error)
-
- sig.eip_state_changed.connect(self._eip_status.update_vpn_state)
- sig.eip_status_changed.connect(self._eip_status.update_vpn_status)
- sig.eip_process_finished.connect(self._eip_finished)
- sig.eip_network_unreachable.connect(self._on_eip_network_unreachable)
- sig.eip_process_restart_tls.connect(self._do_eip_restart)
- sig.eip_process_restart_ping.connect(self._do_eip_restart)
+ # EIP start signals ==============================================
+ self._eip_conductor.connect_backend_signals()
sig.eip_can_start.connect(self._backend_can_start_eip)
sig.eip_cannot_start.connect(self._backend_cannot_start_eip)
+ # ==================================================================
+
+ # Soledad signals
+ # TODO delegate connection to soledad bootstrapper
+ sig.soledad_bootstrap_failed.connect(
+ self._mail_status.set_soledad_failed)
+ sig.soledad_bootstrap_finished.connect(self._on_soledad_ready)
+
+ sig.soledad_offline_failed.connect(
+ self._mail_status.set_soledad_failed)
+ sig.soledad_offline_finished.connect(self._on_soledad_ready)
+
+ sig.soledad_invalid_auth_token.connect(
+ self._mail_status.set_soledad_invalid_auth_token)
+
+ # TODO: connect this with something
+ # sig.soledad_cancelled_bootstrap.connect()
+
def _disconnect_and_untrack(self):
"""
Helper to disconnect the tracked signals.
@@ -468,13 +432,13 @@ class MainWindow(QtGui.QMainWindow):
Some signals are emitted from the wizard, and we want to
ignore those.
"""
- for signal, method in self._backend_connected_signals.items():
+ for signal, method in self._backend_connected_signals:
try:
signal.disconnect(method)
except RuntimeError:
pass # Signal was not connected
- self._backend_connected_signals = {}
+ self._backend_connected_signals = []
@QtCore.Slot()
def _rejected_wizard(self):
@@ -497,7 +461,7 @@ class MainWindow(QtGui.QMainWindow):
# This happens if the user finishes the provider
# setup but does not register
self._wizard = None
- self._backend_connect()
+ self._backend_connect(only_tracked=True)
if self._wizard_firstrun:
self._finish_init()
@@ -586,13 +550,14 @@ class MainWindow(QtGui.QMainWindow):
domain = self._login_widget.get_selected_provider()
logged_user = "{0}@{1}".format(self._logged_user, domain)
- has_mx = True
- if self._logged_user is not None:
- provider_config = self._get_best_provider_config()
- has_mx = provider_config.provides_mx()
+ details = self._provider_details
+ mx_provided = False
+ if details is not None:
+ mx_provided = MX_SERVICE in details.services
- akm = AdvancedKeyManagement(
- self, has_mx, logged_user, self._keymanager, self._soledad)
+ # XXX: handle differently not logged in user?
+ akm = AdvancedKeyManagement(self, mx_provided, logged_user,
+ self._backend, self._soledad_started)
akm.show()
@QtCore.Slot()
@@ -604,11 +569,13 @@ class MainWindow(QtGui.QMainWindow):
Displays the preferences window.
"""
- user = self._login_widget.get_user()
- prov = self._login_widget.get_selected_provider()
- preferences = PreferencesWindow(
- self, self._backend, self._provider_config, self._soledad,
- user, prov)
+ user = self._logged_user
+ domain = self._login_widget.get_selected_provider()
+ mx_provided = False
+ if self._provider_details is not None:
+ mx_provided = MX_SERVICE in self._provider_details.services
+ preferences = PreferencesWindow(self, user, domain, self._backend,
+ self._soledad_started, mx_provided)
self.soledad_ready.connect(preferences.set_soledad_ready)
preferences.show()
@@ -630,7 +597,7 @@ class MainWindow(QtGui.QMainWindow):
default_provider = settings.get_defaultprovider()
if default_provider is None:
- logger.warning("Trying toupdate eip enabled status but there's no"
+ logger.warning("Trying to update eip enabled status but there's no"
" default provider. Disabling EIP for the time"
" being...")
self._backend_cannot_start_eip()
@@ -642,7 +609,7 @@ class MainWindow(QtGui.QMainWindow):
# If we don't want to start eip, we leave everything
# initialized to quickly start it
if not self._trying_to_start_eip:
- self._backend.setup_eip(default_provider, skip_network=True)
+ self._backend.eip_setup(default_provider, skip_network=True)
def _backend_can_start_eip(self):
"""
@@ -670,7 +637,6 @@ class MainWindow(QtGui.QMainWindow):
# so the user needs to log in first
self._eip_status.disable_eip_start()
else:
- self._stop_eip()
self._eip_status.disable_eip_start()
self._eip_status.set_eip_status(self.tr("Disabled"))
@@ -697,7 +663,6 @@ class MainWindow(QtGui.QMainWindow):
# so the user needs to log in first
self._eip_status.disable_eip_start()
else:
- self._stop_eip()
self._eip_status.disable_eip_start()
self._eip_status.set_eip_status(self.tr("Disabled"))
@@ -817,7 +782,7 @@ class MainWindow(QtGui.QMainWindow):
self.eip_needs_login.emit()
self._wizard = None
- self._backend_connect()
+ self._backend_connect(only_tracked=True)
else:
self._update_eip_enabled_status()
@@ -846,16 +811,9 @@ class MainWindow(QtGui.QMainWindow):
"""
providers = self._settings.get_configured_providers()
- services = set()
-
- for prov in providers:
- provider_config = ProviderConfig()
- loaded = provider_config.load(
- provider.get_provider_path(prov))
- if loaded:
- for service in provider_config.get_services():
- services.add(service)
+ self._backend.provider_get_all_services(providers)
+ def _provider_get_all_services(self, services):
self._set_eip_visible(EIP_SERVICE in services)
self._set_mx_visible(MX_SERVICE in services)
@@ -893,14 +851,11 @@ class MainWindow(QtGui.QMainWindow):
"""
Set the login label to reflect offline status.
"""
- if self._logged_in_offline:
- provider = ""
- else:
+ provider = ""
+ if not self._logged_in_offline:
provider = self.ui.lblLoginProvider.text()
- self.ui.lblLoginProvider.setText(
- provider +
- self.tr(" (offline mode)"))
+ self.ui.lblLoginProvider.setText(provider + self.tr(" (offline mode)"))
#
# systray
@@ -923,7 +878,8 @@ class MainWindow(QtGui.QMainWindow):
systrayMenu.addAction(self._action_visible)
systrayMenu.addSeparator()
- eip_status_label = "{0}: {1}".format(self._eip_name, self.tr("OFF"))
+ eip_status_label = "{0}: {1}".format(
+ self._eip_conductor.eip_name, self.tr("OFF"))
self._eip_menu = eip_menu = systrayMenu.addMenu(eip_status_label)
eip_menu.addAction(self._action_eip_startstop)
self._eip_status.set_eip_status_menu(eip_menu)
@@ -1005,7 +961,7 @@ class MainWindow(QtGui.QMainWindow):
# Wait a bit until the window visibility has changed so
# the menu is set with the correct value.
- QtCore.QTimer.singleShot(500, self._update_hideshow_menu)
+ QtDelayedCall(500, self._update_hideshow_menu)
def _center_window(self):
"""
@@ -1154,9 +1110,8 @@ class MainWindow(QtGui.QMainWindow):
provider configuration if it's not present, otherwise will
emit the corresponding signals inmediately
"""
- # XXX should rename this provider, name clash.
- provider = self._login_widget.get_selected_provider()
- self._backend.setup_provider(provider)
+ domain = self._login_widget.get_selected_provider()
+ self._backend.provider_setup(domain)
@QtCore.Slot(dict)
def _load_provider_config(self, data):
@@ -1164,12 +1119,11 @@ class MainWindow(QtGui.QMainWindow):
TRIGGERS:
self._backend.signaler.prov_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
+ Once the provider config has been downloaded, start the second
+ part of the bootstrapping sequence.
:param data: result from the last stage of the
- run_provider_select_checks
+ backend.provider_setup()
:type data: dict
"""
if data[self._backend.PASSED_KEY]:
@@ -1211,7 +1165,6 @@ class MainWindow(QtGui.QMainWindow):
self._set_label_offline()
self.offline_mode_bypass_login.emit()
else:
- leap_assert(self._provider_config, "We need a provider config")
self.ui.action_create_new_account.setEnabled(False)
if self._login_widget.start_login():
self._download_provider_config()
@@ -1250,20 +1203,19 @@ class MainWindow(QtGui.QMainWindow):
Cancel the running defers to avoid app blocking.
"""
# XXX: Should we stop all the backend defers?
- self._backend.cancel_setup_provider()
- self._backend.cancel_login()
+ self._backend.provider_cancel_setup()
+ self._backend.user_cancel_login()
+ self._backend.soledad_cancel_bootstrap()
+ self._backend.soledad_close()
- if self._soledad_defer is not None:
- logger.debug("Cancelling soledad defer.")
- self._soledad_defer.cancel()
- self._soledad_defer = None
+ self._soledad_started = False
@QtCore.Slot()
def _set_login_cancelled(self):
"""
TRIGGERS:
Signaler.prov_cancelled_setup fired by
- self._backend.cancel_setup_provider()
+ self._backend.provider_cancel_setup()
This method re-enables the login widget and display a message for
the cancelled operation.
@@ -1280,16 +1232,14 @@ class MainWindow(QtGui.QMainWindow):
Once the provider configuration is loaded, this starts the SRP
authentication
"""
- leap_assert(self._provider_config, "We need a provider config!")
-
if data[self._backend.PASSED_KEY]:
username = self._login_widget.get_user()
password = self._login_widget.get_password()
self._show_hide_unsupported_services()
- domain = self._provider_config.get_domain()
- self._backend.login(domain, username, password)
+ domain = self._login_widget.get_selected_provider()
+ self._backend.user_login(domain, username, password)
else:
logger.error(data[self._backend.ERROR_KEY])
self._login_problem_provider()
@@ -1307,7 +1257,7 @@ class MainWindow(QtGui.QMainWindow):
self._logged_user = self._login_widget.get_user()
user = self._logged_user
- domain = self._provider_config.get_domain()
+ domain = self._login_widget.get_selected_provider()
full_user_id = make_address(user, domain)
self._mail_conductor.userid = full_user_id
self._start_eip_bootstrap()
@@ -1317,11 +1267,11 @@ class MainWindow(QtGui.QMainWindow):
if MX_SERVICE in self._enabled_services:
btn_enabled = self._login_widget.set_logout_btn_enabled
btn_enabled(False)
- self.soledad_ready.connect(lambda: btn_enabled(True))
- self._soledad_bootstrapper.soledad_failed.connect(
- lambda: btn_enabled(True))
+ sig = self._backend.signaler
+ sig.soledad_bootstrap_failed.connect(lambda: btn_enabled(True))
+ sig.soledad_bootstrap_finished.connect(lambda: btn_enabled(True))
- if not self._get_best_provider_config().provides_mx():
+ if not MX_SERVICE in self._provider_details.services:
self._set_mx_visible(False)
def _start_eip_bootstrap(self):
@@ -1331,11 +1281,10 @@ class MainWindow(QtGui.QMainWindow):
"""
self._login_widget.logged_in()
- provider = self._provider_config.get_domain()
- self.ui.lblLoginProvider.setText(provider)
+ domain = self._login_widget.get_selected_provider()
+ self.ui.lblLoginProvider.setText(domain)
- self._enabled_services = self._settings.get_enabled_services(
- self._provider_config.get_domain())
+ self._enabled_services = self._settings.get_enabled_services(domain)
# TODO separate UI from logic.
if self._provides_mx_and_enabled():
@@ -1345,6 +1294,30 @@ class MainWindow(QtGui.QMainWindow):
self._maybe_start_eip()
+ @QtCore.Slot()
+ def _get_provider_details(self):
+ """
+ TRIGGERS:
+ prov_check_api_certificate
+
+ Set the attributes to know if the EIP and MX services are supported
+ and enabled.
+ This is triggered right after the provider has been set up.
+ """
+ domain = self._login_widget.get_selected_provider()
+ lang = QtCore.QLocale.system().name()
+ self._backend.provider_get_details(domain, lang)
+
+ @QtCore.Slot()
+ def _provider_get_details(self, details):
+ """
+ Set the details for the just downloaded provider.
+
+ :param details: the details of the provider.
+ :type details: ProviderConfigLight
+ """
+ self._provider_details = details
+
def _provides_mx_and_enabled(self):
"""
Defines if the current provider provides mx and if we have it enabled.
@@ -1352,9 +1325,15 @@ class MainWindow(QtGui.QMainWindow):
:returns: True if provides and is enabled, False otherwise
:rtype: bool
"""
- provider_config = self._get_best_provider_config()
- return (provider_config.provides_mx() and
- MX_SERVICE in self._enabled_services)
+ domain = self._login_widget.get_selected_provider()
+ enabled_services = self._settings.get_enabled_services(domain)
+
+ mx_enabled = MX_SERVICE in enabled_services
+ mx_provided = False
+ if self._provider_details is not None:
+ mx_provided = MX_SERVICE in self._provider_details.services
+
+ return mx_enabled and mx_provided
def _provides_eip_and_enabled(self):
"""
@@ -1363,33 +1342,30 @@ class MainWindow(QtGui.QMainWindow):
:returns: True if provides and is enabled, False otherwise
:rtype: bool
"""
- provider_config = self._get_best_provider_config()
- return (provider_config.provides_eip() and
- EIP_SERVICE in self._enabled_services)
+ domain = self._login_widget.get_selected_provider()
+ enabled_services = self._settings.get_enabled_services(domain)
+
+ eip_enabled = EIP_SERVICE in enabled_services
+ eip_provided = False
+ if self._provider_details is not None:
+ eip_provided = EIP_SERVICE in self._provider_details.services
+
+ return eip_enabled and eip_provided
def _maybe_run_soledad_setup_checks(self):
"""
Conditionally start Soledad.
"""
# TODO split.
- if self._already_started_soledad is True:
- return
-
- if not self._provides_mx_and_enabled():
+ if not self._provides_mx_and_enabled() and not flags.OFFLINE:
+ logger.debug("Provider does not offer MX, but it is enabled.")
return
username = self._login_widget.get_user()
password = unicode(self._login_widget.get_password())
provider_domain = self._login_widget.get_selected_provider()
- sb = self._soledad_bootstrapper
- if flags.OFFLINE is True:
- provider_domain = self._login_widget.get_selected_provider()
- sb._password = password
-
- self._provisional_provider_config.load(
- provider.get_provider_path(provider_domain))
-
+ if flags.OFFLINE:
full_user_id = make_address(username, provider_domain)
uuid = self._settings.get_uuid(full_user_id)
self._mail_conductor.userid = full_user_id
@@ -1399,74 +1375,26 @@ class MainWindow(QtGui.QMainWindow):
# this is mostly for internal use/debug for now.
logger.warning("Sorry! Log-in at least one time.")
return
- fun = sb.load_offline_soledad
- fun(full_user_id, password, uuid)
+ self._backend.soledad_load_offline(full_user_id, password, uuid)
else:
- provider_config = self._provider_config
-
if self._logged_user is not None:
- self._soledad_defer = sb.run_soledad_setup_checks(
- provider_config, username, password,
- download_if_needed=True)
+ domain = self._login_widget.get_selected_provider()
+ self._backend.soledad_bootstrap(username, domain, password)
###################################################################
# Service control methods: soledad
- @QtCore.Slot(dict)
- def _soledad_intermediate_stage(self, data):
- # TODO missing param docstring
- """
- TRIGGERS:
- self._soledad_bootstrapper.download_config
-
- If there was a problem, displays it, otherwise it does nothing.
- This is used for intermediate bootstrapping stages, in case
- they fail.
- """
- passed = data[self._soledad_bootstrapper.PASSED_KEY]
- if not passed:
- # TODO display in the GUI:
- # should pass signal to a slot in status_panel
- # that sets the global status
- logger.error("Soledad failed to start: %s" %
- (data[self._soledad_bootstrapper.ERROR_KEY],))
-
- @QtCore.Slot(dict)
- def _soledad_bootstrapped_stage(self, data):
+ @QtCore.Slot()
+ def _on_soledad_ready(self):
"""
TRIGGERS:
- self._soledad_bootstrapper.gen_key
- self._soledad_bootstrapper.local_only_ready
-
- If there was a problem, displays it, otherwise it does nothing.
- This is used for intermediate bootstrapping stages, in case
- they fail.
+ Signaler.soledad_bootstrap_finished
- :param data: result from the bootstrapping stage for Soledad
- :type data: dict
+ Actions to take when Soledad is ready.
"""
- passed = data[self._soledad_bootstrapper.PASSED_KEY]
- if not passed:
- # TODO should actually *display* on the panel.
- logger.debug("ERROR on soledad bootstrapping:")
- logger.error("%r" % data[self._soledad_bootstrapper.ERROR_KEY])
- return
-
logger.debug("Done bootstrapping Soledad")
- # Update the proxy objects to point to
- # the initialized instances.
- setProxiedObject(self._soledad,
- self._soledad_bootstrapper.soledad)
- setProxiedObject(self._keymanager,
- self._soledad_bootstrapper.keymanager)
-
- # Ok, now soledad is ready, so we can allow other things that
- # depend on soledad to start.
- self._soledad_defer = None
-
- # this will trigger start_imap_service
- # and start_smtp_boostrapping
+ self._soledad_started = True
self.soledad_ready.emit()
###################################################################
@@ -1483,19 +1411,7 @@ class MainWindow(QtGui.QMainWindow):
return
if self._provides_mx_and_enabled():
- self._mail_conductor.start_smtp_service(self._provider_config,
- download_if_needed=True)
-
- # XXX --- should remove from here, and connecte directly to the state
- # machine.
- @QtCore.Slot()
- def _stop_smtp_service(self):
- """
- TRIGGERS:
- self.logout
- """
- # TODO call stop_mail_service
- self._mail_conductor.stop_smtp_service()
+ self._mail_conductor.start_smtp_service(download_if_needed=True)
###################################################################
# Service control methods: imap
@@ -1509,69 +1425,14 @@ class MainWindow(QtGui.QMainWindow):
# TODO in the OFFLINE mode we should also modify the rules
# in the mail state machine so it shows that imap is active
# (but not smtp since it's not yet ready for offline use)
- start_fun = self._mail_conductor.start_imap_service
- if flags.OFFLINE is True:
- provider_domain = self._login_widget.get_selected_provider()
- self._provider_config.load(
- provider.get_provider_path(provider_domain))
- provides_mx = self._provider_config.provides_mx()
-
- if flags.OFFLINE is True and provides_mx:
- start_fun()
- return
-
- if self._provides_mx_and_enabled():
- start_fun()
-
- def _on_mail_client_logged_in(self, req):
- """
- Triggers qt signal when client login event is received.
- """
- self.mail_client_logged_in.emit()
-
- @QtCore.Slot()
- def _fetch_incoming_mail(self):
- """
- TRIGGERS:
- self.mail_client_logged_in
- """
- # TODO connect signal directly!!!
- self._mail_conductor.fetch_incoming_mail()
-
- @QtCore.Slot()
- def _stop_imap_service(self):
- """
- TRIGGERS:
- self.logout
- """
- cv = Condition()
- cv.acquire()
- # TODO call stop_mail_service
- threads.deferToThread(self._mail_conductor.stop_imap_service, cv)
- # and wait for it to be stopped
- logger.debug('Waiting for imap service to stop.')
- cv.wait(self.SERVICE_STOP_TIMEOUT)
+ if self._provides_mx_and_enabled() or flags.OFFLINE:
+ self._mail_conductor.start_imap_service()
# end service control methods (imap)
###################################################################
# Service control methods: eip
- def start_eip_machine(self):
- """
- Initializes and starts the EIP state machine
- """
- button = self._eip_status.eip_button
- action = self._action_eip_startstop
- label = self._eip_status.eip_label
- builder = statemachines.ConnectionMachineBuilder(self._eip_connection)
- eip_machine = builder.make_machine(button=button,
- action=action,
- label=label)
- self.eip_machine = eip_machine
- self.eip_machine.start()
- logger.debug('eip machine started')
-
@QtCore.Slot()
def _disable_eip_start_action(self):
"""
@@ -1585,32 +1446,32 @@ class MainWindow(QtGui.QMainWindow):
Enables the EIP start action in the systray menu.
"""
self._action_eip_startstop.setEnabled(True)
+ self._eip_status.enable_eip_start()
@QtCore.Slot()
def _on_eip_connection_connected(self):
"""
TRIGGERS:
- self._eip_status.eip_connection_connected
-
- Emits the EIPConnection.qtsigs.connected_signal
+ self._eip_conductor.qtsigs.connected_signal
This is a little workaround for connecting the vpn-connected
signal that currently is beeing processed under status_panel.
After the refactor to EIPConductor this should not be necessary.
"""
- self._eip_connection.qtsigs.connected_signal.emit()
-
- provider_config = self._get_best_provider_config()
- domain = provider_config.get_domain()
+ domain = self._login_widget.get_selected_provider()
self._eip_status.set_provider(domain)
self._settings.set_defaultprovider(domain)
self._already_started_eip = True
# check for connectivity
+ # we might want to leave a little time here...
self._check_name_resolution(domain)
def _check_name_resolution(self, domain):
+ # FIXME this has to be moved to backend !!!
+ # Should move to netchecks module.
+ # and separate qt from reactor...
"""
Check if we can resolve the given domain name.
@@ -1641,7 +1502,7 @@ class MainWindow(QtGui.QMainWindow):
"missing some helper files that are needed to securely use "
"DNS while {1} is active. To install these helper files, quit "
"this application and start it again."
- ).format(domain, self._eip_name)
+ ).format(domain, self._eip_conductor.eip_name)
show_err = lambda: QtGui.QMessageBox.critical(
self, self.tr("Connection Error"), msg)
@@ -1663,245 +1524,42 @@ class MainWindow(QtGui.QMainWindow):
self._enabled_services = settings.get_enabled_services(
default_provider)
- loaded = self._provisional_provider_config.load(
- provider.get_provider_path(default_provider))
- if loaded and settings.get_autostart_eip():
- # XXX I think we should not try to re-download config every time,
- # it adds some delay.
- # Maybe if it's the first run in a session,
- # or we can try only if it fails.
- self._maybe_start_eip()
- elif settings.get_autostart_eip():
- # XXX: Display a proper message to the user
- self.eip_needs_login.emit()
- logger.error("Unable to load %s config, cannot autostart." %
- (default_provider,))
-
- @QtCore.Slot()
- def _start_EIP(self):
- """
- Starts EIP
- """
- self._eip_status.eip_pre_up()
- self.user_stopped_eip = False
-
- # Until we set an option in the preferences window, we'll assume that
- # by default we try to autostart. If we switch it off manually, it
- # won't try the next time.
- self._settings.set_autostart_eip(True)
-
- self._backend.start_eip()
-
- @QtCore.Slot()
- def _on_eip_connection_aborted(self):
- """
- TRIGGERS:
- Signaler.eip_connection_aborted
- """
- logger.error("Tried to start EIP but cannot find any "
- "available provider!")
-
- eip_status_label = self.tr("Could not load {0} configuration.")
- eip_status_label = eip_status_label.format(self._eip_name)
- self._eip_status.set_eip_status(eip_status_label, error=True)
-
- # signal connection_aborted to state machine:
- qtsigs = self._eip_connection.qtsigs
- qtsigs.connection_aborted_signal.emit()
-
- def _on_eip_openvpn_already_running(self):
- self._eip_status.set_eip_status(
- self.tr("Another openvpn instance is already running, and "
- "could not be stopped."),
- error=True)
- self._set_eipstatus_off()
-
- def _on_eip_alien_openvpn_already_running(self):
- self._eip_status.set_eip_status(
- self.tr("Another openvpn instance is already running, and "
- "could not be stopped because it was not launched by "
- "Bitmask. Please stop it and try again."),
- error=True)
- self._set_eipstatus_off()
-
- def _on_eip_openvpn_not_found_error(self):
- self._eip_status.set_eip_status(
- self.tr("We could not find openvpn binary."),
- error=True)
- self._set_eipstatus_off()
-
- def _on_eip_vpn_launcher_exception(self):
- # XXX We should implement again translatable exceptions so
- # we can pass a translatable string to the panel (usermessage attr)
- self._eip_status.set_eip_status("VPN Launcher error.", error=True)
- self._set_eipstatus_off()
-
- def _on_eip_no_polkit_agent_error(self):
- self._eip_status.set_eip_status(
- # XXX this should change to polkit-kde where
- # applicable.
- self.tr("We could not find any authentication agent in your "
- "system.<br/>Make sure you have"
- "<b>polkit-gnome-authentication-agent-1</b> running and"
- "try again."),
- error=True)
- self._set_eipstatus_off()
-
- def _on_eip_no_pkexec_error(self):
- self._eip_status.set_eip_status(
- self.tr("We could not find <b>pkexec</b> in your system."),
- error=True)
- self._set_eipstatus_off()
-
- def _on_eip_no_tun_kext_error(self):
- self._eip_status.set_eip_status(
- self.tr("{0} cannot be started because the tuntap extension is "
- "not installed properly in your "
- "system.").format(self._eip_name))
- self._set_eipstatus_off()
-
- @QtCore.Slot()
- def _stop_eip(self):
- """
- TRIGGERS:
- self._eip_connection.qtsigs.do_disconnect_signal (via state machine)
-
- Stops vpn process and makes gui adjustments to reflect
- the change of state.
-
- :param abnormal: whether this was an abnormal termination.
- :type abnormal: bool
- """
- self.user_stopped_eip = True
- self._backend.stop_eip()
-
- self._set_eipstatus_off(False)
- self._already_started_eip = False
-
- logger.debug('Setting autostart to: False')
- self._settings.set_autostart_eip(False)
-
- if self._logged_user:
- self._eip_status.set_provider(
- make_address(
- self._logged_user,
- self._get_best_provider_config().get_domain()))
- self._eip_status.eip_stopped()
-
- @QtCore.Slot()
- def _on_eip_network_unreachable(self):
- # XXX Should move to EIP Conductor
- """
- TRIGGERS:
- self._eip_connection.qtsigs.network_unreachable
-
- Displays a "network unreachable" error in the EIP status panel.
- """
- self._eip_status.set_eip_status(self.tr("Network is unreachable"),
- error=True)
- self._eip_status.set_eip_status_icon("error")
-
- @QtCore.Slot()
- def _do_eip_restart(self):
- # XXX Should move to EIP Conductor
- """
- TRIGGERS:
- self._eip_connection.qtsigs.process_restart
-
- Restart the connection.
- """
- # for some reason, emitting the do_disconnect/do_connect
- # signals hangs the UI.
- self._stop_eip()
- QtCore.QTimer.singleShot(2000, self._start_EIP)
-
- def _set_eipstatus_off(self, error=True):
- """
- Sets eip status to off
- """
- # XXX this should be handled by the state machine.
- self._eip_status.set_eip_status("", error=error)
- self._eip_status.set_eip_status_icon("error")
-
- @QtCore.Slot(int)
- def _eip_finished(self, exitCode):
- """
- TRIGGERS:
- Signaler.eip_process_finished
-
- Triggered when the EIP/VPN process finishes to set the UI
- accordingly.
-
- Ideally we would have the right exit code here,
- but the use of different wrappers (pkexec, cocoasudo) swallows
- the openvpn exit code so we get zero exit in some cases where we
- shouldn't. As a workaround we just use a flag to indicate
- a purposeful switch off, and mark everything else as unexpected.
-
- In the near future we should trigger a native notification from here,
- since the user really really wants to know she is unprotected asap.
- And the right thing to do will be to fail-close.
-
- :param exitCode: the exit code of the eip process.
- :type exitCode: int
- """
- # TODO move to EIPConductor.
- # TODO Add error catching to the openvpn log observer
- # so we can have a more precise idea of which type
- # of error did we have (server side, local problem, etc)
-
- logger.info("VPN process finished with exitCode %s..."
- % (exitCode,))
-
- qtsigs = self._eip_connection.qtsigs
- signal = qtsigs.disconnected_signal
-
- # XXX check if these exitCodes are pkexec/cocoasudo specific
- if exitCode in (126, 127):
- eip_status_label = self.tr(
- "{0} could not be launched "
- "because you did not authenticate properly.")
- eip_status_label = eip_status_label.format(self._eip_name)
- self._eip_status.set_eip_status(eip_status_label, error=True)
- signal = qtsigs.connection_aborted_signal
- self._backend.terminate_eip()
-
- elif exitCode != 0 or not self.user_stopped_eip:
- eip_status_label = self.tr("{0} finished in an unexpected manner!")
- eip_status_label = eip_status_label.format(self._eip_name)
- self._eip_status.eip_stopped()
- self._eip_status.set_eip_status_icon("error")
- self._eip_status.set_eip_status(eip_status_label, error=True)
- signal = qtsigs.connection_died_signal
-
- if exitCode == 0 and IS_MAC:
- # XXX remove this warning after I fix cocoasudo.
- logger.warning("The above exit code MIGHT BE WRONG.")
-
- # We emit signals to trigger transitions in the state machine:
- signal.emit()
+ if settings.get_autostart_eip():
+ self._maybe_start_eip(autostart=True)
# eip boostrapping, config etc...
- def _maybe_start_eip(self):
+ def _maybe_start_eip(self, autostart=False):
"""
Start the EIP bootstrapping sequence if the client is configured to
do so.
+
+ :param autostart: we are autostarting EIP when this is True
+ :type autostart: bool
"""
- if self._provides_eip_and_enabled() and not self._already_started_eip:
+ # during autostart we assume that the provider provides EIP
+ if autostart:
+ should_start = EIP_SERVICE in self._enabled_services
+ else:
+ should_start = self._provides_eip_and_enabled()
+
+ if should_start and not self._already_started_eip:
+ if self._eip_status.is_cold_start:
+ self._backend.tear_fw_down()
# XXX this should be handled by the state machine.
+ self._enable_eip_start_action()
self._eip_status.set_eip_status(
self.tr("Starting..."))
+ self._eip_status.eip_button.setEnabled(False)
domain = self._login_widget.get_selected_provider()
- self._backend.setup_eip(domain)
+ self._backend.eip_setup(domain)
self._already_started_eip = True
# we want to start soledad anyway after a certain timeout if eip
# fails to come up
- QtCore.QTimer.singleShot(
- self.EIP_TIMEOUT,
- self._maybe_run_soledad_setup_checks)
+ QtDelayedCall(self.EIP_START_TIMEOUT,
+ self._maybe_run_soledad_setup_checks)
else:
if not self._already_started_eip:
if EIP_SERVICE in self._enabled_services:
@@ -1920,8 +1578,8 @@ class MainWindow(QtGui.QMainWindow):
TRIGGERS:
self._backend.signaler.eip_client_certificate_ready
- Starts the VPN thread if the eip configuration is properly
- loaded
+ Start the VPN thread if the eip configuration is properly
+ loaded.
"""
passed = data[self._backend.PASSED_KEY]
@@ -1933,11 +1591,11 @@ class MainWindow(QtGui.QMainWindow):
return
# DO START EIP Connection!
- self._eip_connection.qtsigs.do_connect_signal.emit()
+ self._eip_conductor.do_connect()
@QtCore.Slot(dict)
def _eip_intermediate_stage(self, data):
- # TODO missing param
+ # TODO missing param documentation
"""
TRIGGERS:
self._backend.signaler.eip_config_ready
@@ -1952,33 +1610,10 @@ class MainWindow(QtGui.QMainWindow):
self.tr("Unable to connect: Problem with provider"))
logger.error(data[self._backend.ERROR_KEY])
self._already_started_eip = False
+ self._eip_status.aborted()
# end of EIP methods ---------------------------------------------
- def _get_best_provider_config(self):
- """
- Returns the best ProviderConfig to use at a moment. We may
- have to use self._provider_config or
- self._provisional_provider_config depending on the start
- status.
-
- :rtype: ProviderConfig
- """
- # TODO move this out of gui.
- leap_assert(self._provider_config is not None or
- self._provisional_provider_config is not None,
- "We need a provider config")
-
- provider_config = None
- if self._provider_config.loaded():
- provider_config = self._provider_config
- elif self._provisional_provider_config.loaded():
- provider_config = self._provisional_provider_config
- else:
- leap_assert(False, "We could not find any usable ProviderConfig.")
-
- return provider_config
-
@QtCore.Slot()
def _logout(self):
"""
@@ -1987,16 +1622,11 @@ class MainWindow(QtGui.QMainWindow):
Starts the logout sequence
"""
- setProxiedObject(self._soledad, None)
-
self._cancel_ongoing_defers()
- # reset soledad status flag
- self._already_started_soledad = False
-
# XXX: If other defers are doing authenticated stuff, this
# might conflict with those. CHECK!
- self._backend.logout()
+ self._backend.user_logout()
self.logout.emit()
@QtCore.Slot()
@@ -2080,59 +1710,37 @@ class MainWindow(QtGui.QMainWindow):
# cleanup and quit methods
#
- def _cleanup_pidfiles(self):
- """
- Removes lockfiles on a clean shutdown.
-
- Triggered after aboutToQuit signal.
+ def _stop_services(self):
"""
- if IS_WIN:
- WindowsLock.release_all_locks()
-
- def _cleanup_and_quit(self):
- """
- Call all the cleanup actions in a serialized way.
- Should be called from the quit function.
+ Stop services and cancel ongoing actions (if any).
"""
- logger.debug('About to quit, doing cleanup...')
-
- self._stop_imap_service()
-
- if self._logged_user is not None:
- self._backend.logout()
+ logger.debug('About to quit, doing cleanup.')
- if self._soledad_bootstrapper.soledad is not None:
- logger.debug("Closing soledad...")
- self._soledad_bootstrapper.soledad.close()
- else:
- logger.error("No instance of soledad was found.")
+ self._cancel_ongoing_defers()
- logger.debug('Terminating vpn')
- self._backend.stop_eip(shutdown=True)
+ self._services_being_stopped = {'imap', 'eip'}
- # We need to give some time to the ongoing signals for shutdown
- # to come into action. This needs to be solved using
- # back-communication from backend.
- QtCore.QTimer.singleShot(3000, self._shutdown)
+ imap_stopped = lambda: self._remove_service('imap')
+ self._backend.signaler.imap_stopped.connect(imap_stopped)
- def _shutdown(self):
- """
- Actually shutdown.
- """
- self._cancel_ongoing_defers()
+ eip_stopped = lambda: self._remove_service('eip')
+ self._backend.signaler.eip_stopped.connect(eip_stopped)
- # TODO missing any more cancels?
+ logger.debug('Stopping mail services')
+ self._backend.imap_stop_service()
+ self._backend.smtp_stop_service()
- logger.debug('Cleaning pidfiles')
- self._cleanup_pidfiles()
- if self._quit_callback:
- self._quit_callback()
+ if self._logged_user is not None:
+ logger.debug("Doing logout")
+ self._backend.user_logout()
- logger.debug('Bye.')
+ logger.debug('Terminating vpn')
+ self._backend.eip_stop(shutdown=True)
def quit(self):
"""
- Cleanup and tidely close the main window before quitting.
+ Start the quit sequence and wait for services to finish.
+ Cleanup and close the main window before quitting.
"""
# TODO separate the shutting down of services from the
# UI stuff.
@@ -2142,25 +1750,72 @@ class MainWindow(QtGui.QMainWindow):
if self._systray is not None:
self._systray.showMessage(
self.tr('Quitting...'),
- self.tr('The app is quitting, please wait.'))
+ self.tr('Bitmask is quitting, please wait.'))
# explicitly process events to display tooltip immediately
- QtCore.QCoreApplication.processEvents()
+ QtCore.QCoreApplication.processEvents(0, 10)
+
+ # Close other windows if any.
+ if self._wizard:
+ self._wizard.close()
+
+ if self._logger_window:
+ self._logger_window.close()
# Set this in case that the app is hidden
QtGui.QApplication.setQuitOnLastWindowClosed(True)
- self._cleanup_and_quit()
+ self._stop_services()
- # We queue the call to stop since we need to wait until EIP is stopped.
- # Otherwise we may exit leaving an unmanaged openvpn process.
- reactor.callLater(0, self._backend.stop)
self._really_quit = True
- if self._wizard:
- self._wizard.close()
+ # call final quit when all the services are stopped
+ self.all_services_stopped.connect(self.final_quit)
+ # or if we reach the timeout
+ self._quit_timeout_callater = reactor.callLater(
+ self.SERVICES_STOP_TIMEOUT, self.final_quit)
- if self._logger_window:
- self._logger_window.close()
+ @QtCore.Slot()
+ def _remove_service(self, service):
+ """
+ Remove the given service from the waiting list and check if we have
+ running services that we need to wait until we quit.
+ Emit self.all_services_stopped signal if we don't need to keep waiting.
+ :param service: the service that we want to remove
+ :type service: str
+ """
+ self._services_being_stopped.discard(service)
+
+ if not self._services_being_stopped:
+ logger.debug("All services stopped.")
+ self.all_services_stopped.emit()
+
+ @QtCore.Slot()
+ def final_quit(self):
+ """
+ Final steps to quit the app, starting from here we don't care about
+ running services or user interaction, just quitting.
+ """
+ logger.debug('Final quit...')
+
+ try:
+ # disconnect signal if we get here due a timeout.
+ self.all_services_stopped.disconnect(self.final_quit)
+ except RuntimeError:
+ pass # Signal was not connected
+
+ # Cancel timeout to avoid being called if we reached here through the
+ # signal
+ if self._quit_timeout_callater.active():
+ self._quit_timeout_callater.cancel()
+
+ # Remove lockfiles on a clean shutdown.
+ logger.debug('Cleaning pidfiles')
+ if IS_WIN:
+ WindowsLock.release_all_locks()
+
+ self._backend.stop()
self.close()
+
+ reactor.callLater(1, self._quit_callback)
diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py
index 2947c5db..a3b81d38 100644
--- a/src/leap/bitmask/gui/preferenceswindow.py
+++ b/src/leap/bitmask/gui/preferenceswindow.py
@@ -23,15 +23,10 @@ import logging
from functools import partial
from PySide import QtCore, QtGui
-from zope.proxy import sameProxiedObjects
-from leap.bitmask.provider import get_provider_path
from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.gui.ui_preferences import Ui_Preferences
-from leap.soledad.client import NoStorageSecret
-from leap.bitmask.util.password import basic_password_checks
-from leap.bitmask.services import get_supported
-from leap.bitmask.config.providerconfig import ProviderConfig
+from leap.bitmask.util.credentials import password_checks
from leap.bitmask.services import get_service_display_name, MX_SERVICE
logger = logging.getLogger(__name__)
@@ -43,32 +38,31 @@ class PreferencesWindow(QtGui.QDialog):
"""
preferences_saved = QtCore.Signal()
- def __init__(self, parent, backend, provider_config,
- soledad, username, domain):
+ def __init__(self, parent, username, domain, backend, soledad_started, mx):
"""
:param parent: parent object of the PreferencesWindow.
:parent type: QWidget
- :param backend: Backend being used
- :type backend: Backend
- :param provider_config: ProviderConfig object.
- :type provider_config: ProviderConfig
- :param soledad: Soledad instance
- :type soledad: Soledad
:param username: the user set in the login widget
:type username: unicode
:param domain: the selected domain in the login widget
:type domain: unicode
+ :param backend: Backend being used
+ :type backend: Backend
+ :param soledad_started: whether soledad has started or not
+ :type soledad_started: bool
+ :param mx: whether the current provider provides mx or not.
+ :type mx: bool
"""
QtGui.QDialog.__init__(self, parent)
self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic")
- self._backend = backend
- self._settings = LeapSettings()
- self._soledad = soledad
- self._provider_config = provider_config
self._username = username
self._domain = domain
+ self._backend = backend
+ self._soledad_started = soledad_started
+ self._mx_provided = mx
+ self._settings = LeapSettings()
self._backend_connect()
# Load UI
@@ -89,50 +83,17 @@ class PreferencesWindow(QtGui.QDialog):
else:
self._add_configured_providers()
- self._backend.get_logged_in_status()
+ if self._username is None:
+ self._not_logged_in()
+ else:
+ self.ui.gbPasswordChange.setEnabled(True)
+ if self._mx_provided:
+ self._provides_mx()
self._select_provider_by_name(domain)
- @QtCore.Slot()
- def _is_logged_in(self):
- """
- TRIGGERS:
- Signaler.srp_status_logged_in
-
- Actions to perform is the user is logged in.
- """
- settings = self._settings
- pw_enabled = True
-
- # check if provider has 'mx' ...
- # TODO: we should move this to the backend.
- if self._provider_config.provides_mx():
- enabled_services = settings.get_enabled_services(self._domain)
- mx_name = get_service_display_name(MX_SERVICE)
-
- # ... and if the user have it enabled
- if MX_SERVICE not in enabled_services:
- msg = self.tr("You need to enable {0} in order to change "
- "the password.".format(mx_name))
- self._set_password_change_status(msg, error=True)
- pw_enabled = False
- else:
- # check if Soledad is bootstrapped
- if sameProxiedObjects(self._soledad, None):
- msg = self.tr(
- "You need to wait until {0} is ready in "
- "order to change the password.".format(mx_name))
- self._set_password_change_status(msg)
- pw_enabled = False
-
- self.ui.gbPasswordChange.setEnabled(pw_enabled)
-
- @QtCore.Slot()
def _not_logged_in(self):
"""
- TRIGGERS:
- Signaler.srp_status_not_logged_in
-
Actions to perform if the user is not logged in.
"""
msg = self.tr(
@@ -140,6 +101,30 @@ class PreferencesWindow(QtGui.QDialog):
self._set_password_change_status(msg)
self.ui.gbPasswordChange.setEnabled(False)
+ def _provides_mx(self):
+ """
+ Actions to perform if the provider provides MX.
+ """
+ pw_enabled = True
+ enabled_services = self._settings.get_enabled_services(self._domain)
+ mx_name = get_service_display_name(MX_SERVICE)
+
+ if MX_SERVICE not in enabled_services:
+ msg = self.tr("You need to enable {0} in order to change "
+ "the password.".format(mx_name))
+ self._set_password_change_status(msg, error=True)
+ pw_enabled = False
+ else:
+ # check if Soledad is bootstrapped
+ if not self._soledad_started:
+ msg = self.tr(
+ "You need to wait until {0} is ready in "
+ "order to change the password.".format(mx_name))
+ self._set_password_change_status(msg)
+ pw_enabled = False
+
+ self.ui.gbPasswordChange.setEnabled(pw_enabled)
+
@QtCore.Slot()
def set_soledad_ready(self):
"""
@@ -200,7 +185,7 @@ class PreferencesWindow(QtGui.QDialog):
new_password = self.ui.leNewPassword.text()
new_password2 = self.ui.leNewPassword2.text()
- ok, msg = basic_password_checks(username, new_password, new_password2)
+ ok, msg = password_checks(username, new_password, new_password2)
if not ok:
self._set_changing_password(False)
@@ -209,10 +194,10 @@ class PreferencesWindow(QtGui.QDialog):
return
self._set_changing_password(True)
- self._backend.change_password(current_password, new_password)
+ self._backend.user_change_password(current_password, new_password)
@QtCore.Slot()
- def _change_password_ok(self):
+ def _srp_change_password_ok(self):
"""
TRIGGERS:
self._backend.signaler.srp_password_change_ok
@@ -221,12 +206,44 @@ class PreferencesWindow(QtGui.QDialog):
"""
new_password = self.ui.leNewPassword.text()
logger.debug("SRP password changed successfully.")
- try:
- self._soledad.change_passphrase(new_password)
- logger.debug("Soledad password changed successfully.")
- except NoStorageSecret:
- logger.debug(
- "No storage secret for password change in Soledad.")
+
+ if self._mx_provided:
+ self._backend.soledad_change_password(new_password)
+ else:
+ self._change_password_success()
+
+ @QtCore.Slot(unicode)
+ def _srp_change_password_problem(self, msg):
+ """
+ TRIGGERS:
+ self._backend.signaler.srp_password_change_error
+ self._backend.signaler.srp_password_change_badpw
+
+ Callback used to display an error on changing password.
+
+ :param msg: the message to show to the user.
+ :type msg: unicode
+ """
+ logger.error("Error changing password")
+ self._set_password_change_status(msg, error=True)
+ self._set_changing_password(False)
+
+ @QtCore.Slot()
+ def _soledad_change_password_ok(self):
+ """
+ TRIGGERS:
+ Signaler.soledad_password_change_ok
+
+ Soledad password change went OK.
+ """
+ logger.debug("Soledad password changed successfully.")
+ self._change_password_success()
+
+ def _change_password_success(self):
+ """
+ Callback used to display a successfully changed password.
+ """
+ logger.debug("Soledad password changed successfully.")
self._set_password_change_status(
self.tr("Password changed successfully."), success=True)
@@ -234,18 +251,17 @@ class PreferencesWindow(QtGui.QDialog):
self._set_changing_password(False)
@QtCore.Slot(unicode)
- def _change_password_problem(self, msg):
+ def _soledad_change_password_problem(self, msg):
"""
TRIGGERS:
- self._backend.signaler.srp_password_change_error
- self._backend.signaler.srp_password_change_badpw
+ Signaler.soledad_password_change_error
Callback used to display an error on changing password.
:param msg: the message to show to the user.
:type msg: unicode
"""
- logger.error("Error changing password")
+ logger.error("Error changing soledad password")
self._set_password_change_status(msg, error=True)
self._set_changing_password(False)
@@ -321,8 +337,7 @@ class PreferencesWindow(QtGui.QDialog):
TRIGGERS:
self.ui.cbProvidersServices.currentIndexChanged[unicode]
- Loads the services that the provider provides into the UI for
- the user to enable or disable.
+ Fill the services list with the selected provider's services.
:param domain: the domain of the provider to load services from.
:type domain: str
@@ -333,10 +348,6 @@ class PreferencesWindow(QtGui.QDialog):
if not domain:
return
- provider_config = self._get_provider_config(domain)
- if provider_config is None:
- return
-
# set the proper connection for the 'save' button
try:
self.ui.pbSaveServices.clicked.disconnect()
@@ -346,7 +357,21 @@ class PreferencesWindow(QtGui.QDialog):
save_services = partial(self._save_enabled_services, domain)
self.ui.pbSaveServices.clicked.connect(save_services)
- services = get_supported(provider_config.get_services())
+ self._backend.provider_get_supported_services(domain)
+
+ @QtCore.Slot(str)
+ def _load_services(self, services):
+ """
+ TRIGGERS:
+ self.ui.cbProvidersServices.currentIndexChanged[unicode]
+
+ Loads the services that the provider provides into the UI for
+ the user to enable or disable.
+
+ :param domain: the domain of the provider to load services from.
+ :type domain: str
+ """
+ domain = self.ui.cbProvidersServices.currentText()
services_conf = self._settings.get_enabled_services(domain)
# discard changes if other provider is selected
@@ -394,36 +419,26 @@ class PreferencesWindow(QtGui.QDialog):
self._set_providers_services_status(msg, success=True)
self.preferences_saved.emit()
- def _get_provider_config(self, domain):
- """
- Helper to return a valid Provider Config from the domain name.
-
- :param domain: the domain name of the provider.
- :type domain: str
-
- :rtype: ProviderConfig or None if there is a problem loading the config
- """
- provider_config = ProviderConfig()
- if not provider_config.load(get_provider_path(domain)):
- provider_config = None
-
- return provider_config
-
def _backend_connect(self):
"""
Helper to connect to backend signals
"""
sig = self._backend.signaler
- sig.srp_status_logged_in.connect(self._is_logged_in)
- sig.srp_status_not_logged_in.connect(self._not_logged_in)
+ sig.prov_get_supported_services.connect(self._load_services)
- sig.srp_password_change_ok.connect(self._change_password_ok)
+ sig.srp_password_change_ok.connect(self._srp_change_password_ok)
- pwd_change_error = lambda: self._change_password_problem(
+ pwd_change_error = lambda: self._srp_change_password_problem(
self.tr("There was a problem changing the password."))
sig.srp_password_change_error.connect(pwd_change_error)
- pwd_change_badpw = lambda: self._change_password_problem(
+ pwd_change_badpw = lambda: self._srp_change_password_problem(
self.tr("You did not enter a correct current password."))
sig.srp_password_change_badpw.connect(pwd_change_badpw)
+
+ sig.soledad_password_change_ok.connect(
+ self._soledad_change_password_ok)
+
+ sig.soledad_password_change_error.connect(
+ self._soledad_change_password_problem)
diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py
index 31938a70..00a1387e 100644
--- a/src/leap/bitmask/gui/statemachines.py
+++ b/src/leap/bitmask/gui/statemachines.py
@@ -504,6 +504,11 @@ class ConnectionMachineBuilder(object):
conn.qtsigs.connection_died_signal,
states[_OFF])
+ # XXX adding this---------------------
+ states[_ON].addTransition(
+ conn.qtsigs.do_disconnect_signal,
+ states[_DIS])
+
# * If we receive the connection_aborted, we transition
# from connecting to the off state
states[_CON].addTransition(
@@ -551,7 +556,8 @@ class ConnectionMachineBuilder(object):
# TODO add tooltip
# OFF State ----------------------
- off = QState()
+ off = SignallingState(
+ None, name=conn.name)
off_label = _tr("Turn {0}").format(
conn.Connected.short_label)
if button:
@@ -559,11 +565,15 @@ class ConnectionMachineBuilder(object):
button, 'text', off_label)
off.assignProperty(
button, 'enabled', True)
+ off.assignProperty(
+ button, 'visible', True)
if action:
off.assignProperty(
action, 'text', off_label)
off.assignProperty(
action, 'enabled', True)
+ off.assignProperty(
+ action, 'visible', True)
off.setObjectName(_OFF)
states[_OFF] = off
@@ -587,7 +597,10 @@ class ConnectionMachineBuilder(object):
states[_CON] = connecting
# ON State ------------------------
- on = QState()
+ on = SignallingState(
+ None, name=conn.name)
+ on_label = _tr("Turn {0}").format(
+ conn.Disconnected.short_label)
if button:
on.assignProperty(
button, 'text', on_label)
diff --git a/src/leap/bitmask/gui/ui/eip_status.ui b/src/leap/bitmask/gui/ui/eip_status.ui
index 64821ad6..7216bb0a 100644
--- a/src/leap/bitmask/gui/ui/eip_status.ui
+++ b/src/leap/bitmask/gui/ui/eip_status.ui
@@ -28,7 +28,7 @@
<property name="verticalSpacing">
<number>0</number>
</property>
- <item row="0" column="2">
+ <item row="0" column="4">
<widget class="QPushButton" name="btnEipStartStop">
<property name="text">
<string>Turn On</string>
@@ -51,7 +51,7 @@
</property>
</widget>
</item>
- <item row="3" column="1">
+ <item row="3" column="2">
<widget class="QLabel" name="lblEIPStatus">
<property name="maximumSize">
<size>
@@ -70,7 +70,7 @@
</property>
</widget>
</item>
- <item row="0" column="1">
+ <item row="0" column="2">
<widget class="QLabel" name="lblEIPMessage">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
@@ -86,7 +86,7 @@
</property>
</widget>
</item>
- <item row="0" column="3">
+ <item row="0" column="5">
<widget class="QLabel" name="lblVPNStatusIcon">
<property name="maximumSize">
<size>
@@ -105,7 +105,7 @@
</property>
</widget>
</item>
- <item row="1" column="1">
+ <item row="1" column="2">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -118,7 +118,7 @@
</property>
</spacer>
</item>
- <item row="2" column="1" colspan="3">
+ <item row="2" column="2" colspan="4">
<widget class="QWidget" name="eip_bandwidth" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
@@ -161,12 +161,13 @@
<property name="text">
<string>0.0 KB/s</string>
</property>
+ <property name="icon">
+ <iconset resource="../../../../../data/resources/mainwindow.qrc">
+ <normaloff>:/images/black/32/arrow-down.png</normaloff>:/images/black/32/arrow-down.png</iconset>
+ </property>
<property name="flat">
<bool>true</bool>
</property>
- <property name="icon">
- <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/down-arrow.png</pixmap>
- </property>
</widget>
</item>
<item>
@@ -211,12 +212,13 @@
<property name="text">
<string>0.0 KB/s</string>
</property>
+ <property name="icon">
+ <iconset resource="../../../../../data/resources/mainwindow.qrc">
+ <normaloff>:/images/black/32/arrow-up.png</normaloff>:/images/black/32/arrow-up.png</iconset>
+ </property>
<property name="flat">
<bool>true</bool>
</property>
- <property name="icon">
- <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/up-arrow.png</pixmap>
- </property>
</widget>
</item>
<item>
@@ -237,6 +239,20 @@
</layout>
</widget>
</item>
+ <item row="0" column="3">
+ <widget class="QPushButton" name="btnFwDown">
+ <property name="text">
+ <string>Turn Off</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="lblGatewayCountryCode">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
</layout>
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index 020a58e2..4d774907 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -26,11 +26,10 @@ from PySide import QtCore, QtGui
from leap.bitmask.config import flags
from leap.bitmask.config.leapsettings import LeapSettings
-from leap.bitmask.config.providerconfig import ProviderConfig
-from leap.bitmask.provider import get_provider_path
from leap.bitmask.services import get_service_display_name, get_supported
+from leap.bitmask.util.credentials import password_checks, username_checks
+from leap.bitmask.util.credentials import USERNAME_REGEX
from leap.bitmask.util.keyring_helpers import has_keyring
-from leap.bitmask.util.password import basic_password_checks
from ui_wizard import Ui_Wizard
@@ -49,8 +48,6 @@ class Wizard(QtGui.QWizard):
REGISTER_USER_PAGE = 4
SERVICES_PAGE = 5
- BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$"
-
def __init__(self, backend, bypass_checks=False):
"""
Constructor for the main Wizard.
@@ -89,10 +86,9 @@ class Wizard(QtGui.QWizard):
self._backend_connect()
self._domain = None
- # HACK!! We need provider_config for the time being, it'll be
- # removed
- self._provider_config = (
- self._backend._components["provider"]._provider_config)
+
+ # this details are set when the provider download is complete.
+ self._provider_details = None
# We will store a reference to the defers for eventual use
# (eg, to cancel them) but not doing anything with them right now.
@@ -118,7 +114,7 @@ class Wizard(QtGui.QWizard):
self.ui.rbExistingProvider.toggled.connect(self._skip_provider_checks)
- usernameRe = QtCore.QRegExp(self.BARE_USERNAME_REGEX)
+ usernameRe = QtCore.QRegExp(USERNAME_REGEX)
self.ui.lblUser.setValidator(
QtGui.QRegExpValidator(usernameRe, self))
@@ -231,6 +227,12 @@ class Wizard(QtGui.QWizard):
if reset:
self._reset_provider_check()
+ def _focus_username(self):
+ """
+ Focus at the username lineedit for the registration page
+ """
+ self.ui.lblUser.setFocus()
+
def _focus_password(self):
"""
Focuses at the password lineedit for the registration page
@@ -253,16 +255,22 @@ class Wizard(QtGui.QWizard):
password = self.ui.lblPassword.text()
password2 = self.ui.lblPassword2.text()
- ok, msg = basic_password_checks(username, password, password2)
- if ok:
+ user_ok, msg = username_checks(username)
+ if user_ok:
+ pass_ok, msg = password_checks(username, password, password2)
+
+ if user_ok and pass_ok:
self._set_register_status(self.tr("Starting registration..."))
- self._backend.register_user(self._domain, username, password)
+ self._backend.user_register(self._domain, username, password)
self._username = username
self._password = password
else:
+ if user_ok:
+ self._focus_password()
+ else:
+ self._focus_username()
self._set_register_status(msg, error=True)
- self._focus_password()
self.ui.btnRegister.setEnabled(True)
def _set_registration_fields_visibility(self, visible):
@@ -406,7 +414,7 @@ class Wizard(QtGui.QWizard):
self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON)
self._provider_select_defer = self._backend.\
- setup_provider(self._domain)
+ provider_setup(self._domain)
@QtCore.Slot(bool)
def _skip_provider_checks(self, skip):
@@ -502,10 +510,12 @@ class Wizard(QtGui.QWizard):
check. Since this check is the last of this set, it also
completes the page if passed
"""
- if self._provider_config.load(get_provider_path(self._domain)):
+ if data[self._backend.PASSED_KEY]:
self._complete_task(data, self.ui.lblProviderInfo,
True, self.SELECT_PROVIDER_PAGE)
self._provider_checks_ok = True
+ lang = QtCore.QLocale.system().name()
+ self._backend.provider_get_details(self._domain, lang)
else:
new_data = {
self._backend.PASSED_KEY: False,
@@ -527,6 +537,16 @@ class Wizard(QtGui.QWizard):
else:
self.ui.cbProviders.setEnabled(True)
+ @QtCore.Slot()
+ def _provider_get_details(self, details):
+ """
+ Set the details for the just downloaded provider.
+
+ :param details: the details of the provider.
+ :type details: ProviderConfigLight
+ """
+ self._provider_details = details
+
@QtCore.Slot(dict)
def _download_ca_cert(self, data):
"""
@@ -594,11 +614,9 @@ class Wizard(QtGui.QWizard):
the user to enable or disable.
"""
self.ui.grpServices.setTitle(
- self.tr("Services by %s") %
- (self._provider_config.get_name(),))
+ self.tr("Services by {0}").format(self._provider_details.name))
- services = get_supported(
- self._provider_config.get_services())
+ services = get_supported(self._provider_details.services)
for service in services:
try:
@@ -641,38 +659,31 @@ class Wizard(QtGui.QWizard):
if not self._provider_setup_ok:
self._reset_provider_setup()
sub_title = self.tr("Gathering configuration options for {0}")
- sub_title = sub_title.format(self._provider_config.get_name())
+ sub_title = sub_title.format(self._provider_details.name)
self.page(pageId).setSubTitle(sub_title)
self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON)
self._provider_setup_defer = self._backend.\
provider_bootstrap(self._domain)
if pageId == self.PRESENT_PROVIDER_PAGE:
- self.page(pageId).setSubTitle(self.tr("Description of services "
- "offered by %s") %
- (self._provider_config
- .get_name(),))
-
- lang = QtCore.QLocale.system().name()
- self.ui.lblProviderName.setText(
- "<b>%s</b>" %
- (self._provider_config.get_name(lang=lang),))
- self.ui.lblProviderURL.setText(
- "https://%s" % (self._provider_config.get_domain(),))
- self.ui.lblProviderDesc.setText(
- "<i>%s</i>" %
- (self._provider_config.get_description(lang=lang),))
-
- self.ui.lblServicesOffered.setText(self._provider_config
- .get_services_string())
- self.ui.lblProviderPolicy.setText(self._provider_config
- .get_enrollment_policy())
+ sub_title = self.tr("Description of services offered by {0}")
+ sub_title = sub_title.format(self._provider_details.name)
+ self.page(pageId).setSubTitle(sub_title)
+
+ details = self._provider_details
+ name = "<b>{0}</b>".format(details.name)
+ domain = "https://{0}".format(details.domain)
+ description = "<i>{0}</i>".format(details.description)
+ self.ui.lblProviderName.setText(name)
+ self.ui.lblProviderURL.setText(domain)
+ self.ui.lblProviderDesc.setText(description)
+ self.ui.lblServicesOffered.setText(details.services_string)
+ self.ui.lblProviderPolicy.setText(details.enrollment_policy)
if pageId == self.REGISTER_USER_PAGE:
- self.page(pageId).setSubTitle(self.tr("Register a new user with "
- "%s") %
- (self._provider_config
- .get_name(),))
+ sub_title = self.tr("Register a new user with {0}")
+ sub_title = sub_title.format(self._provider_details.name)
+ self.page(pageId).setSubTitle(sub_title)
self.ui.chkRemember.setVisible(False)
if pageId == self.SERVICES_PAGE:
@@ -695,8 +706,6 @@ class Wizard(QtGui.QWizard):
if self.currentPage() == self.page(self.SELECT_PROVIDER_PAGE):
if self._use_existing_provider:
self._domain = self.ui.cbProviders.currentText()
- self._provider_config = ProviderConfig.get_provider_config(
- self._domain)
if self._show_register:
return self.REGISTER_USER_PAGE
else:
@@ -721,6 +730,7 @@ class Wizard(QtGui.QWizard):
sig.prov_name_resolution.connect(self._name_resolution)
sig.prov_https_connection.connect(self._https_connection)
sig.prov_download_provider_info.connect(self._download_provider_info)
+ sig.prov_get_details.connect(self._provider_get_details)
sig.prov_download_ca_cert.connect(self._download_ca_cert)
sig.prov_check_ca_fingerprint.connect(self._check_ca_fingerprint)