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.py185
-rw-r--r--src/leap/bitmask/gui/eip_status.py30
-rw-r--r--src/leap/bitmask/gui/loggerwindow.py3
-rw-r--r--src/leap/bitmask/gui/mail_status.py33
-rw-r--r--src/leap/bitmask/gui/mainwindow.py75
-rw-r--r--src/leap/bitmask/gui/preferenceswindow.py35
-rw-r--r--src/leap/bitmask/gui/statemachines.py2
-rw-r--r--src/leap/bitmask/gui/systray.py49
-rw-r--r--src/leap/bitmask/gui/ui/advanced_key_management.ui153
-rw-r--r--src/leap/bitmask/gui/ui/mainwindow.ui13
-rw-r--r--src/leap/bitmask/gui/wizard.py83
11 files changed, 564 insertions, 97 deletions
diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py
new file mode 100644
index 00000000..2c0fa034
--- /dev/null
+++ b/src/leap/bitmask/gui/advanced_key_management.py
@@ -0,0 +1,185 @@
+# -*- coding: utf-8 -*-
+# advanced_key_management.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+Advanced Key Management
+"""
+import logging
+
+from PySide import QtGui
+from zope.proxy import sameProxiedObjects
+
+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
+
+logger = logging.getLogger(__name__)
+
+
+class AdvancedKeyManagement(QtGui.QWidget):
+ """
+ Advanced Key Management
+ """
+ def __init__(self, user, keymanager, soledad):
+ """
+ :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
+ """
+ QtGui.QWidget.__init__(self)
+
+ self.ui = Ui_AdvancedKeyManagement()
+ self.ui.setupUi(self)
+
+ # if Soledad is not started yet
+ if sameProxiedObjects(soledad, None):
+ self.ui.container.setEnabled(False)
+ msg = self.tr("<span style='color:#0000FF;'>NOTE</span>: "
+ "To use this, you need to enable/start {0}.")
+ msg = msg.format(get_service_display_name(MX_SERVICE))
+ self.ui.lblStatus.setText(msg)
+ return
+ else:
+ msg = self.tr(
+ "<span style='color:#ff0000;'>WARNING</span>:<br>"
+ "This is an experimental feature, you can lose access to "
+ "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)
+
+ # 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)
+ self.ui.pbExportKeys.clicked.connect(self._export_keys)
+
+ def _import_keys(self):
+ """
+ Imports the user's key pair.
+ Those keys need to be ascii armored.
+ """
+ fileName, 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
+
+ question = self.tr("Are you sure that you want to replace "
+ "the current key pair whith the imported?")
+ res = QtGui.QMessageBox.question(
+ None, "Change key pair", question,
+ QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
+ QtGui.QMessageBox.No) # default No
+
+ if res == QtGui.QMessageBox.No:
+ return
+
+ 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)
+
+ logger.debug('Import ok')
+
+ QtGui.QMessageBox.information(
+ self, self.tr("Import Successful"),
+ self.tr("The key pair was imported successfully."))
+ else:
+ logger.debug('Import canceled by the user.')
+
+ def _export_keys(self):
+ """
+ Exports the user's key pair.
+ """
+ fileName, 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
+ else:
+ logger.debug('Export canceled by the user.')
diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py
index 324586c0..1899d6a4 100644
--- a/src/leap/bitmask/gui/eip_status.py
+++ b/src/leap/bitmask/gui/eip_status.py
@@ -26,6 +26,7 @@ from PySide import QtCore, QtGui
from leap.bitmask.services.eip.connection import EIPConnection
from leap.bitmask.services.eip.vpnprocess import VPNManager
+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
from leap.common.check import leap_assert_type
@@ -58,6 +59,7 @@ class EIPStatusWidget(QtGui.QWidget):
# set systray tooltip status
self._eip_status = ""
+ self._service_name = get_service_display_name(EIP_SERVICE)
self.ui.eip_bandwidth.hide()
@@ -181,21 +183,24 @@ class EIPStatusWidget(QtGui.QWidget):
def set_systray(self, systray):
"""
- Sets the systray object to use.
+ Sets the systray object to use and adds the service line for EIP.
:param systray: Systray object
:type systray: QtGui.QSystemTrayIcon
"""
leap_assert_type(systray, QtGui.QSystemTrayIcon)
self._systray = systray
- self._systray.setToolTip(self.tr("All services are OFF"))
+ eip_status = self.tr("{0}: OFF").format(self._service_name)
+ self._systray.set_service_tooltip(EIP_SERVICE, eip_status)
def _update_systray_tooltip(self):
"""
- Updates the system tray icon tooltip using the eip and mx status.
+ Updates the system tray tooltip using the eip status.
"""
- status = self.tr("Encrypted Internet: {0}").format(self._eip_status)
- self._systray.setToolTip(status)
+ if self._systray is not None:
+ eip_status = u"{0}: {1}".format(
+ self._service_name, self._eip_status)
+ self._systray.set_service_tooltip(EIP_SERVICE, eip_status)
def set_action_eip_startstop(self, action_eip_startstop):
"""
@@ -245,7 +250,7 @@ class EIPStatusWidget(QtGui.QWidget):
# probably the best thing would be to make a transitional
# transition there, but that's more involved.
self.eip_button.hide()
- msg = self.tr("You must login to use Encrypted Internet")
+ msg = self.tr("You must login to use {0}".format(self._service_name))
self.eip_label.setText(msg)
@QtCore.Slot()
@@ -393,10 +398,8 @@ class EIPStatusWidget(QtGui.QWidget):
# the UI won't update properly
QtCore.QTimer.singleShot(
0, self.eipconnection.qtsigs.do_disconnect_signal)
- QtCore.QTimer.singleShot(0, partial(self.set_eip_status,
- self.tr("Unable to start VPN, "
- "it's already "
- "running.")))
+ msg = self.tr("Unable to start VPN, it's already running.")
+ QtCore.QTimer.singleShot(0, partial(self.set_eip_status, msg))
else:
self.set_eip_status(status)
@@ -418,14 +421,15 @@ class EIPStatusWidget(QtGui.QWidget):
"""
selected_pixmap = self.ERROR_ICON
selected_pixmap_tray = self.ERROR_ICON_TRAY
- tray_message = self.tr("Encrypted Internet: OFF")
+ tray_message = self.tr("{0}: OFF".format(self._service_name))
if status in ("WAIT", "AUTH", "GET_CONFIG",
"RECONNECTING", "ASSIGN_IP"):
selected_pixmap = self.CONNECTING_ICON
selected_pixmap_tray = self.CONNECTING_ICON_TRAY
- tray_message = self.tr("Encrypted Internet: Starting...")
+ tray_message = self.tr("{0}: Starting...").format(
+ self._service_name)
elif status in ("CONNECTED"):
- tray_message = self.tr("Encrypted Internet: ON")
+ tray_message = self.tr("{0}: ON".format(self._service_name))
selected_pixmap = self.CONNECTED_ICON
selected_pixmap_tray = self.CONNECTED_ICON_TRAY
self._eip_status = 'ON'
diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py
index ad2ceded..6ef58558 100644
--- a/src/leap/bitmask/gui/loggerwindow.py
+++ b/src/leap/bitmask/gui/loggerwindow.py
@@ -19,6 +19,7 @@
History log window
"""
import logging
+import cgi
from PySide import QtGui
@@ -90,7 +91,7 @@ class LoggerWindow(QtGui.QDialog):
logging.CRITICAL: "background: red; color: white; font: bold;"
}
level = log[LeapLogHandler.RECORD_KEY].levelno
- message = log[LeapLogHandler.MESSAGE_KEY]
+ message = cgi.escape(log[LeapLogHandler.MESSAGE_KEY])
if self._logs_to_display[level]:
open_tag = "<tr style='" + html_style[level] + "'>"
diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py
index c1e82d4d..3c933c9a 100644
--- a/src/leap/bitmask/gui/mail_status.py
+++ b/src/leap/bitmask/gui/mail_status.py
@@ -22,6 +22,7 @@ import logging
from PySide import QtCore, QtGui
from leap.bitmask.platform_init import IS_LINUX
+from leap.bitmask.services import get_service_display_name, MX_SERVICE
from leap.common.check import leap_assert, leap_assert_type
from leap.common.events import register
from leap.common.events import events_pb2 as proto
@@ -58,6 +59,7 @@ class MailStatusWidget(QtGui.QWidget):
# set systray tooltip status
self._mx_status = ""
+ self._service_name = get_service_display_name(MX_SERVICE)
# Set the Mail status icons
self.CONNECTING_ICON = None
@@ -150,29 +152,23 @@ class MailStatusWidget(QtGui.QWidget):
def set_systray(self, systray):
"""
- Sets the systray object to use.
+ Sets the systray object to use and adds the service line for MX.
:param systray: Systray object
:type systray: QtGui.QSystemTrayIcon
"""
leap_assert_type(systray, QtGui.QSystemTrayIcon)
self._systray = systray
- self._systray.setToolTip(self.tr("All services are OFF"))
+ mx_status = self.tr("{0}: OFF").format(self._service_name)
+ self._systray.set_service_tooltip(MX_SERVICE, mx_status)
def _update_systray_tooltip(self):
"""
- Updates the system tray icon tooltip using the eip and mx status.
+ Updates the system tray tooltip using the mx status.
"""
- # TODO: Figure out how to handle this with the two status in different
- # classes
- # XXX right now we could connect the state transition signals of the
- # two connection machines (EIP/Mail) to a class that keeps track of the
- # state -- kali
- # status = self.tr("Encrypted Internet: {0}").format(self._eip_status)
- # status += '\n'
- # status += self.tr("Mail is {0}").format(self._mx_status)
- # self._systray.setToolTip(status)
- pass
+ if self._systray is not None:
+ mx_status = u"{0}: {1}".format(self._service_name, self._mx_status)
+ self._systray.set_service_tooltip(MX_SERVICE, mx_status)
def set_action_mail_status(self, action_mail_status):
"""
@@ -213,7 +209,8 @@ class MailStatusWidget(QtGui.QWidget):
icon = self.ERROR_ICON
if ready == 0:
self.ui.lblMailStatus.setText(
- self.tr("You must be logged in to use encrypted email."))
+ self.tr("You must be logged in to use {0}.").format(
+ self._service_name))
elif ready == 1:
icon = self.CONNECTING_ICON
self._mx_status = self.tr('Starting..')
@@ -296,7 +293,8 @@ class MailStatusWidget(QtGui.QWidget):
# elif req.event == proto.KEYMANAGER_KEY_NOT_FOUND:
# ext_status = self.tr("Key not found!")
elif req.event == proto.KEYMANAGER_STARTED_KEY_GENERATION:
- ext_status = self.tr("Generating new key, please wait...")
+ ext_status = self.tr(
+ "Generating new key, this may take a few minutes.")
elif req.event == proto.KEYMANAGER_FINISHED_KEY_GENERATION:
ext_status = self.tr("Finished generating key!")
elif req.event == proto.KEYMANAGER_DONE_UPLOADING_KEYS:
@@ -434,5 +432,6 @@ class MailStatusWidget(QtGui.QWidget):
Displays the correct UI for the disabled state.
"""
self._disabled = True
- self._set_mail_status(
- self.tr("You must be logged in to use encrypted email."), -1)
+ status = self.tr("You must be logged in to use {0}.").format(
+ self._service_name)
+ self._set_mail_status(status, -1)
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index 5eb9e6dc..b0f25af1 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -29,6 +29,7 @@ from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.crypto.srpauth import SRPAuth
from leap.bitmask.gui.loggerwindow import LoggerWindow
+from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement
from leap.bitmask.gui.login import LoginWidget
from leap.bitmask.gui.preferenceswindow import PreferencesWindow
from leap.bitmask.gui.eip_preferenceswindow import EIPPreferencesWindow
@@ -36,6 +37,7 @@ from leap.bitmask.gui import statemachines
from leap.bitmask.gui.eip_status import EIPStatusWidget
from leap.bitmask.gui.mail_status import MailStatusWidget
from leap.bitmask.gui.wizard import Wizard
+from leap.bitmask.gui.systray import SysTray
from leap.bitmask import provider
from leap.bitmask.platform_init import IS_WIN, IS_MAC
@@ -44,6 +46,7 @@ from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper
from leap.bitmask.services.mail import conductor as mail_conductor
+from leap.bitmask.services import EIP_SERVICE, MX_SERVICE
from leap.bitmask.services.eip import eipconfig
from leap.bitmask.services.eip import get_openvpn_management
from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper
@@ -86,9 +89,6 @@ class MainWindow(QtGui.QMainWindow):
LOGIN_INDEX = 0
EIP_STATUS_INDEX = 1
- OPENVPN_SERVICE = "openvpn"
- MX_SERVICE = "mx"
-
# Signals
eip_needs_login = QtCore.Signal([])
new_updates = QtCore.Signal(object)
@@ -253,6 +253,9 @@ class MainWindow(QtGui.QMainWindow):
self.ui.action_create_new_account.triggered.connect(
self._launch_wizard)
+ self.ui.action_advanced_key_management.triggered.connect(
+ self._show_AKM)
+
if IS_MAC:
self.ui.menuFile.menuAction().setText(self.tr("File"))
@@ -334,7 +337,7 @@ class MainWindow(QtGui.QMainWindow):
self.eip_machine = None
# start event machines
self.start_eip_machine()
- self._mail_conductor.start_mail_machine(parent=self)
+ self._mail_conductor.start_mail_machine()
if self._first_run():
self._wizard_firstrun = True
@@ -438,6 +441,20 @@ class MainWindow(QtGui.QMainWindow):
else:
self._logger_window.setVisible(not self._logger_window.isVisible())
+ def _show_AKM(self):
+ """
+ SLOT
+ TRIGGERS:
+ self.ui.action_advanced_key_management.triggered
+
+ Displays the Advanced Key Management dialog.
+ """
+ domain = self._login_widget.get_selected_provider()
+ logged_user = "{0}@{1}".format(self._logged_user, domain)
+ self._akm = AdvancedKeyManagement(
+ logged_user, self._keymanager, self._soledad)
+ self._akm.show()
+
def _show_preferences(self):
"""
SLOT
@@ -447,11 +464,10 @@ class MainWindow(QtGui.QMainWindow):
Displays the preferences window.
"""
preferences_window = PreferencesWindow(self, self._srp_auth,
- self._provider_config)
-
- self.soledad_ready.connect(
- lambda: preferences_window.set_soledad_ready(self._soledad))
+ self._provider_config,
+ self._soledad)
+ self.soledad_ready.connect(preferences_window.set_soledad_ready)
preferences_window.show()
def _show_eip_preferences(self):
@@ -597,8 +613,8 @@ class MainWindow(QtGui.QMainWindow):
for service in provider_config.get_services():
services.add(service)
- self.ui.eipWidget.setVisible(self.OPENVPN_SERVICE in services)
- self.ui.mailWidget.setVisible(self.MX_SERVICE in services)
+ self.ui.eipWidget.setVisible(EIP_SERVICE in services)
+ self.ui.mailWidget.setVisible(MX_SERVICE in services)
#
# systray
@@ -628,12 +644,13 @@ class MainWindow(QtGui.QMainWindow):
systrayMenu.addAction(self._action_mail_status)
systrayMenu.addSeparator()
systrayMenu.addAction(self.ui.action_quit)
- self._systray = QtGui.QSystemTrayIcon(self)
+ self._systray = SysTray(self)
self._systray.setContextMenu(systrayMenu)
self._systray.setIcon(self._eip_status.ERROR_ICON_TRAY)
self._systray.setVisible(True)
self._systray.activated.connect(self._tray_activated)
+ self._mail_status.set_systray(self._systray)
self._eip_status.set_systray(self._systray)
def _tray_activated(self, reason=None):
@@ -674,10 +691,9 @@ class MainWindow(QtGui.QMainWindow):
Toggles the window visibility
"""
visible = self.isVisible() and self.isActiveWindow()
- qApp = QtCore.QCoreApplication.instance()
if not visible:
- qApp.setQuitOnLastWindowClosed(True)
+ QtGui.QApplication.setQuitOnLastWindowClosed(True)
self.show()
self.activateWindow()
self.raise_()
@@ -685,7 +701,7 @@ class MainWindow(QtGui.QMainWindow):
# We set this in order to avoid dialogs shutting down the
# app on close, as they will be the only visible window.
# e.g.: PreferencesWindow, LoggerWindow
- qApp.setQuitOnLastWindowClosed(False)
+ QtGui.QApplication.setQuitOnLastWindowClosed(False)
self.hide()
# Wait a bit until the window visibility has changed so
@@ -905,7 +921,6 @@ class MainWindow(QtGui.QMainWindow):
Once the user is properly authenticated, try starting the EIP
service
"""
-
# In general we want to "filter" likely complicated error
# messages, but in this case, the messages make more sense as
# they come. Since they are "Unknown user" or "Unknown
@@ -914,19 +929,19 @@ class MainWindow(QtGui.QMainWindow):
if ok:
self._logged_user = self._login_widget.get_user()
- # We leave a bit of room for the user to see the
- # "Succeeded" message and then we switch to the EIP status
- # panel
- QtCore.QTimer.singleShot(1000, self._switch_to_status)
+ user = self._logged_user
+ domain = self._provider_config.get_domain()
+ userid = "%s@%s" % (user, domain)
+ self._mail_conductor.userid = userid
self._login_defer = None
+ self._start_eip_bootstrap()
else:
self._login_widget.set_enabled(True)
- def _switch_to_status(self):
- # TODO this method name is confusing as hell.
+ def _start_eip_bootstrap(self):
"""
Changes the stackedWidget index to the EIP status one and
- triggers the eip bootstrapping
+ triggers the eip bootstrapping.
"""
self._login_widget.logged_in()
@@ -938,7 +953,7 @@ class MainWindow(QtGui.QMainWindow):
# TODO separate UI from logic.
# TODO soledad should check if we want to run only over EIP.
if self._provider_config.provides_mx() and \
- self._enabled_services.count(self.MX_SERVICE) > 0:
+ self._enabled_services.count(MX_SERVICE) > 0:
self._mail_status.about_to_start()
self._soledad_bootstrapper.run_soledad_setup_checks(
@@ -949,6 +964,8 @@ class MainWindow(QtGui.QMainWindow):
else:
self._mail_status.set_disabled()
+ # XXX the config should be downloaded from the start_eip
+ # method.
self._download_eip_config()
###################################################################
@@ -1037,7 +1054,7 @@ class MainWindow(QtGui.QMainWindow):
# TODO for simmetry, this should be called start_smtp_service
# (and delegate all the checks to the conductor)
if self._provider_config.provides_mx() and \
- self._enabled_services.count(self.MX_SERVICE) > 0:
+ self._enabled_services.count(MX_SERVICE) > 0:
self._mail_conductor.smtp_bootstrapper.run_smtp_setup_checks(
self._provider_config,
self._mail_conductor.smtp_config,
@@ -1066,7 +1083,7 @@ class MainWindow(QtGui.QMainWindow):
self.soledad_ready
"""
if self._provider_config.provides_mx() and \
- self._enabled_services.count(self.MX_SERVICE) > 0:
+ self._enabled_services.count(MX_SERVICE) > 0:
self._mail_conductor.start_imap_service()
def _on_mail_client_logged_in(self, req):
@@ -1416,7 +1433,7 @@ class MainWindow(QtGui.QMainWindow):
provider_config = self._get_best_provider_config()
if provider_config.provides_eip() and \
- self._enabled_services.count(self.OPENVPN_SERVICE) > 0 and \
+ self._enabled_services.count(EIP_SERVICE) > 0 and \
not self._already_started_eip:
# XXX this should be handled by the state machine.
@@ -1427,7 +1444,7 @@ class MainWindow(QtGui.QMainWindow):
download_if_needed=True)
self._already_started_eip = True
elif not self._already_started_eip:
- if self._enabled_services.count(self.OPENVPN_SERVICE) > 0:
+ if self._enabled_services.count(EIP_SERVICE) > 0:
self._eip_status.set_eip_status(
self.tr("Not supported"),
error=True)
@@ -1522,6 +1539,7 @@ class MainWindow(QtGui.QMainWindow):
"""
self._soledad_bootstrapper.cancel_bootstrap()
+ setProxiedObject(self._soledad, None)
# XXX: If other defers are doing authenticated stuff, this
# might conflict with those. CHECK!
@@ -1657,8 +1675,7 @@ class MainWindow(QtGui.QMainWindow):
# UI stuff.
# Set this in case that the app is hidden
- qApp = QtCore.QCoreApplication.instance()
- qApp.setQuitOnLastWindowClosed(True)
+ QtGui.QApplication.setQuitOnLastWindowClosed(True)
self._cleanup_and_quit()
diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py
index acb39b07..8e9ef95a 100644
--- a/src/leap/bitmask/gui/preferenceswindow.py
+++ b/src/leap/bitmask/gui/preferenceswindow.py
@@ -22,7 +22,9 @@ import os
import logging
from functools import partial
+
from PySide import QtCore, QtGui
+from zope.proxy import sameProxiedObjects
from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.gui.ui_preferences import Ui_Preferences
@@ -31,7 +33,7 @@ from leap.bitmask.crypto.srpauth import SRPAuthBadUserOrPassword
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.services import get_service_display_name
+from leap.bitmask.services import get_service_display_name, MX_SERVICE
logger = logging.getLogger(__name__)
@@ -40,7 +42,7 @@ class PreferencesWindow(QtGui.QDialog):
"""
Window that displays the preferences.
"""
- def __init__(self, parent, srp_auth, provider_config):
+ def __init__(self, parent, srp_auth, provider_config, soledad):
"""
:param parent: parent object of the PreferencesWindow.
:parent type: QWidget
@@ -48,13 +50,15 @@ class PreferencesWindow(QtGui.QDialog):
:type srp_auth: SRPAuth
:param provider_config: ProviderConfig object.
:type provider_config: ProviderConfig
+ :param soledad: Soledad instance
+ :type soledad: Soledad
"""
QtGui.QDialog.__init__(self, parent)
self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic")
self._srp_auth = srp_auth
self._settings = LeapSettings()
- self._soledad = None
+ self._soledad = soledad
# Load UI
self.ui = Ui_Preferences()
@@ -81,20 +85,25 @@ class PreferencesWindow(QtGui.QDialog):
# check if provider has 'mx' ...
domain = provider_config.get_domain()
self._select_provider_by_name(domain)
+
if provider_config.provides_mx():
enabled_services = self._settings.get_enabled_services(domain)
- mx_name = get_service_display_name('mx')
+ mx_name = get_service_display_name(MX_SERVICE)
# ... and if the user have it enabled
- if 'mx' not in enabled_services:
+ 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)
else:
- 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)
+ 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)
+ else:
+ # Soledad is bootstrapped
+ pw_enabled = True
else:
pw_enabled = True
else:
@@ -104,18 +113,14 @@ class PreferencesWindow(QtGui.QDialog):
self.ui.gbPasswordChange.setEnabled(pw_enabled)
- def set_soledad_ready(self, soledad):
+ def set_soledad_ready(self):
"""
SLOT
TRIGGERS:
parent.soledad_ready
- It sets the soledad object as ready to use.
-
- :param soledad: Soledad object configured in the main app.
- :type soledad: Soledad
+ It notifies when the soledad object as ready to use.
"""
- self._soledad = soledad
self.ui.lblPasswordChangeStatus.setVisible(False)
self.ui.gbPasswordChange.setEnabled(True)
diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py
index 386cb75f..93731ce0 100644
--- a/src/leap/bitmask/gui/statemachines.py
+++ b/src/leap/bitmask/gui/statemachines.py
@@ -357,7 +357,7 @@ class ConnectionMachineBuilder(object):
parent = kwargs.get('parent', None)
# 1. create machine
- machine = CompositeMachine(parent=parent)
+ machine = CompositeMachine()
# 2. create states
off = States.Off(conn.qtsigs.disconnected_signal,
diff --git a/src/leap/bitmask/gui/systray.py b/src/leap/bitmask/gui/systray.py
new file mode 100644
index 00000000..d30b5f32
--- /dev/null
+++ b/src/leap/bitmask/gui/systray.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# systray.py
+# Copyright (C) 2013 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+Custom system tray manager.
+"""
+
+from PySide import QtGui
+
+from leap.common.check import leap_assert_type
+
+
+class SysTray(QtGui.QSystemTrayIcon):
+ """
+ Custom system tray that allows us to use a more 'intelligent' tooltip.
+ """
+
+ def __init__(self, parent=None):
+ QtGui.QSystemTrayIcon.__init__(self, parent)
+ self._services = {}
+
+ def set_service_tooltip(self, service, tooltip):
+ """
+ Sets the service tooltip.
+
+ :param service: service name identifier
+ :type service: unicode
+ :param tooltip: tooltip to display for that service
+ :type tooltip: unicode
+ """
+ leap_assert_type(service, unicode)
+ leap_assert_type(tooltip, unicode)
+
+ self._services[service] = tooltip
+ tooltip = "\n".join(self._services.values())
+ self.setToolTip(tooltip)
diff --git a/src/leap/bitmask/gui/ui/advanced_key_management.ui b/src/leap/bitmask/gui/ui/advanced_key_management.ui
new file mode 100644
index 00000000..d61aa87e
--- /dev/null
+++ b/src/leap/bitmask/gui/ui/advanced_key_management.ui
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AdvancedKeyManagement</class>
+ <widget class="QWidget" name="AdvancedKeyManagement">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>431</width>
+ <height>188</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Advanced Key Management</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../../../../../data/resources/mainwindow.qrc">
+ <normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <widget class="QWidget" name="container" native="true">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>User:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="leUser">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>user_name@provider</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Key ID:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="leKeyID">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>key ID</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Key fingerprint:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="leFingerprint">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>fingerprint</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="4" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="1">
+ <widget class="QPushButton" name="pbExportKeys">
+ <property name="text">
+ <string>Export current key pair</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" rowspan="2">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="1">
+ <widget class="QPushButton" name="pbImportKeys">
+ <property name="text">
+ <string>Import custom key pair</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="lblStatus">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="../../../../../data/resources/mainwindow.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui
index badd291d..3b83788e 100644
--- a/src/leap/bitmask/gui/ui/mainwindow.ui
+++ b/src/leap/bitmask/gui/ui/mainwindow.ui
@@ -75,7 +75,7 @@
<x>0</x>
<y>0</y>
<width>524</width>
- <height>636</height>
+ <height>651</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -380,7 +380,7 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb
<x>0</x>
<y>0</y>
<width>524</width>
- <height>22</height>
+ <height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@@ -388,6 +388,7 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb
<string>&amp;Bitmask</string>
</property>
<addaction name="action_create_new_account"/>
+ <addaction name="action_advanced_key_management"/>
<addaction name="separator"/>
<addaction name="action_quit"/>
</widget>
@@ -439,6 +440,14 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb
<string>Create a new account...</string>
</property>
</action>
+ <action name="action_advanced_key_management">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Advanced Key Management</string>
+ </property>
+ </action>
</widget>
<resources>
<include location="../../../../../data/resources/mainwindow.qrc"/>
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index 6ba65410..5f5224ae 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -111,8 +111,9 @@ class Wizard(QtGui.QWizard):
self.currentIdChanged.connect(self._current_id_changed)
- self.ui.lnProvider.textChanged.connect(
- self._enable_check)
+ self.ui.lnProvider.textChanged.connect(self._enable_check)
+ self.ui.rbNewProvider.toggled.connect(
+ lambda x: self._enable_check())
self.ui.lblUser.returnPressed.connect(
self._focus_password)
@@ -146,6 +147,26 @@ class Wizard(QtGui.QWizard):
self._load_configured_providers()
+ self._provider_checks_ok = False
+ self._provider_setup_ok = False
+ self.finished.connect(self._wizard_finished)
+
+ @QtCore.Slot()
+ def _wizard_finished(self):
+ """
+ SLOT
+ TRIGGER:
+ self.finished
+
+ This method is called when the wizard is accepted or rejected.
+ Here we do the cleanup needed to use the wizard again reusing the
+ instance.
+ """
+ self._provider_checks_ok = False
+ self._provider_setup_ok = False
+ self.ui.lnProvider.setText('')
+ self.ui.grpCheckProvider.setVisible(False)
+
def _load_configured_providers(self):
"""
Loads the configured providers into the wizard providers combo box.
@@ -193,9 +214,22 @@ class Wizard(QtGui.QWizard):
def get_services(self):
return self._selected_services
- def _enable_check(self, text):
- self.ui.btnCheck.setEnabled(len(self.ui.lnProvider.text()) != 0)
- self._reset_provider_check()
+ @QtCore.Slot()
+ def _enable_check(self, reset=True):
+ """
+ SLOT
+ TRIGGER:
+ self.ui.lnProvider.textChanged
+
+ Enables/disables the 'check' button in the SELECT_PROVIDER_PAGE
+ depending on the lnProvider content.
+ """
+ enabled = len(self.ui.lnProvider.text()) != 0
+ enabled = enabled and self.ui.rbNewProvider.isChecked()
+ self.ui.btnCheck.setEnabled(enabled)
+
+ if reset:
+ self._reset_provider_check()
def _focus_password(self):
"""
@@ -226,9 +260,7 @@ class Wizard(QtGui.QWizard):
self._registration_finished)
threads.deferToThread(
- partial(register.register_user,
- username.encode("utf8"),
- password.encode("utf8")))
+ partial(register.register_user, username, password))
self._username = username
self._password = password
@@ -282,7 +314,8 @@ class Wizard(QtGui.QWizard):
old_username = self._username
self._username = None
self._password = None
- error_msg = self.tr("Unknown error")
+ error_msg = self.tr("Something has gone wrong. "
+ "Please try again.")
try:
content, _ = get_content(req)
json_content = json.loads(content)
@@ -339,6 +372,12 @@ class Wizard(QtGui.QWizard):
if len(self.ui.lnProvider.text()) == 0:
return
+ self._provider_checks_ok = False
+
+ # just in case that the user has already setup a provider and
+ # go 'back' to check a provider
+ self._provider_setup_ok = False
+
self.ui.grpCheckProvider.setVisible(True)
self.ui.btnCheck.setEnabled(False)
self.ui.lnProvider.setEnabled(False)
@@ -448,6 +487,7 @@ class Wizard(QtGui.QWizard):
"provider.json")):
self._complete_task(data, self.ui.lblProviderInfo,
True, self.SELECT_PROVIDER_PAGE)
+ self._provider_checks_ok = True
else:
new_data = {
self._provider_bootstrapper.PASSED_KEY: False,
@@ -499,6 +539,7 @@ class Wizard(QtGui.QWizard):
"""
self._complete_task(data, self.ui.lblCheckApiCert,
True, self.SETUP_PROVIDER_PAGE)
+ self._provider_setup_ok = True
def _service_selection_changed(self, service, state):
"""
@@ -556,18 +597,22 @@ class Wizard(QtGui.QWizard):
Prepares the pages when they appear
"""
if pageId == self.SELECT_PROVIDER_PAGE:
- self._reset_provider_check()
- self._enable_check("")
+ skip = self.ui.rbExistingProvider.isChecked()
+ if not self._provider_checks_ok:
+ self._enable_check()
+ self._skip_provider_checks(skip)
+ else:
+ self._enable_check(reset=False)
if pageId == self.SETUP_PROVIDER_PAGE:
- self._reset_provider_setup()
- self.page(pageId).setSubTitle(self.tr("Gathering configuration "
- "options for %s") %
- (self._provider_config
- .get_name(),))
- self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON)
- self._provider_setup_defer = self._provider_bootstrapper.\
- run_provider_setup_checks(self._provider_config)
+ if 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())
+ self.page(pageId).setSubTitle(sub_title)
+ self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON)
+ self._provider_setup_defer = self._provider_bootstrapper.\
+ run_provider_setup_checks(self._provider_config)
if pageId == self.PRESENT_PROVIDER_PAGE:
self.page(pageId).setSubTitle(self.tr("Description of services "