summaryrefslogtreecommitdiff
path: root/src/leap/bitmask
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2013-11-01 10:50:38 -0300
committerTomás Touceda <chiiph@leap.se>2013-11-01 10:50:38 -0300
commit23e9034664edf6b2a01a677c4fa4e6efd880e364 (patch)
treefc90f154cf81cbaf48dd2d4a738a379591c4e7c7 /src/leap/bitmask
parenta06b5719f028e619a4b3800fb346ed3c984e4d25 (diff)
parent80fde752f0bf7946869361f6a77d1952f9339636 (diff)
Merge branch 'release-0.3.6'
Diffstat (limited to 'src/leap/bitmask')
-rw-r--r--src/leap/bitmask/app.py18
-rw-r--r--src/leap/bitmask/config/providerconfig.py5
-rw-r--r--src/leap/bitmask/crypto/srpauth.py29
-rw-r--r--src/leap/bitmask/gui/eip_preferenceswindow.py36
-rw-r--r--src/leap/bitmask/gui/eip_status.py4
-rw-r--r--src/leap/bitmask/gui/login.py47
-rw-r--r--src/leap/bitmask/gui/mail_status.py103
-rw-r--r--src/leap/bitmask/gui/mainwindow.py264
-rw-r--r--src/leap/bitmask/gui/preferenceswindow.py50
-rw-r--r--src/leap/bitmask/gui/statemachines.py414
-rw-r--r--src/leap/bitmask/gui/ui/eip_status.ui6
-rw-r--r--src/leap/bitmask/gui/ui/eippreferences.ui99
-rw-r--r--src/leap/bitmask/gui/ui/mainwindow.ui172
-rw-r--r--src/leap/bitmask/provider/providerbootstrapper.py15
-rw-r--r--src/leap/bitmask/services/__init__.py7
-rw-r--r--src/leap/bitmask/services/connections.py10
-rw-r--r--src/leap/bitmask/services/eip/connection.py2
-rw-r--r--src/leap/bitmask/services/eip/darwinvpnlauncher.py5
-rw-r--r--src/leap/bitmask/services/eip/linuxvpnlauncher.py5
-rw-r--r--src/leap/bitmask/services/eip/vpnlauncher.py3
-rw-r--r--src/leap/bitmask/services/eip/vpnprocess.py18
-rw-r--r--src/leap/bitmask/services/mail/conductor.py384
-rw-r--r--src/leap/bitmask/services/mail/connection.py103
-rw-r--r--src/leap/bitmask/services/mail/imap.py7
-rw-r--r--src/leap/bitmask/services/soledad/soledadbootstrapper.py28
-rw-r--r--src/leap/bitmask/util/compat.py31
26 files changed, 1372 insertions, 493 deletions
diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py
index 40a77075..3bb9c8c3 100644
--- a/src/leap/bitmask/app.py
+++ b/src/leap/bitmask/app.py
@@ -39,7 +39,6 @@
# M:::::::::::~NMMM7???7MMMM:::::::::::::::::::::::NMMMI??I7MMMM:::::::::::::M
# M::::::::::::::7MMMMMMM+:::::::::::::::::::::::::::?MMMMMMMZ:::::::::::::::M
# (thanks to: http://www.glassgiant.com/ascii/)
-
import logging
import signal
import sys
@@ -105,9 +104,16 @@ def add_logger_handlers(debug=False, logfile=None):
formatter = logging.Formatter(log_format)
# Console handler
- console = logging.StreamHandler()
- console.setLevel(level)
- console.setFormatter(formatter)
+ try:
+ import coloredlogs
+ console = coloredlogs.ColoredStreamHandler(level=level)
+ except ImportError:
+ console = logging.StreamHandler()
+ console.setLevel(level)
+ console.setFormatter(formatter)
+ using_coloredlog = False
+ else:
+ using_coloredlog = True
silencer = log_silencer.SelectiveSilencerFilter()
console.addFilter(silencer)
@@ -131,6 +137,9 @@ def add_logger_handlers(debug=False, logfile=None):
logger.addHandler(fileh)
logger.debug('File handler plugged!')
+ if not using_coloredlog:
+ replace_stdout_stderr_with_logging(logger)
+
return logger
@@ -185,7 +194,6 @@ def main():
BaseConfig.standalone = standalone
logger = add_logger_handlers(debug, logfile)
- replace_stdout_stderr_with_logging(logger)
# And then we import all the other stuff
from leap.bitmask.gui import locale_rc
diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py
index 44698d83..e80b2337 100644
--- a/src/leap/bitmask/config/providerconfig.py
+++ b/src/leap/bitmask/config/providerconfig.py
@@ -169,11 +169,12 @@ class ProviderConfig(BaseConfig):
checking if the cert exists because we
are about to write it.
:type about_to_download: bool
+
+ :rtype: unicode
"""
cert_path = os.path.join(get_path_prefix(), "leap", "providers",
- self.get_domain(),
- "keys", "ca", "cacert.pem")
+ self.get_domain(), "keys", "ca", "cacert.pem")
if not about_to_download:
cert_exists = os.path.exists(cert_path)
diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py
index 47ed21b0..ab98850d 100644
--- a/src/leap/bitmask/crypto/srpauth.py
+++ b/src/leap/bitmask/crypto/srpauth.py
@@ -17,6 +17,7 @@
import binascii
import logging
+import sys
import requests
import srp
@@ -31,6 +32,7 @@ from PySide import QtCore
from twisted.internet import threads
from leap.bitmask.util import request_helpers as reqhelper
+from leap.bitmask.util.compat import requests_has_max_retries
from leap.bitmask.util.constants import REQUEST_TIMEOUT
from leap.common.check import leap_assert
from leap.common.events import signal as events_signal
@@ -184,7 +186,11 @@ class SRPAuth(QtCore.QObject):
# NOTE: This is a workaround for the moment, the server
# side seems to return correctly every time, but it fails
# on the client end.
- self._session.mount('https://', HTTPAdapter(max_retries=30))
+ if requests_has_max_retries:
+ adapter = HTTPAdapter(max_retries=30)
+ else:
+ adapter = HTTPAdapter()
+ self._session.mount('https://', adapter)
def _safe_unhexlify(self, val):
"""
@@ -211,10 +217,9 @@ class SRPAuth(QtCore.QObject):
"""
logger.debug("Authentication preprocessing...")
- self._srp_user = self._srp.User(username,
- password,
- self._hashfun,
- self._ng)
+ self._srp_user = self._srp.User(username.encode('utf-8'),
+ password.encode('utf-8'),
+ self._hashfun, self._ng)
_, A = self._srp_user.start_authentication()
self._srp_a = A
@@ -249,10 +254,13 @@ class SRPAuth(QtCore.QObject):
(self._provider_config.get_api_uri(),
self._provider_config.get_api_version(),
"sessions")
+
+ ca_cert_path = self._provider_config.get_ca_cert_path()
+ ca_cert_path = ca_cert_path.encode(sys.getfilesystemencoding())
+
init_session = self._session.post(sessions_url,
data=auth_data,
- verify=self._provider_config.
- get_ca_cert_path(),
+ verify=ca_cert_path,
timeout=REQUEST_TIMEOUT)
# Clean up A value, we don't need it anymore
self._srp_a = None
@@ -478,7 +486,8 @@ class SRPAuth(QtCore.QObject):
self.get_uid())
salt, verifier = self._srp.create_salted_verification_key(
- self._username, new_password, self._hashfun, self._ng)
+ self._username.encode('utf-8'), new_password.encode('utf-8'),
+ self._hashfun, self._ng)
cookies = {self.SESSION_ID_KEY: self.get_session_id()}
headers = {
@@ -509,9 +518,9 @@ class SRPAuth(QtCore.QObject):
Might raise SRPAuthenticationError
:param username: username for this session
- :type username: str
+ :type username: unicode
:param password: password for this user
- :type password: str
+ :type password: unicode
:returns: A defer on a different thread
:rtype: twisted.internet.defer.Deferred
diff --git a/src/leap/bitmask/gui/eip_preferenceswindow.py b/src/leap/bitmask/gui/eip_preferenceswindow.py
index 9f8c4ff4..e0c5d51f 100644
--- a/src/leap/bitmask/gui/eip_preferenceswindow.py
+++ b/src/leap/bitmask/gui/eip_preferenceswindow.py
@@ -50,7 +50,6 @@ class EIPPreferencesWindow(QtGui.QDialog):
self.ui = Ui_EIPPreferences()
self.ui.setupUi(self)
self.ui.lblProvidersGatewayStatus.setVisible(False)
- self.ui.lblAutoStartEIPStatus.setVisible(False)
# Connections
self.ui.cbProvidersGateway.currentIndexChanged[unicode].connect(
@@ -59,40 +58,8 @@ class EIPPreferencesWindow(QtGui.QDialog):
self.ui.cbGateways.currentIndexChanged[unicode].connect(
lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False))
- self.ui.cbProvidersEIP.currentIndexChanged[unicode].connect(
- lambda x: self.ui.lblAutoStartEIPStatus.setVisible(False))
-
- self.ui.cbAutoStartEIP.toggled.connect(
- lambda x: self.ui.lblAutoStartEIPStatus.setVisible(False))
-
- self.ui.pbSaveAutoStartEIP.clicked.connect(self._save_auto_start_eip)
-
self._add_configured_providers()
- # Load auto start EIP settings
- self.ui.cbAutoStartEIP.setChecked(self._settings.get_autostart_eip())
- default_provider = self._settings.get_defaultprovider()
- idx = self.ui.cbProvidersEIP.findText(default_provider)
- self.ui.cbProvidersEIP.setCurrentIndex(idx)
-
- def _save_auto_start_eip(self):
- """
- SLOT
- TRIGGER:
- self.ui.cbAutoStartEIP.toggled
-
- Saves the automatic start of EIP user preference.
- """
- default_provider = self.ui.cbProvidersEIP.currentText()
- enabled = self.ui.cbAutoStartEIP.isChecked()
-
- self._settings.set_autostart_eip(enabled)
- self._settings.set_defaultprovider(default_provider)
-
- self.ui.lblAutoStartEIPStatus.show()
- logger.debug('Auto start EIP saved: {0} {1}.'.format(
- default_provider, enabled))
-
def _set_providers_gateway_status(self, status, success=False,
error=False):
"""
@@ -120,16 +87,13 @@ class EIPPreferencesWindow(QtGui.QDialog):
Add the client's configured providers to the providers combo boxes.
"""
self.ui.cbProvidersGateway.clear()
- self.ui.cbProvidersEIP.clear()
providers = self._settings.get_configured_providers()
if not providers:
- self.ui.gbAutomaticEIP.setEnabled(False)
self.ui.gbGatewaySelector.setEnabled(False)
return
for provider in providers:
self.ui.cbProvidersGateway.addItem(provider)
- self.ui.cbProvidersEIP.addItem(provider)
def _save_selected_gateway(self, provider):
"""
diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py
index 77685cd3..324586c0 100644
--- a/src/leap/bitmask/gui/eip_status.py
+++ b/src/leap/bitmask/gui/eip_status.py
@@ -269,6 +269,10 @@ class EIPStatusWidget(QtGui.QWidget):
:type error: bool
"""
leap_assert_type(error, bool)
+ if error:
+ logger.error(status)
+ else:
+ logger.debug(status)
self._eip_status = status
if error:
status = "<font color='red'>%s</font>" % (status,)
diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py
index ac34fe23..b21057f0 100644
--- a/src/leap/bitmask/gui/login.py
+++ b/src/leap/bitmask/gui/login.py
@@ -49,6 +49,9 @@ class LoginWidget(QtGui.QWidget):
BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$"
+ # Keyring
+ KEYRING_KEY = "bitmask"
+
def __init__(self, settings, parent=None):
"""
Constructs the LoginWidget.
@@ -168,7 +171,7 @@ class LoginWidget(QtGui.QWidget):
"""
Returns the user that appears in the widget.
- :rtype: str
+ :rtype: unicode
"""
return self.ui.lnUser.text()
@@ -177,7 +180,7 @@ class LoginWidget(QtGui.QWidget):
Sets the password for the widget
:param password: password to set
- :type password: str
+ :type password: unicode
"""
self.ui.lnPassword.setText(password)
@@ -185,7 +188,7 @@ class LoginWidget(QtGui.QWidget):
"""
Returns the password that appears in the widget
- :rtype: str
+ :rtype: unicode
"""
return self.ui.lnPassword.text()
@@ -366,3 +369,41 @@ class LoginWidget(QtGui.QWidget):
self.ui.btnLogout.setText(self.tr("Logout"))
self.ui.btnLogout.setEnabled(True)
self.ui.clblErrorMsg.hide()
+
+ def load_user_from_keyring(self, saved_user):
+ """
+ Tries to load a user from the keyring, returns True if it was
+ loaded successfully, False otherwise.
+
+ :param saved_user: String containing the saved username as
+ user@domain
+ :type saved_user: unicode
+
+ :rtype: bool
+ """
+ leap_assert_type(saved_user, unicode)
+
+ try:
+ username, domain = saved_user.split('@')
+ except ValueError as e:
+ # if the saved_user does not contain an '@'
+ logger.error('Username@provider malformed. %r' % (e, ))
+ return False
+
+ self.set_user(username)
+
+ self.set_remember(True)
+
+ saved_password = None
+ try:
+ saved_password = keyring.get_password(self.KEYRING_KEY,
+ saved_user
+ .encode("utf8"))
+ except ValueError as e:
+ logger.debug("Incorrect Password. %r." % (e,))
+
+ if saved_password is not None:
+ self.set_password(saved_password)
+ return True
+
+ return False
diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py
index 83533666..c1e82d4d 100644
--- a/src/leap/bitmask/gui/mail_status.py
+++ b/src/leap/bitmask/gui/mail_status.py
@@ -50,6 +50,8 @@ class MailStatusWidget(QtGui.QWidget):
QtGui.QWidget.__init__(self, parent)
self._systray = None
+ self._disabled = True
+ self._started = False
self.ui = Ui_MailStatusWidget()
self.ui.setupUi(self)
@@ -98,29 +100,16 @@ class MailStatusWidget(QtGui.QWidget):
callback=self._mail_handle_soledad_events,
reqcbk=lambda req, resp: None)
- register(signal=proto.SMTP_SERVICE_STARTED,
- callback=self._mail_handle_smtp_events,
- reqcbk=lambda req, resp: None)
-
- register(signal=proto.SMTP_SERVICE_FAILED_TO_START,
- callback=self._mail_handle_smtp_events,
- reqcbk=lambda req, resp: None)
-
- register(signal=proto.IMAP_SERVICE_STARTED,
+ register(signal=proto.IMAP_UNREAD_MAIL,
callback=self._mail_handle_imap_events,
reqcbk=lambda req, resp: None)
-
- register(signal=proto.IMAP_SERVICE_FAILED_TO_START,
+ register(signal=proto.IMAP_SERVICE_STARTED,
callback=self._mail_handle_imap_events,
reqcbk=lambda req, resp: None)
-
- register(signal=proto.IMAP_UNREAD_MAIL,
+ register(signal=proto.SMTP_SERVICE_STARTED,
callback=self._mail_handle_imap_events,
reqcbk=lambda req, resp: None)
- self._smtp_started = False
- self._imap_started = False
-
self._soledad_event.connect(
self._mail_handle_soledad_events_slot)
self._imap_event.connect(
@@ -176,6 +165,9 @@ class MailStatusWidget(QtGui.QWidget):
"""
# 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)
@@ -292,11 +284,9 @@ class MailStatusWidget(QtGui.QWidget):
"""
# We want to ignore this kind of events once everything has
# started
- if self._smtp_started and self._imap_started:
+ if self._started:
return
- self._set_mail_status(self.tr("Starting..."), ready=1)
-
ext_status = ""
if req.event == proto.KEYMANAGER_LOOKING_FOR_KEY:
@@ -340,14 +330,9 @@ class MailStatusWidget(QtGui.QWidget):
ext_status = ""
if req.event == proto.SMTP_SERVICE_STARTED:
- ext_status = self.tr("SMTP has started...")
self._smtp_started = True
- if self._smtp_started and self._imap_started:
- self._set_mail_status(self.tr("ON"), ready=2)
- ext_status = ""
elif req.event == proto.SMTP_SERVICE_FAILED_TO_START:
ext_status = self.tr("SMTP failed to start, check the logs.")
- self._set_mail_status(self.tr("Failed"))
else:
leap_assert(False,
"Don't know how to handle this state: %s"
@@ -355,6 +340,8 @@ class MailStatusWidget(QtGui.QWidget):
self._set_mail_status(ext_status, ready=2)
+ # ----- XXX deprecate (move to mail conductor)
+
def _mail_handle_imap_events(self, req):
"""
Callback for the IMAP events
@@ -376,27 +363,15 @@ class MailStatusWidget(QtGui.QWidget):
"""
ext_status = None
- if req.event == proto.IMAP_SERVICE_STARTED:
- ext_status = self.tr("IMAP has started...")
- self._imap_started = True
- if self._smtp_started and self._imap_started:
- self._set_mail_status(self.tr("ON"), ready=2)
- ext_status = ""
- elif req.event == proto.IMAP_SERVICE_FAILED_TO_START:
- ext_status = self.tr("IMAP failed to start, check the logs.")
- self._set_mail_status(self.tr("Failed"))
- elif req.event == proto.IMAP_UNREAD_MAIL:
- if self._smtp_started and self._imap_started:
+ if req.event == proto.IMAP_UNREAD_MAIL:
+ if self._started:
if req.content != "0":
self._set_mail_status(self.tr("%s Unread Emails") %
(req.content,), ready=2)
else:
self._set_mail_status("", ready=2)
- else:
- leap_assert(False, # XXX ???
- "Don't know how to handle this state: %s"
- % (req.event))
-
+ elif req.event == proto.IMAP_SERVICE_STARTED:
+ self._imap_started = True
if ext_status is not None:
self._set_mail_status(ext_status, ready=1)
@@ -414,8 +389,50 @@ class MailStatusWidget(QtGui.QWidget):
"""
self._set_mail_status(self.tr("Disabled"), -1)
- def stopped_mail(self):
+ # statuses
+
+ # XXX make the signal emit the label and state.
+
+ @QtCore.Slot()
+ def mail_state_disconnected(self):
+ """
+ Displays the correct UI for the disconnected state.
+ """
+ # XXX this should handle the disabled state better.
+ self._started = False
+ if self._disabled:
+ self.mail_state_disabled()
+ else:
+ self._set_mail_status(self.tr("OFF"), -1)
+
+ @QtCore.Slot()
+ def mail_state_connecting(self):
+ """
+ Displays the correct UI for the connecting state.
+ """
+ self._disabled = False
+ self._started = True
+ self._set_mail_status(self.tr("Starting..."), 1)
+
+ @QtCore.Slot()
+ def mail_state_disconnecting(self):
+ """
+ Displays the correct UI for the connecting state.
+ """
+ self._set_mail_status(self.tr("Disconnecting..."), 1)
+
+ @QtCore.Slot()
+ def mail_state_connected(self):
+ """
+ Displays the correct UI for the connected state.
+ """
+ self._set_mail_status(self.tr("ON"), 2)
+
+ @QtCore.Slot()
+ def mail_state_disabled(self):
"""
- Displayes the correct UI for the stopped state.
+ Displays the correct UI for the disabled state.
"""
- self._set_mail_status(self.tr("OFF"))
+ self._disabled = True
+ self._set_mail_status(
+ self.tr("You must be logged in to use encrypted email."), -1)
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index f5631c69..5eb9e6dc 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -20,10 +20,9 @@ Main window for Bitmask.
import logging
import os
-import keyring
-
from PySide import QtCore, QtGui
from twisted.internet import threads
+from zope.proxy import ProxyBase, setProxiedObject, sameProxiedObjects
from leap.bitmask import __version__ as VERSION
from leap.bitmask.config.leapsettings import LeapSettings
@@ -39,18 +38,15 @@ from leap.bitmask.gui.mail_status import MailStatusWidget
from leap.bitmask.gui.wizard import Wizard
from leap.bitmask import provider
-from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper
-from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper
-from leap.bitmask.services.eip import eipconfig
-# XXX: Soledad might not work out of the box in Windows, issue #2932
-from leap.bitmask.services.soledad.soledadbootstrapper import \
- SoledadBootstrapper
-from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper
-from leap.bitmask.services.mail import imap
from leap.bitmask.platform_init import IS_WIN, IS_MAC
from leap.bitmask.platform_init.initializers import init_platform
+from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper
+
+from leap.bitmask.services.mail import conductor as mail_conductor
+from leap.bitmask.services.eip import eipconfig
from leap.bitmask.services.eip import get_openvpn_management
+from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper
from leap.bitmask.services.eip.connection import EIPConnection
from leap.bitmask.services.eip.vpnprocess import VPN
from leap.bitmask.services.eip.vpnprocess import OpenVPNAlreadyRunning
@@ -62,12 +58,12 @@ from leap.bitmask.services.eip.linuxvpnlauncher import EIPNoPkexecAvailable
from leap.bitmask.services.eip.linuxvpnlauncher import \
EIPNoPolkitAuthAgentAvailable
from leap.bitmask.services.eip.darwinvpnlauncher import EIPNoTunKextLoaded
+from leap.bitmask.services.soledad.soledadbootstrapper import \
+ SoledadBootstrapper
from leap.bitmask.util.keyring_helpers import has_keyring
from leap.bitmask.util.leap_log_handler import LeapLogHandler
-from leap.bitmask.services.mail.smtpconfig import SMTPConfig
-
if IS_WIN:
from leap.bitmask.platform_init.locks import WindowsLock
from leap.bitmask.platform_init.locks import raise_window_ack
@@ -90,13 +86,6 @@ class MainWindow(QtGui.QMainWindow):
LOGIN_INDEX = 0
EIP_STATUS_INDEX = 1
- # Keyring
- KEYRING_KEY = "bitmask"
-
- # SMTP
- PORT_KEY = "port"
- IP_KEY = "ip_address"
-
OPENVPN_SERVICE = "openvpn"
MX_SERVICE = "mx"
@@ -257,10 +246,6 @@ class MainWindow(QtGui.QMainWindow):
self._soledad_bootstrapper.soledad_failed.connect(
self._mail_status.set_soledad_failed)
- self._smtp_bootstrapper = SMTPBootstrapper()
- self._smtp_bootstrapper.download_config.connect(
- self._smtp_bootstrapped_stage)
-
self.ui.action_about_leap.triggered.connect(self._about)
self.ui.action_quit.triggered.connect(self.quit)
self.ui.action_wizard.triggered.connect(self._launch_wizard)
@@ -307,12 +292,13 @@ class MainWindow(QtGui.QMainWindow):
# Services signals/slots connection
self.new_updates.connect(self._react_to_new_updates)
+
+ # 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.soledad_ready.connect(self._set_soledad_ready)
self.mail_client_logged_in.connect(self._fetch_incoming_mail)
self.logout.connect(self._stop_imap_service)
self.logout.connect(self._stop_smtp_service)
- self.logout.connect(self._mail_status.stopped_mail)
################################# end Qt Signals connection ########
@@ -325,17 +311,18 @@ class MainWindow(QtGui.QMainWindow):
self._bypass_checks = bypass_checks
- self._soledad = None
- self._soledad_ready = False
- self._keymanager = None
- self._smtp_service = None
- self._smtp_port = None
- self._imap_service = None
+ # 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._login_defer = None
self._download_provider_defer = None
- self._smtp_config = SMTPConfig()
+ self._mail_conductor = mail_conductor.MailConductor(
+ self._soledad, self._keymanager)
+ self._mail_conductor.connect_mail_signals(self._mail_status)
# Eip machine is a public attribute where the state machine for
# the eip connection will be available to the different components.
@@ -347,6 +334,7 @@ class MainWindow(QtGui.QMainWindow):
self.eip_machine = None
# start event machines
self.start_eip_machine()
+ self._mail_conductor.start_mail_machine(parent=self)
if self._first_run():
self._wizard_firstrun = True
@@ -458,13 +446,11 @@ class MainWindow(QtGui.QMainWindow):
Displays the preferences window.
"""
- preferences_window = PreferencesWindow(self, self._srp_auth)
+ preferences_window = PreferencesWindow(self, self._srp_auth,
+ self._provider_config)
- if self._soledad_ready:
- preferences_window.set_soledad_ready(self._soledad)
- else:
- self.soledad_ready.connect(
- lambda: preferences_window.set_soledad_ready(self._soledad))
+ self.soledad_ready.connect(
+ lambda: preferences_window.set_soledad_ready(self._soledad))
preferences_window.show()
@@ -478,16 +464,6 @@ class MainWindow(QtGui.QMainWindow):
"""
EIPPreferencesWindow(self).show()
- def _set_soledad_ready(self):
- """
- SLOT
- TRIGGERS:
- self.soledad_ready
-
- It sets the soledad object as ready to use.
- """
- self._soledad_ready = True
-
#
# updates
#
@@ -562,6 +538,8 @@ class MainWindow(QtGui.QMainWindow):
if IS_MAC:
self.raise_()
+ self._hide_unsupported_services()
+
if self._wizard:
possible_username = self._wizard.get_username()
possible_password = self._wizard.get_password()
@@ -594,32 +572,34 @@ class MainWindow(QtGui.QMainWindow):
saved_user = self._settings.get_user()
- try:
- username, domain = saved_user.split('@')
- except (ValueError, AttributeError) as e:
- # if the saved_user does not contain an '@' or its None
- logger.error('Username@provider malformed. %r' % (e, ))
- saved_user = None
-
if saved_user is not None and has_keyring():
- # fill the username
- self._login_widget.set_user(username)
-
- self._login_widget.set_remember(True)
-
- saved_password = None
- try:
- saved_password = keyring.get_password(self.KEYRING_KEY,
- saved_user
- .encode("utf8"))
- except ValueError, e:
- logger.debug("Incorrect Password. %r." % (e,))
-
- if saved_password is not None:
- self._login_widget.set_password(
- saved_password.decode("utf8"))
+ if self._login_widget.load_user_from_keyring(saved_user):
self._login()
+ def _hide_unsupported_services(self):
+ """
+ Given a set of configured providers, it creates a set of
+ available services among all of them and displays the service
+ widgets of only those.
+
+ This means, for example, that with just one provider with EIP
+ only, the mail widget won't be displayed.
+ """
+ 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.ui.eipWidget.setVisible(self.OPENVPN_SERVICE in services)
+ self.ui.mailWidget.setVisible(self.MX_SERVICE in services)
+
#
# systray
#
@@ -803,6 +783,7 @@ 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()
pb = self._provider_bootstrapper
@@ -823,6 +804,7 @@ class MainWindow(QtGui.QMainWindow):
:type data: dict
"""
if data[self._provider_bootstrapper.PASSED_KEY]:
+ # XXX should rename this provider, name clash.
provider = self._login_widget.get_selected_provider()
# If there's no loaded provider or
@@ -895,8 +877,10 @@ class MainWindow(QtGui.QMainWindow):
leap_assert(self._provider_config, "We need a provider config!")
if data[self._provider_bootstrapper.PASSED_KEY]:
- username = self._login_widget.get_user().encode("utf8")
- password = self._login_widget.get_password().encode("utf8")
+ username = self._login_widget.get_user()
+ password = self._login_widget.get_password()
+
+ self._hide_unsupported_services()
if self._srp_auth is None:
self._srp_auth = SRPAuth(self._provider_config)
@@ -1023,114 +1007,58 @@ class MainWindow(QtGui.QMainWindow):
logger.debug("ERROR on soledad bootstrapping:")
logger.error("%r" % data[self._soledad_bootstrapper.ERROR_KEY])
return
- else:
- logger.debug("Done bootstrapping Soledad")
- self._soledad = self._soledad_bootstrapper.soledad
- self._keymanager = self._soledad_bootstrapper.keymanager
+ 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.
# this will trigger start_imap_service
+ # and start_smtp_boostrapping
self.soledad_ready.emit()
- # TODO connect all these activations to the soledad_ready
- # signal so the logic is clearer to follow.
-
- if self._provider_config.provides_mx() and \
- self._enabled_services.count(self.MX_SERVICE) > 0:
- self._smtp_bootstrapper.run_smtp_setup_checks(
- self._provider_config,
- self._smtp_config,
- True)
-
###################################################################
# Service control methods: smtp
- def _smtp_bootstrapped_stage(self, data):
+ @QtCore.Slot()
+ def _start_smtp_bootstrapping(self):
"""
SLOT
TRIGGERS:
- self._smtp_bootstrapper.download_config
-
- If there was a problem, displays it, otherwise it does nothing.
- This is used for intermediate bootstrapping stages, in case
- they fail.
-
- :param data: result from the bootstrapping stage for Soledad
- :type data: dict
- """
- passed = data[self._smtp_bootstrapper.PASSED_KEY]
- if not passed:
- logger.error(data[self._smtp_bootstrapper.ERROR_KEY])
- return
- logger.debug("Done bootstrapping SMTP")
- self._check_smtp_config()
-
- def _check_smtp_config(self):
- """
- Checks smtp config and tries to download smtp client cert if needed.
+ self.soledad_ready
"""
- hosts = self._smtp_config.get_hosts()
- # TODO handle more than one host and define how to choose
- if len(hosts) > 0:
- hostname = hosts.keys()[0]
- logger.debug("Using hostname %s for SMTP" % (hostname,))
- host = hosts[hostname][self.IP_KEY].encode("utf-8")
- port = hosts[hostname][self.PORT_KEY]
-
- client_cert = self._smtp_config.get_client_cert_path(
+ # 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._mail_conductor.smtp_bootstrapper.run_smtp_setup_checks(
self._provider_config,
- about_to_download=True)
-
- if not os.path.isfile(client_cert):
- self._smtp_bootstrapper._download_client_certificates()
- if os.path.isfile(client_cert):
- self._start_smtp_service(host, port, client_cert)
- else:
- logger.warning("Tried to download email client "
- "certificate, but could not find any")
-
- else:
- logger.warning("No smtp hosts configured")
-
- def _start_smtp_service(self, host, port, cert):
- """
- Starts the smtp service.
- """
- # TODO Make the encrypted_only configurable
- # TODO pick local smtp port in a better way
- # TODO remove hard-coded port and let leap.mail set
- # the specific default.
-
- from leap.mail.smtp import setup_smtp_relay
- self._smtp_service, self._smtp_port = setup_smtp_relay(
- port=2013,
- keymanager=self._keymanager,
- smtp_host=host,
- smtp_port=port,
- smtp_cert=cert,
- smtp_key=cert,
- encrypted_only=False)
+ self._mail_conductor.smtp_config,
+ download_if_needed=True)
+ # XXX --- should remove from here, and connecte directly to the state
+ # machine.
+ @QtCore.Slot()
def _stop_smtp_service(self):
"""
SLOT
TRIGGERS:
self.logout
"""
- # There is a subtle difference here:
- # we are stopping the factory for the smtp service here,
- # but in the imap case we are just stopping the fetcher.
- if self._smtp_service is not None:
- logger.debug('Stopping smtp service.')
- self._smtp_port.stopListening()
- self._smtp_service.doStop()
+ # TODO call stop_mail_service
+ self._mail_conductor.stop_smtp_service()
###################################################################
# Service control methods: imap
+ @QtCore.Slot()
def _start_imap_service(self):
"""
SLOT
@@ -1139,11 +1067,7 @@ class MainWindow(QtGui.QMainWindow):
"""
if self._provider_config.provides_mx() and \
self._enabled_services.count(self.MX_SERVICE) > 0:
- logger.debug('Starting imap service')
-
- self._imap_service = imap.start_imap_service(
- self._soledad,
- self._keymanager)
+ self._mail_conductor.start_imap_service()
def _on_mail_client_logged_in(self, req):
"""
@@ -1151,30 +1075,25 @@ class MainWindow(QtGui.QMainWindow):
"""
self.mail_client_logged_in.emit()
+ @QtCore.Slot()
def _fetch_incoming_mail(self):
"""
SLOT
TRIGGERS:
self.mail_client_logged_in
"""
- # TODO have a mutex over fetch operation.
- if self._imap_service:
- logger.debug('Client connected, fetching mail...')
- self._imap_service.fetch()
+ # TODO connect signal directly!!!
+ self._mail_conductor.fetch_incoming_mail()
+ @QtCore.Slot()
def _stop_imap_service(self):
"""
SLOT
TRIGGERS:
self.logout
"""
- # There is a subtle difference here:
- # we are just stopping the fetcher here,
- # but in the smtp case we are stopping the factory.
- # We should homogenize both services.
- if self._imap_service is not None:
- logger.debug('Stopping imap service.')
- self._imap_service.stop()
+ # TODO call stop_mail_service
+ self._mail_conductor.stop_imap_service()
# end service control methods (imap)
@@ -1623,8 +1542,8 @@ class MainWindow(QtGui.QMainWindow):
if ok:
self._logged_user = None
-
self._login_widget.logged_out()
+ self._mail_status.mail_state_disabled()
else:
self._login_widget.set_login_status(
@@ -1700,8 +1619,7 @@ class MainWindow(QtGui.QMainWindow):
"""
logger.debug('About to quit, doing cleanup...')
- if self._imap_service is not None:
- self._imap_service.stop()
+ self._mail_conductor.stop_imap_service()
if self._srp_auth is not None:
if self._srp_auth.get_session_id() is not None or \
diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py
index 58cb05ba..acb39b07 100644
--- a/src/leap/bitmask/gui/preferenceswindow.py
+++ b/src/leap/bitmask/gui/preferenceswindow.py
@@ -40,12 +40,14 @@ class PreferencesWindow(QtGui.QDialog):
"""
Window that displays the preferences.
"""
- def __init__(self, parent, srp_auth):
+ def __init__(self, parent, srp_auth, provider_config):
"""
:param parent: parent object of the PreferencesWindow.
:parent type: QWidget
:param srp_auth: SRPAuth object configured in the main app.
:type srp_auth: SRPAuth
+ :param provider_config: ProviderConfig object.
+ :type provider_config: ProviderConfig
"""
QtGui.QDialog.__init__(self, parent)
self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic")
@@ -72,6 +74,36 @@ class PreferencesWindow(QtGui.QDialog):
else:
self._add_configured_providers()
+ pw_enabled = False
+
+ # check if the user is logged in
+ if srp_auth is not None and srp_auth.get_token() is not None:
+ # 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')
+
+ # ... and if the user have it enabled
+ if 'mx' 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)
+ else:
+ pw_enabled = True
+ else:
+ msg = self.tr(
+ "In order to change your password you need to be logged in.")
+ self._set_password_change_status(msg)
+
+ self.ui.gbPasswordChange.setEnabled(pw_enabled)
+
def set_soledad_ready(self, soledad):
"""
SLOT
@@ -84,6 +116,7 @@ class PreferencesWindow(QtGui.QDialog):
:type soledad: Soledad
"""
self._soledad = soledad
+ self.ui.lblPasswordChangeStatus.setVisible(False)
self.ui.gbPasswordChange.setEnabled(True)
def _set_password_change_status(self, status, error=False, success=False):
@@ -98,6 +131,9 @@ class PreferencesWindow(QtGui.QDialog):
elif success:
status = "<font color='green'><b>%s</b></font>" % (status,)
+ if not self.ui.gbPasswordChange.isEnabled():
+ status = "<font color='black'>%s</font>" % (status,)
+
self.ui.lblPasswordChangeStatus.setVisible(True)
self.ui.lblPasswordChangeStatus.setText(status)
@@ -156,7 +192,7 @@ class PreferencesWindow(QtGui.QDialog):
"""
logger.debug("SRP password changed successfully.")
try:
- self._soledad.change_passphrase(str(new_password))
+ self._soledad.change_passphrase(new_password)
logger.debug("Soledad password changed successfully.")
except NoStorageSecret:
logger.debug(
@@ -218,6 +254,16 @@ class PreferencesWindow(QtGui.QDialog):
for provider in self._settings.get_configured_providers():
self.ui.cbProvidersServices.addItem(provider)
+ def _select_provider_by_name(self, name):
+ """
+ Given a provider name/domain, selects it in the combobox.
+
+ :param name: name or domain for the provider
+ :type name: str
+ """
+ provider_index = self.ui.cbProvidersServices.findText(name)
+ self.ui.cbProvidersServices.setCurrentIndex(provider_index)
+
def _service_selection_changed(self, service, state):
"""
SLOT
diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py
index 94726720..386cb75f 100644
--- a/src/leap/bitmask/gui/statemachines.py
+++ b/src/leap/bitmask/gui/statemachines.py
@@ -19,7 +19,8 @@ State machines for the Bitmask app.
"""
import logging
-from PySide.QtCore import QStateMachine, QState
+from PySide import QtCore
+from PySide.QtCore import QStateMachine, QState, Signal
from PySide.QtCore import QObject
from leap.bitmask.services import connections
@@ -36,28 +37,255 @@ _CON = "connecting"
_DIS = "disconnecting"
-class IntermediateState(QState):
+class SignallingState(QState):
"""
- Intermediate state that emits a custom signal on entry
+ A state that emits a custom signal on entry.
"""
- def __init__(self, signal):
+ def __init__(self, signal, parent=None, name=None):
"""
Initializer.
:param signal: the signal to be emitted on entry on this state.
:type signal: QtCore.QSignal
"""
- super(IntermediateState, self).__init__()
+ super(SignallingState, self).__init__(parent)
self._signal = signal
+ self._name = name
def onEntry(self, *args):
"""
Emits the signal on entry.
"""
- logger.debug('IntermediateState entered. Emitting signal ...')
+ logger.debug('State %s::%s entered. Emitting signal ...'
+ % (self._name, self.objectName()))
if self._signal is not None:
self._signal.emit()
+class States(object):
+ """
+ States for composite objects
+ """
+
+ class Off(SignallingState):
+ pass
+
+ class Connecting(SignallingState):
+ pass
+
+ class On(SignallingState):
+ pass
+
+ class Disconnecting(SignallingState):
+ pass
+
+ class StepsTrack(QObject):
+ state_change = Signal()
+
+ def __init__(self, target):
+ super(States.StepsTrack, self).__init__()
+ self.received = set([])
+ self.target = set(target)
+
+ def is_all_done(self):
+ return all([ev in self.target for ev in self.received])
+
+ def is_any_done(self):
+ return any([ev in self.target for ev in self.received])
+
+ def seen(self, _type):
+ if _type in self.target:
+ self.received.add(_type)
+
+ def reset_seen(self):
+ self.received = set([])
+
+ class TransitionOR(QtCore.QSignalTransition):
+
+ def __init__(self, state):
+ super(States.TransitionOR, self).__init__(
+ state, QtCore.SIGNAL('state_change()'))
+ self.state = state
+
+ def eventTest(self, e):
+ self.state.seen(e.type())
+ done = self.state.is_any_done()
+ if done:
+ self.state.reset_seen()
+ return done
+
+ def onTransition(self, e):
+ pass
+
+ class TransitionAND(QtCore.QSignalTransition):
+
+ def __init__(self, state):
+ super(States.TransitionAND, self).__init__(
+ state, QtCore.SIGNAL('state_change()'))
+ self.state = state
+
+ def eventTest(self, e):
+ self.state.seen(e.type())
+ done = self.state.is_all_done()
+ if done:
+ self.state.reset_seen()
+ return done
+
+ def onTransition(self, e):
+ pass
+
+
+class CompositeEvent(QtCore.QEvent):
+ def __init__(self):
+ super(CompositeEvent, self).__init__(
+ QtCore.QEvent.Type(self.ID))
+
+
+class Composite(object):
+ # TODO we should generate the connectingEvents dinamycally,
+ # depending on how much composite states do we get.
+ # This only supports up to 2 composite states.
+
+ class ConnectingEvent1(CompositeEvent):
+ ID = QtCore.QEvent.User + 1
+
+ class ConnectingEvent2(CompositeEvent):
+ ID = QtCore.QEvent.User + 2
+
+ class ConnectedEvent1(CompositeEvent):
+ ID = QtCore.QEvent.User + 3
+
+ class ConnectedEvent2(CompositeEvent):
+ ID = QtCore.QEvent.User + 4
+
+ class DisconnectingEvent1(CompositeEvent):
+ ID = QtCore.QEvent.User + 5
+
+ class DisconnectingEvent2(CompositeEvent):
+ ID = QtCore.QEvent.User + 6
+
+ class DisconnectedEvent1(CompositeEvent):
+ ID = QtCore.QEvent.User + 7
+
+ class DisconnectedEvent2(CompositeEvent):
+ ID = QtCore.QEvent.User + 8
+
+
+class Events(QtCore.QObject):
+ """
+ A Wrapper object for containing the events that will be
+ posted to a composite state machine.
+ """
+ def __init__(self, parent=None):
+ """
+ Initializes the QObject with the given parent.
+ """
+ QtCore.QObject.__init__(self, parent)
+
+
+class CompositeMachine(QStateMachine):
+
+ def __init__(self, parent=None):
+ QStateMachine.__init__(self, parent)
+
+ # events
+ self.events = Events(parent)
+ self.create_events()
+
+ def create_events(self):
+ """
+ Creates a bunch of events to be posted to the state machine when
+ the transitions say so.
+ """
+ # XXX refactor into a dictionary?
+ self.events.con_ev1 = Composite.ConnectingEvent1()
+ self.events.con_ev2 = Composite.ConnectingEvent2()
+ self.events.on_ev1 = Composite.ConnectedEvent1()
+ self.events.on_ev2 = Composite.ConnectedEvent2()
+ self.events.dis_ev1 = Composite.DisconnectingEvent1()
+ self.events.dis_ev2 = Composite.DisconnectingEvent2()
+ self.events.off_ev1 = Composite.DisconnectedEvent1()
+ self.events.off_ev2 = Composite.DisconnectedEvent2()
+
+ def beginSelectTransitions(self, e):
+ """
+ Weird. Having this method makes underlying backtraces
+ to appear magically on the transitions.
+ :param e: the received event
+ :type e: QEvent
+ """
+ pass
+
+ def _connect_children(self, child1, child2):
+ """
+ Connects the state transition signals for children machines.
+
+ :param child1: the first child machine
+ :type child1: QStateMachine
+ :param child2: the second child machine
+ :type child2: QStateMachine
+ """
+ # TODO refactor and generalize for composites
+ # of more than 2 connections.
+
+ c1 = child1.conn
+ c1.qtsigs.connecting_signal.connect(self.con_ev1_slot)
+ c1.qtsigs.connected_signal.connect(self.on_ev1_slot)
+ c1.qtsigs.disconnecting_signal.connect(self.dis_ev1_slot)
+ c1.qtsigs.disconnected_signal.connect(self.off_ev1_slot)
+
+ c2 = child2.conn
+ c2.qtsigs.connecting_signal.connect(self.con_ev2_slot)
+ c2.qtsigs.connected_signal.connect(self.on_ev2_slot)
+ c2.qtsigs.disconnecting_signal.connect(self.dis_ev2_slot)
+ c2.qtsigs.disconnected_signal.connect(self.off_ev2_slot)
+
+ # XXX why is this getting deletec in c++?
+ #Traceback (most recent call last):
+ #self.postEvent(self.events.on_ev2)
+ #RuntimeError: Internal C++ object (ConnectedEvent2) already deleted.
+ # XXX trying the following workaround, since
+ # I cannot find why in the world this is getting deleted :(
+ # XXX refactor!
+
+ # slots connection1
+
+ def con_ev1_slot(self):
+ # XXX if we just postEvent, we get the Internal C++ object deleted...
+ # so the workaround is to re-create it each time.
+ self.events.con_ev1 = Composite.ConnectingEvent1()
+ self.postEvent(self.events.con_ev1)
+
+ def on_ev1_slot(self):
+ self.events.on_ev1 = Composite.ConnectedEvent1()
+ self.postEvent(self.events.on_ev1)
+
+ def dis_ev1_slot(self):
+ self.events.dis_ev1 = Composite.DisconnectingEvent1()
+ self.postEvent(self.events.dis_ev1)
+
+ def off_ev1_slot(self):
+ self.events.off_ev1 = Composite.DisconnectedEvent1()
+ self.postEvent(self.events.off_ev1)
+
+ # slots connection2
+
+ def con_ev2_slot(self):
+ self.events.con_ev2 = Composite.ConnectingEvent2()
+ self.postEvent(self.events.con_ev2)
+
+ def on_ev2_slot(self):
+ self.events.on_ev2 = Composite.ConnectedEvent2()
+ self.postEvent(self.events.on_ev2)
+
+ def dis_ev2_slot(self):
+ self.events.dis_ev2 = Composite.DisconnectingEvent2()
+ self.postEvent(self.events.dis_ev2)
+
+ def off_ev2_slot(self):
+ self.events.off_ev2 = Composite.DisconnectedEvent2()
+ self.postEvent(self.events.off_ev2)
+
+
class ConnectionMachineBuilder(object):
"""
Builder class for state machines made from LEAPConnections.
@@ -65,16 +293,161 @@ class ConnectionMachineBuilder(object):
def __init__(self, connection):
"""
:param connection: an instance of a concrete LEAPConnection
- we will be building a state machine for.
+ we will be building a state machine for.
:type connection: AbstractLEAPConnection
"""
self._conn = connection
leap_assert_type(self._conn, connections.AbstractLEAPConnection)
- def make_machine(self, button=None, action=None, label=None):
+ def make_machine(self, **kwargs):
"""
Creates a statemachine associated with the passed controls.
+ It returns the state machine if the connection used for initializing
+ the ConnectionMachineBuilder inherits exactly from
+ LEAPAbstractConnection, and a tuple with the Composite Machine and its
+ individual parts in case that it is a composite machine which
+ connection definition inherits from more than one class that, on their
+ time, inherit from LEAPAbstractConnection.
+
+ :params: see parameters for ``_make_simple_machine``
+ :returns: a QStateMachine, or a tuple with the form:
+ (CompositeStateMachine, (StateMachine1, StateMachine2))
+ :rtype: QStateMachine or tuple
+ """
+ components = self._conn.components
+
+ if components is None:
+ # simple case: connection definition inherits directly from
+ # the abstract connection.
+
+ leap_assert_type(self._conn, connections.AbstractLEAPConnection)
+ return self._make_simple_machine(self._conn, **kwargs)
+
+ if components:
+ # composite case: connection definition inherits from several
+ # classes, each one of which inherit from the abstract connection.
+ child_machines = tuple(
+ [ConnectionMachineBuilder(connection()).make_machine()
+ for connection in components])
+ composite_machine = self._make_composite_machine(
+ self._conn, child_machines, **kwargs)
+
+ composite_machine._connect_children(
+ *child_machines)
+
+ # XXX should also connect its own states with the signals
+ # for the composite machine itself
+
+ return (composite_machine, child_machines)
+
+ def _make_composite_machine(self, conn, children,
+ **kwargs):
+ """
+ Creates a composite machine.
+
+ :param conn: an instance of a connection definition.
+ :type conn: LEAPAbstractConnection
+ :param children: children machines
+ :type children: tuple of state machines
+ :returns: A composite state machine
+ :rtype: QStateMachine
+ """
+ # TODO split this method in smaller utility functions.
+ parent = kwargs.get('parent', None)
+
+ # 1. create machine
+ machine = CompositeMachine(parent=parent)
+
+ # 2. create states
+ off = States.Off(conn.qtsigs.disconnected_signal,
+ parent=machine,
+ name=conn.name)
+ off.setObjectName("off")
+
+ on = States.On(conn.qtsigs.connected_signal,
+ parent=machine,
+ name=conn.name)
+ on.setObjectName("on")
+
+ connecting_state = States.Connecting(
+ conn.qtsigs.connecting_signal,
+ parent=machine,
+ name=conn.name)
+ connecting_state.setObjectName("connecting")
+
+ disconnecting_state = States.Disconnecting(
+ conn.qtsigs.disconnecting_signal,
+ parent=machine,
+ name=conn.name)
+ disconnecting_state.setObjectName("disconnecting")
+
+ # 3. TODO create as many connectingEvents as needed (dynamically create
+ # classses for that)
+ # (we have manually created classes for events under CompositeEvent for
+ # now, to begin with the simple 2 states case for mail.
+
+ # 4. state tracking objects for each transition stage
+
+ connecting_track0 = States.StepsTrack(
+ (Composite.ConnectingEvent1.ID,
+ Composite.ConnectingEvent2.ID))
+ connecting_track0.setObjectName("connecting_step_0")
+
+ connecting_track1 = States.StepsTrack(
+ (Composite.ConnectedEvent1.ID,
+ Composite.ConnectedEvent2.ID))
+ connecting_track1.setObjectName("connecting_step_1")
+
+ disconnecting_track0 = States.StepsTrack(
+ (Composite.DisconnectingEvent1.ID,
+ Composite.DisconnectingEvent2.ID))
+ disconnecting_track0.setObjectName("disconnecting_step_0")
+
+ disconnecting_track1 = States.StepsTrack(
+ (Composite.DisconnectedEvent1.ID,
+ Composite.DisconnectedEvent2.ID))
+ disconnecting_track1.setObjectName("disconnecting_step_1")
+
+ # 5. definte the transitions with the matching state-tracking
+ # objects.
+
+ # off -> connecting
+ connecting_transition = States.TransitionOR(
+ connecting_track0)
+ connecting_transition.setTargetState(connecting_state)
+ off.addTransition(connecting_transition)
+
+ # connecting -> on
+ connected_transition = States.TransitionAND(
+ connecting_track1)
+ connected_transition.setTargetState(on)
+ connecting_state.addTransition(connected_transition)
+
+ # on -> disconnecting
+ disconnecting_transition = States.TransitionOR(
+ disconnecting_track0)
+ disconnecting_transition.setTargetState(disconnecting_state)
+ on.addTransition(disconnecting_transition)
+
+ # disconnecting -> off
+ disconnected_transition = States.TransitionAND(
+ disconnecting_track1)
+ disconnected_transition.setTargetState(off)
+ disconnecting_state.addTransition(disconnected_transition)
+
+ machine.setInitialState(off)
+ machine.conn = conn
+ return machine
+
+ def _make_simple_machine(self, conn,
+ button=None, action=None, label=None):
+ """
+ Creates a statemachine associated with the passed controls.
+
+ :param conn: the connection instance that defines this machine.
+ :type conn: AbstractLEAPConnection
+
:param button: the switch button.
:type button: QPushButton
@@ -88,9 +461,7 @@ class ConnectionMachineBuilder(object):
:rtype: QStateMachine
"""
machine = QStateMachine()
- conn = self._conn
-
- states = self._make_states(button, action, label)
+ states = self._make_states(conn, button, action, label)
# transitions:
@@ -151,11 +522,17 @@ class ConnectionMachineBuilder(object):
for state in states.itervalues():
machine.addState(state)
machine.setInitialState(states[_OFF])
+
+ machine.conn = conn
return machine
- def _make_states(self, button, action, label):
+ def _make_states(self, conn, button, action, label):
"""
- Creates the four states for the state machine
+ Creates the four states for the simple state machine.
+ Adds the needed properties for the passed controls.
+
+ :param conn: the connection instance that defines this machine.
+ :type conn: AbstractLEAPConnection
:param button: the switch button.
:type button: QPushButton
@@ -169,7 +546,6 @@ class ConnectionMachineBuilder(object):
:returns: a dict of states
:rtype: dict
"""
- conn = self._conn
states = {}
# TODO add tooltip
@@ -190,8 +566,9 @@ class ConnectionMachineBuilder(object):
states[_OFF] = off
# CONNECTING State ----------------
- connecting = IntermediateState(
- conn.qtsigs.connecting_signal)
+ connecting = SignallingState(
+ conn.qtsigs.connecting_signal,
+ name=conn.name)
on_label = _tr("Turn {0}").format(
conn.Disconnected.short_label)
if button:
@@ -224,8 +601,9 @@ class ConnectionMachineBuilder(object):
states[_ON] = on
# DISCONNECTING State -------------
- disconnecting = IntermediateState(
- conn.qtsigs.disconnecting_signal)
+ disconnecting = SignallingState(
+ conn.qtsigs.disconnecting_signal,
+ name=conn.name)
if button:
disconnecting.assignProperty(
button, 'enabled', False)
diff --git a/src/leap/bitmask/gui/ui/eip_status.ui b/src/leap/bitmask/gui/ui/eip_status.ui
index 25831118..d078ca0c 100644
--- a/src/leap/bitmask/gui/ui/eip_status.ui
+++ b/src/leap/bitmask/gui/ui/eip_status.ui
@@ -185,6 +185,9 @@
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
+ <property name="styleSheet">
+ <string notr="true">text-align: left;</string>
+ </property>
<property name="text">
<string>0.0 KB/s</string>
</property>
@@ -249,6 +252,9 @@
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
+ <property name="styleSheet">
+ <string notr="true">text-align: left;</string>
+ </property>
<property name="text">
<string>0.0 KB/s</string>
</property>
diff --git a/src/leap/bitmask/gui/ui/eippreferences.ui b/src/leap/bitmask/gui/ui/eippreferences.ui
index cc77c82e..a3050683 100644
--- a/src/leap/bitmask/gui/ui/eippreferences.ui
+++ b/src/leap/bitmask/gui/ui/eippreferences.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>435</width>
- <height>273</height>
+ <height>144</height>
</rect>
</property>
<property name="windowTitle">
@@ -18,7 +18,7 @@
<normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout_2">
- <item row="1" column="0">
+ <item row="0" column="0">
<widget class="QGroupBox" name="gbGatewaySelector">
<property name="enabled">
<bool>true</bool>
@@ -33,7 +33,7 @@
<item row="0" column="0">
<widget class="QLabel" name="lblSelectProvider">
<property name="text">
- <string>&amp;Select provider:</string>
+ <string>Select &amp;provider:</string>
</property>
<property name="buddy">
<cstring>cbProvidersGateway</cstring>
@@ -52,7 +52,7 @@
<item row="7" column="2">
<widget class="QPushButton" name="pbSaveGateway">
<property name="text">
- <string>Save this provider settings</string>
+ <string>&amp;Save this provider settings</string>
</property>
</widget>
</item>
@@ -69,7 +69,10 @@
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
- <string>Select gateway:</string>
+ <string>Select &amp;gateway:</string>
+ </property>
+ <property name="buddy">
+ <cstring>cbGateways</cstring>
</property>
</widget>
</item>
@@ -85,76 +88,9 @@
</layout>
</widget>
</item>
- <item row="0" column="0">
- <widget class="QGroupBox" name="gbAutomaticEIP">
- <property name="title">
- <string>Automatic Encrypted Internet start</string>
- </property>
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="3" column="0">
- <widget class="QLabel" name="lblAutoStartEIPStatus">
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="styleSheet">
- <string notr="true"/>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Plain</enum>
- </property>
- <property name="text">
- <string>&lt;font color='green'&gt;&lt;b&gt;Automatic EIP start saved!&lt;/b&gt;&lt;/font&gt;</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QPushButton" name="pbSaveAutoStartEIP">
- <property name="text">
- <string>Save auto start setting</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QCheckBox" name="cbAutoStartEIP">
- <property name="layoutDirection">
- <enum>Qt::LeftToRight</enum>
- </property>
- <property name="text">
- <string>Enable Automatic start:</string>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QComboBox" name="cbProvidersEIP">
- <item>
- <property name="text">
- <string>&lt;Select provider&gt;</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- <zorder>cbAutoStartEIP</zorder>
- <zorder>lblAutoStartEIPStatus</zorder>
- <zorder>pbSaveAutoStartEIP</zorder>
- <zorder>cbProvidersEIP</zorder>
- </widget>
- </item>
</layout>
</widget>
<tabstops>
- <tabstop>cbAutoStartEIP</tabstop>
- <tabstop>cbProvidersEIP</tabstop>
- <tabstop>pbSaveAutoStartEIP</tabstop>
<tabstop>cbProvidersGateway</tabstop>
<tabstop>cbGateways</tabstop>
<tabstop>pbSaveGateway</tabstop>
@@ -162,22 +98,5 @@
<resources>
<include location="../../../../../data/resources/mainwindow.qrc"/>
</resources>
- <connections>
- <connection>
- <sender>cbAutoStartEIP</sender>
- <signal>toggled(bool)</signal>
- <receiver>cbProvidersEIP</receiver>
- <slot>setEnabled(bool)</slot>
- <hints>
- <hint type="sourcelabel">
- <x>180</x>
- <y>53</y>
- </hint>
- <hint type="destinationlabel">
- <x>238</x>
- <y>53</y>
- </hint>
- </hints>
- </connection>
- </connections>
+ <connections/>
</ui>
diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui
index 10c77057..badd291d 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>635</height>
+ <height>636</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -86,89 +86,101 @@
<number>0</number>
</property>
<item>
- <widget class="QFrame" name="frame_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="styleSheet">
- <string notr="true">QFrame{background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(160, 160, 160, 128), stop:1 rgba(255, 255, 255, 0));}</string>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <property name="leftMargin">
- <number>24</number>
+ <widget class="QWidget" name="eipWidget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>0</number>
</property>
- <property name="rightMargin">
- <number>24</number>
+ <property name="margin">
+ <number>0</number>
</property>
<item>
- <widget class="QLabel" name="label_2">
- <property name="font">
- <font>
- <pointsize>16</pointsize>
- <weight>75</weight>
- <bold>true</bold>
- </font>
+ <widget class="QFrame" name="frame_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
<property name="styleSheet">
- <string notr="true">background-color: rgba(255, 255, 255, 0);</string>
- </property>
- <property name="text">
- <string>Encrypted Internet</string>
+ <string notr="true">QFrame{background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(160, 160, 160, 128), stop:1 rgba(255, 255, 255, 0));}</string>
</property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="leftMargin">
+ <number>24</number>
+ </property>
+ <property name="rightMargin">
+ <number>24</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="font">
+ <font>
+ <pointsize>16</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">background-color: rgba(255, 255, 255, 0);</string>
+ </property>
+ <property name="text">
+ <string>Encrypted Internet</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnEIPPreferences">
+ <property name="maximumSize">
+ <size>
+ <width>48</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="icon">
+ <iconset resource="../../../../../data/resources/mainwindow.qrc">
+ <normaloff>:/images/black/32/gear.png</normaloff>:/images/black/32/gear.png</iconset>
+ </property>
+ <property name="autoDefault">
+ <bool>false</bool>
+ </property>
+ <property name="default">
+ <bool>false</bool>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
<item>
- <widget class="QPushButton" name="btnEIPPreferences">
- <property name="maximumSize">
- <size>
- <width>48</width>
- <height>20</height>
- </size>
+ <layout class="QVBoxLayout" name="eipLayout">
+ <property name="leftMargin">
+ <number>12</number>
</property>
- <property name="styleSheet">
- <string notr="true"/>
+ <property name="topMargin">
+ <number>0</number>
</property>
- <property name="text">
- <string/>
+ <property name="rightMargin">
+ <number>12</number>
</property>
- <property name="icon">
- <iconset resource="../../../../../data/resources/mainwindow.qrc">
- <normaloff>:/images/black/32/gear.png</normaloff>:/images/black/32/gear.png</iconset>
- </property>
- <property name="autoDefault">
- <bool>false</bool>
+ <property name="bottomMargin">
+ <number>0</number>
</property>
- <property name="default">
- <bool>false</bool>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- </widget>
+ </layout>
</item>
</layout>
</widget>
</item>
<item>
- <layout class="QVBoxLayout" name="eipLayout">
- <property name="leftMargin">
- <number>12</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>12</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- </layout>
- </item>
- <item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -253,14 +265,26 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb
</widget>
</item>
<item>
- <layout class="QVBoxLayout" name="mailLayout" stretch="">
- <property name="spacing">
- <number>-1</number>
- </property>
- <property name="margin">
- <number>12</number>
- </property>
- </layout>
+ <widget class="QWidget" name="mailWidget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QVBoxLayout" name="mailLayout">
+ <property name="spacing">
+ <number>-1</number>
+ </property>
+ <property name="margin">
+ <number>12</number>
+ </property>
+ </layout>
+ </item>
+ </layout>
+ </widget>
</item>
<item>
<spacer name="verticalSpacer">
diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py
index 1b5947e1..2adf8aa8 100644
--- a/src/leap/bitmask/provider/providerbootstrapper.py
+++ b/src/leap/bitmask/provider/providerbootstrapper.py
@@ -20,6 +20,7 @@ Provider bootstrapping
import logging
import socket
import os
+import sys
import requests
@@ -125,9 +126,13 @@ class ProviderBootstrapper(AbstractBootstrapper):
# err --- but we can do it after a failure, to diagnose what went
# wrong. Right now we're just adding connection overhead. -- kali
+ verify = self.verify
+ if verify:
+ verify = self.verify.encode(sys.getfilesystemencoding())
+
try:
res = self._session.get("https://%s" % (self._domain,),
- verify=self.verify,
+ verify=verify,
timeout=REQUEST_TIMEOUT)
res.raise_for_status()
except requests.exceptions.SSLError as exc:
@@ -180,6 +185,8 @@ class ProviderBootstrapper(AbstractBootstrapper):
# no ca? then download from main domain again.
pass
+ if verify:
+ verify = verify.encode(sys.getfilesystemencoding())
logger.debug("Requesting for provider.json... "
"uri: {0}, verify: {1}, headers: {2}".format(
uri, verify, headers))
@@ -336,9 +343,9 @@ class ProviderBootstrapper(AbstractBootstrapper):
test_uri = "%s/%s/cert" % (self._provider_config.get_api_uri(),
self._provider_config.get_api_version())
- res = self._session.get(test_uri,
- verify=self._provider_config
- .get_ca_cert_path(),
+ ca_cert_path = self._provider_config.get_ca_cert_path()
+ ca_cert_path = ca_cert_path.encode(sys.getfilesystemencoding())
+ res = self._session.get(test_uri, verify=ca_cert_path,
timeout=REQUEST_TIMEOUT)
res.raise_for_status()
diff --git a/src/leap/bitmask/services/__init__.py b/src/leap/bitmask/services/__init__.py
index f9456159..e62277b6 100644
--- a/src/leap/bitmask/services/__init__.py
+++ b/src/leap/bitmask/services/__init__.py
@@ -19,6 +19,7 @@ Services module.
"""
import logging
import os
+import sys
from PySide import QtCore
@@ -135,8 +136,12 @@ def download_service_config(provider_config, service_config,
if token is not None:
headers["Authorization"] = 'Token token="{0}"'.format(token)
+ verify = provider_config.get_ca_cert_path()
+ if verify:
+ verify = verify.encode(sys.getfilesystemencoding())
+
res = session.get(config_uri,
- verify=provider_config.get_ca_cert_path(),
+ verify=verify,
headers=headers,
timeout=REQUEST_TIMEOUT,
cookies=cookies)
diff --git a/src/leap/bitmask/services/connections.py b/src/leap/bitmask/services/connections.py
index 8aeb4e0c..ecfd35ff 100644
--- a/src/leap/bitmask/services/connections.py
+++ b/src/leap/bitmask/services/connections.py
@@ -41,9 +41,6 @@ The different services should declare a ServiceConnection class that
inherits from AbstractLEAPConnection, so an instance of such class
can be used to inform the StateMachineBuilder of the particularities
of the state transitions for each particular connection.
-
-In the future, we will extend this class to allow composites in connections,
-so we can apply conditional logic to the transitions.
"""
@@ -79,12 +76,7 @@ class AbstractLEAPConnection(object):
"""
return self._qtsigs
- # XXX for conditional transitions with composites,
- # we might want to add
- # a field with dependencies: what this connection
- # needs for (ON) state.
- # XXX Look also at child states in the state machine.
- #depends = ()
+ components = None
# Signals that derived classes
# have to implement.
diff --git a/src/leap/bitmask/services/eip/connection.py b/src/leap/bitmask/services/eip/connection.py
index 962d9cf2..8a35d550 100644
--- a/src/leap/bitmask/services/eip/connection.py
+++ b/src/leap/bitmask/services/eip/connection.py
@@ -46,5 +46,5 @@ class EIPConnectionSignals(QtCore.QObject):
class EIPConnection(AbstractLEAPConnection):
def __init__(self):
- # XXX this should be public instead
self._qtsigs = EIPConnectionSignals()
+ self._connection_name = "Encrypted Internet"
diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py
index f3b6bfc8..fe3fe4c1 100644
--- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py
+++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py
@@ -21,6 +21,7 @@ import commands
import getpass
import logging
import os
+import sys
from leap.bitmask.services.eip.vpnlauncher import VPNLauncher
from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException
@@ -185,6 +186,8 @@ class DarwinVPNLauncher(VPNLauncher):
:rtype: dict
"""
+ ld_library_path = os.path.join(get_path_prefix(), "..", "lib")
+ ld_library_path.encode(sys.getfilesystemencoding())
return {
- "DYLD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib")
+ "DYLD_LIBRARY_PATH": ld_library_path
}
diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py
index efb23285..d02f6f96 100644
--- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py
+++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py
@@ -21,6 +21,7 @@ import commands
import logging
import os
import subprocess
+import sys
import time
from leap.bitmask.config import flags
@@ -231,6 +232,8 @@ class LinuxVPNLauncher(VPNLauncher):
:rtype: dict
"""
+ ld_library_path = os.path.join(get_path_prefix(), "..", "lib")
+ ld_library_path.encode(sys.getfilesystemencoding())
return {
- "LD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib")
+ "LD_LIBRARY_PATH": ld_library_path
}
diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py
index bce3599b..07497814 100644
--- a/src/leap/bitmask/services/eip/vpnlauncher.py
+++ b/src/leap/bitmask/services/eip/vpnlauncher.py
@@ -250,9 +250,6 @@ class VPNLauncher(object):
'--ping-restart', '30']
command_and_args = [openvpn] + args
- logger.debug("Running VPN with command:")
- logger.debug(" ".join(command_and_args))
-
return command_and_args
@classmethod
diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py
index 19e1aa7b..51f0f738 100644
--- a/src/leap/bitmask/services/eip/vpnprocess.py
+++ b/src/leap/bitmask/services/eip/vpnprocess.py
@@ -23,6 +23,7 @@ import psutil
import psutil.error
import shutil
import socket
+import sys
from itertools import chain, repeat
@@ -864,15 +865,26 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):
"""
Gets the vpn command from the aproppriate launcher.
- Might throw: VPNLauncherException, OpenVPNNotFoundException.
+ Might throw:
+ VPNLauncherException,
+ OpenVPNNotFoundException.
+
+ :rtype: list of str
"""
- cmd = self._launcher.get_vpn_command(
+ command = self._launcher.get_vpn_command(
eipconfig=self._eipconfig,
providerconfig=self._providerconfig,
socket_host=self._socket_host,
socket_port=self._socket_port,
openvpn_verb=self._openvpn_verb)
- return map(str, cmd)
+
+ encoding = sys.getfilesystemencoding()
+ for i, c in enumerate(command):
+ if not isinstance(c, str):
+ command[i] = c.encode(encoding)
+
+ logger.debug("Running VPN with command: {0}".format(command))
+ return command
# shutdown
diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py
new file mode 100644
index 00000000..c294381b
--- /dev/null
+++ b/src/leap/bitmask/services/mail/conductor.py
@@ -0,0 +1,384 @@
+# -*- coding: utf-8 -*-
+# conductor.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/>.
+"""
+Mail Services Conductor
+"""
+import logging
+import os
+
+from PySide import QtCore
+from zope.proxy import sameProxiedObjects
+
+from leap.bitmask.gui import statemachines
+from leap.bitmask.services.mail import imap
+from leap.bitmask.services.mail import connection as mail_connection
+from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper
+from leap.bitmask.services.mail.smtpconfig import SMTPConfig
+from leap.bitmask.util import is_file
+
+from leap.common.check import leap_assert
+
+from leap.common.events import register as leap_register
+from leap.common.events import events_pb2 as leap_events
+
+logger = logging.getLogger(__name__)
+
+
+class IMAPControl(object):
+ """
+ Methods related to IMAP control.
+ """
+ def __init__(self):
+ """
+ Initializes smtp variables.
+ """
+ self.imap_machine = None
+ self.imap_service = None
+ self.imap_port = None
+ self.imap_factory = None
+ self.imap_connection = None
+
+ leap_register(signal=leap_events.IMAP_SERVICE_STARTED,
+ callback=self._handle_imap_events,
+ reqcbk=lambda req, resp: None)
+ leap_register(signal=leap_events.IMAP_SERVICE_FAILED_TO_START,
+ callback=self._handle_imap_events,
+ reqcbk=lambda req, resp: None)
+
+ def set_imap_connection(self, imap_connection):
+ """
+ Sets the imap connection to an initialized connection.
+
+ :param imap_connection: an initialized imap connection
+ :type imap_connection: IMAPConnection instance.
+ """
+ self.imap_connection = imap_connection
+
+ def start_imap_service(self):
+ """
+ Starts imap service.
+ """
+ logger.debug('Starting imap service')
+ leap_assert(sameProxiedObjects(self._soledad, None)
+ is not True,
+ "We need a non-null soledad for initializing imap service")
+ leap_assert(sameProxiedObjects(self._keymanager, None)
+ is not True,
+ "We need a non-null keymanager for initializing imap "
+ "service")
+
+ if self.imap_service is None:
+ # first time.
+ self.imap_service, \
+ self.imap_port, \
+ self.imap_factory = imap.start_imap_service(
+ self._soledad,
+ self._keymanager)
+ else:
+ # we have the fetcher. just start it.
+ self.imap_service.start_loop()
+
+ def stop_imap_service(self):
+ """
+ Stops imap service (fetcher, factory and port).
+ """
+ self.imap_connection.qtsigs.disconnecting_signal.emit()
+ # TODO We should homogenize both services.
+ if self.imap_service is not None:
+ logger.debug('Stopping imap service.')
+ # Stop the loop call in the fetcher
+ self.imap_service.stop()
+ # Stop listening on the IMAP port
+ self.imap_port.stopListening()
+ # Stop the protocol
+ self.imap_factory.doStop()
+
+ def fetch_incoming_mail(self):
+ """
+ Fetches incoming mail.
+ """
+ # TODO have a mutex over fetch operation.
+ if self.imap_service:
+ logger.debug('Client connected, fetching mail...')
+ self.imap_service.fetch()
+
+ # handle events
+
+ def _handle_imap_events(self, req):
+ """
+ Callback handler for the IMAP events
+
+ :param req: Request type
+ :type req: leap.common.events.events_pb2.SignalRequest
+ """
+ if req.event == leap_events.IMAP_SERVICE_STARTED:
+ self.on_imap_connected()
+ elif req.event == leap_events.IMAP_SERVICE_FAILED_TO_START:
+ self.on_imap_failed()
+
+ # emit connection signals
+
+ def on_imap_connecting(self):
+ """
+ Callback for IMAP connecting state.
+ """
+ self.imap_connection.qtsigs.connecting_signal.emit()
+
+ def on_imap_connected(self):
+ """
+ Callback for IMAP connected state.
+ """
+ self.imap_connection.qtsigs.connected_signal.emit()
+
+ def on_imap_failed(self):
+ """
+ Callback for IMAP failed state.
+ """
+ self.imap_connection.qtsigs.connetion_aborted_signal.emit()
+
+
+class SMTPControl(object):
+
+ PORT_KEY = "port"
+ IP_KEY = "ip_address"
+
+ def __init__(self):
+ """
+ Initializes smtp variables.
+ """
+ self.smtp_config = SMTPConfig()
+ self.smtp_connection = None
+ self.smtp_machine = None
+ self._smtp_service = None
+ self._smtp_port = None
+
+ self.smtp_bootstrapper = SMTPBootstrapper()
+ self.smtp_bootstrapper.download_config.connect(
+ self.smtp_bootstrapped_stage)
+
+ leap_register(signal=leap_events.SMTP_SERVICE_STARTED,
+ callback=self._handle_smtp_events,
+ reqcbk=lambda req, resp: None)
+ leap_register(signal=leap_events.SMTP_SERVICE_FAILED_TO_START,
+ callback=self._handle_smtp_events,
+ reqcbk=lambda req, resp: None)
+
+ def set_smtp_connection(self, smtp_connection):
+ """
+ Sets the smtp connection to an initialized connection.
+ :param smtp_connection: an initialized smtp connection
+ :type smtp_connection: SMTPConnection instance.
+ """
+ self.smtp_connection = smtp_connection
+
+ def start_smtp_service(self, host, port, cert):
+ """
+ Starts the smtp service.
+
+ :param host: the hostname of the remove SMTP server.
+ :type host: str
+ :param port: the port of the remote SMTP server
+ :type port: str
+ :param cert: the client certificate for authentication
+ :type cert: str
+ """
+ # TODO Make the encrypted_only configurable
+ # TODO pick local smtp port in a better way
+ # TODO remove hard-coded port and let leap.mail set
+ # the specific default.
+ self.smtp_connection.qtsigs.connecting_signal.emit()
+ from leap.mail.smtp import setup_smtp_relay
+ self._smtp_service, self._smtp_port = setup_smtp_relay(
+ port=2013,
+ keymanager=self._keymanager,
+ smtp_host=host,
+ smtp_port=port,
+ smtp_cert=cert,
+ smtp_key=cert,
+ encrypted_only=False)
+
+ def stop_smtp_service(self):
+ """
+ Stops the smtp service (port and factory).
+ """
+ self.smtp_connection.qtsigs.disconnecting_signal.emit()
+ # TODO We should homogenize both services.
+ if self._smtp_service is not None:
+ logger.debug('Stopping smtp service.')
+ self._smtp_port.stopListening()
+ self._smtp_service.doStop()
+
+ @QtCore.Slot()
+ def smtp_bootstrapped_stage(self, data):
+ """
+ SLOT
+ TRIGGERS:
+ self.smtp_bootstrapper.download_config
+
+ If there was a problem, displays it, otherwise it does nothing.
+ This is used for intermediate bootstrapping stages, in case
+ they fail.
+
+ :param data: result from the bootstrapping stage for Soledad
+ :type data: dict
+ """
+ passed = data[self.smtp_bootstrapper.PASSED_KEY]
+ if not passed:
+ logger.error(data[self.smtp_bootstrapper.ERROR_KEY])
+ return
+ logger.debug("Done bootstrapping SMTP")
+ self.check_smtp_config()
+
+ def check_smtp_config(self):
+ """
+ Checks smtp config and tries to download smtp client cert if needed.
+ Currently called when smtp_bootstrapped_stage has successfuly finished.
+ """
+ logger.debug("Checking SMTP config...")
+ leap_assert(self.smtp_bootstrapper._provider_config,
+ "smtp bootstrapper does not have a provider_config")
+
+ provider_config = self.smtp_bootstrapper._provider_config
+ smtp_config = self.smtp_config
+ hosts = smtp_config.get_hosts()
+ # TODO handle more than one host and define how to choose
+ if len(hosts) > 0:
+ hostname = hosts.keys()[0]
+ logger.debug("Using hostname %s for SMTP" % (hostname,))
+ host = hosts[hostname][self.IP_KEY].encode("utf-8")
+ port = hosts[hostname][self.PORT_KEY]
+
+ client_cert = smtp_config.get_client_cert_path(
+ provider_config,
+ about_to_download=True)
+
+ # XXX change this logic!
+ # check_config should be called from within start_service,
+ # and not the other way around.
+ if not is_file(client_cert):
+ self.smtp_bootstrapper._download_client_certificates()
+ if os.path.isfile(client_cert):
+ self.start_smtp_service(host, port, client_cert)
+ else:
+ logger.warning("Tried to download email client "
+ "certificate, but could not find any")
+
+ else:
+ logger.warning("No smtp hosts configured")
+
+ # handle smtp events
+
+ def _handle_smtp_events(self, req):
+ """
+ Callback handler for the SMTP events.
+
+ :param req: Request type
+ :type req: leap.common.events.events_pb2.SignalRequest
+ """
+ if req.event == leap_events.SMTP_SERVICE_STARTED:
+ self.on_smtp_connected()
+ elif req.event == leap_events.SMTP_SERVICE_FAILED_TO_START:
+ self.on_smtp_failed()
+
+ # emit connection signals
+
+ def on_smtp_connecting(self):
+ """
+ Callback for SMTP connecting state.
+ """
+ self.smtp_connection.qtsigs.connecting_signal.emit()
+
+ def on_smtp_connected(self):
+ """
+ Callback for SMTP connected state.
+ """
+ self.smtp_connection.qtsigs.connected_signal.emit()
+
+ def on_smtp_failed(self):
+ """
+ Callback for SMTP failed state.
+ """
+ self.smtp_connection.qtsigs.connection_aborted_signal.emit()
+
+
+class MailConductor(IMAPControl, SMTPControl):
+ """
+ This class encapsulates everything related to the initialization and
+ process control for the mail services.
+ Currently, it initializes IMAPConnection and SMPTConnection.
+ """
+ # XXX We could consider to use composition instead of inheritance here.
+
+ def __init__(self, soledad, keymanager):
+ """
+ Initializes the mail conductor.
+
+ :param soledad: a transparent proxy that eventually will point to a
+ Soledad Instance.
+ :type soledad: zope.proxy.ProxyBase
+
+ :param keymanager: a transparent proxy that eventually will point to a
+ Keymanager Instance.
+ :type soledad: zope.proxy.ProxyBase
+ """
+ IMAPControl.__init__(self)
+ SMTPControl.__init__(self)
+ self._soledad = soledad
+ self._keymanager = keymanager
+
+ self._mail_machine = None
+
+ self._mail_connection = mail_connection.MailConnection()
+
+ def start_mail_machine(self, **kwargs):
+ """
+ Starts mail machine.
+ """
+ logger.debug("Starting mail state machine...")
+ builder = statemachines.ConnectionMachineBuilder(self._mail_connection)
+ (mail, (imap, smtp)) = builder.make_machine(**kwargs)
+
+ # we have instantiated the connections while building the composite
+ # machines, and we have to use the qtsigs instantiated there.
+ # XXX we could probably use a proxy here too to make the thing
+ # transparent.
+ self.set_imap_connection(imap.conn)
+ self.set_smtp_connection(smtp.conn)
+
+ self._mail_machine = mail
+ # XXX -------------------
+ # need to keep a reference?
+ #self._mail_events = mail.events
+ self._mail_machine.start()
+
+ self._imap_machine = imap
+ self._imap_machine.start()
+ self._smtp_machine = smtp
+ self._smtp_machine.start()
+
+ def connect_mail_signals(self, widget):
+ """
+ Connects the mail signals to the mail_status widget slots.
+
+ :param widget: the widget containing the slots.
+ :type widget: QtCore.QWidget
+ """
+ qtsigs = self._mail_connection.qtsigs
+ qtsigs.connected_signal.connect(widget.mail_state_connected)
+ qtsigs.connecting_signal.connect(widget.mail_state_connecting)
+ qtsigs.disconnecting_signal.connect(widget.mail_state_disconnecting)
+ qtsigs.disconnected_signal.connect(widget.mail_state_disconnected)
diff --git a/src/leap/bitmask/services/mail/connection.py b/src/leap/bitmask/services/mail/connection.py
new file mode 100644
index 00000000..29378f62
--- /dev/null
+++ b/src/leap/bitmask/services/mail/connection.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+# connection.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/>.
+"""
+Email Connections
+"""
+from PySide import QtCore
+
+from leap.bitmask.services.connections import AbstractLEAPConnection
+
+
+class IMAPConnectionSignals(QtCore.QObject):
+ """
+ Qt Signals used by IMAPConnection
+ """
+ # commands
+ do_connect_signal = QtCore.Signal()
+ do_disconnect_signal = QtCore.Signal()
+
+ # intermediate stages
+ connecting_signal = QtCore.Signal()
+ disconnecting_signal = QtCore.Signal()
+
+ connected_signal = QtCore.Signal()
+ disconnected_signal = QtCore.Signal()
+
+ connection_died_signal = QtCore.Signal()
+ connection_aborted_signal = QtCore.Signal()
+
+
+class IMAPConnection(AbstractLEAPConnection):
+
+ _connection_name = "IMAP"
+
+ def __init__(self):
+ self._qtsigs = IMAPConnectionSignals()
+
+
+class SMTPConnectionSignals(QtCore.QObject):
+ """
+ Qt Signals used by SMTPConnection
+ """
+ # commands
+ do_connect_signal = QtCore.Signal()
+ do_disconnect_signal = QtCore.Signal()
+
+ # intermediate stages
+ connecting_signal = QtCore.Signal()
+ disconnecting_signal = QtCore.Signal()
+
+ connected_signal = QtCore.Signal()
+ disconnected_signal = QtCore.Signal()
+
+ connection_died_signal = QtCore.Signal()
+ connection_aborted_signal = QtCore.Signal()
+
+
+class SMTPConnection(AbstractLEAPConnection):
+
+ _connection_name = "IMAP"
+
+ def __init__(self):
+ self._qtsigs = SMTPConnectionSignals()
+
+
+class MailConnectionSignals(QtCore.QObject):
+ """
+ Qt Signals used by MailConnection
+ """
+ # commands
+ do_connect_signal = QtCore.Signal()
+ do_disconnect_signal = QtCore.Signal()
+
+ connecting_signal = QtCore.Signal()
+ disconnecting_signal = QtCore.Signal()
+
+ connected_signal = QtCore.Signal()
+ disconnected_signal = QtCore.Signal()
+
+ connection_died_signal = QtCore.Signal()
+ connection_aborted_signal = QtCore.Signal()
+
+
+class MailConnection(AbstractLEAPConnection):
+
+ components = IMAPConnection, SMTPConnection
+ _connection_name = "Mail"
+
+ def __init__(self):
+ self._qtsigs = MailConnectionSignals()
diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py
index 4828180e..2667f156 100644
--- a/src/leap/bitmask/services/mail/imap.py
+++ b/src/leap/bitmask/services/mail/imap.py
@@ -41,9 +41,10 @@ def get_mail_check_period():
try:
period = int(period_str)
except (ValueError, TypeError):
- logger.warning("BAD value found for %s: %s" % (
- INCOMING_CHECK_PERIOD_ENV,
- period_str))
+ if period is not None:
+ logger.warning("BAD value found for %s: %s" % (
+ INCOMING_CHECK_PERIOD_ENV,
+ period_str))
except Exception as exc:
logger.warning("Unhandled error while getting %s: %r" % (
INCOMING_CHECK_PERIOD_ENV,
diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
index 4619ba80..54ef67eb 100644
--- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py
+++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
@@ -20,11 +20,13 @@ Soledad bootstrapping
import logging
import os
import socket
+import sys
from ssl import SSLError
from PySide import QtCore
from u1db import errors as u1db_errors
+from zope.proxy import sameProxiedObjects
from leap.bitmask.config import flags
from leap.bitmask.config.providerconfig import ProviderConfig
@@ -39,7 +41,7 @@ from leap.common.check import leap_assert, leap_assert_type, leap_check
from leap.common.files import which
from leap.keymanager import KeyManager, openpgp
from leap.keymanager.errors import KeyNotFound
-from leap.soledad.client import Soledad
+from leap.soledad.client import Soledad, BootstrapSequenceError
logger = logging.getLogger(__name__)
@@ -190,8 +192,8 @@ class SoledadBootstrapper(AbstractBootstrapper):
# soledad-launcher in the gui.
raise
- leap_check(self._soledad is not None,
- "Null soledad, error while initializing")
+ leap_assert(not sameProxiedObjects(self._soledad, None),
+ "Null soledad, error while initializing")
# and now, let's sync
sync_tries = self.MAX_SYNC_RETRIES
@@ -239,14 +241,15 @@ class SoledadBootstrapper(AbstractBootstrapper):
"""
# TODO: If selected server fails, retry with another host
# (issue #3309)
+ encoding = sys.getfilesystemencoding()
try:
self._soledad = Soledad(
uuid,
- self._password.encode("utf-8"),
- secrets_path=secrets_path,
- local_db_path=local_db_path,
+ self._password,
+ secrets_path=secrets_path.encode(encoding),
+ local_db_path=local_db_path.encode(encoding),
server_url=server_url,
- cert_file=cert_file,
+ cert_file=cert_file.encode(encoding),
auth_token=auth_token)
# XXX All these errors should be handled by soledad itself,
@@ -257,7 +260,10 @@ class SoledadBootstrapper(AbstractBootstrapper):
logger.debug("SOLEDAD initialization TIMED OUT...")
self.soledad_timeout.emit()
except socket.error as exc:
- logger.error("Socket error while initializing soledad")
+ logger.warning("Socket error while initializing soledad")
+ self.soledad_timeout.emit()
+ except BootstrapSequenceError as exc:
+ logger.warning("Error while initializing soledad")
self.soledad_timeout.emit()
# unrecoverable
@@ -280,7 +286,7 @@ class SoledadBootstrapper(AbstractBootstrapper):
Raises SoledadSyncError if not successful.
"""
try:
- logger.error("trying to sync soledad....")
+ logger.debug("trying to sync soledad....")
self._soledad.sync()
except SSLError as exc:
logger.error("%r" % (exc,))
@@ -412,9 +418,9 @@ class SoledadBootstrapper(AbstractBootstrapper):
:param provider_config: Provider configuration
:type provider_config: ProviderConfig
:param user: User's login
- :type user: str
+ :type user: unicode
:param password: User's password
- :type password: str
+ :type password: unicode
:param download_if_needed: If True, it will only download
files if the have changed since the
time it was previously downloaded.
diff --git a/src/leap/bitmask/util/compat.py b/src/leap/bitmask/util/compat.py
new file mode 100644
index 00000000..e34b9ead
--- /dev/null
+++ b/src/leap/bitmask/util/compat.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# compat.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/>.
+"""
+Utilities for dealing with compat versions.
+"""
+from distutils.version import LooseVersion as V
+
+from requests import __version__ as _requests_version
+
+
+def _requests_has_max_retries():
+ """
+ Returns True if we can use the max_retries parameter
+ """
+ return V(_requests_version) > V('1.1.0')
+
+requests_has_max_retries = _requests_has_max_retries()