summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2013-10-04 12:09:58 -0300
committerTomás Touceda <chiiph@leap.se>2013-10-04 12:09:58 -0300
commit584866689560bd2ebea01ecc5e6ae5e79ce7fc81 (patch)
tree4fe300c3c105e17721f79eeece9bbd3745b43d78 /src
parent1f0f8efc4cb985c082b3b8fe7b3dc45aed047a47 (diff)
parent759d73ae0728f074e0fb0740269249d0e5066574 (diff)
Merge branch 'release-0.3.4'0.3.4
Diffstat (limited to 'src')
-rw-r--r--src/leap/bitmask/app.py20
-rw-r--r--src/leap/bitmask/config/leapsettings.py36
-rw-r--r--src/leap/bitmask/config/providerconfig.py19
-rw-r--r--src/leap/bitmask/config/tests/test_leapsettings.py5
-rw-r--r--src/leap/bitmask/config/tests/test_providerconfig.py12
-rw-r--r--src/leap/bitmask/crypto/srpauth.py37
-rw-r--r--src/leap/bitmask/crypto/tests/test_srpauth.py11
-rw-r--r--src/leap/bitmask/gui/clickablelabel.py28
-rw-r--r--src/leap/bitmask/gui/eip_preferenceswindow.py218
-rw-r--r--src/leap/bitmask/gui/eip_status.py435
-rw-r--r--src/leap/bitmask/gui/login.py131
-rw-r--r--src/leap/bitmask/gui/mail_status.py408
-rw-r--r--src/leap/bitmask/gui/mainwindow.py389
-rw-r--r--src/leap/bitmask/gui/preferenceswindow.py122
-rw-r--r--src/leap/bitmask/gui/statemachines.py16
-rw-r--r--src/leap/bitmask/gui/statuspanel.py710
-rw-r--r--src/leap/bitmask/gui/ui/eip_status.ui287
-rw-r--r--src/leap/bitmask/gui/ui/eippreferences.ui183
-rw-r--r--src/leap/bitmask/gui/ui/login.ui302
-rw-r--r--src/leap/bitmask/gui/ui/mail_status.ui98
-rw-r--r--src/leap/bitmask/gui/ui/mainwindow.ui437
-rw-r--r--src/leap/bitmask/gui/ui/preferences.ui175
-rw-r--r--src/leap/bitmask/gui/ui/statuspanel.ui393
-rw-r--r--src/leap/bitmask/gui/ui/wizard.ui186
-rw-r--r--src/leap/bitmask/gui/wizard.py79
-rw-r--r--src/leap/bitmask/platform_init/initializers.py10
-rw-r--r--src/leap/bitmask/provider/__init__.py34
-rw-r--r--src/leap/bitmask/provider/providerbootstrapper.py (renamed from src/leap/bitmask/services/eip/providerbootstrapper.py)74
-rw-r--r--src/leap/bitmask/provider/tests/__init__.py (renamed from src/leap/bitmask/config/__init__.py)0
-rw-r--r--src/leap/bitmask/provider/tests/test_providerbootstrapper.py (renamed from src/leap/bitmask/services/eip/tests/test_providerbootstrapper.py)61
-rw-r--r--src/leap/bitmask/services/__init__.py12
-rw-r--r--src/leap/bitmask/services/connections.py1
-rw-r--r--src/leap/bitmask/services/eip/__init__.py27
-rw-r--r--src/leap/bitmask/services/eip/connection.py1
-rw-r--r--src/leap/bitmask/services/eip/darwinvpnlauncher.py190
-rw-r--r--src/leap/bitmask/services/eip/eipbootstrapper.py1
-rw-r--r--src/leap/bitmask/services/eip/eipconfig.py40
-rw-r--r--src/leap/bitmask/services/eip/linuxvpnlauncher.py232
-rw-r--r--src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py17
-rw-r--r--src/leap/bitmask/services/eip/tests/test_eipconfig.py12
-rw-r--r--src/leap/bitmask/services/eip/vpnlauncher.py290
-rw-r--r--src/leap/bitmask/services/eip/vpnlaunchers.py963
-rw-r--r--src/leap/bitmask/services/eip/vpnprocess.py4
-rw-r--r--src/leap/bitmask/services/eip/windowsvpnlauncher.py69
-rw-r--r--src/leap/bitmask/services/soledad/soledadbootstrapper.py366
-rw-r--r--src/leap/bitmask/util/__init__.py14
-rw-r--r--src/leap/bitmask/util/constants.py2
47 files changed, 4106 insertions, 3051 deletions
diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py
index 02b1693d..c1859478 100644
--- a/src/leap/bitmask/app.py
+++ b/src/leap/bitmask/app.py
@@ -152,12 +152,6 @@ def main():
"""
Starts the main event loop and launches the main window.
"""
- try:
- event_server.ensure_server(event_server.SERVER_PORT)
- except Exception as e:
- # We don't even have logger configured in here
- print "Could not ensure server: %r" % (e,)
-
_, opts = leap_argparse.init_leapc_args()
if opts.version:
@@ -170,6 +164,12 @@ def main():
logfile = opts.log_file
openvpn_verb = opts.openvpn_verb
+ try:
+ event_server.ensure_server(event_server.SERVER_PORT)
+ except Exception as e:
+ # We don't even have logger configured in here
+ print "Could not ensure server: %r" % (e,)
+
#############################################################
# Given how paths and bundling works, we need to delay the imports
# of certain parts that depend on this path settings.
@@ -179,6 +179,9 @@ def main():
flags.STANDALONE = standalone
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
from leap.bitmask.gui import twisted_main
@@ -190,9 +193,6 @@ def main():
# pylint: avoid unused import
assert(locale_rc)
- logger = add_logger_handlers(debug, logfile)
- replace_stdout_stderr_with_logging(logger)
-
if not we_are_the_one_and_only():
# Bitmask is already running
logger.warning("Tried to launch more than one instance "
@@ -211,7 +211,7 @@ def main():
# We force the style if on KDE so that it doesn't load all the kde
# libs, which causes a compatibility issue in some systems.
# For more info, see issue #3194
- if os.environ.get("KDE_SESSION_UID") is not None:
+ if flags.STANDALONE and os.environ.get("KDE_SESSION_UID") is not None:
sys.argv.append("-style")
sys.argv.append("Cleanlooks")
diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py
index 338fa475..4660535a 100644
--- a/src/leap/bitmask/config/leapsettings.py
+++ b/src/leap/bitmask/config/leapsettings.py
@@ -65,8 +65,10 @@ class LeapSettings(object):
PROPERPROVIDER_KEY = "ProperProvider"
REMEMBER_KEY = "RememberUserAndPass"
DEFAULTPROVIDER_KEY = "DefaultProvider"
+ AUTOSTARTEIP_KEY = "AutoStartEIP"
ALERTMISSING_KEY = "AlertMissingScripts"
GATEWAY_KEY = "Gateway"
+ PINNED_KEY = "Pinned"
# values
GATEWAY_AUTOMATIC = "Automatic"
@@ -134,6 +136,22 @@ class LeapSettings(object):
return providers
+ def is_pinned_provider(self, domain):
+ """
+ Returns True if the domain 'domain' is pinned with the application.
+ False otherwise.
+
+ :param provider: provider domain
+ :type provider: str
+
+ :rtype: bool
+ """
+ leap_assert(len(domain) > 0, "We need a nonempty domain.")
+ pinned_key = "{0}/{1}".format(domain, self.PINNED_KEY)
+ result = to_bool(self._settings.value(pinned_key, False))
+
+ return result
+
def get_selected_gateway(self, provider):
"""
Returns the configured gateway for the given provider.
@@ -285,6 +303,24 @@ class LeapSettings(object):
else:
self._settings.setValue(self.DEFAULTPROVIDER_KEY, provider)
+ def get_autostart_eip(self):
+ """
+ Gets whether the app should autostart EIP.
+
+ :rtype: bool
+ """
+ return to_bool(self._settings.value(self.AUTOSTARTEIP_KEY, False))
+
+ def set_autostart_eip(self, autostart):
+ """
+ Sets whether the app should autostart EIP.
+
+ :param autostart: True if we should try to autostart EIP.
+ :type autostart: bool
+ """
+ leap_assert_type(autostart, bool)
+ self._settings.setValue(self.AUTOSTARTEIP_KEY, autostart)
+
def get_alert_missing_scripts(self):
"""
Returns the setting for alerting of missing up/down scripts.
diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py
index c8c8a59e..44698d83 100644
--- a/src/leap/bitmask/config/providerconfig.py
+++ b/src/leap/bitmask/config/providerconfig.py
@@ -44,6 +44,25 @@ class ProviderConfig(BaseConfig):
def __init__(self):
BaseConfig.__init__(self)
+ @classmethod
+ def get_provider_config(self, domain):
+ """
+ Helper to return a valid Provider Config from the domain name.
+
+ :param domain: the domain name of the provider.
+ :type domain: str
+
+ :rtype: ProviderConfig or None if there is a problem loading the config
+ """
+ provider_config = ProviderConfig()
+ provider_config_path = os.path.join(
+ "leap", "providers", domain, "provider.json")
+
+ if not provider_config.load(provider_config_path):
+ provider_config = None
+
+ return provider_config
+
def _get_schema(self):
"""
Returns the schema corresponding to the version given.
diff --git a/src/leap/bitmask/config/tests/test_leapsettings.py b/src/leap/bitmask/config/tests/test_leapsettings.py
index 18166923..b45abfdd 100644
--- a/src/leap/bitmask/config/tests/test_leapsettings.py
+++ b/src/leap/bitmask/config/tests/test_leapsettings.py
@@ -29,6 +29,7 @@ import mock
from leap.common.testing.basetest import BaseLeapTest
from leap.bitmask.config.leapsettings import LeapSettings
+from leap.bitmask.config import flags
class LeapSettingsTest(BaseLeapTest):
@@ -44,6 +45,7 @@ class LeapSettingsTest(BaseLeapTest):
"""
Test that the config file IS NOT stored under the CWD.
"""
+ flags.STANDALONE = False
self._leapsettings = LeapSettings()
with mock.patch('os.listdir') as os_listdir:
# use this method only to spy where LeapSettings is looking for
@@ -57,7 +59,8 @@ class LeapSettingsTest(BaseLeapTest):
"""
Test that the config file IS stored under the CWD.
"""
- self._leapsettings = LeapSettings(standalone=True)
+ flags.STANDALONE = True
+ self._leapsettings = LeapSettings()
with mock.patch('os.listdir') as os_listdir:
# use this method only to spy where LeapSettings is looking for
self._leapsettings.get_configured_providers()
diff --git a/src/leap/bitmask/config/tests/test_providerconfig.py b/src/leap/bitmask/config/tests/test_providerconfig.py
index 7661a1ce..fe27e683 100644
--- a/src/leap/bitmask/config/tests/test_providerconfig.py
+++ b/src/leap/bitmask/config/tests/test_providerconfig.py
@@ -175,10 +175,9 @@ class ProviderConfigTest(BaseLeapTest):
def test_get_ca_cert_path_as_expected(self):
pc = self._provider_config
- pc.get_path_prefix = Mock(return_value='test')
provider_domain = sample_config['domain']
- expected_path = os.path.join('test', 'leap', 'providers',
+ expected_path = os.path.join('leap', 'providers',
provider_domain, 'keys', 'ca',
'cacert.pem')
@@ -186,24 +185,21 @@ class ProviderConfigTest(BaseLeapTest):
os.path.exists = Mock(return_value=True)
cert_path = pc.get_ca_cert_path()
- self.assertEqual(cert_path, expected_path)
+ self.assertTrue(cert_path.endswith(expected_path))
def test_get_ca_cert_path_about_to_download(self):
pc = self._provider_config
- pc.get_path_prefix = Mock(return_value='test')
provider_domain = sample_config['domain']
- expected_path = os.path.join('test', 'leap', 'providers',
+ expected_path = os.path.join('leap', 'providers',
provider_domain, 'keys', 'ca',
'cacert.pem')
cert_path = pc.get_ca_cert_path(about_to_download=True)
-
- self.assertEqual(cert_path, expected_path)
+ self.assertTrue(cert_path.endswith(expected_path))
def test_get_ca_cert_path_fails(self):
pc = self._provider_config
- pc.get_path_prefix = Mock(return_value='test')
# mock 'get_domain' so we don't need to load a config
provider_domain = 'test.provider.com'
diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py
index bf85f75c..cbff4b49 100644
--- a/src/leap/bitmask/crypto/srpauth.py
+++ b/src/leap/bitmask/crypto/srpauth.py
@@ -52,13 +52,6 @@ class SRPAuthConnectionError(SRPAuthenticationError):
pass
-class SRPAuthUnknownUser(SRPAuthenticationError):
- """
- Exception raised when trying to authenticate an unknown user
- """
- pass
-
-
class SRPAuthBadStatusCode(SRPAuthenticationError):
"""
Exception raised when we received an unknown bad status code
@@ -97,7 +90,7 @@ class SRPAuthJSONDecodeError(SRPAuthenticationError):
pass
-class SRPAuthBadPassword(SRPAuthenticationError):
+class SRPAuthBadUserOrPassword(SRPAuthenticationError):
"""
Exception raised when the user provided a bad password to auth.
"""
@@ -136,6 +129,7 @@ class SRPAuth(QtCore.QObject):
SESSION_ID_KEY = "_session_id"
USER_VERIFIER_KEY = 'user[password_verifier]'
USER_SALT_KEY = 'user[password_salt]'
+ AUTHORIZATION_KEY = "Authorization"
def __init__(self, provider_config):
"""
@@ -219,7 +213,6 @@ class SRPAuth(QtCore.QObject):
Might raise all SRPAuthenticationError based:
SRPAuthenticationError
SRPAuthConnectionError
- SRPAuthUnknownUser
SRPAuthBadStatusCode
SRPAuthNoSalt
SRPAuthNoB
@@ -266,7 +259,7 @@ class SRPAuth(QtCore.QObject):
"Status code = %r. Content: %r" %
(init_session.status_code, content))
if init_session.status_code == 422:
- raise SRPAuthUnknownUser(self._WRONG_USER_PASS)
+ raise SRPAuthBadUserOrPassword(self._WRONG_USER_PASS)
raise SRPAuthBadStatusCode(self.tr("There was a problem with"
" authentication"))
@@ -296,7 +289,7 @@ class SRPAuth(QtCore.QObject):
SRPAuthBadDataFromServer
SRPAuthConnectionError
SRPAuthJSONDecodeError
- SRPAuthBadPassword
+ SRPAuthBadUserOrPassword
:param salt_B: salt and B parameters for the username
:type salt_B: tuple
@@ -355,7 +348,7 @@ class SRPAuth(QtCore.QObject):
"received: %s", (content,))
logger.error("[%s] Wrong password (HAMK): [%s]" %
(auth_result.status_code, error))
- raise SRPAuthBadPassword(self._WRONG_USER_PASS)
+ raise SRPAuthBadUserOrPassword(self._WRONG_USER_PASS)
if auth_result.status_code not in (200,):
logger.error("No valid response (HAMK): "
@@ -452,7 +445,7 @@ class SRPAuth(QtCore.QObject):
It requires to be authenticated.
Might raise:
- SRPAuthBadPassword
+ SRPAuthBadUserOrPassword
requests.exceptions.HTTPError
:param current_password: the current password for the logged user.
@@ -463,7 +456,7 @@ class SRPAuth(QtCore.QObject):
leap_assert(self.get_uid() is not None)
if current_password != self._password:
- raise SRPAuthBadPassword
+ raise SRPAuthBadUserOrPassword
url = "%s/%s/users/%s.json" % (
self._provider_config.get_api_uri(),
@@ -474,6 +467,10 @@ class SRPAuth(QtCore.QObject):
self._username, new_password, self._hashfun, self._ng)
cookies = {self.SESSION_ID_KEY: self.get_session_id()}
+ headers = {
+ self.AUTHORIZATION_KEY:
+ "Token token={0}".format(self.get_token())
+ }
user_data = {
self.USER_VERIFIER_KEY: binascii.hexlify(verifier),
self.USER_SALT_KEY: binascii.hexlify(salt)
@@ -483,7 +480,8 @@ class SRPAuth(QtCore.QObject):
url, data=user_data,
verify=self._provider_config.get_ca_cert_path(),
cookies=cookies,
- timeout=REQUEST_TIMEOUT)
+ timeout=REQUEST_TIMEOUT,
+ headers=headers)
# In case of non 2xx it raises HTTPError
change_password.raise_for_status()
@@ -510,6 +508,8 @@ class SRPAuth(QtCore.QObject):
self._username = username
self._password = password
+ self._session = self._fetcher.session()
+
d = threads.deferToThread(self._authentication_preprocessing,
username=username,
password=password)
@@ -605,6 +605,13 @@ class SRPAuth(QtCore.QObject):
# Store instance reference as the only member in the handle
self.__dict__['_SRPAuth__instance'] = SRPAuth.__instance
+ # Generally, we initialize this with a provider_config once,
+ # and after that initialize it without one and use the one
+ # that was assigned before. But we need to update it if we
+ # want to be able to logout and login into another provider.
+ if provider_config is not None:
+ SRPAuth.__instance._provider_config = provider_config
+
def authenticate(self, username, password):
"""
Executes the whole authentication process for a user
diff --git a/src/leap/bitmask/crypto/tests/test_srpauth.py b/src/leap/bitmask/crypto/tests/test_srpauth.py
index 6fb2b739..e63c1385 100644
--- a/src/leap/bitmask/crypto/tests/test_srpauth.py
+++ b/src/leap/bitmask/crypto/tests/test_srpauth.py
@@ -246,7 +246,7 @@ class SRPAuthTestCase(unittest.TestCase):
d = self._prepare_auth_test(422)
def wrapper(_):
- with self.assertRaises(srpauth.SRPAuthUnknownUser):
+ with self.assertRaises(srpauth.SRPAuthBadUserOrPassword):
with mock.patch(
'leap.bitmask.util.request_helpers.get_content',
new=mock.create_autospec(get_content)) as content:
@@ -425,7 +425,7 @@ class SRPAuthTestCase(unittest.TestCase):
new=mock.create_autospec(get_content)) as \
content:
content.return_value = ("", 0)
- with self.assertRaises(srpauth.SRPAuthBadPassword):
+ with self.assertRaises(srpauth.SRPAuthBadUserOrPassword):
self.auth_backend._process_challenge(
salt_B,
username=self.TEST_USER)
@@ -449,7 +449,7 @@ class SRPAuthTestCase(unittest.TestCase):
new=mock.create_autospec(get_content)) as \
content:
content.return_value = ("[]", 0)
- with self.assertRaises(srpauth.SRPAuthBadPassword):
+ with self.assertRaises(srpauth.SRPAuthBadUserOrPassword):
self.auth_backend._process_challenge(
salt_B,
username=self.TEST_USER)
@@ -680,10 +680,7 @@ class SRPAuthTestCase(unittest.TestCase):
self.auth_backend._session.delete,
side_effect=Exception())
- def wrapper(*args):
- self.auth_backend.logout()
-
- d = threads.deferToThread(wrapper)
+ d = threads.deferToThread(self.auth.logout)
return d
@deferred()
diff --git a/src/leap/bitmask/gui/clickablelabel.py b/src/leap/bitmask/gui/clickablelabel.py
new file mode 100644
index 00000000..2808a601
--- /dev/null
+++ b/src/leap/bitmask/gui/clickablelabel.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# clickablelabel.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/>.
+
+"""
+Clickable label
+"""
+from PySide import QtCore, QtGui
+
+
+class ClickableLabel(QtGui.QLabel):
+ clicked = QtCore.Signal()
+
+ def mousePressEvent(self, event):
+ self.clicked.emit()
diff --git a/src/leap/bitmask/gui/eip_preferenceswindow.py b/src/leap/bitmask/gui/eip_preferenceswindow.py
new file mode 100644
index 00000000..9f8c4ff4
--- /dev/null
+++ b/src/leap/bitmask/gui/eip_preferenceswindow.py
@@ -0,0 +1,218 @@
+# -*- coding: utf-8 -*-
+# eip_preferenceswindow.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/>.
+
+"""
+EIP Preferences window
+"""
+import os
+import logging
+
+from functools import partial
+from PySide import QtGui
+
+from leap.bitmask.config.leapsettings import LeapSettings
+from leap.bitmask.config.providerconfig import ProviderConfig
+from leap.bitmask.gui.ui_eippreferences import Ui_EIPPreferences
+from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector
+
+logger = logging.getLogger(__name__)
+
+
+class EIPPreferencesWindow(QtGui.QDialog):
+ """
+ Window that displays the EIP preferences.
+ """
+ def __init__(self, parent):
+ """
+ :param parent: parent object of the EIPPreferencesWindow.
+ :parent type: QWidget
+ """
+ QtGui.QDialog.__init__(self, parent)
+ self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic")
+
+ self._settings = LeapSettings()
+
+ # Load UI
+ 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(
+ self._populate_gateways)
+
+ 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):
+ """
+ Sets the status label for the gateway change.
+
+ :param status: status message to display, can be HTML
+ :type status: str
+ :param success: is set to True if we should display the
+ message as green
+ :type success: bool
+ :param error: is set to True if we should display the
+ message as red
+ :type error: bool
+ """
+ if success:
+ status = "<font color='green'><b>%s</b></font>" % (status,)
+ elif error:
+ status = "<font color='red'><b>%s</b></font>" % (status,)
+
+ self.ui.lblProvidersGatewayStatus.setVisible(True)
+ self.ui.lblProvidersGatewayStatus.setText(status)
+
+ def _add_configured_providers(self):
+ """
+ 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):
+ """
+ SLOT
+ TRIGGERS:
+ self.ui.pbSaveGateway.clicked
+
+ Saves the new gateway setting to the configuration file.
+
+ :param provider: the provider config that we need to save.
+ :type provider: str
+ """
+ gateway = self.ui.cbGateways.currentText()
+
+ if gateway == self.AUTOMATIC_GATEWAY_LABEL:
+ gateway = self._settings.GATEWAY_AUTOMATIC
+ else:
+ idx = self.ui.cbGateways.currentIndex()
+ gateway = self.ui.cbGateways.itemData(idx)
+
+ self._settings.set_selected_gateway(provider, gateway)
+
+ msg = self.tr(
+ "Gateway settings for provider '{0}' saved.").format(provider)
+ self._set_providers_gateway_status(msg, success=True)
+
+ def _populate_gateways(self, domain):
+ """
+ SLOT
+ TRIGGERS:
+ self.ui.cbProvidersGateway.currentIndexChanged[unicode]
+
+ Loads the gateways that the provider provides into the UI for
+ the user to select.
+
+ :param domain: the domain of the provider to load gateways from.
+ :type domain: str
+ """
+ # We hide the maybe-visible status label after a change
+ self.ui.lblProvidersGatewayStatus.setVisible(False)
+
+ if not domain:
+ return
+
+ try:
+ # disconnect previously connected save method
+ self.ui.pbSaveGateway.clicked.disconnect()
+ except RuntimeError:
+ pass # Signal was not connected
+
+ # set the proper connection for the 'save' button
+ save_gateway = partial(self._save_selected_gateway, domain)
+ self.ui.pbSaveGateway.clicked.connect(save_gateway)
+
+ eip_config = EIPConfig()
+ provider_config = ProviderConfig.get_provider_config(domain)
+
+ eip_config_path = os.path.join("leap", "providers",
+ domain, "eip-service.json")
+ api_version = provider_config.get_api_version()
+ eip_config.set_api_version(api_version)
+ eip_loaded = eip_config.load(eip_config_path)
+
+ if not eip_loaded or provider_config is None:
+ self._set_providers_gateway_status(
+ self.tr("There was a problem with configuration files."),
+ error=True)
+ return
+
+ gateways = VPNGatewaySelector(eip_config).get_gateways_list()
+ logger.debug(gateways)
+
+ self.ui.cbGateways.clear()
+ self.ui.cbGateways.addItem(self.AUTOMATIC_GATEWAY_LABEL)
+
+ # Add the available gateways and
+ # select the one stored in configuration file.
+ selected_gateway = self._settings.get_selected_gateway(domain)
+ index = 0
+ for idx, (gw_name, gw_ip) in enumerate(gateways):
+ gateway = "{0} ({1})".format(gw_name, gw_ip)
+ self.ui.cbGateways.addItem(gateway, gw_ip)
+ if gw_ip == selected_gateway:
+ index = idx + 1
+
+ self.ui.cbGateways.setCurrentIndex(index)
diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py
new file mode 100644
index 00000000..946eaa4e
--- /dev/null
+++ b/src/leap/bitmask/gui/eip_status.py
@@ -0,0 +1,435 @@
+# -*- coding: utf-8 -*-
+# eip_status.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/>.
+"""
+EIP Status Panel widget implementation
+"""
+import logging
+
+from datetime import datetime
+from functools import partial
+
+from PySide import QtCore, QtGui
+
+from leap.bitmask.services.eip.connection import EIPConnection
+from leap.bitmask.services.eip.vpnprocess import VPNManager
+from leap.bitmask.platform_init import IS_LINUX
+from leap.bitmask.util.averages import RateMovingAverage
+from leap.common.check import leap_assert_type
+
+from ui_eip_status import Ui_EIPStatus
+
+logger = logging.getLogger(__name__)
+
+
+class EIPStatusWidget(QtGui.QWidget):
+ """
+ EIP Status widget that displays the current state of the EIP service
+ """
+ DISPLAY_TRAFFIC_RATES = True
+ RATE_STR = "%14.2f KB/s"
+ TOTAL_STR = "%14.2f Kb"
+
+ eip_connection_connected = QtCore.Signal()
+
+ def __init__(self, parent=None):
+ QtGui.QWidget.__init__(self, parent)
+
+ self._systray = None
+ self._eip_status_menu = None
+
+ self.ui = Ui_EIPStatus()
+ self.ui.setupUi(self)
+
+ self.eipconnection = EIPConnection()
+
+ # set systray tooltip status
+ self._eip_status = ""
+
+ self.ui.eip_bandwidth.hide()
+
+ # Set the EIP status icons
+ self.CONNECTING_ICON = None
+ self.CONNECTED_ICON = None
+ self.ERROR_ICON = None
+ self.CONNECTING_ICON_TRAY = None
+ self.CONNECTED_ICON_TRAY = None
+ self.ERROR_ICON_TRAY = None
+ self._set_eip_icons()
+
+ self._set_traffic_rates()
+ self._make_status_clickable()
+
+ self._provider = ""
+
+ def _make_status_clickable(self):
+ """
+ Makes upload and download figures clickable.
+ """
+ onclicked = self._on_VPN_status_clicked
+ self.ui.btnUpload.clicked.connect(onclicked)
+ self.ui.btnDownload.clicked.connect(onclicked)
+
+ def _on_VPN_status_clicked(self):
+ """
+ SLOT
+ TRIGGER: self.ui.btnUpload.clicked
+ self.ui.btnDownload.clicked
+
+ Toggles between rate and total throughput display for vpn
+ status figures.
+ """
+ self.DISPLAY_TRAFFIC_RATES = not self.DISPLAY_TRAFFIC_RATES
+ self.update_vpn_status(None) # refresh
+
+ def _set_traffic_rates(self):
+ """
+ Initializes up and download rates.
+ """
+ self._up_rate = RateMovingAverage()
+ self._down_rate = RateMovingAverage()
+
+ self.ui.btnUpload.setText(self.RATE_STR % (0,))
+ self.ui.btnDownload.setText(self.RATE_STR % (0,))
+
+ def _reset_traffic_rates(self):
+ """
+ Resets up and download rates, and cleans up the labels.
+ """
+ self._up_rate.reset()
+ self._down_rate.reset()
+ self.update_vpn_status(None)
+
+ def _update_traffic_rates(self, up, down):
+ """
+ Updates up and download rates.
+
+ :param up: upload total.
+ :type up: int
+ :param down: download total.
+ :type down: int
+ """
+ ts = datetime.now()
+ self._up_rate.append((ts, up))
+ self._down_rate.append((ts, down))
+
+ def _get_traffic_rates(self):
+ """
+ Gets the traffic rates (in KB/s).
+
+ :returns: a tuple with the (up, down) rates
+ :rtype: tuple
+ """
+ up = self._up_rate
+ down = self._down_rate
+
+ return (up.get_average(), down.get_average())
+
+ def _get_traffic_totals(self):
+ """
+ Gets the traffic total throughput (in Kb).
+
+ :returns: a tuple with the (up, down) totals
+ :rtype: tuple
+ """
+ up = self._up_rate
+ down = self._down_rate
+
+ return (up.get_total(), down.get_total())
+
+ def _set_eip_icons(self):
+ """
+ Sets the EIP status icons for the main window and for the tray
+
+ MAC : dark icons
+ LINUX : dark icons in window, light icons in tray
+ WIN : light icons
+ """
+ EIP_ICONS = EIP_ICONS_TRAY = (
+ ":/images/black/32/wait.png",
+ ":/images/black/32/on.png",
+ ":/images/black/32/off.png")
+
+ if IS_LINUX:
+ EIP_ICONS_TRAY = (
+ ":/images/white/32/wait.png",
+ ":/images/white/32/on.png",
+ ":/images/white/32/off.png")
+
+ self.CONNECTING_ICON = QtGui.QPixmap(EIP_ICONS[0])
+ self.CONNECTED_ICON = QtGui.QPixmap(EIP_ICONS[1])
+ self.ERROR_ICON = QtGui.QPixmap(EIP_ICONS[2])
+
+ self.CONNECTING_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[0])
+ self.CONNECTED_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[1])
+ self.ERROR_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[2])
+
+ # Systray and actions
+
+ def set_systray(self, systray):
+ """
+ Sets the systray object to use.
+
+ :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"))
+
+ def _update_systray_tooltip(self):
+ """
+ Updates the system tray icon tooltip using the eip and mx status.
+ """
+ status = self.tr("Encrypted Internet: {0}").format(self._eip_status)
+ self._systray.setToolTip(status)
+
+ def set_action_eip_startstop(self, action_eip_startstop):
+ """
+ Sets the action_eip_startstop to use.
+
+ :param action_eip_startstop: action_eip_status to be used
+ :type action_eip_startstop: QtGui.QAction
+ """
+ self._action_eip_startstop = action_eip_startstop
+
+ def set_eip_status_menu(self, eip_status_menu):
+ """
+ Sets the eip_status_menu to use.
+
+ :param eip_status_menu: eip_status_menu to be used
+ :type eip_status_menu: QtGui.QMenu
+ """
+ leap_assert_type(eip_status_menu, QtGui.QMenu)
+ self._eip_status_menu = eip_status_menu
+
+ # EIP status ---
+
+ @property
+ def eip_button(self):
+ return self.ui.btnEipStartStop
+
+ @property
+ def eip_label(self):
+ return self.ui.lblEIPStatus
+
+ def eip_pre_up(self):
+ """
+ Triggered when the app activates eip.
+ Hides the status box and disables the start/stop button.
+ """
+ self.set_startstop_enabled(False)
+
+ @QtCore.Slot()
+ def disable_eip_start(self):
+ """
+ Triggered when a default provider_config has not been found.
+ Disables the start button and adds instructions to the user.
+ """
+ logger.debug('Hiding EIP start button')
+ # you might be tempted to change this for a .setEnabled(False).
+ # it won't work. it's under the claws of the state machine.
+ # probably the best thing would be to make a transitional
+ # transition there, but that's more involved.
+ self.eip_button.hide()
+ msg = self.tr("You must login to use Encrypted Internet")
+ self.eip_label.setText(msg)
+
+ @QtCore.Slot()
+ def enable_eip_start(self):
+ """
+ Triggered after a successful login.
+ Enables the start button.
+ """
+ logger.debug('Showing EIP start button')
+ self.eip_button.show()
+
+ # XXX disable (later) --------------------------
+ def set_eip_status(self, status, error=False):
+ """
+ Sets the status label at the VPN stage to status
+
+ :param status: status message
+ :type status: str or unicode
+ :param error: if the status is an erroneous one, then set this
+ to True
+ :type error: bool
+ """
+ leap_assert_type(error, bool)
+ self._eip_status = status
+ if error:
+ status = "<font color='red'>%s</font>" % (status,)
+ self.ui.lblEIPStatus.setText(status)
+ self.ui.lblEIPStatus.show()
+ self._update_systray_tooltip()
+
+ # XXX disable ---------------------------------
+ def set_startstop_enabled(self, value):
+ """
+ Enable or disable btnEipStartStop and _action_eip_startstop
+ based on value
+
+ :param value: True for enabled, False otherwise
+ :type value: bool
+ """
+ # TODO use disable_eip_start instead
+ # this should be handled by the state machine
+ leap_assert_type(value, bool)
+ self.ui.btnEipStartStop.setEnabled(value)
+ self._action_eip_startstop.setEnabled(value)
+
+ # XXX disable -----------------------------
+ def eip_started(self):
+ """
+ Sets the state of the widget to how it should look after EIP
+ has started
+ """
+ self.ui.btnEipStartStop.setText(self.tr("Turn OFF"))
+ self.ui.btnEipStartStop.disconnect(self)
+ self.ui.btnEipStartStop.clicked.connect(
+ self.eipconnection.qtsigs.do_connect_signal)
+
+ # XXX disable -----------------------------
+ def eip_stopped(self):
+ """
+ Sets the state of the widget to how it should look after EIP
+ has stopped
+ """
+ # XXX should connect this to EIPConnection.disconnected_signal
+ self._reset_traffic_rates()
+ # XXX disable -----------------------------
+ self.ui.btnEipStartStop.setText(self.tr("Turn ON"))
+ self.ui.btnEipStartStop.disconnect(self)
+ self.ui.btnEipStartStop.clicked.connect(
+ self.eipconnection.qtsigs.do_disconnect_signal)
+
+ self.ui.eip_bandwidth.hide()
+ self.ui.lblEIPMessage.setText(
+ self.tr("Traffic is being routed in the clear"))
+ self.ui.lblEIPStatus.show()
+
+ def update_vpn_status(self, data):
+ """
+ SLOT
+ TRIGGER: VPN.status_changed
+
+ Updates the download/upload labels based on the data provided
+ by the VPN thread.
+
+ :param data: a dictionary with the tcp/udp write and read totals.
+ If data is None, we just will refresh the display based
+ on the previous data.
+ :type data: dict
+ """
+ if data:
+ upload = float(data[VPNManager.TCPUDP_WRITE_KEY] or "0")
+ download = float(data[VPNManager.TCPUDP_READ_KEY] or "0")
+ self._update_traffic_rates(upload, download)
+
+ if self.DISPLAY_TRAFFIC_RATES:
+ uprate, downrate = self._get_traffic_rates()
+ upload_str = self.RATE_STR % (uprate,)
+ download_str = self.RATE_STR % (downrate,)
+
+ else: # display total throughput
+ uptotal, downtotal = self._get_traffic_totals()
+ upload_str = self.TOTAL_STR % (uptotal,)
+ download_str = self.TOTAL_STR % (downtotal,)
+
+ self.ui.btnUpload.setText(upload_str)
+ self.ui.btnDownload.setText(download_str)
+
+ def update_vpn_state(self, data):
+ """
+ SLOT
+ TRIGGER: VPN.state_changed
+
+ Updates the displayed VPN state based on the data provided by
+ the VPN thread.
+
+ Emits:
+ If the status is connected, we emit EIPConnection.qtsigs.
+ connected_signal
+ """
+ status = data[VPNManager.STATUS_STEP_KEY]
+ self.set_eip_status_icon(status)
+ if status == "CONNECTED":
+ self.ui.eip_bandwidth.show()
+ self.ui.lblEIPStatus.hide()
+
+ # XXX should be handled by the state machine too.
+ self.eip_connection_connected.emit()
+
+ # XXX should lookup status map in EIPConnection
+ elif status == "AUTH":
+ self.set_eip_status(self.tr("Authenticating..."))
+ elif status == "GET_CONFIG":
+ self.set_eip_status(self.tr("Retrieving configuration..."))
+ elif status == "WAIT":
+ self.set_eip_status(self.tr("Waiting to start..."))
+ elif status == "ASSIGN_IP":
+ self.set_eip_status(self.tr("Assigning IP"))
+ elif status == "RECONNECTING":
+ self.set_eip_status(self.tr("Reconnecting..."))
+ elif status == "ALREADYRUNNING":
+ # Put the following calls in Qt's event queue, otherwise
+ # the UI won't update properly
+ QtCore.QTimer.singleShot(
+ 0, self.eipconnection.qtsigs.do_disconnect_signal)
+ QtCore.QTimer.singleShot(0, partial(self.set_eip_status,
+ self.tr("Unable to start VPN, "
+ "it's already "
+ "running.")))
+ else:
+ self.set_eip_status(status)
+
+ def set_eip_icon(self, icon):
+ """
+ Sets the icon to display for EIP
+
+ :param icon: icon to display
+ :type icon: QPixmap
+ """
+ self.ui.lblVPNStatusIcon.setPixmap(icon)
+
+ def set_eip_status_icon(self, status):
+ """
+ Given a status step from the VPN thread, set the icon properly
+
+ :param status: status step
+ :type status: str
+ """
+ selected_pixmap = self.ERROR_ICON
+ selected_pixmap_tray = self.ERROR_ICON_TRAY
+ tray_message = self.tr("Encrypted Internet: OFF")
+ 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...")
+ elif status in ("CONNECTED"):
+ tray_message = self.tr("Encrypted Internet: ON")
+ selected_pixmap = self.CONNECTED_ICON
+ selected_pixmap_tray = self.CONNECTED_ICON_TRAY
+
+ self.set_eip_icon(selected_pixmap)
+ self._systray.setIcon(QtGui.QIcon(selected_pixmap_tray))
+ self._eip_status_menu.setTitle(tray_message)
+
+ def set_provider(self, provider):
+ self._provider = provider
+ self.ui.lblEIPMessage.setText(
+ self.tr("Route traffic through: {0}").format(self._provider))
diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py
index db7b8e2a..582f26be 100644
--- a/src/leap/bitmask/gui/login.py
+++ b/src/leap/bitmask/gui/login.py
@@ -14,16 +14,18 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
"""
Login widget implementation
"""
import logging
+import keyring
+
from PySide import QtCore, QtGui
from ui_login import Ui_LoginWidget
from leap.bitmask.util.keyring_helpers import has_keyring
+from leap.common.check import leap_assert_type
logger = logging.getLogger(__name__)
@@ -33,10 +35,11 @@ class LoginWidget(QtGui.QWidget):
Login widget that emits signals to display the wizard or to
perform login.
"""
-
# Emitted when the login button is clicked
login = QtCore.Signal()
+ logged_in_signal = QtCore.Signal()
cancel_login = QtCore.Signal()
+ logout = QtCore.Signal()
# Emitted when the user selects "Other..." in the provider
# combobox or click "Create Account"
@@ -76,13 +79,21 @@ class LoginWidget(QtGui.QWidget):
self.ui.cmbProviders.currentIndexChanged.connect(
self._current_provider_changed)
- self.ui.btnCreateAccount.clicked.connect(
- self.show_wizard)
+
+ self.ui.btnLogout.clicked.connect(
+ self.logout)
username_re = QtCore.QRegExp(self.BARE_USERNAME_REGEX)
self.ui.lnUser.setValidator(
QtGui.QRegExpValidator(username_re, self))
+ self.logged_out()
+
+ self.ui.btnLogout.clicked.connect(self.start_logout)
+
+ self.ui.clblErrorMsg.hide()
+ self.ui.clblErrorMsg.clicked.connect(self.ui.clblErrorMsg.hide)
+
def _remember_state_changed(self, state):
"""
Saves the remember state in the LeapSettings
@@ -185,8 +196,10 @@ class LoginWidget(QtGui.QWidget):
if len(status) > self.MAX_STATUS_WIDTH:
status = status[:self.MAX_STATUS_WIDTH] + "..."
if error:
- status = "<font color='red'><b>%s</b></font>" % (status,)
- self.ui.lblStatus.setText(status)
+ self.ui.clblErrorMsg.show()
+ self.ui.clblErrorMsg.setText(status)
+ else:
+ self.ui.lblStatus.setText(status)
def set_enabled(self, enabled=False):
"""
@@ -211,6 +224,7 @@ class LoginWidget(QtGui.QWidget):
"""
text = self.tr("Cancel")
login_or_cancel = self.cancel_login
+ hide_remember = enabled
if not enabled:
text = self.tr("Log In")
@@ -220,6 +234,8 @@ class LoginWidget(QtGui.QWidget):
self.ui.btnLogin.clicked.disconnect()
self.ui.btnLogin.clicked.connect(login_or_cancel)
+ self.ui.chkRemember.setVisible(not hide_remember)
+ self.ui.lblStatus.setVisible(hide_remember)
def _focus_password(self):
"""
@@ -243,3 +259,106 @@ class LoginWidget(QtGui.QWidget):
self.ui.cmbProviders.blockSignals(False)
else:
self._selected_provider_index = param
+
+ def start_login(self):
+ """
+ Setups the login widgets for actually performing the login and
+ performs some basic checks.
+
+ :returns: True if everything's good to go, False otherwise
+ :rtype: bool
+ """
+ username = self.get_user()
+ password = self.get_password()
+ provider = self.get_selected_provider()
+
+ self._enabled_services = self._settings.get_enabled_services(
+ self.get_selected_provider())
+
+ if len(provider) == 0:
+ self.set_status(
+ self.tr("Please select a valid provider"))
+ return False
+
+ if len(username) == 0:
+ self.set_status(
+ self.tr("Please provide a valid username"))
+ return False
+
+ if len(password) == 0:
+ self.set_status(
+ self.tr("Please provide a valid password"))
+ return False
+
+ self.set_status(self.tr("Logging in..."), error=False)
+ self.set_enabled(False)
+ self.ui.clblErrorMsg.hide()
+
+ if self.get_remember() and has_keyring():
+ # in the keyring and in the settings
+ # we store the value 'usename@provider'
+ username_domain = (username + '@' + provider).encode("utf8")
+ try:
+ keyring.set_password(self.KEYRING_KEY,
+ username_domain,
+ password.encode("utf8"))
+ # Only save the username if it was saved correctly in
+ # the keyring
+ self._settings.set_user(username_domain)
+ except Exception as e:
+ logger.exception("Problem saving data to keyring. %r"
+ % (e,))
+ return True
+
+ def logged_in(self):
+ """
+ Sets the widgets to the logged in state
+ """
+ self.ui.login_widget.hide()
+ self.ui.logged_widget.show()
+ self.ui.lblUser.setText("%s@%s" % (self.get_user(),
+ self.get_selected_provider()))
+ self.set_login_status("")
+ self.logged_in_signal.emit()
+
+ def logged_out(self):
+ """
+ Sets the widgets to the logged out state
+ """
+ self.ui.login_widget.show()
+ self.ui.logged_widget.hide()
+
+ self.set_password("")
+ self.set_enabled(True)
+ self.set_status("", error=False)
+
+ def set_login_status(self, msg, error=False):
+ """
+ Sets the status label for the logged in state.
+
+ :param msg: status message
+ :type msg: str or unicode
+ :param error: if the status is an erroneous one, then set this
+ to True
+ :type error: bool
+ """
+ leap_assert_type(error, bool)
+ if error:
+ msg = "<font color='red'><b>%s</b></font>" % (msg,)
+ self.ui.lblLoginStatus.setText(msg)
+ self.ui.lblLoginStatus.show()
+
+ def start_logout(self):
+ """
+ Sets the widgets to the logging out state
+ """
+ self.ui.btnLogout.setText(self.tr("Loggin out..."))
+ self.ui.btnLogout.setEnabled(False)
+
+ def done_logout(self):
+ """
+ Sets the widgets to the logged out state
+ """
+ self.ui.btnLogout.setText(self.tr("Logout"))
+ self.ui.btnLogout.setEnabled(True)
+ self.ui.clblErrorMsg.hide()
diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py
new file mode 100644
index 00000000..ab9052d7
--- /dev/null
+++ b/src/leap/bitmask/gui/mail_status.py
@@ -0,0 +1,408 @@
+# -*- coding: utf-8 -*-
+# mail_status.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 Status Panel widget implementation
+"""
+import logging
+
+from PySide import QtCore, QtGui
+
+from leap.bitmask.platform_init import IS_LINUX
+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
+
+from ui_mail_status import Ui_MailStatusWidget
+
+logger = logging.getLogger(__name__)
+
+
+class MailStatusWidget(QtGui.QWidget):
+ """
+ Status widget that displays the state of the LEAP Mail service
+ """
+ eip_connection_connected = QtCore.Signal()
+ _soledad_event = QtCore.Signal(object)
+ _smtp_event = QtCore.Signal(object)
+ _imap_event = QtCore.Signal(object)
+ _keymanager_event = QtCore.Signal(object)
+
+ def __init__(self, parent=None):
+ """
+ Constructor for MailStatusWidget
+
+ :param parent: parent widget for this one.
+ :type parent: QtGui.QWidget
+ """
+ QtGui.QWidget.__init__(self, parent)
+
+ self._systray = None
+
+ self.ui = Ui_MailStatusWidget()
+ self.ui.setupUi(self)
+
+ # set systray tooltip status
+ self._mx_status = ""
+
+ # Set the Mail status icons
+ self.CONNECTING_ICON = None
+ self.CONNECTED_ICON = None
+ self.ERROR_ICON = None
+ self.CONNECTING_ICON_TRAY = None
+ self.CONNECTED_ICON_TRAY = None
+ self.ERROR_ICON_TRAY = None
+ self._set_mail_icons()
+
+ register(signal=proto.KEYMANAGER_LOOKING_FOR_KEY,
+ callback=self._mail_handle_keymanager_events,
+ reqcbk=lambda req, resp: None)
+
+ register(signal=proto.KEYMANAGER_KEY_FOUND,
+ callback=self._mail_handle_keymanager_events,
+ reqcbk=lambda req, resp: None)
+
+ # register(signal=proto.KEYMANAGER_KEY_NOT_FOUND,
+ # callback=self._mail_handle_keymanager_events,
+ # reqcbk=lambda req, resp: None)
+
+ register(signal=proto.KEYMANAGER_STARTED_KEY_GENERATION,
+ callback=self._mail_handle_keymanager_events,
+ reqcbk=lambda req, resp: None)
+
+ register(signal=proto.KEYMANAGER_FINISHED_KEY_GENERATION,
+ callback=self._mail_handle_keymanager_events,
+ reqcbk=lambda req, resp: None)
+
+ register(signal=proto.KEYMANAGER_DONE_UPLOADING_KEYS,
+ callback=self._mail_handle_keymanager_events,
+ reqcbk=lambda req, resp: None)
+
+ register(signal=proto.SOLEDAD_DONE_DOWNLOADING_KEYS,
+ callback=self._mail_handle_soledad_events,
+ reqcbk=lambda req, resp: None)
+
+ register(signal=proto.SOLEDAD_DONE_UPLOADING_KEYS,
+ 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,
+ callback=self._mail_handle_imap_events,
+ reqcbk=lambda req, resp: None)
+
+ register(signal=proto.IMAP_SERVICE_FAILED_TO_START,
+ callback=self._mail_handle_imap_events,
+ reqcbk=lambda req, resp: None)
+
+ register(signal=proto.IMAP_UNREAD_MAIL,
+ 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(
+ self._mail_handle_imap_events_slot)
+ self._smtp_event.connect(
+ self._mail_handle_smtp_events_slot)
+ self._keymanager_event.connect(
+ self._mail_handle_keymanager_events_slot)
+
+ def _set_mail_icons(self):
+ """
+ Sets the Mail status icons for the main window and for the tray
+
+ MAC : dark icons
+ LINUX : dark icons in window, light icons in tray
+ WIN : light icons
+ """
+ EIP_ICONS = EIP_ICONS_TRAY = (
+ ":/images/black/32/wait.png",
+ ":/images/black/32/on.png",
+ ":/images/black/32/off.png")
+
+ if IS_LINUX:
+ EIP_ICONS_TRAY = (
+ ":/images/white/32/wait.png",
+ ":/images/white/32/on.png",
+ ":/images/white/32/off.png")
+
+ self.CONNECTING_ICON = QtGui.QPixmap(EIP_ICONS[0])
+ self.CONNECTED_ICON = QtGui.QPixmap(EIP_ICONS[1])
+ self.ERROR_ICON = QtGui.QPixmap(EIP_ICONS[2])
+
+ self.CONNECTING_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[0])
+ self.CONNECTED_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[1])
+ self.ERROR_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[2])
+
+ # Systray and actions
+
+ def set_systray(self, systray):
+ """
+ Sets the systray object to use.
+
+ :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"))
+
+ def _update_systray_tooltip(self):
+ """
+ Updates the system tray icon tooltip using the eip and mx status.
+ """
+ # TODO: Figure out how to handle this with the two status in different
+ # classes
+ # 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
+
+ def set_action_mail_status(self, action_mail_status):
+ """
+ Sets the action_mail_status to use.
+
+ :param action_mail_status: action_mail_status to be used
+ :type action_mail_status: QtGui.QAction
+ """
+ leap_assert_type(action_mail_status, QtGui.QAction)
+ self._action_mail_status = action_mail_status
+
+ def _set_mail_status(self, status, ready=0):
+ """
+ Sets the Mail status in the label and in the tray icon.
+
+ :param status: the status text to display
+ :type status: unicode
+ :param ready: 2 or >2 if mx is ready, 0 if stopped, 1 if it's
+ starting, < 0 if disabled.
+ :type ready: int
+ """
+ self.ui.lblMailStatus.setText(status)
+
+ self._mx_status = self.tr('OFF')
+ tray_status = self.tr('Mail is OFF')
+
+ icon = self.ERROR_ICON
+ if ready == 0:
+ self.ui.lblMailStatus.setText(
+ self.tr("You must be logged in to use encrypted email."))
+ elif ready == 1:
+ icon = self.CONNECTING_ICON
+ self._mx_status = self.tr('Starting..')
+ tray_status = self.tr('Mail is starting')
+ elif ready >= 2:
+ icon = self.CONNECTED_ICON
+ self._mx_status = self.tr('ON')
+ tray_status = self.tr('Mail is ON')
+ elif ready < 0:
+ tray_status = self.tr("Mail is disabled")
+
+ self.ui.lblMailStatusIcon.setPixmap(icon)
+ self._action_mail_status.setText(tray_status)
+ self._update_systray_tooltip()
+
+ def _mail_handle_soledad_events(self, req):
+ """
+ Callback for handling events that are emitted from Soledad
+
+ :param req: Request type
+ :type req: leap.common.events.events_pb2.SignalRequest
+ """
+ self._soledad_event.emit(req)
+
+ def _mail_handle_soledad_events_slot(self, req):
+ """
+ SLOT
+ TRIGGER: _mail_handle_soledad_events
+
+ Reacts to an Soledad event
+
+ :param req: Request type
+ :type req: leap.common.events.events_pb2.SignalRequest
+ """
+ self._set_mail_status(self.tr("Starting..."), ready=1)
+
+ ext_status = ""
+
+ if req.event == proto.SOLEDAD_DONE_UPLOADING_KEYS:
+ ext_status = self.tr("Soledad has started...")
+ elif req.event == proto.SOLEDAD_DONE_DOWNLOADING_KEYS:
+ ext_status = self.tr("Soledad is starting, please wait...")
+ else:
+ leap_assert(False,
+ "Don't know how to handle this state: %s"
+ % (req.event))
+
+ self._set_mail_status(ext_status, ready=1)
+
+ def _mail_handle_keymanager_events(self, req):
+ """
+ Callback for the KeyManager events
+
+ :param req: Request type
+ :type req: leap.common.events.events_pb2.SignalRequest
+ """
+ self._keymanager_event.emit(req)
+
+ def _mail_handle_keymanager_events_slot(self, req):
+ """
+ SLOT
+ TRIGGER: _mail_handle_keymanager_events
+
+ Reacts to an KeyManager event
+
+ :param req: Request type
+ :type req: leap.common.events.events_pb2.SignalRequest
+ """
+ # We want to ignore this kind of events once everything has
+ # started
+ if self._smtp_started and self._imap_started:
+ return
+
+ self._set_mail_status(self.tr("Starting..."), ready=1)
+
+ ext_status = ""
+
+ if req.event == proto.KEYMANAGER_LOOKING_FOR_KEY:
+ ext_status = self.tr("Looking for key for this user")
+ elif req.event == proto.KEYMANAGER_KEY_FOUND:
+ ext_status = self.tr("Found key! Starting mail...")
+ # 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...")
+ elif req.event == proto.KEYMANAGER_FINISHED_KEY_GENERATION:
+ ext_status = self.tr("Finished generating key!")
+ elif req.event == proto.KEYMANAGER_DONE_UPLOADING_KEYS:
+ ext_status = self.tr("Starting mail...")
+ else:
+ leap_assert(False,
+ "Don't know how to handle this state: %s"
+ % (req.event))
+
+ self._set_mail_status(ext_status, ready=1)
+
+ def _mail_handle_smtp_events(self, req):
+ """
+ Callback for the SMTP events
+
+ :param req: Request type
+ :type req: leap.common.events.events_pb2.SignalRequest
+ """
+ self._smtp_event.emit(req)
+
+ def _mail_handle_smtp_events_slot(self, req):
+ """
+ SLOT
+ TRIGGER: _mail_handle_smtp_events
+
+ Reacts to an SMTP event
+
+ :param req: Request type
+ :type req: leap.common.events.events_pb2.SignalRequest
+ """
+ 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"
+ % (req.event))
+
+ self._set_mail_status(ext_status, ready=2)
+
+ def _mail_handle_imap_events(self, req):
+ """
+ Callback for the IMAP events
+
+ :param req: Request type
+ :type req: leap.common.events.events_pb2.SignalRequest
+ """
+ self._imap_event.emit(req)
+
+ def _mail_handle_imap_events_slot(self, req):
+ """
+ SLOT
+ TRIGGER: _mail_handle_imap_events
+
+ Reacts to an IMAP event
+
+ :param req: Request type
+ :type req: leap.common.events.events_pb2.SignalRequest
+ """
+ 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:
+ self._set_mail_status(self.tr("%s Unread Emails") %
+ (req.content), ready=2)
+ else:
+ leap_assert(False, # XXX ???
+ "Don't know how to handle this state: %s"
+ % (req.event))
+
+ if ext_status is not None:
+ self._set_mail_status(ext_status, ready=1)
+
+ def about_to_start(self):
+ """
+ Displays the correct UI for the point where mail components
+ haven't really started, but they are about to in a second.
+ """
+ self._set_mail_status(self.tr("About to start, please wait..."),
+ ready=1)
+
+ def set_disabled(self):
+ """
+ Displays the correct UI for disabled mail.
+ """
+ self._set_mail_status(self.tr("Disabled"), -1)
+
+ def stopped_mail(self):
+ """
+ Displayes the correct UI for the stopped state.
+ """
+ self._set_mail_status(self.tr("OFF"))
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index 200d68aa..84f09fd9 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -32,13 +32,16 @@ from leap.bitmask.crypto.srpauth import SRPAuth
from leap.bitmask.gui.loggerwindow import LoggerWindow
from leap.bitmask.gui.login import LoginWidget
from leap.bitmask.gui.preferenceswindow import PreferencesWindow
+from leap.bitmask.gui.eip_preferenceswindow import EIPPreferencesWindow
from leap.bitmask.gui import statemachines
-from leap.bitmask.gui.statuspanel import StatusPanelWidget
+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 import provider
+from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper
from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper
-from leap.bitmask.services.eip.eipconfig import EIPConfig
-from leap.bitmask.services.eip.providerbootstrapper import ProviderBootstrapper
+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
@@ -53,12 +56,12 @@ from leap.bitmask.services.eip.vpnprocess import VPN
from leap.bitmask.services.eip.vpnprocess import OpenVPNAlreadyRunning
from leap.bitmask.services.eip.vpnprocess import AlienOpenVPNAlreadyRunning
-from leap.bitmask.services.eip.vpnlaunchers import VPNLauncherException
-from leap.bitmask.services.eip.vpnlaunchers import OpenVPNNotFoundException
-from leap.bitmask.services.eip.vpnlaunchers import EIPNoPkexecAvailable
-from leap.bitmask.services.eip.vpnlaunchers import \
+from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException
+from leap.bitmask.services.eip.vpnlauncher import OpenVPNNotFoundException
+from leap.bitmask.services.eip.linuxvpnlauncher import EIPNoPkexecAvailable
+from leap.bitmask.services.eip.linuxvpnlauncher import \
EIPNoPolkitAuthAgentAvailable
-from leap.bitmask.services.eip.vpnlaunchers import EIPNoTunKextLoaded
+from leap.bitmask.services.eip.darwinvpnlauncher import EIPNoTunKextLoaded
from leap.bitmask.util.keyring_helpers import has_keyring
from leap.bitmask.util.leap_log_handler import LeapLogHandler
@@ -98,6 +101,7 @@ class MainWindow(QtGui.QMainWindow):
MX_SERVICE = "mx"
# Signals
+ eip_needs_login = QtCore.Signal([])
new_updates = QtCore.Signal(object)
raise_window = QtCore.Signal([])
soledad_ready = QtCore.Signal([])
@@ -147,7 +151,7 @@ class MainWindow(QtGui.QMainWindow):
self._login_widget = LoginWidget(
self._settings,
- self.ui.stackedWidget.widget(self.LOGIN_INDEX))
+ self)
self.ui.loginLayout.addWidget(self._login_widget)
# Qt Signal Connections #####################################
@@ -155,17 +159,18 @@ class MainWindow(QtGui.QMainWindow):
self._login_widget.login.connect(self._login)
self._login_widget.cancel_login.connect(self._cancel_login)
- self._login_widget.show_wizard.connect(
- self._launch_wizard)
-
- self.ui.btnShowLog.clicked.connect(self._show_logger_window)
- self.ui.btnPreferences.clicked.connect(self._show_preferences)
+ self._login_widget.show_wizard.connect(self._launch_wizard)
+ self._login_widget.logout.connect(self._logout)
- self._status_panel = StatusPanelWidget(
- self.ui.stackedWidget.widget(self.EIP_STATUS_INDEX))
- self.ui.statusLayout.addWidget(self._status_panel)
+ self._eip_status = EIPStatusWidget(self)
+ self.ui.eipLayout.addWidget(self._eip_status)
+ self._login_widget.logged_in_signal.connect(
+ self._eip_status.enable_eip_start)
+ self._login_widget.logged_in_signal.connect(
+ self._enable_eip_start_action)
- self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX)
+ self._mail_status = MailStatusWidget(self)
+ self.ui.mailLayout.addWidget(self._mail_status)
self._eip_connection = EIPConnection()
@@ -173,15 +178,19 @@ class MainWindow(QtGui.QMainWindow):
self._start_eip)
self._eip_connection.qtsigs.disconnecting_signal.connect(
self._stop_eip)
- self._status_panel.eip_connection_connected.connect(
+ self._eip_status.eip_connection_connected.connect(
self._on_eip_connected)
+ self.eip_needs_login.connect(
+ self._eip_status.disable_eip_start)
+ self.eip_needs_login.connect(
+ self._disable_eip_start_action)
# This is loaded only once, there's a bug when doing that more
# than once
self._provider_config = ProviderConfig()
# Used for automatic start of EIP
self._provisional_provider_config = ProviderConfig()
- self._eip_config = EIPConfig()
+ self._eip_config = eipconfig.EIPConfig()
self._already_started_eip = False
@@ -221,9 +230,9 @@ class MainWindow(QtGui.QMainWindow):
self._finish_eip_bootstrap)
self._vpn = VPN(openvpn_verb=openvpn_verb)
self._vpn.qtsigs.state_changed.connect(
- self._status_panel.update_vpn_state)
+ self._eip_status.update_vpn_state)
self._vpn.qtsigs.status_changed.connect(
- self._status_panel.update_vpn_status)
+ self._eip_status.update_vpn_status)
self._vpn.qtsigs.process_finished.connect(
self._eip_finished)
@@ -234,17 +243,23 @@ class MainWindow(QtGui.QMainWindow):
self._soledad_bootstrapped_stage)
self._soledad_bootstrapper.soledad_timeout.connect(
self._retry_soledad_connection)
+ # XXX missing connect to soledad_failed (signal unrecoverable to user)
+ # TODO wait until chiiph ui refactor.
self._smtp_bootstrapper = SMTPBootstrapper()
self._smtp_bootstrapper.download_config.connect(
self._smtp_bootstrapped_stage)
- self.ui.action_log_out.setEnabled(False)
- self.ui.action_log_out.triggered.connect(self._logout)
self.ui.action_about_leap.triggered.connect(self._about)
self.ui.action_quit.triggered.connect(self.quit)
self.ui.action_wizard.triggered.connect(self._launch_wizard)
self.ui.action_show_logs.triggered.connect(self._show_logger_window)
+ self.ui.action_create_new_account.triggered.connect(
+ self._launch_wizard)
+
+ if IS_MAC:
+ self.ui.menuFile.menuAction().setText(self.tr("Util"))
+
self.raise_window.connect(self._do_raise_mainwindow)
# Used to differentiate between real quits and close to tray
@@ -254,17 +269,17 @@ class MainWindow(QtGui.QMainWindow):
self._action_mail_status = QtGui.QAction(self.tr("Mail is OFF"), self)
self._action_mail_status.setEnabled(False)
- self._status_panel.set_action_mail_status(self._action_mail_status)
+ self._mail_status.set_action_mail_status(self._action_mail_status)
self._action_eip_startstop = QtGui.QAction("", self)
- self._status_panel.set_action_eip_startstop(self._action_eip_startstop)
-
- self._action_preferences = QtGui.QAction(self.tr("Preferences"), self)
- self._action_preferences.triggered.connect(self._show_preferences)
+ self._eip_status.set_action_eip_startstop(self._action_eip_startstop)
self._action_visible = QtGui.QAction(self.tr("Hide Main Window"), self)
self._action_visible.triggered.connect(self._toggle_visible)
+ self.ui.btnPreferences.clicked.connect(self._show_preferences)
+ self.ui.btnEIPPreferences.clicked.connect(self._show_eip_preferences)
+
self._enabled_services = []
self._center_window()
@@ -280,6 +295,7 @@ class MainWindow(QtGui.QMainWindow):
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 ########
@@ -296,6 +312,7 @@ class MainWindow(QtGui.QMainWindow):
self._soledad_ready = False
self._keymanager = None
self._smtp_service = None
+ self._smtp_port = None
self._imap_service = None
self._login_defer = None
@@ -303,27 +320,29 @@ class MainWindow(QtGui.QMainWindow):
self._smtp_config = SMTPConfig()
- if self._first_run():
- self._wizard_firstrun = True
- self._wizard = Wizard(bypass_checks=bypass_checks)
- # Give this window time to finish init and then show the wizard
- QtCore.QTimer.singleShot(1, self._launch_wizard)
- self._wizard.accepted.connect(self._finish_init)
- self._wizard.rejected.connect(self._rejected_wizard)
- else:
- self._finish_init()
-
# Eip machine is a public attribute where the state machine for
# the eip connection will be available to the different components.
# Remember that this will not live in the +1600LOC mainwindow for
# all the eternity, so at some point we will be moving this to
# the EIPConductor or some other clever component that we will
# instantiate from here.
- self.eip_machine = None
+ self.eip_machine = None
# start event machines
self.start_eip_machine()
+ if self._first_run():
+ self._wizard_firstrun = True
+ self._wizard = Wizard(bypass_checks=bypass_checks)
+ # Give this window time to finish init and then show the wizard
+ QtCore.QTimer.singleShot(1, self._launch_wizard)
+ self._wizard.accepted.connect(self._finish_init)
+ self._wizard.rejected.connect(self._rejected_wizard)
+ else:
+ # during finish_init, we disable the eip start button
+ # so this has to be done after eip_machine is started
+ self._finish_init()
+
def _rejected_wizard(self):
"""
SLOT
@@ -391,7 +410,6 @@ class MainWindow(QtGui.QMainWindow):
SLOT
TRIGGERS:
self.ui.action_show_logs.triggered
- self.ui.btnShowLog.clicked
Displays the window with the history of messages logged until now
and displays the new ones on arrival.
@@ -405,18 +423,13 @@ class MainWindow(QtGui.QMainWindow):
self._logger_window = LoggerWindow(handler=leap_log_handler)
self._logger_window.setVisible(
not self._logger_window.isVisible())
- self.ui.btnShowLog.setChecked(self._logger_window.isVisible())
else:
self._logger_window.setVisible(not self._logger_window.isVisible())
- self.ui.btnShowLog.setChecked(self._logger_window.isVisible())
-
- self._logger_window.finished.connect(self._uncheck_logger_button)
def _show_preferences(self):
"""
SLOT
TRIGGERS:
- self.ui.action_show_preferences.triggered
self.ui.btnPreferences.clicked
Displays the preferences window.
@@ -431,22 +444,25 @@ class MainWindow(QtGui.QMainWindow):
preferences_window.show()
- def _set_soledad_ready(self):
+ def _show_eip_preferences(self):
"""
SLOT
TRIGGERS:
- self.soledad_ready
+ self.ui.btnEIPPreferences.clicked
- It sets the soledad object as ready to use.
+ Displays the EIP preferences window.
"""
- self._soledad_ready = True
+ EIPPreferencesWindow(self).show()
- def _uncheck_logger_button(self):
+ def _set_soledad_ready(self):
"""
SLOT
- Sets the checked state of the loggerwindow button to false.
+ TRIGGERS:
+ self.soledad_ready
+
+ It sets the soledad object as ready to use.
"""
- self.ui.btnShowLog.setChecked(False)
+ self._soledad_ready = True
def _new_updates_available(self, req):
"""
@@ -579,21 +595,29 @@ class MainWindow(QtGui.QMainWindow):
"""
Tries to autostart EIP
"""
- default_provider = self._settings.get_defaultprovider()
+ settings = self._settings
+
+ should_autostart = settings.get_autostart_eip()
+ if not should_autostart:
+ logger.debug('Will not autostart EIP since it is setup '
+ 'to not to do it')
+ self.eip_needs_login.emit()
+ return
+
+ default_provider = settings.get_defaultprovider()
if default_provider is None:
logger.info("Cannot autostart Encrypted Internet because there is "
"no default provider configured")
+ self.eip_needs_login.emit()
return
- self._enabled_services = self._settings.get_enabled_services(
+ self._enabled_services = settings.get_enabled_services(
default_provider)
- if self._provisional_provider_config.load(
- os.path.join("leap",
- "providers",
- default_provider,
- "provider.json")):
+ loaded = self._provisional_provider_config.load(
+ provider.get_provider_path(default_provider))
+ if loaded:
# XXX I think we should not try to re-download config every time,
# it adds some delay.
# Maybe if it's the first run in a session,
@@ -601,6 +625,7 @@ class MainWindow(QtGui.QMainWindow):
self._download_eip_config()
else:
# XXX: Display a proper message to the user
+ self.eip_needs_login.emit()
logger.error("Unable to load %s config, cannot autostart." %
(default_provider,))
@@ -621,24 +646,20 @@ class MainWindow(QtGui.QMainWindow):
systrayMenu.addAction(self._action_visible)
systrayMenu.addSeparator()
- eip_menu = systrayMenu.addMenu(self.tr("Encrypted Internet is OFF"))
+ eip_menu = systrayMenu.addMenu(self.tr("Encrypted Internet: OFF"))
eip_menu.addAction(self._action_eip_startstop)
- self._status_panel.set_eip_status_menu(eip_menu)
+ self._eip_status.set_eip_status_menu(eip_menu)
systrayMenu.addAction(self._action_mail_status)
systrayMenu.addSeparator()
- systrayMenu.addAction(self._action_preferences)
- systrayMenu.addAction(help_action)
- systrayMenu.addSeparator()
- systrayMenu.addAction(self.ui.action_log_out)
systrayMenu.addAction(self.ui.action_quit)
self._systray = QtGui.QSystemTrayIcon(self)
self._systray.setContextMenu(systrayMenu)
- self._systray.setIcon(self._status_panel.ERROR_ICON_TRAY)
+ self._systray.setIcon(self._eip_status.ERROR_ICON_TRAY)
self._systray.setVisible(True)
self._systray.activated.connect(self._tray_activated)
- self._status_panel.set_systray(self._systray)
+ self._eip_status.set_systray(self._systray)
def _tray_activated(self, reason=None):
"""
@@ -746,9 +767,10 @@ class MainWindow(QtGui.QMainWindow):
"""
Reimplements the changeEvent method to minimize to tray
"""
- if QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \
- e.type() == QtCore.QEvent.WindowStateChange and \
- self.isMinimized():
+ if not IS_MAC and \
+ QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \
+ e.type() == QtCore.QEvent.WindowStateChange and \
+ self.isMinimized():
self._toggle_visible()
e.accept()
return
@@ -844,47 +866,8 @@ class MainWindow(QtGui.QMainWindow):
"""
leap_assert(self._provider_config, "We need a provider config")
- username = self._login_widget.get_user()
- password = self._login_widget.get_password()
- provider = self._login_widget.get_selected_provider()
-
- self._enabled_services = self._settings.get_enabled_services(
- self._login_widget.get_selected_provider())
-
- if len(provider) == 0:
- self._login_widget.set_status(
- self.tr("Please select a valid provider"))
- return
-
- if len(username) == 0:
- self._login_widget.set_status(
- self.tr("Please provide a valid username"))
- return
-
- if len(password) == 0:
- self._login_widget.set_status(
- self.tr("Please provide a valid Password"))
- return
-
- self._login_widget.set_status(self.tr("Logging in..."), error=False)
- self._login_widget.set_enabled(False)
-
- if self._login_widget.get_remember() and has_keyring():
- # in the keyring and in the settings
- # we store the value 'usename@provider'
- username_domain = (username + '@' + provider).encode("utf8")
- try:
- keyring.set_password(self.KEYRING_KEY,
- username_domain,
- password.encode("utf8"))
- # Only save the username if it was saved correctly in
- # the keyring
- self._settings.set_user(username_domain)
- except Exception as e:
- logger.error("Problem saving data to keyring. %r"
- % (e,))
-
- self._download_provider_config()
+ if self._login_widget.start_login():
+ self._download_provider_config()
def _cancel_login(self):
"""
@@ -952,7 +935,6 @@ class MainWindow(QtGui.QMainWindow):
if ok:
self._logged_user = self._login_widget.get_user()
- self.ui.action_log_out.setEnabled(True)
# We leave a bit of room for the user to see the
# "Succeeded" message and then we switch to the EIP status
# panel
@@ -967,20 +949,25 @@ class MainWindow(QtGui.QMainWindow):
Changes the stackedWidget index to the EIP status one and
triggers the eip bootstrapping
"""
- if not self._already_started_eip:
- self._status_panel.set_provider(
- "%s@%s" % (self._login_widget.get_user(),
- self._get_best_provider_config().get_domain()))
- self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX)
+ self._login_widget.logged_in()
+
+ self._enabled_services = self._settings.get_enabled_services(
+ self._provider_config.get_domain())
# TODO separate UI from logic.
# TODO soledad should check if we want to run only over EIP.
- self._soledad_bootstrapper.run_soledad_setup_checks(
- self._provider_config,
- self._login_widget.get_user(),
- self._login_widget.get_password(),
- download_if_needed=True)
+ if self._provider_config.provides_mx() and \
+ self._enabled_services.count(self.MX_SERVICE) > 0:
+ self._mail_status.about_to_start()
+
+ self._soledad_bootstrapper.run_soledad_setup_checks(
+ self._provider_config,
+ self._login_widget.get_user(),
+ self._login_widget.get_password(),
+ download_if_needed=True)
+ else:
+ self._mail_status.set_disabled()
self._download_eip_config()
@@ -1007,6 +994,7 @@ class MainWindow(QtGui.QMainWindow):
"""
Retries soledad connection.
"""
+ # XXX should move logic to soledad boostrapper itself
logger.debug("Retrying soledad connection.")
if self._soledad_bootstrapper.should_retry_initialization():
self._soledad_bootstrapper.increment_retries_count()
@@ -1031,8 +1019,9 @@ class MainWindow(QtGui.QMainWindow):
"""
passed = data[self._soledad_bootstrapper.PASSED_KEY]
if not passed:
+ # TODO should actually *display* on the panel.
logger.debug("ERROR on soledad bootstrapping:")
- logger.error(data[self._soledad_bootstrapper.ERROR_KEY])
+ logger.error("%r" % data[self._soledad_bootstrapper.ERROR_KEY])
return
else:
logger.debug("Done bootstrapping Soledad")
@@ -1116,7 +1105,7 @@ class MainWindow(QtGui.QMainWindow):
# the specific default.
from leap.mail.smtp import setup_smtp_relay
- self._smtp_service = setup_smtp_relay(
+ self._smtp_service, self._smtp_port = setup_smtp_relay(
port=2013,
keymanager=self._keymanager,
smtp_host=host,
@@ -1136,6 +1125,7 @@ class MainWindow(QtGui.QMainWindow):
# 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()
###################################################################
@@ -1195,22 +1185,37 @@ class MainWindow(QtGui.QMainWindow):
"""
Initializes and starts the EIP state machine
"""
- button = self._status_panel.eip_button
+ button = self._eip_status.eip_button
action = self._action_eip_startstop
- label = self._status_panel.eip_label
+ label = self._eip_status.eip_label
builder = statemachines.ConnectionMachineBuilder(self._eip_connection)
eip_machine = builder.make_machine(button=button,
action=action,
label=label)
self.eip_machine = eip_machine
self.eip_machine.start()
+ logger.debug('eip machine started')
+
+ @QtCore.Slot()
+ def _disable_eip_start_action(self):
+ """
+ Disables the EIP start action in the systray menu.
+ """
+ self._action_eip_startstop.setEnabled(False)
+
+ @QtCore.Slot()
+ def _enable_eip_start_action(self):
+ """
+ Enables the EIP start action in the systray menu.
+ """
+ self._action_eip_startstop.setEnabled(True)
@QtCore.Slot()
def _on_eip_connected(self):
"""
SLOT
TRIGGERS:
- self._status_panel.eip_connection_connected
+ self._eip_status.eip_connection_connected
Emits the EIPConnection.qtsigs.connected_signal
This is a little workaround for connecting the vpn-connected
@@ -1225,7 +1230,7 @@ class MainWindow(QtGui.QMainWindow):
"""
SLOT
TRIGGERS:
- self._status_panel.start_eip
+ self._eip_status.start_eip
self._action_eip_startstop.triggered
or called from _finish_eip_bootstrap
@@ -1233,9 +1238,30 @@ class MainWindow(QtGui.QMainWindow):
"""
provider_config = self._get_best_provider_config()
provider = provider_config.get_domain()
- self._status_panel.eip_pre_up()
+ self._eip_status.eip_pre_up()
self.user_stopped_eip = False
+ # until we set an option in the preferences window,
+ # we'll assume that by default we try to autostart.
+ # If we switch it off manually, it won't try the next
+ # time.
+ self._settings.set_autostart_eip(True)
+
+ loaded = eipconfig.load_eipconfig_if_needed(
+ provider_config, self._eip_config, provider)
+
+ if not loaded:
+ self._eip_status.set_eip_status(
+ self.tr("Could not load Encrypted Internet "
+ "Configuration."),
+ error=True)
+ # signal connection aborted to state machine
+ qtsigs = self._eip_connection.qtsigs
+ qtsigs.connection_aborted_signal.emit()
+ logger.error("Tried to start EIP but cannot find any "
+ "available provider!")
+ return
+
try:
# XXX move this to EIPConductor
host, port = get_openvpn_management()
@@ -1244,16 +1270,14 @@ class MainWindow(QtGui.QMainWindow):
socket_host=host,
socket_port=port)
self._settings.set_defaultprovider(provider)
- if self._logged_user is not None:
- provider = "%s@%s" % (self._logged_user, provider)
# XXX move to the state machine too
- self._status_panel.set_provider(provider)
+ self._eip_status.set_provider(provider)
# TODO refactor exceptions so they provide translatable
# usef-facing messages.
except EIPNoPolkitAuthAgentAvailable:
- self._status_panel.set_global_status(
+ self._eip_status.set_eip_status(
# XXX this should change to polkit-kde where
# applicable.
self.tr("We could not find any "
@@ -1266,30 +1290,30 @@ class MainWindow(QtGui.QMainWindow):
error=True)
self._set_eipstatus_off()
except EIPNoTunKextLoaded:
- self._status_panel.set_global_status(
+ self._eip_status.set_eip_status(
self.tr("Encrypted Internet cannot be started because "
"the tuntap extension is not installed properly "
"in your system."))
self._set_eipstatus_off()
except EIPNoPkexecAvailable:
- self._status_panel.set_global_status(
+ self._eip_status.set_eip_status(
self.tr("We could not find <b>pkexec</b> "
"in your system."),
error=True)
self._set_eipstatus_off()
except OpenVPNNotFoundException:
- self._status_panel.set_global_status(
+ self._eip_status.set_eip_status(
self.tr("We could not find openvpn binary."),
error=True)
self._set_eipstatus_off()
except OpenVPNAlreadyRunning as e:
- self._status_panel.set_global_status(
+ self._eip_status.set_eip_status(
self.tr("Another openvpn instance is already running, and "
"could not be stopped."),
error=True)
self._set_eipstatus_off()
except AlienOpenVPNAlreadyRunning as e:
- self._status_panel.set_global_status(
+ self._eip_status.set_eip_status(
self.tr("Another openvpn instance is already running, and "
"could not be stopped because it was not launched by "
"Bitmask. Please stop it and try again."),
@@ -1298,17 +1322,17 @@ class MainWindow(QtGui.QMainWindow):
except VPNLauncherException as e:
# XXX We should implement again translatable exceptions so
# we can pass a translatable string to the panel (usermessage attr)
- self._status_panel.set_global_status("%s" % (e,), error=True)
+ self._eip_status.set_eip_status("%s" % (e,), error=True)
self._set_eipstatus_off()
else:
self._already_started_eip = True
@QtCore.Slot()
- def _stop_eip(self, abnormal=False):
+ def _stop_eip(self):
"""
SLOT
TRIGGERS:
- self._status_panel.stop_eip
+ self._eip_status.stop_eip
self._action_eip_startstop.triggered
or called from _eip_finished
@@ -1318,29 +1342,28 @@ class MainWindow(QtGui.QMainWindow):
:param abnormal: whether this was an abnormal termination.
:type abnormal: bool
"""
- if abnormal:
- logger.warning("Abnormal EIP termination.")
-
self.user_stopped_eip = True
self._vpn.terminate()
- self._set_eipstatus_off()
-
+ self._set_eipstatus_off(False)
self._already_started_eip = False
- # XXX do via signal
- self._settings.set_defaultprovider(None)
+ logger.debug('Setting autostart to: False')
+ self._settings.set_autostart_eip(False)
+
if self._logged_user:
- self._status_panel.set_provider(
+ self._eip_status.set_provider(
"%s@%s" % (self._logged_user,
self._get_best_provider_config().get_domain()))
+ self._eip_status.eip_stopped()
- def _set_eipstatus_off(self):
+ def _set_eipstatus_off(self, error=True):
"""
Sets eip status to off
"""
- self._status_panel.set_eip_status(self.tr("OFF"), error=True)
- self._status_panel.set_eip_status_icon("error")
+ self._eip_status.set_eip_status(self.tr("EIP has stopped"),
+ error=error)
+ self._eip_status.set_eip_status_icon("error")
def _download_eip_config(self):
"""
@@ -1355,7 +1378,7 @@ class MainWindow(QtGui.QMainWindow):
not self._already_started_eip:
# XXX this should be handled by the state machine.
- self._status_panel.set_eip_status(
+ self._eip_status.set_eip_status(
self.tr("Starting..."))
self._eip_bootstrapper.run_eip_setup_checks(
provider_config,
@@ -1363,11 +1386,11 @@ class MainWindow(QtGui.QMainWindow):
self._already_started_eip = True
elif not self._already_started_eip:
if self._enabled_services.count(self.OPENVPN_SERVICE) > 0:
- self._status_panel.set_eip_status(
+ self._eip_status.set_eip_status(
self.tr("Not supported"),
error=True)
else:
- self._status_panel.set_eip_status(self.tr("Disabled"))
+ self._eip_status.set_eip_status(self.tr("Disabled"))
def _finish_eip_bootstrap(self, data):
"""
@@ -1382,7 +1405,7 @@ class MainWindow(QtGui.QMainWindow):
if not passed:
error_msg = self.tr("There was a problem with the provider")
- self._status_panel.set_eip_status(error_msg, error=True)
+ self._eip_status.set_eip_status(error_msg, error=True)
logger.error(data[self._eip_bootstrapper.ERROR_KEY])
self._already_started_eip = False
return
@@ -1390,19 +1413,15 @@ class MainWindow(QtGui.QMainWindow):
provider_config = self._get_best_provider_config()
domain = provider_config.get_domain()
- loaded = self._eip_config.loaded()
- if not loaded:
- eip_config_path = os.path.join("leap", "providers",
- domain, "eip-service.json")
- api_version = provider_config.get_api_version()
- self._eip_config.set_api_version(api_version)
- loaded = self._eip_config.load(eip_config_path)
+ # XXX move check to _start_eip ?
+ loaded = eipconfig.load_eipconfig_if_needed(
+ provider_config, self._eip_config, domain)
if loaded:
# DO START EIP Connection!
self._eip_connection.qtsigs.do_connect_signal.emit()
else:
- self._status_panel.set_eip_status(
+ self._eip_status.set_eip_status(
self.tr("Could not load Encrypted Internet "
"Configuration."),
error=True)
@@ -1437,7 +1456,7 @@ class MainWindow(QtGui.QMainWindow):
def _logout(self):
"""
SLOT
- TRIGGER: self.ui.action_log_out.triggered
+ TRIGGER: self._login_widget.logout
Starts the logout sequence
"""
@@ -1457,16 +1476,17 @@ class MainWindow(QtGui.QMainWindow):
Switches the stackedWidget back to the login stage after
logging out
"""
+ self._login_widget.done_logout()
+
if ok:
self._logged_user = None
- self.ui.action_log_out.setEnabled(False)
- self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX)
- self._login_widget.set_password("")
- self._login_widget.set_enabled(True)
- self._login_widget.set_status("")
+
+ self._login_widget.logged_out()
+
else:
- status_text = self.tr("Something went wrong with the logout.")
- self._status_panel.set_global_status(status_text, error=True)
+ self._login_widget.set_login_status(
+ self.tr("Something went wrong with the logout."),
+ error=True)
def _intermediate_stage(self, data):
"""
@@ -1531,37 +1551,36 @@ class MainWindow(QtGui.QMainWindow):
# TODO we should have a way of parsing the latest lines in the vpn
# log buffer so we can have a more precise idea of which type
# of error did we have (server side, local problem, etc)
- abnormal = True
+
+ qtsigs = self._eip_connection.qtsigs
+ signal = qtsigs.disconnected_signal
# XXX check if these exitCodes are pkexec/cocoasudo specific
if exitCode in (126, 127):
- self._status_panel.set_global_status(
+ self._eip_status.set_eip_status(
self.tr("Encrypted Internet could not be launched "
"because you did not authenticate properly."),
error=True)
self._vpn.killit()
+ signal = qtsigs.connection_aborted_signal
+
elif exitCode != 0 or not self.user_stopped_eip:
- self._status_panel.set_global_status(
+ self._eip_status.set_eip_status(
self.tr("Encrypted Internet finished in an "
"unexpected manner!"), error=True)
- else:
- abnormal = False
+ signal = qtsigs.connection_died_signal
+
if exitCode == 0 and IS_MAC:
# XXX remove this warning after I fix cocoasudo.
logger.warning("The above exit code MIGHT BE WRONG.")
- # We emit signals to trigger transitions in the state machine:
- qtsigs = self._eip_connection.qtsigs
- if abnormal:
- signal = qtsigs.connection_died_signal
- else:
- signal = qtsigs.disconnected_signal
-
# XXX verify that the logic kees the same w/o the abnormal flag
# after the refactor to EIPConnection has been completed
# (eipconductor taking the most of the logic under transitions
# that right now are handled under status_panel)
#self._stop_eip(abnormal)
+
+ # We emit signals to trigger transitions in the state machine:
signal.emit()
def _on_raise_window_event(self, req):
diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py
index 2d17f6c2..58cb05ba 100644
--- a/src/leap/bitmask/gui/preferenceswindow.py
+++ b/src/leap/bitmask/gui/preferenceswindow.py
@@ -27,11 +27,10 @@ from PySide import QtCore, QtGui
from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.gui.ui_preferences import Ui_Preferences
from leap.soledad.client import NoStorageSecret
-from leap.bitmask.crypto.srpauth import SRPAuthBadPassword
+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.eip.eipconfig import EIPConfig, VPNGatewaySelector
from leap.bitmask.services import get_service_display_name
logger = logging.getLogger(__name__)
@@ -60,7 +59,6 @@ class PreferencesWindow(QtGui.QDialog):
self.ui.setupUi(self)
self.ui.lblPasswordChangeStatus.setVisible(False)
self.ui.lblProvidersServicesStatus.setVisible(False)
- self.ui.lblProvidersGatewayStatus.setVisible(False)
self._selected_services = set()
@@ -68,11 +66,6 @@ class PreferencesWindow(QtGui.QDialog):
self.ui.pbChangePassword.clicked.connect(self._change_password)
self.ui.cbProvidersServices.currentIndexChanged[unicode].connect(
self._populate_services)
- self.ui.cbProvidersGateway.currentIndexChanged[unicode].connect(
- self._populate_gateways)
-
- self.ui.cbGateways.currentIndexChanged[unicode].connect(
- lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False))
if not self._settings.get_configured_providers():
self.ui.gbEnabledServices.setEnabled(False)
@@ -185,7 +178,7 @@ class PreferencesWindow(QtGui.QDialog):
logger.error("Error changing password: %s", (failure, ))
problem = self.tr("There was a problem changing the password.")
- if failure.check(SRPAuthBadPassword):
+ if failure.check(SRPAuthBadUserOrPassword):
problem = self.tr("You did not enter a correct current password.")
self._set_password_change_status(problem, error=True)
@@ -217,37 +210,13 @@ class PreferencesWindow(QtGui.QDialog):
self.ui.lblProvidersServicesStatus.setVisible(True)
self.ui.lblProvidersServicesStatus.setText(status)
- def _set_providers_gateway_status(self, status, success=False,
- error=False):
- """
- Sets the status label for the gateway change.
-
- :param status: status message to display, can be HTML
- :type status: str
- :param success: is set to True if we should display the
- message as green
- :type success: bool
- :param error: is set to True if we should display the
- message as red
- :type error: bool
- """
- if success:
- status = "<font color='green'><b>%s</b></font>" % (status,)
- elif error:
- status = "<font color='red'><b>%s</b></font>" % (status,)
-
- self.ui.lblProvidersGatewayStatus.setVisible(True)
- self.ui.lblProvidersGatewayStatus.setText(status)
-
def _add_configured_providers(self):
"""
Add the client's configured providers to the providers combo boxes.
"""
self.ui.cbProvidersServices.clear()
- self.ui.cbProvidersGateway.clear()
for provider in self._settings.get_configured_providers():
self.ui.cbProvidersServices.addItem(provider)
- self.ui.cbProvidersGateway.addItem(provider)
def _service_selection_changed(self, service, state):
"""
@@ -366,90 +335,3 @@ class PreferencesWindow(QtGui.QDialog):
provider_config = None
return provider_config
-
- def _save_selected_gateway(self, provider):
- """
- SLOT
- TRIGGERS:
- self.ui.pbSaveGateway.clicked
-
- Saves the new gateway setting to the configuration file.
-
- :param provider: the provider config that we need to save.
- :type provider: str
- """
- gateway = self.ui.cbGateways.currentText()
-
- if gateway == self.AUTOMATIC_GATEWAY_LABEL:
- gateway = self._settings.GATEWAY_AUTOMATIC
- else:
- idx = self.ui.cbGateways.currentIndex()
- gateway = self.ui.cbGateways.itemData(idx)
-
- self._settings.set_selected_gateway(provider, gateway)
-
- msg = self.tr(
- "Gateway settings for provider '{0}' saved.".format(provider))
- logger.debug(msg)
- self._set_providers_gateway_status(msg, success=True)
-
- def _populate_gateways(self, domain):
- """
- SLOT
- TRIGGERS:
- self.ui.cbProvidersGateway.currentIndexChanged[unicode]
-
- Loads the gateways that the provider provides into the UI for
- the user to select.
-
- :param domain: the domain of the provider to load gateways from.
- :type domain: str
- """
- # We hide the maybe-visible status label after a change
- self.ui.lblProvidersGatewayStatus.setVisible(False)
-
- if not domain:
- return
-
- try:
- # disconnect prevoiusly connected save method
- self.ui.pbSaveGateway.clicked.disconnect()
- except RuntimeError:
- pass # Signal was not connected
-
- # set the proper connection for the 'save' button
- save_gateway = partial(self._save_selected_gateway, domain)
- self.ui.pbSaveGateway.clicked.connect(save_gateway)
-
- eip_config = EIPConfig()
- provider_config = self._get_provider_config(domain)
-
- eip_config_path = os.path.join("leap", "providers",
- domain, "eip-service.json")
- api_version = provider_config.get_api_version()
- eip_config.set_api_version(api_version)
- eip_loaded = eip_config.load(eip_config_path)
-
- if not eip_loaded or provider_config is None:
- self._set_providers_gateway_status(
- self.tr("There was a problem with configuration files."),
- error=True)
- return
-
- gateways = VPNGatewaySelector(eip_config).get_gateways_list()
- logger.debug(gateways)
-
- self.ui.cbGateways.clear()
- self.ui.cbGateways.addItem(self.AUTOMATIC_GATEWAY_LABEL)
-
- # Add the available gateways and
- # select the one stored in configuration file.
- selected_gateway = self._settings.get_selected_gateway(domain)
- index = 0
- for idx, (gw_name, gw_ip) in enumerate(gateways):
- gateway = "{0} ({1})".format(gw_name, gw_ip)
- self.ui.cbGateways.addItem(gateway, gw_ip)
- if gw_ip == selected_gateway:
- index = idx + 1
-
- self.ui.cbGateways.setCurrentIndex(index)
diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py
index c3dd5ed3..94726720 100644
--- a/src/leap/bitmask/gui/statemachines.py
+++ b/src/leap/bitmask/gui/statemachines.py
@@ -128,11 +128,25 @@ class ConnectionMachineBuilder(object):
states[_OFF])
# * If we receive the connection_died, we transition
- # to the off state
+ # from on directly to the off state
states[_ON].addTransition(
conn.qtsigs.connection_died_signal,
states[_OFF])
+ # * If we receive the connection_aborted, we transition
+ # from connecting to the off state
+ states[_CON].addTransition(
+ conn.qtsigs.connection_aborted_signal,
+ states[_OFF])
+ # * Connection died can in some cases also be
+ # triggered while we are in CONNECTING
+ # state. I should be avoided, since connection_aborted
+ # is clearer (and reserve connection_died
+ # for transitions from on->off
+ states[_CON].addTransition(
+ conn.qtsigs.connection_died_signal,
+ states[_OFF])
+
# adding states to the machine
for state in states.itervalues():
machine.addState(state)
diff --git a/src/leap/bitmask/gui/statuspanel.py b/src/leap/bitmask/gui/statuspanel.py
deleted file mode 100644
index 679f00b1..00000000
--- a/src/leap/bitmask/gui/statuspanel.py
+++ /dev/null
@@ -1,710 +0,0 @@
-# -*- coding: utf-8 -*-
-# statuspanel.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/>.
-"""
-Status Panel widget implementation
-"""
-import logging
-
-from datetime import datetime
-from functools import partial
-
-from PySide import QtCore, QtGui
-
-from leap.bitmask.services.eip.connection import EIPConnection
-from leap.bitmask.services.eip.vpnprocess import VPNManager
-from leap.bitmask.platform_init import IS_WIN, IS_LINUX
-from leap.bitmask.util.averages import RateMovingAverage
-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
-
-from ui_statuspanel import Ui_StatusPanel
-
-logger = logging.getLogger(__name__)
-
-
-class StatusPanelWidget(QtGui.QWidget):
- """
- Status widget that displays the current state of the LEAP services
- """
- DISPLAY_TRAFFIC_RATES = True
- RATE_STR = "%14.2f KB/s"
- TOTAL_STR = "%14.2f Kb"
-
- MAIL_OFF_ICON = ":/images/mail-unlocked.png"
- MAIL_ON_ICON = ":/images/mail-locked.png"
-
- eip_connection_connected = QtCore.Signal()
- _soledad_event = QtCore.Signal(object)
- _smtp_event = QtCore.Signal(object)
- _imap_event = QtCore.Signal(object)
- _keymanager_event = QtCore.Signal(object)
-
- def __init__(self, parent=None):
- QtGui.QWidget.__init__(self, parent)
-
- self._systray = None
- self._eip_status_menu = None
-
- self.ui = Ui_StatusPanel()
- self.ui.setupUi(self)
-
- self.eipconnection = EIPConnection()
-
- self.hide_status_box()
-
- # set systray tooltip statuses
- self._eip_status = self._mx_status = ""
-
- # Set the EIP status icons
- self.CONNECTING_ICON = None
- self.CONNECTED_ICON = None
- self.ERROR_ICON = None
- self.CONNECTING_ICON_TRAY = None
- self.CONNECTED_ICON_TRAY = None
- self.ERROR_ICON_TRAY = None
- self._set_eip_icons()
-
- self._set_traffic_rates()
- self._make_status_clickable()
-
- register(signal=proto.KEYMANAGER_LOOKING_FOR_KEY,
- callback=self._mail_handle_keymanager_events,
- reqcbk=lambda req, resp: None)
-
- register(signal=proto.KEYMANAGER_KEY_FOUND,
- callback=self._mail_handle_keymanager_events,
- reqcbk=lambda req, resp: None)
-
- # register(signal=proto.KEYMANAGER_KEY_NOT_FOUND,
- # callback=self._mail_handle_keymanager_events,
- # reqcbk=lambda req, resp: None)
-
- register(signal=proto.KEYMANAGER_STARTED_KEY_GENERATION,
- callback=self._mail_handle_keymanager_events,
- reqcbk=lambda req, resp: None)
-
- register(signal=proto.KEYMANAGER_FINISHED_KEY_GENERATION,
- callback=self._mail_handle_keymanager_events,
- reqcbk=lambda req, resp: None)
-
- register(signal=proto.KEYMANAGER_DONE_UPLOADING_KEYS,
- callback=self._mail_handle_keymanager_events,
- reqcbk=lambda req, resp: None)
-
- register(signal=proto.SOLEDAD_DONE_DOWNLOADING_KEYS,
- callback=self._mail_handle_soledad_events,
- reqcbk=lambda req, resp: None)
-
- register(signal=proto.SOLEDAD_DONE_UPLOADING_KEYS,
- 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,
- callback=self._mail_handle_imap_events,
- reqcbk=lambda req, resp: None)
-
- register(signal=proto.IMAP_SERVICE_FAILED_TO_START,
- callback=self._mail_handle_imap_events,
- reqcbk=lambda req, resp: None)
-
- register(signal=proto.IMAP_UNREAD_MAIL,
- callback=self._mail_handle_imap_events,
- reqcbk=lambda req, resp: None)
-
- self._set_long_mail_status("")
- self.ui.lblUnread.setVisible(False)
-
- self._smtp_started = False
- self._imap_started = False
-
- self._soledad_event.connect(
- self._mail_handle_soledad_events_slot)
- self._imap_event.connect(
- self._mail_handle_imap_events_slot)
- self._smtp_event.connect(
- self._mail_handle_smtp_events_slot)
- self._keymanager_event.connect(
- self._mail_handle_keymanager_events_slot)
-
- def _make_status_clickable(self):
- """
- Makes upload and download figures clickable.
- """
- onclicked = self._on_VPN_status_clicked
- self.ui.btnUpload.clicked.connect(onclicked)
- self.ui.btnDownload.clicked.connect(onclicked)
-
- def _on_VPN_status_clicked(self):
- """
- SLOT
- TRIGGER: self.ui.btnUpload.clicked
- self.ui.btnDownload.clicked
-
- Toggles between rate and total throughput display for vpn
- status figures.
- """
- self.DISPLAY_TRAFFIC_RATES = not self.DISPLAY_TRAFFIC_RATES
- self.update_vpn_status(None) # refresh
-
- def _set_traffic_rates(self):
- """
- Initializes up and download rates.
- """
- self._up_rate = RateMovingAverage()
- self._down_rate = RateMovingAverage()
-
- self.ui.btnUpload.setText(self.RATE_STR % (0,))
- self.ui.btnDownload.setText(self.RATE_STR % (0,))
-
- def _reset_traffic_rates(self):
- """
- Resets up and download rates, and cleans up the labels.
- """
- self._up_rate.reset()
- self._down_rate.reset()
- self.update_vpn_status(None)
-
- def _update_traffic_rates(self, up, down):
- """
- Updates up and download rates.
-
- :param up: upload total.
- :type up: int
- :param down: download total.
- :type down: int
- """
- ts = datetime.now()
- self._up_rate.append((ts, up))
- self._down_rate.append((ts, down))
-
- def _get_traffic_rates(self):
- """
- Gets the traffic rates (in KB/s).
-
- :returns: a tuple with the (up, down) rates
- :rtype: tuple
- """
- up = self._up_rate
- down = self._down_rate
-
- return (up.get_average(), down.get_average())
-
- def _get_traffic_totals(self):
- """
- Gets the traffic total throughput (in Kb).
-
- :returns: a tuple with the (up, down) totals
- :rtype: tuple
- """
- up = self._up_rate
- down = self._down_rate
-
- return (up.get_total(), down.get_total())
-
- def _set_eip_icons(self):
- """
- Sets the EIP status icons for the main window and for the tray
-
- MAC : dark icons
- LINUX : dark icons in window, light icons in tray
- WIN : light icons
- """
- EIP_ICONS = EIP_ICONS_TRAY = (
- ":/images/conn_connecting-light.png",
- ":/images/conn_connected-light.png",
- ":/images/conn_error-light.png")
-
- if IS_LINUX:
- EIP_ICONS_TRAY = (
- ":/images/conn_connecting.png",
- ":/images/conn_connected.png",
- ":/images/conn_error.png")
- elif IS_WIN:
- EIP_ICONS = EIP_ICONS_TRAY = (
- ":/images/conn_connecting.png",
- ":/images/conn_connected.png",
- ":/images/conn_error.png")
-
- self.CONNECTING_ICON = QtGui.QPixmap(EIP_ICONS[0])
- self.CONNECTED_ICON = QtGui.QPixmap(EIP_ICONS[1])
- self.ERROR_ICON = QtGui.QPixmap(EIP_ICONS[2])
-
- self.CONNECTING_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[0])
- self.CONNECTED_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[1])
- self.ERROR_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[2])
-
- # Systray and actions
-
- def set_systray(self, systray):
- """
- Sets the systray object to use.
-
- :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"))
-
- def _update_systray_tooltip(self):
- """
- Updates the system tray icon tooltip using the eip and mx statuses.
- """
- status = self.tr("Encrypted Internet is {0}").format(self._eip_status)
- status += '\n'
- status += self.tr("Mail is {0}").format(self._mx_status)
- self._systray.setToolTip(status)
-
- def set_action_eip_startstop(self, action_eip_startstop):
- """
- Sets the action_eip_startstop to use.
-
- :param action_eip_startstop: action_eip_status to be used
- :type action_eip_startstop: QtGui.QAction
- """
- self._action_eip_startstop = action_eip_startstop
-
- def set_eip_status_menu(self, eip_status_menu):
- """
- Sets the eip_status_menu to use.
-
- :param eip_status_menu: eip_status_menu to be used
- :type eip_status_menu: QtGui.QMenu
- """
- leap_assert_type(eip_status_menu, QtGui.QMenu)
- self._eip_status_menu = eip_status_menu
-
- def set_action_mail_status(self, action_mail_status):
- """
- Sets the action_mail_status to use.
-
- :param action_mail_status: action_mail_status to be used
- :type action_mail_status: QtGui.QAction
- """
- leap_assert_type(action_mail_status, QtGui.QAction)
- self._action_mail_status = action_mail_status
-
- def set_global_status(self, status, error=False):
- """
- Sets the global status label.
-
- :param status: status message
- :type status: str or unicode
- :param error: if the status is an erroneous one, then set this
- to True
- :type error: bool
- """
- leap_assert_type(error, bool)
- if error:
- status = "<font color='red'><b>%s</b></font>" % (status,)
- self.ui.lblGlobalStatus.setText(status)
- self.ui.globalStatusBox.show()
-
- def hide_status_box(self):
- """
- Hide global status box.
- """
- self.ui.globalStatusBox.hide()
-
- # EIP status ---
-
- @property
- def eip_button(self):
- return self.ui.btnEipStartStop
-
- @property
- def eip_label(self):
- return self.ui.lblEIPStatus
-
- def eip_pre_up(self):
- """
- Triggered when the app activates eip.
- Hides the status box and disables the start/stop button.
- """
- self.hide_status_box()
- self.set_startstop_enabled(False)
-
- # XXX disable (later) --------------------------
- def set_eip_status(self, status, error=False):
- """
- Sets the status label at the VPN stage to status
-
- :param status: status message
- :type status: str or unicode
- :param error: if the status is an erroneous one, then set this
- to True
- :type error: bool
- """
- leap_assert_type(error, bool)
-
- self._eip_status = status
-
- if error:
- status = "<font color='red'>%s</font>" % (status,)
- self.ui.lblEIPStatus.setText(status)
- self._update_systray_tooltip()
-
- # XXX disable ---------------------------------
- def set_startstop_enabled(self, value):
- """
- Enable or disable btnEipStartStop and _action_eip_startstop
- based on value
-
- :param value: True for enabled, False otherwise
- :type value: bool
- """
- leap_assert_type(value, bool)
- self.ui.btnEipStartStop.setEnabled(value)
- self._action_eip_startstop.setEnabled(value)
-
- # XXX disable -----------------------------
- def eip_started(self):
- """
- Sets the state of the widget to how it should look after EIP
- has started
- """
- self.ui.btnEipStartStop.setText(self.tr("Turn OFF"))
- self.ui.btnEipStartStop.disconnect(self)
- self.ui.btnEipStartStop.clicked.connect(
- self.eipconnection.qtsigs.do_connect_signal)
-
- # XXX disable -----------------------------
- def eip_stopped(self):
- """
- Sets the state of the widget to how it should look after EIP
- has stopped
- """
- # XXX should connect this to EIPConnection.disconnected_signal
- self._reset_traffic_rates()
- # XXX disable -----------------------------
- self.ui.btnEipStartStop.setText(self.tr("Turn ON"))
- self.ui.btnEipStartStop.disconnect(self)
- self.ui.btnEipStartStop.clicked.connect(
- self.eipconnection.qtsigs.do_disconnect_signal)
-
- def update_vpn_status(self, data):
- """
- SLOT
- TRIGGER: VPN.status_changed
-
- Updates the download/upload labels based on the data provided
- by the VPN thread.
-
- :param data: a dictionary with the tcp/udp write and read totals.
- If data is None, we just will refresh the display based
- on the previous data.
- :type data: dict
- """
- if data:
- upload = float(data[VPNManager.TCPUDP_WRITE_KEY] or "0")
- download = float(data[VPNManager.TCPUDP_READ_KEY] or "0")
- self._update_traffic_rates(upload, download)
-
- if self.DISPLAY_TRAFFIC_RATES:
- uprate, downrate = self._get_traffic_rates()
- upload_str = self.RATE_STR % (uprate,)
- download_str = self.RATE_STR % (downrate,)
-
- else: # display total throughput
- uptotal, downtotal = self._get_traffic_totals()
- upload_str = self.TOTAL_STR % (uptotal,)
- download_str = self.TOTAL_STR % (downtotal,)
-
- self.ui.btnUpload.setText(upload_str)
- self.ui.btnDownload.setText(download_str)
-
- def update_vpn_state(self, data):
- """
- SLOT
- TRIGGER: VPN.state_changed
-
- Updates the displayed VPN state based on the data provided by
- the VPN thread.
-
- Emits:
- If the status is connected, we emit EIPConnection.qtsigs.
- connected_signal
- """
- status = data[VPNManager.STATUS_STEP_KEY]
- self.set_eip_status_icon(status)
- if status == "CONNECTED":
- # XXX should be handled by the state machine too.
- self.set_eip_status(self.tr("ON"))
- logger.debug("STATUS IS CONNECTED --- emitting signal")
- self.eip_connection_connected.emit()
-
- # XXX should lookup status map in EIPConnection
- elif status == "AUTH":
- self.set_eip_status(self.tr("Authenticating..."))
- elif status == "GET_CONFIG":
- self.set_eip_status(self.tr("Retrieving configuration..."))
- elif status == "WAIT":
- self.set_eip_status(self.tr("Waiting to start..."))
- elif status == "ASSIGN_IP":
- self.set_eip_status(self.tr("Assigning IP"))
- elif status == "RECONNECTING":
- self.set_eip_status(self.tr("Reconnecting..."))
- elif status == "ALREADYRUNNING":
- # Put the following calls in Qt's event queue, otherwise
- # the UI won't update properly
- QtCore.QTimer.singleShot(
- 0, self.eipconnection.qtsigs.do_disconnect_signal)
- QtCore.QTimer.singleShot(0, partial(self.set_global_status,
- self.tr("Unable to start VPN, "
- "it's already "
- "running.")))
- else:
- self.set_eip_status(status)
-
- def set_eip_icon(self, icon):
- """
- Sets the icon to display for EIP
-
- :param icon: icon to display
- :type icon: QPixmap
- """
- self.ui.lblVPNStatusIcon.setPixmap(icon)
-
- def set_eip_status_icon(self, status):
- """
- Given a status step from the VPN thread, set the icon properly
-
- :param status: status step
- :type status: str
- """
- selected_pixmap = self.ERROR_ICON
- selected_pixmap_tray = self.ERROR_ICON_TRAY
- tray_message = self.tr("Encrypted Internet is OFF")
- 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 is STARTING")
- elif status in ("CONNECTED"):
- tray_message = self.tr("Encrypted Internet is ON")
- selected_pixmap = self.CONNECTED_ICON
- selected_pixmap_tray = self.CONNECTED_ICON_TRAY
-
- self.set_eip_icon(selected_pixmap)
- self._systray.setIcon(QtGui.QIcon(selected_pixmap_tray))
- self._eip_status_menu.setTitle(tray_message)
-
- def set_provider(self, provider):
- self.ui.lblProvider.setText(provider)
-
- #
- # mail methods
- #
-
- def _set_mail_status(self, status, ready=False):
- """
- Sets the Mail status in the label and in the tray icon.
-
- :param status: the status text to display
- :type status: unicode
- :param ready: if mx is ready or not.
- :type ready: bool
- """
- self.ui.lblMailStatus.setText(status)
-
- self._mx_status = self.tr('OFF')
- tray_status = self.tr('Mail is OFF')
-
- icon = QtGui.QPixmap(self.MAIL_OFF_ICON)
- if ready:
- icon = QtGui.QPixmap(self.MAIL_ON_ICON)
- self._mx_status = self.tr('ON')
- tray_status = self.tr('Mail is ON')
-
- self.ui.lblMailIcon.setPixmap(icon)
- self._action_mail_status.setText(tray_status)
- self._update_systray_tooltip()
-
- def _mail_handle_soledad_events(self, req):
- """
- Callback for ...
-
- :param req: Request type
- :type req: leap.common.events.events_pb2.SignalRequest
- """
- self._soledad_event.emit(req)
-
- def _mail_handle_soledad_events_slot(self, req):
- """
- SLOT
- TRIGGER: _mail_handle_soledad_events
-
- Reacts to an Soledad event
-
- :param req: Request type
- :type req: leap.common.events.events_pb2.SignalRequest
- """
- self._set_mail_status(self.tr("Starting..."))
-
- ext_status = ""
-
- if req.event == proto.SOLEDAD_DONE_UPLOADING_KEYS:
- ext_status = self.tr("Soledad has started...")
- elif req.event == proto.SOLEDAD_DONE_DOWNLOADING_KEYS:
- ext_status = self.tr("Soledad is starting, please wait...")
- else:
- leap_assert(False,
- "Don't know how to handle this state: %s"
- % (req.event))
-
- self._set_long_mail_status(ext_status)
-
- def _mail_handle_keymanager_events(self, req):
- """
- Callback for the KeyManager events
-
- :param req: Request type
- :type req: leap.common.events.events_pb2.SignalRequest
- """
- self._keymanager_event.emit(req)
-
- def _mail_handle_keymanager_events_slot(self, req):
- """
- SLOT
- TRIGGER: _mail_handle_keymanager_events
-
- Reacts to an KeyManager event
-
- :param req: Request type
- :type req: leap.common.events.events_pb2.SignalRequest
- """
- # We want to ignore this kind of events once everything has
- # started
- if self._smtp_started and self._imap_started:
- return
-
- self._set_mail_status(self.tr("Starting..."))
-
- ext_status = ""
-
- if req.event == proto.KEYMANAGER_LOOKING_FOR_KEY:
- ext_status = self.tr("Looking for key for this user")
- elif req.event == proto.KEYMANAGER_KEY_FOUND:
- ext_status = self.tr("Found key! Starting mail...")
- # 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...")
- elif req.event == proto.KEYMANAGER_FINISHED_KEY_GENERATION:
- ext_status = self.tr("Finished generating key!")
- elif req.event == proto.KEYMANAGER_DONE_UPLOADING_KEYS:
- ext_status = self.tr("Starting mail...")
- else:
- leap_assert(False,
- "Don't know how to handle this state: %s"
- % (req.event))
-
- self._set_long_mail_status(ext_status)
-
- def _mail_handle_smtp_events(self, req):
- """
- Callback for the SMTP events
-
- :param req: Request type
- :type req: leap.common.events.events_pb2.SignalRequest
- """
- self._smtp_event.emit(req)
-
- def _mail_handle_smtp_events_slot(self, req):
- """
- SLOT
- TRIGGER: _mail_handle_smtp_events
-
- Reacts to an SMTP event
-
- :param req: Request type
- :type req: leap.common.events.events_pb2.SignalRequest
- """
- 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=True)
- 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"
- % (req.event))
-
- self._set_long_mail_status(ext_status)
-
- def _mail_handle_imap_events(self, req):
- """
- Callback for the IMAP events
-
- :param req: Request type
- :type req: leap.common.events.events_pb2.SignalRequest
- """
- self._imap_event.emit(req)
-
- def _mail_handle_imap_events_slot(self, req):
- """
- SLOT
- TRIGGER: _mail_handle_imap_events
-
- Reacts to an IMAP event
-
- :param req: Request type
- :type req: leap.common.events.events_pb2.SignalRequest
- """
- 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=True)
- 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:
- self.ui.lblUnread.setText(
- self.tr("%s Unread Emails") % (req.content))
- self.ui.lblUnread.setVisible(req.content != "0")
- self._set_mail_status(self.tr("ON"), ready=True)
- else:
- leap_assert(False, # XXX ???
- "Don't know how to handle this state: %s"
- % (req.event))
-
- if ext_status is not None:
- self._set_long_mail_status(ext_status)
-
- def _set_long_mail_status(self, ext_status):
- self.ui.lblLongMailStatus.setText(ext_status)
- self.ui.grpMailStatus.setVisible(len(ext_status) > 0)
diff --git a/src/leap/bitmask/gui/ui/eip_status.ui b/src/leap/bitmask/gui/ui/eip_status.ui
new file mode 100644
index 00000000..27df3f31
--- /dev/null
+++ b/src/leap/bitmask/gui/ui/eip_status.ui
@@ -0,0 +1,287 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EIPStatus</class>
+ <widget class="QWidget" name="EIPStatus">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>470</width>
+ <height>157</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="bottomMargin">
+ <number>24</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="2">
+ <widget class="QPushButton" name="btnEipStartStop">
+ <property name="text">
+ <string>Turn On</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/black/32/earth.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="lblEIPStatus">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">color: rgb(80, 80, 80);</string>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="lblEIPMessage">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>14</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Traffic is being routed in the clear</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="lblVPNStatusIcon">
+ <property name="maximumSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/black/32/off.png</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="1" colspan="3">
+ <widget class="QWidget" name="eip_bandwidth" native="true">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>32</height>
+ </size>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="eip_bandwidth_layout">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="sizeConstraint">
+ <enum>QLayout::SetDefaultConstraint</enum>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/down-arrow.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnDownload">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>120</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>11</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="cursor">
+ <cursorShape>PointingHandCursor</cursorShape>
+ </property>
+ <property name="text">
+ <string>0.0 KB/s</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>10</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/up-arrow.png</pixmap>
+ </property>
+ </widget>
+ </item>
+ <item alignment="Qt::AlignLeft">
+ <widget class="QPushButton" name="btnUpload">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>120</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>11</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="cursor">
+ <cursorShape>PointingHandCursor</cursorShape>
+ </property>
+ <property name="text">
+ <string>0.0 KB/s</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="../../../../../data/resources/mainwindow.qrc"/>
+ <include location="../../../../../data/resources/icons.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/leap/bitmask/gui/ui/eippreferences.ui b/src/leap/bitmask/gui/ui/eippreferences.ui
new file mode 100644
index 00000000..9493d330
--- /dev/null
+++ b/src/leap/bitmask/gui/ui/eippreferences.ui
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EIPPreferences</class>
+ <widget class="QDialog" name="EIPPreferences">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>398</width>
+ <height>262</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>EIP Preferences</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_2">
+ <item row="1" column="0">
+ <widget class="QGroupBox" name="gbGatewaySelector">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>Select gateway for provider</string>
+ </property>
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="lblSelectProvider">
+ <property name="text">
+ <string>&amp;Select provider:</string>
+ </property>
+ <property name="buddy">
+ <cstring>cbProvidersGateway</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="2">
+ <widget class="QComboBox" name="cbProvidersGateway">
+ <item>
+ <property name="text">
+ <string>&lt;Select provider&gt;</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="7" column="2">
+ <widget class="QPushButton" name="pbSaveGateway">
+ <property name="text">
+ <string>Save this provider settings</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" colspan="3">
+ <widget class="QLabel" name="lblProvidersGatewayStatus">
+ <property name="text">
+ <string>&lt; Providers Gateway Status &gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Select gateway:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" colspan="2">
+ <widget class="QComboBox" name="cbGateways">
+ <item>
+ <property name="text">
+ <string>Automatic</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="gbAutomaticEIP">
+ <property name="title">
+ <string>Automatic EIP 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 of EIP</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>
+ </tabstops>
+ <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>
+</ui>
diff --git a/src/leap/bitmask/gui/ui/login.ui b/src/leap/bitmask/gui/ui/login.ui
index 42a6897a..a1842608 100644
--- a/src/leap/bitmask/gui/ui/login.ui
+++ b/src/leap/bitmask/gui/ui/login.ui
@@ -6,127 +6,273 @@
<rect>
<x>0</x>
<y>0</y>
- <width>356</width>
- <height>223</height>
+ <width>468</width>
+ <height>350</height>
</rect>
</property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
<property name="windowTitle">
<string>Form</string>
</property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
<layout class="QGridLayout" name="gridLayout">
- <item row="5" column="2">
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item row="2" column="2" colspan="2">
+ <widget class="QWidget" name="logged_widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- <property name="sizeHint" stdset="0">
+ <property name="minimumSize">
<size>
- <width>40</width>
- <height>20</height>
+ <width>0</width>
+ <height>0</height>
</size>
</property>
- </spacer>
- </item>
- <item row="1" column="1" colspan="2">
- <widget class="QComboBox" name="cmbProviders"/>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <property name="bottomMargin">
+ <number>24</number>
+ </property>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="lblUser">
+ <property name="font">
+ <font>
+ <pointsize>15</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QPushButton" name="btnLogout">
+ <property name="text">
+ <string>Logout</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <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="0" colspan="2">
+ <widget class="QLabel" name="lblLoginStatus">
+ <property name="styleSheet">
+ <string notr="true">color: rgb(132, 132, 132);
+font: 75 12pt &quot;Lucida Grande&quot;;</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
- <item row="5" column="0">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <item row="1" column="1" rowspan="2">
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- <property name="sizeHint" stdset="0">
+ <property name="maximumSize">
<size>
- <width>40</width>
- <height>20</height>
+ <width>16777215</width>
+ <height>800</height>
</size>
</property>
- </spacer>
- </item>
- <item row="6" column="1">
- <widget class="QPushButton" name="btnCreateAccount">
<property name="text">
- <string>Create a new account</string>
+ <string/>
</property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_4">
- <property name="text">
- <string>&lt;b&gt;Provider:&lt;/b&gt;</string>
+ <property name="pixmap">
+ <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/black/32/user.png</pixmap>
</property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ <property name="scaledContents">
+ <bool>false</bool>
</property>
- </widget>
- </item>
- <item row="3" column="1" colspan="2">
- <widget class="QLineEdit" name="lnPassword">
- <property name="inputMask">
- <string/>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
- </widget>
- </item>
- <item row="2" column="1" colspan="2">
- <widget class="QLineEdit" name="lnUser"/>
- </item>
- <item row="4" column="1" colspan="2">
- <widget class="QCheckBox" name="chkRemember">
- <property name="text">
- <string>Remember username and password</string>
+ <property name="margin">
+ <number>0</number>
</property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>&lt;b&gt;Username:&lt;/b&gt;</string>
+ <item row="1" column="2" colspan="2">
+ <widget class="QWidget" name="login_widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
</property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <property name="rightMargin">
+ <number>24</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>&lt;b&gt;Provider:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QPushButton" name="btnLogin">
+ <property name="text">
+ <string>Log In</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QCheckBox" name="chkRemember">
+ <property name="text">
+ <string>Remember username and password</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="lblStatus">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="cmbProviders"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>&lt;b&gt;Username:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="lnUser"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>&lt;b&gt;Password:&lt;/b&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="lnPassword">
+ <property name="inputMask">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>&lt;b&gt;Password:&lt;/b&gt;</string>
+ <item row="1" column="0">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
</property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ <property name="sizeType">
+ <enum>QSizePolicy::Maximum</enum>
</property>
- </widget>
- </item>
- <item row="5" column="1">
- <widget class="QPushButton" name="btnLogin">
- <property name="text">
- <string>Log In</string>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>12</width>
+ <height>0</height>
+ </size>
</property>
- </widget>
+ </spacer>
</item>
- <item row="0" column="0" colspan="3">
- <widget class="QLabel" name="lblStatus">
+ <item row="0" column="0" colspan="4">
+ <widget class="ClickableLabel" name="clblErrorMsg">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>50</height>
+ </size>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">background-color: rgb(255, 127, 114);</string>
+ </property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
</widget>
</item>
</layout>
</widget>
+ <customwidgets>
+ <customwidget>
+ <class>ClickableLabel</class>
+ <extends>QLabel</extends>
+ <header>clickablelabel.h</header>
+ </customwidget>
+ </customwidgets>
<tabstops>
- <tabstop>cmbProviders</tabstop>
- <tabstop>lnUser</tabstop>
- <tabstop>lnPassword</tabstop>
<tabstop>chkRemember</tabstop>
- <tabstop>btnLogin</tabstop>
- <tabstop>btnCreateAccount</tabstop>
</tabstops>
- <resources/>
+ <resources>
+ <include location="../../../../../data/resources/mainwindow.qrc"/>
+ </resources>
<connections/>
</ui>
diff --git a/src/leap/bitmask/gui/ui/mail_status.ui b/src/leap/bitmask/gui/ui/mail_status.ui
new file mode 100644
index 00000000..1327f9e7
--- /dev/null
+++ b/src/leap/bitmask/gui/ui/mail_status.ui
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MailStatusWidget</class>
+ <widget class="QWidget" name="MailStatusWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>72</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1" rowspan="2">
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="1" column="0" colspan="3">
+ <widget class="QLabel" name="lblMailStatus">
+ <property name="styleSheet">
+ <string notr="true">color: rgb(80, 80, 80);</string>
+ </property>
+ <property name="text">
+ <string>You must login to use encrypted email.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Email</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="lblMailStatusIcon">
+ <property name="maximumSize">
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/black/32/off.png</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/black/32/email.png</pixmap>
+ </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 17837642..920160b8 100644
--- a/src/leap/bitmask/gui/ui/mainwindow.ui
+++ b/src/leap/bitmask/gui/ui/mainwindow.ui
@@ -6,10 +6,28 @@
<rect>
<x>0</x>
<y>0</y>
- <width>429</width>
- <height>579</height>
+ <width>524</width>
+ <height>722</height>
</rect>
</property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>524</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>524</width>
+ <height>16777215</height>
+ </size>
+ </property>
<property name="windowTitle">
<string>Bitmask</string>
</property>
@@ -28,9 +46,222 @@
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0" colspan="5">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item row="1" column="0" colspan="2">
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>524</width>
+ <height>635</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="widget_2" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </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="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="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>
+ <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>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>24</number>
+ </property>
+ <property name="rightMargin">
+ <number>24</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="lblLoginProvider">
+ <property name="font">
+ <font>
+ <pointsize>16</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Login</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="btnPreferences">
+ <property name="maximumSize">
+ <size>
+ <width>48</width>
+ <height>20</height>
+ </size>
+ </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>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="loginLayout"/>
+ </item>
+ <item>
+ <widget class="Line" name="line_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="mailLayout">
+ <property name="margin">
+ <number>12</number>
+ </property>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_4">
- <item row="2" column="0">
+ <item row="1" column="1">
+ <widget class="QLabel" name="lblNewUpdates">
+ <property name="text">
+ <string>There are new updates available, please restart.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
<spacer name="horizontalSpacer_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -43,7 +274,7 @@
</property>
</spacer>
</item>
- <item row="1" column="1">
+ <item row="0" column="1">
<spacer name="horizontalSpacer_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -56,17 +287,7 @@
</property>
</spacer>
</item>
- <item row="2" column="1">
- <widget class="QLabel" name="lblNewUpdates">
- <property name="text">
- <string>There are new updates available, please restart.</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
+ <item row="1" column="2">
<widget class="QPushButton" name="btnMore">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
@@ -82,7 +303,7 @@
</property>
</widget>
</item>
- <item row="2" column="3">
+ <item row="1" column="3">
<spacer name="horizontalSpacer_9">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -97,164 +318,6 @@
</item>
</layout>
</item>
- <item row="6" column="2">
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="10" column="0" colspan="5">
- <widget class="QStackedWidget" name="stackedWidget">
- <property name="currentIndex">
- <number>1</number>
- </property>
- <widget class="QWidget" name="loginPage">
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="0" column="1">
- <layout class="QHBoxLayout" name="loginLayout"/>
- </item>
- <item row="0" column="2">
- <spacer name="horizontalSpacer_4">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="0">
- <spacer name="horizontalSpacer_3">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- <widget class="QWidget" name="page_2">
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="0" column="0">
- <layout class="QVBoxLayout" name="statusLayout"/>
- </item>
- </layout>
- </widget>
- </widget>
- </item>
- <item row="7" column="2">
- <widget class="QLabel" name="label">
- <property name="autoFillBackground">
- <bool>false</bool>
- </property>
- <property name="text">
- <string/>
- </property>
- <property name="pixmap">
- <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/mask-launcher.png</pixmap>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item row="7" column="3" colspan="2">
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="17" column="2">
- <spacer name="verticalSpacer_2">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="7" column="0" colspan="2">
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="18" column="2">
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <widget class="QPushButton" name="btnPreferences">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="text">
- <string>Preferences</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_10">
- <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>
- <widget class="QPushButton" name="btnShowLog">
- <property name="text">
- <string>Show Log</string>
- </property>
- <property name="checkable">
- <bool>true</bool>
- </property>
- <property name="checked">
- <bool>false</bool>
- </property>
- <property name="flat">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
@@ -262,15 +325,15 @@
<rect>
<x>0</x>
<y>0</y>
- <width>429</width>
- <height>21</height>
+ <width>524</width>
+ <height>22</height>
</rect>
</property>
- <widget class="QMenu" name="menuSession">
+ <widget class="QMenu" name="menuFile">
<property name="title">
- <string>&amp;Session</string>
+ <string>&amp;Bitmask</string>
</property>
- <addaction name="action_log_out"/>
+ <addaction name="action_create_new_account"/>
<addaction name="separator"/>
<addaction name="action_quit"/>
</widget>
@@ -279,16 +342,17 @@
<string>Help</string>
</property>
<addaction name="action_help"/>
+ <addaction name="action_show_logs"/>
<addaction name="separator"/>
<addaction name="action_about_leap"/>
</widget>
- <addaction name="menuSession"/>
+ <addaction name="menuFile"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
- <action name="action_log_out">
+ <action name="action_preferences">
<property name="text">
- <string>Log &amp;out</string>
+ <string>Preferences...</string>
</property>
</action>
<action name="action_quit">
@@ -313,7 +377,12 @@
</action>
<action name="action_show_logs">
<property name="text">
- <string>Show &amp;logs</string>
+ <string>Show &amp;Log</string>
+ </property>
+ </action>
+ <action name="action_create_new_account">
+ <property name="text">
+ <string>Create a new account...</string>
</property>
</action>
</widget>
diff --git a/src/leap/bitmask/gui/ui/preferences.ui b/src/leap/bitmask/gui/ui/preferences.ui
index e66a2d68..e187c016 100644
--- a/src/leap/bitmask/gui/ui/preferences.ui
+++ b/src/leap/bitmask/gui/ui/preferences.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>503</width>
- <height>529</height>
+ <height>401</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_3">
- <item row="5" column="0">
+ <item row="4" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -31,64 +31,80 @@
</property>
</spacer>
</item>
- <item row="1" column="0">
- <widget class="QGroupBox" name="gbGatewaySelector">
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="gbPasswordChange">
<property name="enabled">
- <bool>true</bool>
+ <bool>false</bool>
</property>
<property name="title">
- <string>Select gateway for provider</string>
- </property>
- <property name="checkable">
- <bool>false</bool>
+ <string>Password Change</string>
</property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="1" colspan="2">
- <widget class="QComboBox" name="cbProvidersGateway">
- <item>
- <property name="text">
- <string>&lt;Select provider&gt;</string>
- </property>
- </item>
- </widget>
- </item>
+ <layout class="QFormLayout" name="formLayout">
+ <property name="fieldGrowthPolicy">
+ <enum>QFormLayout::ExpandingFieldsGrow</enum>
+ </property>
<item row="0" column="0">
- <widget class="QLabel" name="lblSelectProvider">
+ <widget class="QLabel" name="lblCurrentPassword">
<property name="text">
- <string>&amp;Select provider:</string>
+ <string>&amp;Current password:</string>
</property>
<property name="buddy">
- <cstring>cbProvidersGateway</cstring>
+ <cstring>leCurrentPassword</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="leCurrentPassword">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="1" column="0">
- <widget class="QLabel" name="label">
+ <widget class="QLabel" name="lblNewPassword">
<property name="text">
- <string>Select gateway:</string>
+ <string>&amp;New password:</string>
+ </property>
+ <property name="buddy">
+ <cstring>leNewPassword</cstring>
</property>
</widget>
</item>
- <item row="1" column="1" colspan="2">
- <widget class="QComboBox" name="cbGateways">
- <item>
- <property name="text">
- <string>Automatic</string>
- </property>
- </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="leNewPassword">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
</widget>
</item>
- <item row="5" column="2">
- <widget class="QPushButton" name="pbSaveGateway">
+ <item row="2" column="0">
+ <widget class="QLabel" name="lblNewPassword2">
<property name="text">
- <string>Save this provider settings</string>
+ <string>&amp;Re-enter new password:</string>
+ </property>
+ <property name="buddy">
+ <cstring>leNewPassword2</cstring>
</property>
</widget>
</item>
- <item row="2" column="0" colspan="3">
- <widget class="QLabel" name="lblProvidersGatewayStatus">
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="leNewPassword2">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QPushButton" name="pbChangePassword">
<property name="text">
- <string>&lt; Providers Gateway Status &gt;</string>
+ <string>Change</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QLabel" name="lblPasswordChangeStatus">
+ <property name="text">
+ <string>&lt;Password change status&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -98,7 +114,7 @@
</layout>
</widget>
</item>
- <item row="4" column="0">
+ <item row="3" column="0">
<widget class="QGroupBox" name="gbEnabledServices">
<property name="title">
<string>Enabled services</string>
@@ -155,89 +171,6 @@
</layout>
</widget>
</item>
- <item row="0" column="0">
- <widget class="QGroupBox" name="gbPasswordChange">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="title">
- <string>Password Change</string>
- </property>
- <layout class="QFormLayout" name="formLayout">
- <property name="fieldGrowthPolicy">
- <enum>QFormLayout::ExpandingFieldsGrow</enum>
- </property>
- <item row="0" column="0">
- <widget class="QLabel" name="lblCurrentPassword">
- <property name="text">
- <string>&amp;Current password:</string>
- </property>
- <property name="buddy">
- <cstring>leCurrentPassword</cstring>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QLineEdit" name="leCurrentPassword">
- <property name="echoMode">
- <enum>QLineEdit::Password</enum>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="lblNewPassword">
- <property name="text">
- <string>&amp;New password:</string>
- </property>
- <property name="buddy">
- <cstring>leNewPassword</cstring>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLineEdit" name="leNewPassword">
- <property name="echoMode">
- <enum>QLineEdit::Password</enum>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="lblNewPassword2">
- <property name="text">
- <string>&amp;Re-enter new password:</string>
- </property>
- <property name="buddy">
- <cstring>leNewPassword2</cstring>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLineEdit" name="leNewPassword2">
- <property name="echoMode">
- <enum>QLineEdit::Password</enum>
- </property>
- </widget>
- </item>
- <item row="4" column="1">
- <widget class="QPushButton" name="pbChangePassword">
- <property name="text">
- <string>Change</string>
- </property>
- </widget>
- </item>
- <item row="3" column="0" colspan="2">
- <widget class="QLabel" name="lblPasswordChangeStatus">
- <property name="text">
- <string>&lt;Password change status&gt;</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
</layout>
</widget>
<resources>
diff --git a/src/leap/bitmask/gui/ui/statuspanel.ui b/src/leap/bitmask/gui/ui/statuspanel.ui
deleted file mode 100644
index d77af1da..00000000
--- a/src/leap/bitmask/gui/ui/statuspanel.ui
+++ /dev/null
@@ -1,393 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>StatusPanel</class>
- <widget class="QWidget" name="StatusPanel">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>470</width>
- <height>477</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>Form</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QLabel" name="lblProvider">
- <property name="styleSheet">
- <string notr="true">font: bold;</string>
- </property>
- <property name="text">
- <string>user@domain.org</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QWidget" name="status_rows" native="true">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="styleSheet">
- <string notr="true"/>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="5" column="1">
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="1" column="0" colspan="3">
- <widget class="QLabel" name="lblUnread">
- <property name="text">
- <string>0 Unread Emails</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="lblMailStatus">
- <property name="styleSheet">
- <string notr="true">font: bold;</string>
- </property>
- <property name="text">
- <string>Disabled</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_4">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Encrypted Mail:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <spacer name="horizontalSpacer_4">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="2" column="0" colspan="3">
- <spacer name="verticalSpacer_3">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>1</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </item>
- <item row="1" column="1">
- <layout class="QHBoxLayout" name="eip_bandwidth">
- <property name="spacing">
- <number>4</number>
- </property>
- <property name="sizeConstraint">
- <enum>QLayout::SetDefaultConstraint</enum>
- </property>
- <item>
- <widget class="QLabel" name="label_5">
- <property name="text">
- <string/>
- </property>
- <property name="pixmap">
- <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/down-arrow.png</pixmap>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="btnDownload">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>120</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="cursor">
- <cursorShape>PointingHandCursor</cursorShape>
- </property>
- <property name="text">
- <string>0.0 KB/s</string>
- </property>
- <property name="flat">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_3">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QLabel" name="label_7">
- <property name="text">
- <string/>
- </property>
- <property name="pixmap">
- <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/up-arrow.png</pixmap>
- </property>
- </widget>
- </item>
- <item alignment="Qt::AlignLeft">
- <widget class="QPushButton" name="btnUpload">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>120</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="cursor">
- <cursorShape>PointingHandCursor</cursorShape>
- </property>
- <property name="text">
- <string>0.0 KB/s</string>
- </property>
- <property name="flat">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </item>
- <item row="2" column="0">
- <spacer name="verticalSpacer_2">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Preferred</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>0</width>
- <height>11</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="0" rowspan="2">
- <widget class="QLabel" name="lblVPNStatusIcon">
- <property name="maximumSize">
- <size>
- <width>64</width>
- <height>64</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- <property name="pixmap">
- <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/64/network-eip-down.png</pixmap>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item row="4" column="0" rowspan="2">
- <widget class="QLabel" name="lblMailIcon">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>64</width>
- <height>64</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>64</width>
- <height>999</height>
- </size>
- </property>
- <property name="text">
- <string/>
- </property>
- <property name="pixmap">
- <pixmap resource="../../../../../data/resources/icons.qrc">:/images/mail-unlocked.png</pixmap>
- </property>
- </widget>
- </item>
- <item row="7" column="0" colspan="2">
- <widget class="QGroupBox" name="grpMailStatus">
- <property name="title">
- <string/>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QLabel" name="lblLongMailStatus">
- <property name="text">
- <string>...</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item row="3" column="0" colspan="2">
- <widget class="QGroupBox" name="globalStatusBox">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="0" column="0">
- <widget class="QLabel" name="lblGlobalStatus">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>...</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item row="0" column="1">
- <layout class="QHBoxLayout" name="eip_controls">
- <item>
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Encrypted Internet: </string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="lblEIPStatus">
- <property name="styleSheet">
- <string notr="true">font: bold;</string>
- </property>
- <property name="text">
- <string>Off</string>
- </property>
- <property name="textFormat">
- <enum>Qt::AutoText</enum>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- <property name="wordWrap">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item>
- <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>
- <widget class="QPushButton" name="btnEipStartStop">
- <property name="text">
- <string>Turn On</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <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>
- </layout>
- </widget>
- <resources>
- <include location="../../../../../data/resources/icons.qrc"/>
- </resources>
- <connections/>
-</ui>
diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui
index 2a412784..0f6eef6e 100644
--- a/src/leap/bitmask/gui/ui/wizard.ui
+++ b/src/leap/bitmask/gui/ui/wizard.ui
@@ -134,17 +134,7 @@
</property>
</spacer>
</item>
- <item row="1" column="1">
- <widget class="QLineEdit" name="lnProvider"/>
- </item>
- <item row="1" column="2">
- <widget class="QPushButton" name="btnCheck">
- <property name="text">
- <string>Check</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
+ <item row="5" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -157,17 +147,7 @@
</property>
</spacer>
</item>
- <item row="1" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>https://</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="4" column="0" colspan="3">
+ <item row="6" column="0" colspan="3">
<widget class="QGroupBox" name="grpCheckProvider">
<property name="title">
<string>Checking for a valid provider</string>
@@ -276,13 +256,76 @@
</layout>
</widget>
</item>
- <item row="2" column="1" colspan="2">
+ <item row="4" column="1" colspan="2">
<widget class="QLabel" name="lblProviderSelectStatus">
<property name="text">
<string/>
</property>
</widget>
</item>
+ <item row="1" column="0" colspan="3">
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Configure or select a provider</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="0" column="0">
+ <widget class="QRadioButton" name="rbNewProvider">
+ <property name="text">
+ <string>Configure new provider:</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QRadioButton" name="rbExistingProvider">
+ <property name="text">
+ <string>Use existing one:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>https://</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="lnProvider"/>
+ </item>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="btnCheck">
+ <property name="text">
+ <string>Check</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>https://</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QComboBox" name="cbProviders">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
</layout>
</widget>
<widget class="QWizardPage" name="provider_info_page">
@@ -626,10 +669,18 @@
</widget>
</item>
<item row="3" column="1" colspan="2">
- <widget class="QLineEdit" name="lblPassword"/>
+ <widget class="QLineEdit" name="lblPassword">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
</item>
<item row="4" column="1" colspan="2">
- <widget class="QLineEdit" name="lblPassword2"/>
+ <widget class="QLineEdit" name="lblPassword2">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="lblUser"/>
@@ -756,12 +807,91 @@
<tabstop>btnRegister</tabstop>
<tabstop>rdoRegister</tabstop>
<tabstop>rdoLogin</tabstop>
- <tabstop>lnProvider</tabstop>
- <tabstop>btnCheck</tabstop>
</tabstops>
<resources>
<include location="../../../../../data/resources/mainwindow.qrc"/>
<include location="../../../../../data/resources/locale.qrc"/>
</resources>
- <connections/>
+ <connections>
+ <connection>
+ <sender>rbExistingProvider</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>cbProviders</receiver>
+ <slot>setFocus()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>167</x>
+ <y>192</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>265</x>
+ <y>191</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>rbNewProvider</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>lnProvider</receiver>
+ <slot>setFocus()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>171</x>
+ <y>164</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>246</x>
+ <y>164</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>rbExistingProvider</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>lnProvider</receiver>
+ <slot>setDisabled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>169</x>
+ <y>196</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>327</x>
+ <y>163</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>rbNewProvider</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>cbProviders</receiver>
+ <slot>setDisabled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>169</x>
+ <y>162</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>269</x>
+ <y>193</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>rbExistingProvider</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>btnCheck</receiver>
+ <slot>setDisabled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>154</x>
+ <y>193</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>498</x>
+ <y>170</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
</ui>
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index 45734b81..e3f5904e 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -14,28 +14,27 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
"""
First run wizard
"""
import os
import logging
import json
+import random
from functools import partial
from PySide import QtCore, QtGui
from twisted.internet import threads
-from leap.bitmask.config import flags
+from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.crypto.srpregister import SRPRegister
-from leap.bitmask.util.privilege_policies import is_missing_policy_permissions
+from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper
+from leap.bitmask.services import get_service_display_name, get_supported
from leap.bitmask.util.request_helpers import get_content
from leap.bitmask.util.keyring_helpers import has_keyring
from leap.bitmask.util.password import basic_password_checks
-from leap.bitmask.services.eip.providerbootstrapper import ProviderBootstrapper
-from leap.bitmask.services import get_service_display_name, get_supported
from ui_wizard import Ui_Wizard
@@ -84,6 +83,8 @@ class Wizard(QtGui.QWizard):
self._show_register = False
+ self._use_existing_provider = False
+
self.ui.grpCheckProvider.setVisible(False)
self.ui.btnCheck.clicked.connect(self._check_provider)
self.ui.lnProvider.returnPressed.connect(self._check_provider)
@@ -113,9 +114,6 @@ class Wizard(QtGui.QWizard):
self.currentIdChanged.connect(self._current_id_changed)
- self.ui.lblPassword.setEchoMode(QtGui.QLineEdit.Password)
- self.ui.lblPassword2.setEchoMode(QtGui.QLineEdit.Password)
-
self.ui.lnProvider.textChanged.connect(
self._enable_check)
@@ -128,6 +126,8 @@ class Wizard(QtGui.QWizard):
self.ui.btnRegister.clicked.connect(
self._register)
+ self.ui.rbExistingProvider.toggled.connect(self._skip_provider_checks)
+
usernameRe = QtCore.QRegExp(self.BARE_USERNAME_REGEX)
self.ui.lblUser.setValidator(
QtGui.QRegExpValidator(usernameRe, self))
@@ -147,6 +147,40 @@ class Wizard(QtGui.QWizard):
self.ui.label_12.setVisible(False)
self.ui.lblProviderPolicy.setVisible(False)
+ self._load_configured_providers()
+
+ def _load_configured_providers(self):
+ """
+ Loads the configured providers into the wizard providers combo box.
+ """
+ ls = LeapSettings()
+ providers = ls.get_configured_providers()
+ if not providers:
+ self.ui.rbExistingProvider.setEnabled(False)
+ self.ui.label_8.setEnabled(False) # 'https://' label
+ self.ui.cbProviders.setEnabled(False)
+ return
+
+ pinned = []
+ user_added = []
+
+ # separate pinned providers from user added ones
+ for p in providers:
+ if ls.is_pinned_provider(p):
+ pinned.append(p)
+ else:
+ user_added.append(p)
+
+ if user_added:
+ self.ui.cbProviders.addItems(user_added)
+
+ if user_added and pinned:
+ self.ui.cbProviders.addItem('---')
+
+ if pinned:
+ random.shuffle(pinned) # don't prioritize alphabetically
+ self.ui.cbProviders.addItems(pinned)
+
def get_domain(self):
return self._domain
@@ -318,6 +352,25 @@ class Wizard(QtGui.QWizard):
self._provider_select_defer = self._provider_bootstrapper.\
run_provider_select_checks(self._domain)
+ def _skip_provider_checks(self, skip):
+ """
+ SLOT
+ Triggered:
+ self.ui.rbExistingProvider.toggled
+
+ Allows the user to move to the next page without make any checks,
+ used when we are selecting an already configured provider.
+
+ :param skip: if we should skip checks or not
+ :type skip: bool
+ """
+ if skip:
+ self._reset_provider_check()
+
+ self.page(self.SELECT_PROVIDER_PAGE).set_completed(skip)
+ self.button(QtGui.QWizard.NextButton).setEnabled(skip)
+ self._use_existing_provider = skip
+
def _complete_task(self, data, label, complete=False, complete_page=-1):
"""
Checks a task and completes a page if specified
@@ -564,4 +617,14 @@ class Wizard(QtGui.QWizard):
else:
return self.SERVICES_PAGE
+ if self.currentPage() == self.page(self.SELECT_PROVIDER_PAGE):
+ if self._use_existing_provider:
+ self._domain = self.ui.cbProviders.currentText()
+ self._provider_config = ProviderConfig.get_provider_config(
+ self._domain)
+ if self._show_register:
+ return self.REGISTER_USER_PAGE
+ else:
+ return self.SERVICES_PAGE
+
return QtGui.QWizard.nextId(self)
diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py
index 831c6a1c..d93efbc6 100644
--- a/src/leap/bitmask/platform_init/initializers.py
+++ b/src/leap/bitmask/platform_init/initializers.py
@@ -29,7 +29,9 @@ import tempfile
from PySide import QtGui
from leap.bitmask.config.leapsettings import LeapSettings
-from leap.bitmask.services.eip import vpnlaunchers
+from leap.bitmask.services.eip import get_vpn_launcher
+from leap.bitmask.services.eip.linuxvpnlauncher import LinuxVPNLauncher
+from leap.bitmask.services.eip.darwinvpnlauncher import DarwinVPNLauncher
from leap.bitmask.util import first
from leap.bitmask.util import privilege_policies
@@ -106,7 +108,7 @@ def check_missing():
config = LeapSettings()
alert_missing = config.get_alert_missing_scripts()
- launcher = vpnlaunchers.get_platform_launcher()
+ launcher = get_vpn_launcher()
missing_scripts = launcher.missing_updown_scripts
missing_other = launcher.missing_other_files
@@ -251,7 +253,7 @@ def _darwin_install_missing_scripts(badexec, notfound):
"..",
"Resources",
"openvpn")
- launcher = vpnlaunchers.DarwinVPNLauncher
+ launcher = DarwinVPNLauncher
if os.path.isdir(installer_path):
fd, tempscript = tempfile.mkstemp(prefix="leap_installer-")
@@ -356,7 +358,7 @@ def _linux_install_missing_scripts(badexec, notfound):
"""
success = False
installer_path = os.path.join(os.getcwd(), "apps", "eip", "files")
- launcher = vpnlaunchers.LinuxVPNLauncher
+ launcher = LinuxVPNLauncher
# XXX refactor with darwin, same block.
diff --git a/src/leap/bitmask/provider/__init__.py b/src/leap/bitmask/provider/__init__.py
index e69de29b..53587d65 100644
--- a/src/leap/bitmask/provider/__init__.py
+++ b/src/leap/bitmask/provider/__init__.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# __init.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/>.
+"""
+Module initialization for leap.bitmask.provider
+"""
+import os
+from leap.common.check import leap_assert
+
+
+def get_provider_path(domain):
+ """
+ Returns relative path for provider config.
+
+ :param domain: the domain to which this providerconfig belongs to.
+ :type domain: str
+ :returns: the path
+ :rtype: str
+ """
+ leap_assert(domain is not None, "get_provider_path: We need a domain")
+ return os.path.join("leap", "providers", domain, "provider.json")
diff --git a/src/leap/bitmask/services/eip/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py
index 3b7c9899..1b5947e1 100644
--- a/src/leap/bitmask/services/eip/providerbootstrapper.py
+++ b/src/leap/bitmask/provider/providerbootstrapper.py
@@ -14,7 +14,6 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
"""
Provider bootstrapping
"""
@@ -28,15 +27,15 @@ from PySide import QtCore
from leap.bitmask.config.providerconfig import ProviderConfig, MissingCACert
from leap.bitmask.util.request_helpers import get_content
-from leap.bitmask.util import get_path_prefix
+from leap.bitmask import util
from leap.bitmask.util.constants import REQUEST_TIMEOUT
from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper
from leap.bitmask.provider.supportedapis import SupportedAPIs
+from leap.common import ca_bundle
from leap.common.certs import get_digest
from leap.common.files import check_and_fix_urw_only, get_mtime, mkdir_p
from leap.common.check import leap_assert, leap_assert_type, leap_check
-
logger = logging.getLogger(__name__)
@@ -85,16 +84,32 @@ class ProviderBootstrapper(AbstractBootstrapper):
self._provider_config = None
self._download_if_needed = False
+ @property
+ def verify(self):
+ """
+ Verify parameter for requests.
+
+ :returns: either False, if checks are skipped, or the
+ path to the ca bundle.
+ :rtype: bool or str
+ """
+ if self._bypass_checks:
+ verify = False
+ else:
+ verify = ca_bundle.where()
+ return verify
+
def _check_name_resolution(self):
"""
Checks that the name resolution for the provider name works
"""
leap_assert(self._domain, "Cannot check DNS without a domain")
-
logger.debug("Checking name resolution for %s" % (self._domain))
# We don't skip this check, since it's basic for the whole
# system to work
+ # err --- but we can do it after a failure, to diagnose what went
+ # wrong. Right now we're just adding connection overhead. -- kali
socket.gethostbyname(self._domain)
def _check_https(self, *args):
@@ -102,24 +117,29 @@ class ProviderBootstrapper(AbstractBootstrapper):
Checks that https is working and that the provided certificate
checks out
"""
-
leap_assert(self._domain, "Cannot check HTTPS without a domain")
-
logger.debug("Checking https for %s" % (self._domain))
# We don't skip this check, since it's basic for the whole
- # system to work
+ # system to work.
+ # err --- but we can do it after a failure, to diagnose what went
+ # wrong. Right now we're just adding connection overhead. -- kali
try:
res = self._session.get("https://%s" % (self._domain,),
- verify=not self._bypass_checks,
+ verify=self.verify,
timeout=REQUEST_TIMEOUT)
res.raise_for_status()
- except requests.exceptions.SSLError:
+ except requests.exceptions.SSLError as exc:
+ logger.exception(exc)
self._err_msg = self.tr("Provider certificate could "
"not be verified")
raise
- except Exception:
+ except Exception as exc:
+ # XXX careful!. The error might be also a SSL handshake
+ # timeout error, in which case we should retry a couple of times
+ # more, for cases where the ssl server gives high latencies.
+ logger.exception(exc)
self._err_msg = self.tr("Provider does not support HTTPS")
raise
@@ -129,12 +149,16 @@ class ProviderBootstrapper(AbstractBootstrapper):
"""
leap_assert(self._domain,
"Cannot download provider info without a domain")
-
logger.debug("Downloading provider info for %s" % (self._domain))
- headers = {}
+ # --------------------------------------------------------------
+ # TODO factor out with the download routines in services.
+ # Watch out! We're handling the verify paramenter differently here.
- provider_json = os.path.join(get_path_prefix(), "leap", "providers",
+ headers = {}
+ provider_json = os.path.join(util.get_path_prefix(),
+ "leap",
+ "providers",
self._domain, "provider.json")
mtime = get_mtime(provider_json)
@@ -142,16 +166,18 @@ class ProviderBootstrapper(AbstractBootstrapper):
headers['if-modified-since'] = mtime
uri = "https://%s/%s" % (self._domain, "provider.json")
- verify = not self._bypass_checks
+ verify = self.verify
if mtime: # the provider.json exists
- provider_config = ProviderConfig()
- provider_config.load(provider_json)
+ # So, we're getting it from the api.* and checking against
+ # the provider ca.
try:
- verify = provider_config.get_ca_cert_path()
+ provider_config = ProviderConfig()
+ provider_config.load(provider_json)
uri = provider_config.get_api_uri() + '/provider.json'
+ verify = provider_config.get_ca_cert_path()
except MissingCACert:
- # get_ca_cert_path fails if the certificate does not exists.
+ # no ca? then download from main domain again.
pass
logger.debug("Requesting for provider.json... "
@@ -165,6 +191,9 @@ class ProviderBootstrapper(AbstractBootstrapper):
# Not modified
if res.status_code == 304:
logger.debug("Provider definition has not been modified")
+ # --------------------------------------------------------------
+ # end refactor, more or less...
+ # XXX Watch out, have to check the supported api yet.
else:
provider_definition, mtime = get_content(res)
@@ -181,8 +210,8 @@ class ProviderBootstrapper(AbstractBootstrapper):
else:
api_supported = ', '.join(SupportedAPIs.SUPPORTED_APIS)
error = ('Unsupported provider API version. '
- 'Supported versions are: {}. '
- 'Found: {}.').format(api_supported, api_version)
+ 'Supported versions are: {0}. '
+ 'Found: {1}.').format(api_supported, api_version)
logger.error(error)
raise UnsupportedProviderAPI(error)
@@ -230,7 +259,8 @@ class ProviderBootstrapper(AbstractBootstrapper):
"""
Downloads the CA cert that is going to be used for the api URL
"""
-
+ # XXX maybe we can skip this step if
+ # we have a fresh one.
leap_assert(self._provider_config, "Cannot download the ca cert "
"without a provider config!")
@@ -244,7 +274,7 @@ class ProviderBootstrapper(AbstractBootstrapper):
return
res = self._session.get(self._provider_config.get_ca_cert_uri(),
- verify=not self._bypass_checks,
+ verify=self.verify,
timeout=REQUEST_TIMEOUT)
res.raise_for_status()
diff --git a/src/leap/bitmask/config/__init__.py b/src/leap/bitmask/provider/tests/__init__.py
index e69de29b..e69de29b 100644
--- a/src/leap/bitmask/config/__init__.py
+++ b/src/leap/bitmask/provider/tests/__init__.py
diff --git a/src/leap/bitmask/services/eip/tests/test_providerbootstrapper.py b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py
index b0685676..88a4ff0b 100644
--- a/src/leap/bitmask/services/eip/tests/test_providerbootstrapper.py
+++ b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py
@@ -14,15 +14,12 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-
"""
Tests for the Provider Boostrapper checks
These will be whitebox tests since we want to make sure the private
implementation is checking what we expect.
"""
-
import os
import mock
import socket
@@ -39,13 +36,13 @@ from nose.twistedtools import deferred, reactor
from twisted.internet import threads
from requests.models import Response
-from leap.bitmask.services.eip.providerbootstrapper import ProviderBootstrapper
-from leap.bitmask.services.eip.providerbootstrapper import \
- UnsupportedProviderAPI
-from leap.bitmask.services.eip.providerbootstrapper import WrongFingerprint
-from leap.bitmask.provider.supportedapis import SupportedAPIs
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.crypto.tests import fake_provider
+from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper
+from leap.bitmask.provider.providerbootstrapper import UnsupportedProviderAPI
+from leap.bitmask.provider.providerbootstrapper import WrongFingerprint
+from leap.bitmask.provider.supportedapis import SupportedAPIs
+from leap.bitmask import util
from leap.common.files import mkdir_p
from leap.common.testing.https_server import where
from leap.common.testing.basetest import BaseLeapTest
@@ -319,16 +316,19 @@ class ProviderBootstrapperActiveTest(unittest.TestCase):
# tearDown so we are sure everything is as expected for each
# test. If we do it inside each specific test, a failure in
# the test will leave the implementation with the mock.
- self.old_gpp = ProviderConfig.get_path_prefix
+ self.old_gpp = util.get_path_prefix
+
self.old_load = ProviderConfig.load
self.old_save = ProviderConfig.save
self.old_api_version = ProviderConfig.get_api_version
+ self.old_api_uri = ProviderConfig.get_api_uri
def tearDown(self):
- ProviderConfig.get_path_prefix = self.old_gpp
+ util.get_path_prefix = self.old_gpp
ProviderConfig.load = self.old_load
ProviderConfig.save = self.old_save
ProviderConfig.get_api_version = self.old_api_version
+ ProviderConfig.get_api_uri = self.old_api_uri
def test_check_https_succeeds(self):
# XXX: Need a proper CA signed cert to test this
@@ -376,10 +376,11 @@ class ProviderBootstrapperActiveTest(unittest.TestCase):
paths
:type path_prefix: str
"""
- ProviderConfig.get_path_prefix = mock.MagicMock(
- return_value=path_prefix)
+ util.get_path_prefix = mock.MagicMock(return_value=path_prefix)
ProviderConfig.get_api_version = mock.MagicMock(
return_value=api)
+ ProviderConfig.get_api_uri = mock.MagicMock(
+ return_value="https://localhost:%s" % (self.https_port,))
ProviderConfig.load = mock.MagicMock()
ProviderConfig.save = mock.MagicMock()
@@ -404,10 +405,8 @@ class ProviderBootstrapperActiveTest(unittest.TestCase):
:returns: the provider.json path used
:rtype: str
"""
- provider_dir = os.path.join(ProviderConfig()
- .get_path_prefix(),
- "leap",
- "providers",
+ provider_dir = os.path.join(util.get_path_prefix(),
+ "leap", "providers",
self.pb._domain)
mkdir_p(provider_dir)
provider_path = os.path.join(provider_dir,
@@ -417,6 +416,9 @@ class ProviderBootstrapperActiveTest(unittest.TestCase):
p.write("A")
return provider_path
+ @mock.patch(
+ 'leap.bitmask.config.providerconfig.ProviderConfig.get_domain',
+ lambda x: where('testdomain.com'))
def test_download_provider_info_new_provider(self):
self._setup_provider_config_with("1", tempfile.mkdtemp())
self._setup_providerbootstrapper(True)
@@ -435,10 +437,7 @@ class ProviderBootstrapperActiveTest(unittest.TestCase):
# set mtime to something really new
os.utime(provider_path, (-1, time.time()))
- with mock.patch.object(
- ProviderConfig, 'get_api_uri',
- return_value="https://localhost:%s" % (self.https_port,)):
- self.pb._download_provider_info()
+ self.pb._download_provider_info()
# we check that it doesn't save the provider
# config, because it's new enough
self.assertFalse(ProviderConfig.save.called)
@@ -454,10 +453,7 @@ class ProviderBootstrapperActiveTest(unittest.TestCase):
# set mtime to something really new
os.utime(provider_path, (-1, time.time()))
- with mock.patch.object(
- ProviderConfig, 'get_api_uri',
- return_value="https://localhost:%s" % (self.https_port,)):
- self.pb._download_provider_info()
+ self.pb._download_provider_info()
# we check that it doesn't save the provider
# config, because it's new enough
self.assertFalse(ProviderConfig.save.called)
@@ -473,10 +469,7 @@ class ProviderBootstrapperActiveTest(unittest.TestCase):
# set mtime to something really old
os.utime(provider_path, (-1, 100))
- with mock.patch.object(
- ProviderConfig, 'get_api_uri',
- return_value="https://localhost:%s" % (self.https_port,)):
- self.pb._download_provider_info()
+ self.pb._download_provider_info()
self.assertTrue(ProviderConfig.load.called)
self.assertTrue(ProviderConfig.save.called)
@@ -488,11 +481,8 @@ class ProviderBootstrapperActiveTest(unittest.TestCase):
self._setup_providerbootstrapper(False)
self._produce_dummy_provider_json()
- with mock.patch.object(
- ProviderConfig, 'get_api_uri',
- return_value="https://localhost:%s" % (self.https_port,)):
- with self.assertRaises(UnsupportedProviderAPI):
- self.pb._download_provider_info()
+ with self.assertRaises(UnsupportedProviderAPI):
+ self.pb._download_provider_info()
@mock.patch(
'leap.bitmask.config.providerconfig.ProviderConfig.get_ca_cert_path',
@@ -503,10 +493,7 @@ class ProviderBootstrapperActiveTest(unittest.TestCase):
self._setup_providerbootstrapper(False)
self._produce_dummy_provider_json()
- with mock.patch.object(
- ProviderConfig, 'get_api_uri',
- return_value="https://localhost:%s" % (self.https_port,)):
- self.pb._download_provider_info()
+ self.pb._download_provider_info()
@mock.patch(
'leap.bitmask.config.providerconfig.ProviderConfig.get_api_uri',
diff --git a/src/leap/bitmask/services/__init__.py b/src/leap/bitmask/services/__init__.py
index afce72f6..f9456159 100644
--- a/src/leap/bitmask/services/__init__.py
+++ b/src/leap/bitmask/services/__init__.py
@@ -27,7 +27,7 @@ from leap.bitmask.crypto.srpauth import SRPAuth
from leap.bitmask.util.constants import REQUEST_TIMEOUT
from leap.bitmask.util.privilege_policies import is_missing_policy_permissions
from leap.bitmask.util.request_helpers import get_content
-from leap.bitmask.util import get_path_prefix
+from leap.bitmask import util
from leap.common.check import leap_assert
from leap.common.config.baseconfig import BaseConfig
@@ -105,10 +105,11 @@ def download_service_config(provider_config, service_config,
service_name = service_config.name
service_json = "{0}-service.json".format(service_name)
headers = {}
- mtime = get_mtime(os.path.join(get_path_prefix(),
+ mtime = get_mtime(os.path.join(util.get_path_prefix(),
"leap", "providers",
provider_config.get_domain(),
service_json))
+
if download_if_needed and mtime:
headers['if-modified-since'] = mtime
@@ -125,10 +126,15 @@ def download_service_config(provider_config, service_config,
# XXX make and use @with_srp_auth decorator
srp_auth = SRPAuth(provider_config)
session_id = srp_auth.get_session_id()
+ token = srp_auth.get_token()
cookies = None
- if session_id:
+ if session_id is not None:
cookies = {"_session_id": session_id}
+ # API v2 will only support token auth, but in v1 we can send both
+ if token is not None:
+ headers["Authorization"] = 'Token token="{0}"'.format(token)
+
res = session.get(config_uri,
verify=provider_config.get_ca_cert_path(),
headers=headers,
diff --git a/src/leap/bitmask/services/connections.py b/src/leap/bitmask/services/connections.py
index f3ab9e8e..8aeb4e0c 100644
--- a/src/leap/bitmask/services/connections.py
+++ b/src/leap/bitmask/services/connections.py
@@ -103,6 +103,7 @@ class AbstractLEAPConnection(object):
# Bypass stages
connection_died_signal = None
+ connection_aborted_signal = None
class Disconnected(State):
"""Disconnected state"""
diff --git a/src/leap/bitmask/services/eip/__init__.py b/src/leap/bitmask/services/eip/__init__.py
index dd010027..6030cac3 100644
--- a/src/leap/bitmask/services/eip/__init__.py
+++ b/src/leap/bitmask/services/eip/__init__.py
@@ -20,7 +20,11 @@ leap.bitmask.services.eip module initialization
import os
import tempfile
-from leap.bitmask.platform_init import IS_WIN
+from leap.bitmask.services.eip.darwinvpnlauncher import DarwinVPNLauncher
+from leap.bitmask.services.eip.linuxvpnlauncher import LinuxVPNLauncher
+from leap.bitmask.services.eip.windowsvpnlauncher import WindowsVPNLauncher
+from leap.bitmask.platform_init import IS_LINUX, IS_MAC, IS_WIN
+from leap.common.check import leap_assert
def get_openvpn_management():
@@ -40,3 +44,24 @@ def get_openvpn_management():
port = "unix"
return host, port
+
+
+def get_vpn_launcher():
+ """
+ Return the VPN launcher for the current platform.
+ """
+ if not (IS_LINUX or IS_MAC or IS_WIN):
+ error_msg = "VPN Launcher not implemented for this platform."
+ raise NotImplementedError(error_msg)
+
+ launcher = None
+ if IS_LINUX:
+ launcher = LinuxVPNLauncher
+ elif IS_MAC:
+ launcher = DarwinVPNLauncher
+ elif IS_WIN:
+ launcher = WindowsVPNLauncher
+
+ leap_assert(launcher is not None)
+
+ return launcher()
diff --git a/src/leap/bitmask/services/eip/connection.py b/src/leap/bitmask/services/eip/connection.py
index 5f05ba07..08b29070 100644
--- a/src/leap/bitmask/services/eip/connection.py
+++ b/src/leap/bitmask/services/eip/connection.py
@@ -40,6 +40,7 @@ class EIPConnectionSignals(QtCore.QObject):
disconnected_signal = QtCore.Signal()
connection_died_signal = QtCore.Signal()
+ connection_aborted_signal = QtCore.Signal()
class EIPConnection(AbstractLEAPConnection):
diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py
new file mode 100644
index 00000000..f3b6bfc8
--- /dev/null
+++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+# darwinvpnlauncher.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/>.
+"""
+Darwin VPN launcher implementation.
+"""
+import commands
+import getpass
+import logging
+import os
+
+from leap.bitmask.services.eip.vpnlauncher import VPNLauncher
+from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException
+from leap.bitmask.util import get_path_prefix
+
+logger = logging.getLogger(__name__)
+
+
+class EIPNoTunKextLoaded(VPNLauncherException):
+ pass
+
+
+class DarwinVPNLauncher(VPNLauncher):
+ """
+ VPN launcher for the Darwin Platform
+ """
+ COCOASUDO = "cocoasudo"
+ # XXX need the good old magic translate for these strings
+ # (look for magic in 0.2.0 release)
+ SUDO_MSG = ("Bitmask needs administrative privileges to run "
+ "Encrypted Internet.")
+ INSTALL_MSG = ("\"Bitmask needs administrative privileges to install "
+ "missing scripts and fix permissions.\"")
+
+ INSTALL_PATH = os.path.realpath(os.getcwd() + "/../../")
+ INSTALL_PATH_ESCAPED = os.path.realpath(os.getcwd() + "/../../")
+ OPENVPN_BIN = 'openvpn.leap'
+ OPENVPN_PATH = "%s/Contents/Resources/openvpn" % (INSTALL_PATH,)
+ OPENVPN_PATH_ESCAPED = "%s/Contents/Resources/openvpn" % (
+ INSTALL_PATH_ESCAPED,)
+
+ UP_SCRIPT = "%s/client.up.sh" % (OPENVPN_PATH,)
+ DOWN_SCRIPT = "%s/client.down.sh" % (OPENVPN_PATH,)
+ OPENVPN_DOWN_PLUGIN = '%s/openvpn-down-root.so' % (OPENVPN_PATH,)
+
+ UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT, OPENVPN_DOWN_PLUGIN)
+ OTHER_FILES = []
+
+ @classmethod
+ def cmd_for_missing_scripts(kls, frompath):
+ """
+ Returns a command that can copy the missing scripts.
+ :rtype: str
+ """
+ to = kls.OPENVPN_PATH_ESCAPED
+
+ cmd = "#!/bin/sh\n"
+ cmd += "mkdir -p {0}\n".format(to)
+ cmd += "cp '{0}'/* {1}\n".format(frompath, to)
+ cmd += "chmod 744 {0}/*".format(to)
+
+ return cmd
+
+ @classmethod
+ def is_kext_loaded(kls):
+ """
+ Checks if the needed kext is loaded before launching openvpn.
+
+ :returns: True if kext is loaded, False otherwise.
+ :rtype: bool
+ """
+ return bool(commands.getoutput('kextstat | grep "leap.tun"'))
+
+ @classmethod
+ def _get_icon_path(kls):
+ """
+ Returns the absolute path to the app icon.
+
+ :rtype: str
+ """
+ resources_path = os.path.abspath(
+ os.path.join(os.getcwd(), "../../Contents/Resources"))
+
+ return os.path.join(resources_path, "leap-client.tiff")
+
+ @classmethod
+ def get_cocoasudo_ovpn_cmd(kls):
+ """
+ Returns a string with the cocoasudo command needed to run openvpn
+ as admin with a nice password prompt. The actual command needs to be
+ appended.
+
+ :rtype: (str, list)
+ """
+ # TODO add translation support for this
+ sudo_msg = ("Bitmask needs administrative privileges to run "
+ "Encrypted Internet.")
+ iconpath = kls._get_icon_path()
+ has_icon = os.path.isfile(iconpath)
+ args = ["--icon=%s" % iconpath] if has_icon else []
+ args.append("--prompt=%s" % (sudo_msg,))
+
+ return kls.COCOASUDO, args
+
+ @classmethod
+ def get_cocoasudo_installmissing_cmd(kls):
+ """
+ Returns a string with the cocoasudo command needed to install missing
+ files as admin with a nice password prompt. The actual command needs to
+ be appended.
+
+ :rtype: (str, list)
+ """
+ # TODO add translation support for this
+ install_msg = ('"Bitmask needs administrative privileges to install '
+ 'missing scripts and fix permissions."')
+ iconpath = kls._get_icon_path()
+ has_icon = os.path.isfile(iconpath)
+ args = ["--icon=%s" % iconpath] if has_icon else []
+ args.append("--prompt=%s" % (install_msg,))
+
+ return kls.COCOASUDO, args
+
+ @classmethod
+ def get_vpn_command(kls, eipconfig, providerconfig, socket_host,
+ socket_port="unix", openvpn_verb=1):
+ """
+ Returns the OSX implementation for the vpn launching command.
+
+ Might raise:
+ EIPNoTunKextLoaded,
+ OpenVPNNotFoundException,
+ VPNLauncherException.
+
+ :param eipconfig: eip configuration object
+ :type eipconfig: EIPConfig
+ :param providerconfig: provider specific configuration
+ :type providerconfig: ProviderConfig
+ :param socket_host: either socket path (unix) or socket IP
+ :type socket_host: str
+ :param socket_port: either string "unix" if it's a unix socket,
+ or port otherwise
+ :type socket_port: str
+ :param openvpn_verb: the openvpn verbosity wanted
+ :type openvpn_verb: int
+
+ :return: A VPN command ready to be launched.
+ :rtype: list
+ """
+ if not kls.is_kext_loaded():
+ raise EIPNoTunKextLoaded
+
+ # we use `super` in order to send the class to use
+ command = super(DarwinVPNLauncher, kls).get_vpn_command(
+ eipconfig, providerconfig, socket_host, socket_port, openvpn_verb)
+
+ cocoa, cargs = kls.get_cocoasudo_ovpn_cmd()
+ cargs.extend(command)
+ command = cargs
+ command.insert(0, cocoa)
+
+ command.extend(['--setenv', "LEAPUSER", getpass.getuser()])
+
+ return command
+
+ @classmethod
+ def get_vpn_env(kls):
+ """
+ Returns a dictionary with the custom env for the platform.
+ This is mainly used for setting LD_LIBRARY_PATH to the correct
+ path when distributing a standalone client
+
+ :rtype: dict
+ """
+ return {
+ "DYLD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib")
+ }
diff --git a/src/leap/bitmask/services/eip/eipbootstrapper.py b/src/leap/bitmask/services/eip/eipbootstrapper.py
index 885c4420..5a238a1c 100644
--- a/src/leap/bitmask/services/eip/eipbootstrapper.py
+++ b/src/leap/bitmask/services/eip/eipbootstrapper.py
@@ -28,7 +28,6 @@ from leap.bitmask.services import download_service_config
from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper
from leap.bitmask.services.eip.eipconfig import EIPConfig
from leap.common import certs as leap_certs
-from leap.bitmask.util import get_path_prefix
from leap.common.check import leap_assert, leap_assert_type
from leap.common.files import check_and_fix_urw_only
diff --git a/src/leap/bitmask/services/eip/eipconfig.py b/src/leap/bitmask/services/eip/eipconfig.py
index 7d8995b4..16ed4cc0 100644
--- a/src/leap/bitmask/services/eip/eipconfig.py
+++ b/src/leap/bitmask/services/eip/eipconfig.py
@@ -33,6 +33,45 @@ from leap.common.check import leap_assert, leap_assert_type
logger = logging.getLogger(__name__)
+def get_eipconfig_path(domain):
+ """
+ Returns relative path for EIP config.
+
+ :param domain: the domain to which this eipconfig belongs to.
+ :type domain: str
+ :returns: the path
+ :rtype: str
+ """
+ leap_assert(domain is not None, "get_eipconfig_path: We need a domain")
+ return os.path.join("leap", "providers", domain, "eip-service.json")
+
+
+def load_eipconfig_if_needed(provider_config, eip_config, domain):
+ """
+ Utility function to prime a eip_config object from a loaded
+ provider_config and the chosen provider domain.
+
+ :param provider_config: a loaded instance of ProviderConfig
+ :type provider_config: ProviderConfig
+
+ :param eip_config: the eipconfig object to be primed.
+ :type eip_config: EIPConfig
+
+ :param domain: the chosen provider domain
+ :type domain: str
+
+ :returns: Whether the eip_config object has been succesfully loaded
+ :rtype: bool
+ """
+ loaded = eip_config.loaded()
+ if not loaded:
+ eip_config_path = get_eipconfig_path(domain)
+ api_version = provider_config.get_api_version()
+ eip_config.set_api_version(api_version)
+ loaded = eip_config.load(eip_config_path)
+ return loaded
+
+
class VPNGatewaySelector(object):
"""
VPN Gateway selector.
@@ -59,7 +98,6 @@ class VPNGatewaySelector(object):
tz_offset = self.equivalent_timezones[tz_offset]
self._local_offset = tz_offset
-
self._eipconfig = eipconfig
def get_gateways_list(self):
diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py
new file mode 100644
index 00000000..c2c28627
--- /dev/null
+++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py
@@ -0,0 +1,232 @@
+# -*- coding: utf-8 -*-
+# linuxvpnlauncher.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/>.
+"""
+Linux VPN launcher implementation.
+"""
+import commands
+import logging
+import os
+import subprocess
+import time
+
+from leap.bitmask.config import flags
+from leap.bitmask.util import privilege_policies
+from leap.bitmask.util.privilege_policies import LinuxPolicyChecker
+from leap.common.files import which
+from leap.bitmask.services.eip.vpnlauncher import VPNLauncher
+from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException
+from leap.bitmask.util import get_path_prefix
+from leap.common.check import leap_assert
+from leap.bitmask.util import first
+
+logger = logging.getLogger(__name__)
+
+
+class EIPNoPolkitAuthAgentAvailable(VPNLauncherException):
+ pass
+
+
+class EIPNoPkexecAvailable(VPNLauncherException):
+ pass
+
+
+def _is_pkexec_in_system():
+ """
+ Checks the existence of the pkexec binary in system.
+ """
+ pkexec_path = which('pkexec')
+ if len(pkexec_path) == 0:
+ return False
+ return True
+
+
+def _is_auth_agent_running():
+ """
+ Checks if a polkit daemon is running.
+
+ :return: True if it's running, False if it's not.
+ :rtype: boolean
+ """
+ ps = 'ps aux | grep polkit-%s-authentication-agent-1'
+ opts = (ps % case for case in ['[g]nome', '[k]de'])
+ is_running = map(lambda l: commands.getoutput(l), opts)
+ return any(is_running)
+
+
+def _try_to_launch_agent():
+ """
+ Tries to launch a polkit daemon.
+ """
+ env = None
+ if flags.STANDALONE is True:
+ env = {"PYTHONPATH": os.path.abspath('../../../../lib/')}
+ try:
+ # We need to quote the command because subprocess call
+ # will do "sh -c 'foo'", so if we do not quoute it we'll end
+ # up with a invocation to the python interpreter. And that
+ # is bad.
+ subprocess.call(["python -m leap.bitmask.util.polkit_agent"],
+ shell=True, env=env)
+ except Exception as exc:
+ logger.exception(exc)
+
+
+class LinuxVPNLauncher(VPNLauncher):
+ PKEXEC_BIN = 'pkexec'
+ OPENVPN_BIN = 'openvpn'
+ OPENVPN_BIN_PATH = os.path.join(
+ get_path_prefix(), "..", "apps", "eip", OPENVPN_BIN)
+
+ SYSTEM_CONFIG = "/etc/leap"
+ UP_DOWN_FILE = "resolv-update"
+ UP_DOWN_PATH = "%s/%s" % (SYSTEM_CONFIG, UP_DOWN_FILE)
+
+ # We assume this is there by our openvpn dependency, and
+ # we will put it there on the bundle too.
+ # TODO adapt to the bundle path.
+ OPENVPN_DOWN_ROOT_BASE = "/usr/lib/openvpn/"
+ OPENVPN_DOWN_ROOT_FILE = "openvpn-plugin-down-root.so"
+ OPENVPN_DOWN_ROOT_PATH = "%s/%s" % (
+ OPENVPN_DOWN_ROOT_BASE,
+ OPENVPN_DOWN_ROOT_FILE)
+
+ UP_SCRIPT = DOWN_SCRIPT = UP_DOWN_PATH
+ UPDOWN_FILES = (UP_DOWN_PATH,)
+ POLKIT_PATH = LinuxPolicyChecker.get_polkit_path()
+ OTHER_FILES = (POLKIT_PATH, )
+
+ @classmethod
+ def maybe_pkexec(kls):
+ """
+ Checks whether pkexec is available in the system, and
+ returns the path if found.
+
+ Might raise:
+ EIPNoPkexecAvailable,
+ EIPNoPolkitAuthAgentAvailable.
+
+ :returns: a list of the paths where pkexec is to be found
+ :rtype: list
+ """
+ if _is_pkexec_in_system():
+ if not _is_auth_agent_running():
+ _try_to_launch_agent()
+ time.sleep(0.5)
+ if _is_auth_agent_running():
+ pkexec_possibilities = which(kls.PKEXEC_BIN)
+ leap_assert(len(pkexec_possibilities) > 0,
+ "We couldn't find pkexec")
+ return pkexec_possibilities
+ else:
+ logger.warning("No polkit auth agent found. pkexec " +
+ "will use its own auth agent.")
+ raise EIPNoPolkitAuthAgentAvailable()
+ else:
+ logger.warning("System has no pkexec")
+ raise EIPNoPkexecAvailable()
+
+ @classmethod
+ def missing_other_files(kls):
+ """
+ 'Extend' the VPNLauncher's missing_other_files to check if the polkit
+ files is outdated. If the polkit file that is in OTHER_FILES exists but
+ is not up to date, it is added to the missing list.
+
+ :returns: a list of missing files
+ :rtype: list of str
+ """
+ # we use `super` in order to send the class to use
+ missing = super(LinuxVPNLauncher, kls).missing_other_files()
+ polkit_file = LinuxPolicyChecker.get_polkit_path()
+ if polkit_file not in missing:
+ if privilege_policies.is_policy_outdated(kls.OPENVPN_BIN_PATH):
+ missing.append(polkit_file)
+
+ return missing
+
+ @classmethod
+ def get_vpn_command(kls, eipconfig, providerconfig, socket_host,
+ socket_port="unix", openvpn_verb=1):
+ """
+ Returns the Linux implementation for the vpn launching command.
+
+ Might raise:
+ EIPNoPkexecAvailable,
+ EIPNoPolkitAuthAgentAvailable,
+ OpenVPNNotFoundException,
+ VPNLauncherException.
+
+ :param eipconfig: eip configuration object
+ :type eipconfig: EIPConfig
+ :param providerconfig: provider specific configuration
+ :type providerconfig: ProviderConfig
+ :param socket_host: either socket path (unix) or socket IP
+ :type socket_host: str
+ :param socket_port: either string "unix" if it's a unix socket,
+ or port otherwise
+ :type socket_port: str
+ :param openvpn_verb: the openvpn verbosity wanted
+ :type openvpn_verb: int
+
+ :return: A VPN command ready to be launched.
+ :rtype: list
+ """
+ # we use `super` in order to send the class to use
+ command = super(LinuxVPNLauncher, kls).get_vpn_command(
+ eipconfig, providerconfig, socket_host, socket_port, openvpn_verb)
+
+ pkexec = kls.maybe_pkexec()
+ if pkexec:
+ command.insert(0, first(pkexec))
+
+ return command
+
+ @classmethod
+ def cmd_for_missing_scripts(kls, frompath, pol_file):
+ """
+ Returns a sh script that can copy the missing files.
+
+ :param frompath: The path where the up/down scripts live
+ :type frompath: str
+ :param pol_file: The path where the dynamically generated
+ policy file lives
+ :type pol_file: str
+
+ :rtype: str
+ """
+ to = kls.SYSTEM_CONFIG
+
+ cmd = '#!/bin/sh\n'
+ cmd += 'mkdir -p "%s"\n' % (to, )
+ cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.UP_DOWN_FILE, to)
+ cmd += 'cp "%s" "%s"\n' % (pol_file, kls.POLKIT_PATH)
+ cmd += 'chmod 644 "%s"\n' % (kls.POLKIT_PATH, )
+
+ return cmd
+
+ @classmethod
+ def get_vpn_env(kls):
+ """
+ Returns a dictionary with the custom env for the platform.
+ This is mainly used for setting LD_LIBRARY_PATH to the correct
+ path when distributing a standalone client
+
+ :rtype: dict
+ """
+ return {
+ "LD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib")
+ }
diff --git a/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py b/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py
index d0d78eed..6640a860 100644
--- a/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py
+++ b/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py
@@ -41,6 +41,7 @@ from leap.bitmask.services.eip.eipconfig import EIPConfig
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.crypto.tests import fake_provider
from leap.bitmask.crypto.srpauth import SRPAuth
+from leap.bitmask import util
from leap.common.testing.basetest import BaseLeapTest
from leap.common.files import mkdir_p
@@ -60,13 +61,13 @@ class EIPBootstrapperActiveTest(BaseLeapTest):
def setUp(self):
self.eb = EIPBootstrapper()
- self.old_pp = EIPConfig.get_path_prefix
+ self.old_pp = util.get_path_prefix
self.old_save = EIPConfig.save
self.old_load = EIPConfig.load
self.old_si = SRPAuth.get_session_id
def tearDown(self):
- EIPConfig.get_path_prefix = self.old_pp
+ util.get_path_prefix = self.old_pp
EIPConfig.save = self.old_save
EIPConfig.load = self.old_load
SRPAuth.get_session_id = self.old_si
@@ -97,13 +98,13 @@ class EIPBootstrapperActiveTest(BaseLeapTest):
pc.get_ca_cert_path = mock.MagicMock(return_value=False)
path_prefix = tempfile.mkdtemp()
- EIPConfig.get_path_prefix = mock.MagicMock(return_value=path_prefix)
+ util.get_path_prefix = mock.MagicMock(return_value=path_prefix)
EIPConfig.save = mock.MagicMock()
EIPConfig.load = mock.MagicMock()
self.eb._download_if_needed = ifneeded
- provider_dir = os.path.join(EIPConfig.get_path_prefix(),
+ provider_dir = os.path.join(util.get_path_prefix(),
"leap",
"providers",
pc.get_domain())
@@ -150,10 +151,10 @@ class EIPBootstrapperActiveTest(BaseLeapTest):
def check(*args):
self.eb._eip_config.save.assert_called_once_with(
- ["leap",
+ ("leap",
"providers",
self.eb._provider_config.get_domain(),
- "eip-service.json"])
+ "eip-service.json"))
d.addCallback(check)
return d
@@ -184,13 +185,13 @@ class EIPBootstrapperActiveTest(BaseLeapTest):
pc.get_ca_cert_path = mock.MagicMock(return_value=False)
path_prefix = tempfile.mkdtemp()
- EIPConfig.get_path_prefix = mock.MagicMock(return_value=path_prefix)
+ util.get_path_prefix = mock.MagicMock(return_value=path_prefix)
EIPConfig.save = mock.MagicMock()
EIPConfig.load = mock.MagicMock()
self.eb._download_if_needed = ifneeded
- provider_dir = os.path.join(EIPConfig.get_path_prefix(),
+ provider_dir = os.path.join(util.get_path_prefix(),
"leap",
"providers",
"somedomain")
diff --git a/src/leap/bitmask/services/eip/tests/test_eipconfig.py b/src/leap/bitmask/services/eip/tests/test_eipconfig.py
index 76305e83..4e340f4c 100644
--- a/src/leap/bitmask/services/eip/tests/test_eipconfig.py
+++ b/src/leap/bitmask/services/eip/tests/test_eipconfig.py
@@ -262,15 +262,13 @@ class EIPConfigTest(BaseLeapTest):
def test_get_client_cert_path_as_expected(self):
config = self._get_eipconfig()
- config.get_path_prefix = Mock(return_value='test')
-
provider_config = ProviderConfig()
# mock 'get_domain' so we don't need to load a config
provider_domain = 'test.provider.com'
provider_config.get_domain = Mock(return_value=provider_domain)
- expected_path = os.path.join('test', 'leap', 'providers',
+ expected_path = os.path.join('leap', 'providers',
provider_domain, 'keys', 'client',
'openvpn.pem')
@@ -278,26 +276,24 @@ class EIPConfigTest(BaseLeapTest):
os.path.exists = Mock(return_value=True)
cert_path = config.get_client_cert_path(provider_config)
- self.assertEqual(cert_path, expected_path)
+ self.assertTrue(cert_path.endswith(expected_path))
def test_get_client_cert_path_about_to_download(self):
config = self._get_eipconfig()
- config.get_path_prefix = Mock(return_value='test')
-
provider_config = ProviderConfig()
# mock 'get_domain' so we don't need to load a config
provider_domain = 'test.provider.com'
provider_config.get_domain = Mock(return_value=provider_domain)
- expected_path = os.path.join('test', 'leap', 'providers',
+ expected_path = os.path.join('leap', 'providers',
provider_domain, 'keys', 'client',
'openvpn.pem')
cert_path = config.get_client_cert_path(
provider_config, about_to_download=True)
- self.assertEqual(cert_path, expected_path)
+ self.assertTrue(cert_path.endswith(expected_path))
def test_get_client_cert_path_fails(self):
config = self._get_eipconfig()
diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py
new file mode 100644
index 00000000..935d75f1
--- /dev/null
+++ b/src/leap/bitmask/services/eip/vpnlauncher.py
@@ -0,0 +1,290 @@
+# -*- coding: utf-8 -*-
+# vpnlauncher.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/>.
+"""
+Platform independant VPN launcher interface.
+"""
+import getpass
+import logging
+import os
+import stat
+
+from abc import ABCMeta, abstractmethod
+from functools import partial
+
+from leap.bitmask.config import flags
+from leap.bitmask.config.leapsettings import LeapSettings
+from leap.bitmask.config.providerconfig import ProviderConfig
+from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector
+from leap.bitmask.util import first
+from leap.bitmask.util import get_path_prefix
+from leap.common.check import leap_assert, leap_assert_type
+from leap.common.files import which
+
+logger = logging.getLogger(__name__)
+
+
+class VPNLauncherException(Exception):
+ pass
+
+
+class OpenVPNNotFoundException(VPNLauncherException):
+ pass
+
+
+def _has_updown_scripts(path, warn=True):
+ """
+ Checks the existence of the up/down scripts and its
+ exec bit if applicable.
+
+ :param path: the path to be checked
+ :type path: str
+
+ :param warn: whether we should log the absence
+ :type warn: bool
+
+ :rtype: bool
+ """
+ is_file = os.path.isfile(path)
+ if warn and not is_file:
+ logger.error("Could not find up/down script %s. "
+ "Might produce DNS leaks." % (path,))
+
+ # XXX check if applies in win
+ is_exe = False
+ try:
+ is_exe = (stat.S_IXUSR & os.stat(path)[stat.ST_MODE] != 0)
+ except OSError as e:
+ logger.warn("%s" % (e,))
+ if warn and not is_exe:
+ logger.error("Up/down script %s is not executable. "
+ "Might produce DNS leaks." % (path,))
+ return is_file and is_exe
+
+
+def _has_other_files(path, warn=True):
+ """
+ Checks the existence of other important files.
+
+ :param path: the path to be checked
+ :type path: str
+
+ :param warn: whether we should log the absence
+ :type warn: bool
+
+ :rtype: bool
+ """
+ is_file = os.path.isfile(path)
+ if warn and not is_file:
+ logger.warning("Could not find file during checks: %s. " % (
+ path,))
+ return is_file
+
+
+class VPNLauncher(object):
+ """
+ Abstract launcher class
+ """
+ __metaclass__ = ABCMeta
+
+ UPDOWN_FILES = None
+ OTHER_FILES = None
+
+ @classmethod
+ @abstractmethod
+ def get_vpn_command(kls, eipconfig, providerconfig,
+ socket_host, socket_port, openvpn_verb=1):
+ """
+ Returns the platform dependant vpn launching command
+
+ Might raise:
+ OpenVPNNotFoundException,
+ VPNLauncherException.
+
+ :param eipconfig: eip configuration object
+ :type eipconfig: EIPConfig
+ :param providerconfig: provider specific configuration
+ :type providerconfig: ProviderConfig
+ :param socket_host: either socket path (unix) or socket IP
+ :type socket_host: str
+ :param socket_port: either string "unix" if it's a unix socket,
+ or port otherwise
+ :type socket_port: str
+ :param openvpn_verb: the openvpn verbosity wanted
+ :type openvpn_verb: int
+
+ :return: A VPN command ready to be launched.
+ :rtype: list
+ """
+ leap_assert_type(eipconfig, EIPConfig)
+ leap_assert_type(providerconfig, ProviderConfig)
+
+ kwargs = {}
+ if flags.STANDALONE:
+ kwargs['path_extension'] = os.path.join(
+ get_path_prefix(), "..", "apps", "eip")
+
+ openvpn_possibilities = which(kls.OPENVPN_BIN, **kwargs)
+ if len(openvpn_possibilities) == 0:
+ raise OpenVPNNotFoundException()
+
+ openvpn = first(openvpn_possibilities)
+ args = []
+
+ args += [
+ '--setenv', "LEAPOPENVPN", "1"
+ ]
+
+ if openvpn_verb is not None:
+ args += ['--verb', '%d' % (openvpn_verb,)]
+
+ gateways = []
+ leap_settings = LeapSettings()
+ domain = providerconfig.get_domain()
+ gateway_conf = leap_settings.get_selected_gateway(domain)
+
+ if gateway_conf == leap_settings.GATEWAY_AUTOMATIC:
+ gateway_selector = VPNGatewaySelector(eipconfig)
+ gateways = gateway_selector.get_gateways()
+ else:
+ gateways = [gateway_conf]
+
+ if not gateways:
+ logger.error('No gateway was found!')
+ raise VPNLauncherException(kls.tr('No gateway was found!'))
+
+ logger.debug("Using gateways ips: {0}".format(', '.join(gateways)))
+
+ for gw in gateways:
+ args += ['--remote', gw, '1194', 'udp']
+
+ args += [
+ '--client',
+ '--dev', 'tun',
+ ##############################################################
+ # persist-tun makes ping-restart fail because it leaves a
+ # broken routing table
+ ##############################################################
+ # '--persist-tun',
+ '--persist-key',
+ '--tls-client',
+ '--remote-cert-tls',
+ 'server'
+ ]
+
+ openvpn_configuration = eipconfig.get_openvpn_configuration()
+ for key, value in openvpn_configuration.items():
+ args += ['--%s' % (key,), value]
+
+ user = getpass.getuser()
+
+ ##############################################################
+ # The down-root plugin fails in some situations, so we don't
+ # drop privs for the time being
+ ##############################################################
+ # args += [
+ # '--user', user,
+ # '--group', grp.getgrgid(os.getgroups()[-1]).gr_name
+ # ]
+
+ if socket_port == "unix": # that's always the case for linux
+ args += [
+ '--management-client-user', user
+ ]
+
+ args += [
+ '--management-signal',
+ '--management', socket_host, socket_port,
+ '--script-security', '2'
+ ]
+
+ if _has_updown_scripts(kls.UP_SCRIPT):
+ args += [
+ '--up', '\"%s\"' % (kls.UP_SCRIPT,),
+ ]
+
+ if _has_updown_scripts(kls.DOWN_SCRIPT):
+ args += [
+ '--down', '\"%s\"' % (kls.DOWN_SCRIPT,)
+ ]
+
+ ###########################################################
+ # For the time being we are disabling the usage of the
+ # down-root plugin, because it doesn't quite work as
+ # expected (i.e. it doesn't run route -del as root
+ # when finishing, so it fails to properly
+ # restart/quit)
+ ###########################################################
+ # if _has_updown_scripts(kls.OPENVPN_DOWN_PLUGIN):
+ # args += [
+ # '--plugin', kls.OPENVPN_DOWN_ROOT,
+ # '\'%s\'' % kls.DOWN_SCRIPT # for OSX
+ # '\'script_type=down %s\'' % kls.DOWN_SCRIPT # for Linux
+ # ]
+
+ args += [
+ '--cert', eipconfig.get_client_cert_path(providerconfig),
+ '--key', eipconfig.get_client_cert_path(providerconfig),
+ '--ca', providerconfig.get_ca_cert_path()
+ ]
+
+ command_and_args = [openvpn] + args
+ logger.debug("Running VPN with command:")
+ logger.debug(" ".join(command_and_args))
+
+ return command_and_args
+
+ @classmethod
+ def get_vpn_env(kls):
+ """
+ Returns a dictionary with the custom env for the platform.
+ This is mainly used for setting LD_LIBRARY_PATH to the correct
+ path when distributing a standalone client
+
+ :rtype: dict
+ """
+ return {}
+
+ @classmethod
+ def missing_updown_scripts(kls):
+ """
+ Returns what updown scripts are missing.
+
+ :rtype: list
+ """
+ leap_assert(kls.UPDOWN_FILES is not None,
+ "Need to define UPDOWN_FILES for this particular "
+ "launcher before calling this method")
+ file_exist = partial(_has_updown_scripts, warn=False)
+ zipped = zip(kls.UPDOWN_FILES, map(file_exist, kls.UPDOWN_FILES))
+ missing = filter(lambda (path, exists): exists is False, zipped)
+ return [path for path, exists in missing]
+
+ @classmethod
+ def missing_other_files(kls):
+ """
+ Returns what other important files are missing during startup.
+ Same as missing_updown_scripts but does not check for exec bit.
+
+ :rtype: list
+ """
+ leap_assert(kls.OTHER_FILES is not None,
+ "Need to define OTHER_FILES for this particular "
+ "auncher before calling this method")
+ file_exist = partial(_has_other_files, warn=False)
+ zipped = zip(kls.OTHER_FILES, map(file_exist, kls.OTHER_FILES))
+ missing = filter(lambda (path, exists): exists is False, zipped)
+ return [path for path, exists in missing]
diff --git a/src/leap/bitmask/services/eip/vpnlaunchers.py b/src/leap/bitmask/services/eip/vpnlaunchers.py
deleted file mode 100644
index daa0d81f..00000000
--- a/src/leap/bitmask/services/eip/vpnlaunchers.py
+++ /dev/null
@@ -1,963 +0,0 @@
-# -*- coding: utf-8 -*-
-# vpnlaunchers.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/>.
-
-"""
-Platform dependant VPN launchers
-"""
-import commands
-import logging
-import getpass
-import os
-import platform
-import stat
-import subprocess
-try:
- import grp
-except ImportError:
- pass # ignore, probably windows
-
-from abc import ABCMeta, abstractmethod
-from functools import partial
-from time import sleep
-
-from leap.bitmask.config import flags
-from leap.bitmask.config.leapsettings import LeapSettings
-
-from leap.bitmask.config.providerconfig import ProviderConfig
-from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector
-from leap.bitmask.util import first
-from leap.bitmask.util import get_path_prefix
-from leap.bitmask.util.privilege_policies import LinuxPolicyChecker
-from leap.bitmask.util import privilege_policies
-from leap.common.check import leap_assert, leap_assert_type
-from leap.common.files import which
-
-
-logger = logging.getLogger(__name__)
-
-
-class VPNLauncherException(Exception):
- pass
-
-
-class OpenVPNNotFoundException(VPNLauncherException):
- pass
-
-
-class EIPNoPolkitAuthAgentAvailable(VPNLauncherException):
- pass
-
-
-class EIPNoPkexecAvailable(VPNLauncherException):
- pass
-
-
-class EIPNoTunKextLoaded(VPNLauncherException):
- pass
-
-
-class VPNLauncher(object):
- """
- Abstract launcher class
- """
- __metaclass__ = ABCMeta
-
- UPDOWN_FILES = None
- OTHER_FILES = None
-
- @abstractmethod
- def get_vpn_command(self, eipconfig=None, providerconfig=None,
- socket_host=None, socket_port=None):
- """
- Returns the platform dependant vpn launching command
-
- :param eipconfig: eip configuration object
- :type eipconfig: EIPConfig
- :param providerconfig: provider specific configuration
- :type providerconfig: ProviderConfig
- :param socket_host: either socket path (unix) or socket IP
- :type socket_host: str
- :param socket_port: either string "unix" if it's a unix
- socket, or port otherwise
- :type socket_port: str
-
- :return: A VPN command ready to be launched
- :rtype: list
- """
- return []
-
- @abstractmethod
- def get_vpn_env(self):
- """
- Returns a dictionary with the custom env for the platform.
- This is mainly used for setting LD_LIBRARY_PATH to the correct
- path when distributing a standalone client
-
- :rtype: dict
- """
- return {}
-
- @classmethod
- def missing_updown_scripts(kls):
- """
- Returns what updown scripts are missing.
- :rtype: list
- """
- leap_assert(kls.UPDOWN_FILES is not None,
- "Need to define UPDOWN_FILES for this particular "
- "auncher before calling this method")
- file_exist = partial(_has_updown_scripts, warn=False)
- zipped = zip(kls.UPDOWN_FILES, map(file_exist, kls.UPDOWN_FILES))
- missing = filter(lambda (path, exists): exists is False, zipped)
- return [path for path, exists in missing]
-
- @classmethod
- def missing_other_files(kls):
- """
- Returns what other important files are missing during startup.
- Same as missing_updown_scripts but does not check for exec bit.
- :rtype: list
- """
- leap_assert(kls.UPDOWN_FILES is not None,
- "Need to define OTHER_FILES for this particular "
- "auncher before calling this method")
- file_exist = partial(_has_other_files, warn=False)
- zipped = zip(kls.OTHER_FILES, map(file_exist, kls.OTHER_FILES))
- missing = filter(lambda (path, exists): exists is False, zipped)
- return [path for path, exists in missing]
-
-
-def get_platform_launcher():
- launcher = globals()[platform.system() + "VPNLauncher"]
- leap_assert(launcher, "Unimplemented platform launcher: %s" %
- (platform.system(),))
- return launcher()
-
-
-def _is_pkexec_in_system():
- """
- Checks the existence of the pkexec binary in system.
- """
- pkexec_path = which('pkexec')
- if len(pkexec_path) == 0:
- return False
- return True
-
-
-def _has_updown_scripts(path, warn=True):
- """
- Checks the existence of the up/down scripts and its
- exec bit if applicable.
-
- :param path: the path to be checked
- :type path: str
-
- :param warn: whether we should log the absence
- :type warn: bool
-
- :rtype: bool
- """
- is_file = os.path.isfile(path)
- if warn and not is_file:
- logger.error("Could not find up/down script %s. "
- "Might produce DNS leaks." % (path,))
-
- # XXX check if applies in win
- is_exe = False
- try:
- is_exe = (stat.S_IXUSR & os.stat(path)[stat.ST_MODE] != 0)
- except OSError as e:
- logger.warn("%s" % (e,))
- if warn and not is_exe:
- logger.error("Up/down script %s is not executable. "
- "Might produce DNS leaks." % (path,))
- return is_file and is_exe
-
-
-def _has_other_files(path, warn=True):
- """
- Checks the existence of other important files.
-
- :param path: the path to be checked
- :type path: str
-
- :param warn: whether we should log the absence
- :type warn: bool
-
- :rtype: bool
- """
- is_file = os.path.isfile(path)
- if warn and not is_file:
- logger.warning("Could not find file during checks: %s. " % (
- path,))
- return is_file
-
-
-def _is_auth_agent_running():
- """
- Checks if a polkit daemon is running.
-
- :return: True if it's running, False if it's not.
- :rtype: boolean
- """
- ps = 'ps aux | grep polkit-%s-authentication-agent-1'
- opts = (ps % case for case in ['[g]nome', '[k]de'])
- is_running = map(lambda l: commands.getoutput(l), opts)
- return any(is_running)
-
-
-def _try_to_launch_agent():
- """
- Tries to launch a polkit daemon.
- """
- env = None
- if flags.STANDALONE is True:
- env = {"PYTHONPATH": os.path.abspath('../../../../lib/')}
- try:
- # We need to quote the command because subprocess call
- # will do "sh -c 'foo'", so if we do not quoute it we'll end
- # up with a invocation to the python interpreter. And that
- # is bad.
- subprocess.call(["python -m leap.bitmask.util.polkit_agent"],
- shell=True, env=env)
- except Exception as exc:
- logger.exception(exc)
-
-
-class LinuxVPNLauncher(VPNLauncher):
- """
- VPN launcher for the Linux platform
- """
-
- PKEXEC_BIN = 'pkexec'
- OPENVPN_BIN = 'openvpn'
- OPENVPN_BIN_PATH = os.path.join(
- get_path_prefix(), "..", "apps", "eip", OPENVPN_BIN)
-
- SYSTEM_CONFIG = "/etc/leap"
- UP_DOWN_FILE = "resolv-update"
- UP_DOWN_PATH = "%s/%s" % (SYSTEM_CONFIG, UP_DOWN_FILE)
-
- # We assume this is there by our openvpn dependency, and
- # we will put it there on the bundle too.
- # TODO adapt to the bundle path.
- OPENVPN_DOWN_ROOT_BASE = "/usr/lib/openvpn/"
- OPENVPN_DOWN_ROOT_FILE = "openvpn-plugin-down-root.so"
- OPENVPN_DOWN_ROOT_PATH = "%s/%s" % (
- OPENVPN_DOWN_ROOT_BASE,
- OPENVPN_DOWN_ROOT_FILE)
-
- UPDOWN_FILES = (UP_DOWN_PATH,)
- POLKIT_PATH = LinuxPolicyChecker.get_polkit_path()
- OTHER_FILES = (POLKIT_PATH, )
-
- def missing_other_files(self):
- """
- 'Extend' the VPNLauncher's missing_other_files to check if the polkit
- files is outdated. If the polkit file that is in OTHER_FILES exists but
- is not up to date, it is added to the missing list.
-
- :returns: a list of missing files
- :rtype: list of str
- """
- missing = VPNLauncher.missing_other_files.im_func(self)
- polkit_file = LinuxPolicyChecker.get_polkit_path()
- if polkit_file not in missing:
- if privilege_policies.is_policy_outdated(self.OPENVPN_BIN_PATH):
- missing.append(polkit_file)
-
- return missing
-
- @classmethod
- def cmd_for_missing_scripts(kls, frompath, pol_file):
- """
- Returns a sh script that can copy the missing files.
-
- :param frompath: The path where the up/down scripts live
- :type frompath: str
- :param pol_file: The path where the dynamically generated
- policy file lives
- :type pol_file: str
-
- :rtype: str
- """
- to = kls.SYSTEM_CONFIG
-
- cmd = '#!/bin/sh\n'
- cmd += 'mkdir -p "%s"\n' % (to, )
- cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.UP_DOWN_FILE, to)
- cmd += 'cp "%s" "%s"\n' % (pol_file, kls.POLKIT_PATH)
- cmd += 'chmod 644 "%s"\n' % (kls.POLKIT_PATH, )
-
- return cmd
-
- @classmethod
- def maybe_pkexec(kls):
- """
- Checks whether pkexec is available in the system, and
- returns the path if found.
-
- Might raise EIPNoPkexecAvailable or EIPNoPolkitAuthAgentAvailable
-
- :returns: a list of the paths where pkexec is to be found
- :rtype: list
- """
- if _is_pkexec_in_system():
- if not _is_auth_agent_running():
- _try_to_launch_agent()
- sleep(0.5)
- if _is_auth_agent_running():
- pkexec_possibilities = which(kls.PKEXEC_BIN)
- leap_assert(len(pkexec_possibilities) > 0,
- "We couldn't find pkexec")
- return pkexec_possibilities
- else:
- logger.warning("No polkit auth agent found. pkexec " +
- "will use its own auth agent.")
- raise EIPNoPolkitAuthAgentAvailable()
- else:
- logger.warning("System has no pkexec")
- raise EIPNoPkexecAvailable()
-
- @classmethod
- def maybe_down_plugin(kls):
- """
- Returns the path of the openvpn down-root-plugin, searching first
- in the relative path for the standalone bundle, and then in the system
- path where the debian package puts it.
-
- :returns: the path where the plugin was found, or None
- :rtype: str or None
- """
- cwd = os.getcwd()
- rel_path_in_bundle = os.path.join(
- 'apps', 'eip', 'files', kls.OPENVPN_DOWN_ROOT_FILE)
- abs_path_in_bundle = os.path.join(cwd, rel_path_in_bundle)
- if os.path.isfile(abs_path_in_bundle):
- return abs_path_in_bundle
- abs_path_in_system = kls.OPENVPN_DOWN_ROOT_PATH
- if os.path.isfile(abs_path_in_system):
- return abs_path_in_system
-
- logger.warning("We could not find the down-root-plugin, so no updown "
- "scripts will be run. DNS leaks are likely!")
- return None
-
- def get_vpn_command(self, eipconfig=None, providerconfig=None,
- socket_host=None, socket_port="unix", openvpn_verb=1):
- """
- Returns the platform dependant vpn launching command. It will
- look for openvpn in the regular paths and algo in
- path_prefix/apps/eip/ (in case standalone is set)
-
- Might raise:
- VPNLauncherException,
- OpenVPNNotFoundException.
-
- :param eipconfig: eip configuration object
- :type eipconfig: EIPConfig
-
- :param providerconfig: provider specific configuration
- :type providerconfig: ProviderConfig
-
- :param socket_host: either socket path (unix) or socket IP
- :type socket_host: str
-
- :param socket_port: either string "unix" if it's a unix
- socket, or port otherwise
- :type socket_port: str
-
- :param openvpn_verb: openvpn verbosity wanted
- :type openvpn_verb: int
-
- :return: A VPN command ready to be launched
- :rtype: list
- """
- leap_assert(eipconfig, "We need an eip config")
- leap_assert_type(eipconfig, EIPConfig)
- leap_assert(providerconfig, "We need a provider config")
- leap_assert_type(providerconfig, ProviderConfig)
- leap_assert(socket_host, "We need a socket host!")
- leap_assert(socket_port, "We need a socket port!")
-
- kwargs = {}
- if flags.STANDALONE:
- kwargs['path_extension'] = os.path.join(
- get_path_prefix(), "..", "apps", "eip")
-
- openvpn_possibilities = which(self.OPENVPN_BIN, **kwargs)
-
- if len(openvpn_possibilities) == 0:
- raise OpenVPNNotFoundException()
-
- openvpn = first(openvpn_possibilities)
- args = []
-
- pkexec = self.maybe_pkexec()
- if pkexec:
- args.append(openvpn)
- openvpn = first(pkexec)
-
- args += [
- '--setenv', "LEAPOPENVPN", "1"
- ]
-
- if openvpn_verb is not None:
- args += ['--verb', '%d' % (openvpn_verb,)]
-
- gateways = []
- leap_settings = LeapSettings()
- domain = providerconfig.get_domain()
- gateway_conf = leap_settings.get_selected_gateway(domain)
-
- if gateway_conf == leap_settings.GATEWAY_AUTOMATIC:
- gateway_selector = VPNGatewaySelector(eipconfig)
- gateways = gateway_selector.get_gateways()
- else:
- gateways = [gateway_conf]
-
- if not gateways:
- logger.error('No gateway was found!')
- raise VPNLauncherException(self.tr('No gateway was found!'))
-
- logger.debug("Using gateways ips: {0}".format(', '.join(gateways)))
-
- for gw in gateways:
- args += ['--remote', gw, '1194', 'udp']
-
- args += [
- '--client',
- '--dev', 'tun',
- ##############################################################
- # persist-tun makes ping-restart fail because it leaves a
- # broken routing table
- ##############################################################
- # '--persist-tun',
- '--persist-key',
- '--tls-client',
- '--remote-cert-tls',
- 'server'
- ]
-
- openvpn_configuration = eipconfig.get_openvpn_configuration()
-
- for key, value in openvpn_configuration.items():
- args += ['--%s' % (key,), value]
-
- ##############################################################
- # The down-root plugin fails in some situations, so we don't
- # drop privs for the time being
- ##############################################################
- # args += [
- # '--user', getpass.getuser(),
- # '--group', grp.getgrgid(os.getgroups()[-1]).gr_name
- # ]
-
- if socket_port == "unix": # that's always the case for linux
- args += [
- '--management-client-user', getpass.getuser()
- ]
-
- args += [
- '--management-signal',
- '--management', socket_host, socket_port,
- '--script-security', '2'
- ]
-
- plugin_path = self.maybe_down_plugin()
- # If we do not have the down plugin neither in the bundle
- # nor in the system, we do not do updown scripts. The alternative
- # is leaving the user without the ability to restore dns and routes
- # to its original state.
-
- if plugin_path and _has_updown_scripts(self.UP_DOWN_PATH):
- args += [
- '--up', self.UP_DOWN_PATH,
- '--down', self.UP_DOWN_PATH,
- ##############################################################
- # For the time being we are disabling the usage of the
- # down-root plugin, because it doesn't quite work as
- # expected (i.e. it doesn't run route -del as root
- # when finishing, so it fails to properly
- # restart/quit)
- ##############################################################
- # '--plugin', plugin_path,
- # '\'script_type=down %s\'' % self.UP_DOWN_PATH
- ]
-
- args += [
- '--cert', eipconfig.get_client_cert_path(providerconfig),
- '--key', eipconfig.get_client_cert_path(providerconfig),
- '--ca', providerconfig.get_ca_cert_path()
- ]
-
- logger.debug("Running VPN with command:")
- logger.debug("%s %s" % (openvpn, " ".join(args)))
-
- return [openvpn] + args
-
- def get_vpn_env(self):
- """
- Returns a dictionary with the custom env for the platform.
- This is mainly used for setting LD_LIBRARY_PATH to the correct
- path when distributing a standalone client
-
- :rtype: dict
- """
- return {
- "LD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib")
- }
-
-
-class DarwinVPNLauncher(VPNLauncher):
- """
- VPN launcher for the Darwin Platform
- """
-
- COCOASUDO = "cocoasudo"
- # XXX need the good old magic translate for these strings
- # (look for magic in 0.2.0 release)
- SUDO_MSG = ("Bitmask needs administrative privileges to run "
- "Encrypted Internet.")
- INSTALL_MSG = ("\"Bitmask needs administrative privileges to install "
- "missing scripts and fix permissions.\"")
-
- INSTALL_PATH = os.path.realpath(os.getcwd() + "/../../")
- INSTALL_PATH_ESCAPED = os.path.realpath(os.getcwd() + "/../../")
- OPENVPN_BIN = 'openvpn.leap'
- OPENVPN_PATH = "%s/Contents/Resources/openvpn" % (INSTALL_PATH,)
- OPENVPN_PATH_ESCAPED = "%s/Contents/Resources/openvpn" % (
- INSTALL_PATH_ESCAPED,)
-
- UP_SCRIPT = "%s/client.up.sh" % (OPENVPN_PATH,)
- DOWN_SCRIPT = "%s/client.down.sh" % (OPENVPN_PATH,)
- OPENVPN_DOWN_PLUGIN = '%s/openvpn-down-root.so' % (OPENVPN_PATH,)
-
- UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT, OPENVPN_DOWN_PLUGIN)
- OTHER_FILES = []
-
- @classmethod
- def cmd_for_missing_scripts(kls, frompath):
- """
- Returns a command that can copy the missing scripts.
- :rtype: str
- """
- to = kls.OPENVPN_PATH_ESCAPED
- cmd = "#!/bin/sh\nmkdir -p %s\ncp \"%s/\"* %s\nchmod 744 %s/*" % (
- to, frompath, to, to)
- return cmd
-
- @classmethod
- def maybe_kextloaded(kls):
- """
- Checks if the needed kext is loaded before launching openvpn.
- """
- return bool(commands.getoutput('kextstat | grep "leap.tun"'))
-
- def _get_resource_path(self):
- """
- Returns the absolute path to the app resources directory
-
- :rtype: str
- """
- return os.path.abspath(
- os.path.join(
- os.getcwd(),
- "../../Contents/Resources"))
-
- def _get_icon_path(self):
- """
- Returns the absolute path to the app icon
-
- :rtype: str
- """
- return os.path.join(self._get_resource_path(),
- "leap-client.tiff")
-
- def get_cocoasudo_ovpn_cmd(self):
- """
- Returns a string with the cocoasudo command needed to run openvpn
- as admin with a nice password prompt. The actual command needs to be
- appended.
-
- :rtype: (str, list)
- """
- iconpath = self._get_icon_path()
- has_icon = os.path.isfile(iconpath)
- args = ["--icon=%s" % iconpath] if has_icon else []
- args.append("--prompt=%s" % (self.SUDO_MSG,))
-
- return self.COCOASUDO, args
-
- def get_cocoasudo_installmissing_cmd(self):
- """
- Returns a string with the cocoasudo command needed to install missing
- files as admin with a nice password prompt. The actual command needs to
- be appended.
-
- :rtype: (str, list)
- """
- iconpath = self._get_icon_path()
- has_icon = os.path.isfile(iconpath)
- args = ["--icon=%s" % iconpath] if has_icon else []
- args.append("--prompt=%s" % (self.INSTALL_MSG,))
-
- return self.COCOASUDO, args
-
- def get_vpn_command(self, eipconfig=None, providerconfig=None,
- socket_host=None, socket_port="unix", openvpn_verb=1):
- """
- Returns the platform dependant vpn launching command
-
- Might raise VPNException.
-
- :param eipconfig: eip configuration object
- :type eipconfig: EIPConfig
-
- :param providerconfig: provider specific configuration
- :type providerconfig: ProviderConfig
-
- :param socket_host: either socket path (unix) or socket IP
- :type socket_host: str
-
- :param socket_port: either string "unix" if it's a unix
- socket, or port otherwise
- :type socket_port: str
-
- :param openvpn_verb: openvpn verbosity wanted
- :type openvpn_verb: int
-
- :return: A VPN command ready to be launched
- :rtype: list
- """
- leap_assert(eipconfig, "We need an eip config")
- leap_assert_type(eipconfig, EIPConfig)
- leap_assert(providerconfig, "We need a provider config")
- leap_assert_type(providerconfig, ProviderConfig)
- leap_assert(socket_host, "We need a socket host!")
- leap_assert(socket_port, "We need a socket port!")
-
- if not self.maybe_kextloaded():
- raise EIPNoTunKextLoaded
-
- kwargs = {}
- if flags.STANDALONE:
- kwargs['path_extension'] = os.path.join(
- get_path_prefix(), "..", "apps", "eip")
-
- openvpn_possibilities = which(
- self.OPENVPN_BIN,
- **kwargs)
- if len(openvpn_possibilities) == 0:
- raise OpenVPNNotFoundException()
-
- openvpn = first(openvpn_possibilities)
- args = [openvpn]
-
- args += [
- '--setenv', "LEAPOPENVPN", "1"
- ]
-
- if openvpn_verb is not None:
- args += ['--verb', '%d' % (openvpn_verb,)]
-
- gateways = []
- leap_settings = LeapSettings()
- domain = providerconfig.get_domain()
- gateway_conf = leap_settings.get_selected_gateway(domain)
-
- if gateway_conf == leap_settings.GATEWAY_AUTOMATIC:
- gateway_selector = VPNGatewaySelector(eipconfig)
- gateways = gateway_selector.get_gateways()
- else:
- gateways = [gateway_conf]
-
- if not gateways:
- logger.error('No gateway was found!')
- raise VPNLauncherException(self.tr('No gateway was found!'))
-
- logger.debug("Using gateways ips: {0}".format(', '.join(gateways)))
-
- for gw in gateways:
- args += ['--remote', gw, '1194', 'udp']
-
- args += [
- '--client',
- '--dev', 'tun',
- ##############################################################
- # persist-tun makes ping-restart fail because it leaves a
- # broken routing table
- ##############################################################
- # '--persist-tun',
- '--persist-key',
- '--tls-client',
- '--remote-cert-tls',
- 'server'
- ]
-
- openvpn_configuration = eipconfig.get_openvpn_configuration()
- for key, value in openvpn_configuration.items():
- args += ['--%s' % (key,), value]
-
- user = getpass.getuser()
-
- ##############################################################
- # The down-root plugin fails in some situations, so we don't
- # drop privs for the time being
- ##############################################################
- # args += [
- # '--user', user,
- # '--group', grp.getgrgid(os.getgroups()[-1]).gr_name
- # ]
-
- if socket_port == "unix":
- args += [
- '--management-client-user', user
- ]
-
- args += [
- '--management-signal',
- '--management', socket_host, socket_port,
- '--script-security', '2'
- ]
-
- if _has_updown_scripts(self.UP_SCRIPT):
- args += [
- '--up', '\"%s\"' % (self.UP_SCRIPT,),
- ]
-
- if _has_updown_scripts(self.DOWN_SCRIPT):
- args += [
- '--down', '\"%s\"' % (self.DOWN_SCRIPT,)
- ]
-
- # should have the down script too
- if _has_updown_scripts(self.OPENVPN_DOWN_PLUGIN):
- args += [
- ###########################################################
- # For the time being we are disabling the usage of the
- # down-root plugin, because it doesn't quite work as
- # expected (i.e. it doesn't run route -del as root
- # when finishing, so it fails to properly
- # restart/quit)
- ###########################################################
- # '--plugin', self.OPENVPN_DOWN_PLUGIN,
- # '\'%s\'' % self.DOWN_SCRIPT
- ]
-
- # we set user to be passed to the up/down scripts
- args += [
- '--setenv', "LEAPUSER", "%s" % (user,)]
-
- args += [
- '--cert', eipconfig.get_client_cert_path(providerconfig),
- '--key', eipconfig.get_client_cert_path(providerconfig),
- '--ca', providerconfig.get_ca_cert_path()
- ]
-
- command, cargs = self.get_cocoasudo_ovpn_cmd()
- cmd_args = cargs + args
-
- logger.debug("Running VPN with command:")
- logger.debug("%s %s" % (command, " ".join(cmd_args)))
-
- return [command] + cmd_args
-
- def get_vpn_env(self):
- """
- Returns a dictionary with the custom env for the platform.
- This is mainly used for setting LD_LIBRARY_PATH to the correct
- path when distributing a standalone client
-
- :rtype: dict
- """
- return {
- "DYLD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib")
- }
-
-
-class WindowsVPNLauncher(VPNLauncher):
- """
- VPN launcher for the Windows platform
- """
-
- OPENVPN_BIN = 'openvpn_leap.exe'
-
- # XXX UPDOWN_FILES ... we do not have updown files defined yet!
- # (and maybe we won't)
-
- def get_vpn_command(self, eipconfig=None, providerconfig=None,
- socket_host=None, socket_port="9876", openvpn_verb=1):
- """
- Returns the platform dependant vpn launching command. It will
- look for openvpn in the regular paths and algo in
- path_prefix/apps/eip/ (in case standalone is set)
-
- Might raise VPNException.
-
- :param eipconfig: eip configuration object
- :type eipconfig: EIPConfig
-
- :param providerconfig: provider specific configuration
- :type providerconfig: ProviderConfig
-
- :param socket_host: either socket path (unix) or socket IP
- :type socket_host: str
-
- :param socket_port: either string "unix" if it's a unix
- socket, or port otherwise
- :type socket_port: str
-
- :param openvpn_verb: the openvpn verbosity wanted
- :type openvpn_verb: int
-
- :return: A VPN command ready to be launched
- :rtype: list
- """
- leap_assert(eipconfig, "We need an eip config")
- leap_assert_type(eipconfig, EIPConfig)
- leap_assert(providerconfig, "We need a provider config")
- leap_assert_type(providerconfig, ProviderConfig)
- leap_assert(socket_host, "We need a socket host!")
- leap_assert(socket_port, "We need a socket port!")
- leap_assert(socket_port != "unix",
- "We cannot use unix sockets in windows!")
-
- openvpn_possibilities = which(
- self.OPENVPN_BIN,
- path_extension=os.path.join(get_path_prefix(),
- "..", "apps", "eip"))
-
- if len(openvpn_possibilities) == 0:
- raise OpenVPNNotFoundException()
-
- openvpn = first(openvpn_possibilities)
- args = []
-
- args += [
- '--setenv', "LEAPOPENVPN", "1"
- ]
-
- if openvpn_verb is not None:
- args += ['--verb', '%d' % (openvpn_verb,)]
-
- gateways = []
- leap_settings = LeapSettings()
- domain = providerconfig.get_domain()
- gateway_conf = leap_settings.get_selected_gateway(domain)
-
- if gateway_conf == leap_settings.GATEWAY_AUTOMATIC:
- gateway_selector = VPNGatewaySelector(eipconfig)
- gateways = gateway_selector.get_gateways()
- else:
- gateways = [gateway_conf]
-
- if not gateways:
- logger.error('No gateway was found!')
- raise VPNLauncherException(self.tr('No gateway was found!'))
-
- logger.debug("Using gateways ips: {0}".format(', '.join(gateways)))
-
- for gw in gateways:
- args += ['--remote', gw, '1194', 'udp']
-
- args += [
- '--client',
- '--dev', 'tun',
- ##############################################################
- # persist-tun makes ping-restart fail because it leaves a
- # broken routing table
- ##############################################################
- # '--persist-tun',
- '--persist-key',
- '--tls-client',
- # We make it log to a file because we cannot attach to the
- # openvpn process' stdout since it's a process with more
- # privileges than we are
- '--log-append', 'eip.log',
- '--remote-cert-tls',
- 'server'
- ]
-
- openvpn_configuration = eipconfig.get_openvpn_configuration()
- for key, value in openvpn_configuration.items():
- args += ['--%s' % (key,), value]
-
- ##############################################################
- # The down-root plugin fails in some situations, so we don't
- # drop privs for the time being
- ##############################################################
- # args += [
- # '--user', getpass.getuser(),
- # #'--group', grp.getgrgid(os.getgroups()[-1]).gr_name
- # ]
-
- args += [
- '--management-signal',
- '--management', socket_host, socket_port,
- '--script-security', '2'
- ]
-
- args += [
- '--cert', eipconfig.get_client_cert_path(providerconfig),
- '--key', eipconfig.get_client_cert_path(providerconfig),
- '--ca', providerconfig.get_ca_cert_path()
- ]
-
- logger.debug("Running VPN with command:")
- logger.debug("%s %s" % (openvpn, " ".join(args)))
-
- return [openvpn] + args
-
- def get_vpn_env(self):
- """
- Returns a dictionary with the custom env for the platform.
- This is mainly used for setting LD_LIBRARY_PATH to the correct
- path when distributing a standalone client
-
- :rtype: dict
- """
- return {}
-
-
-if __name__ == "__main__":
- logger = logging.getLogger(name='leap')
- logger.setLevel(logging.DEBUG)
- console = logging.StreamHandler()
- console.setLevel(logging.DEBUG)
- formatter = logging.Formatter(
- '%(asctime)s '
- '- %(name)s - %(levelname)s - %(message)s')
- console.setFormatter(formatter)
- logger.addHandler(console)
-
- try:
- abs_launcher = VPNLauncher()
- except Exception as e:
- assert isinstance(e, TypeError), "Something went wrong"
- print "Abstract Prefixer class is working as expected"
-
- vpnlauncher = get_platform_launcher()
-
- eipconfig = EIPConfig()
- eipconfig.set_api_version('1')
- if eipconfig.load("leap/providers/bitmask.net/eip-service.json"):
- provider = ProviderConfig()
- if provider.load("leap/providers/bitmask.net/provider.json"):
- vpnlauncher.get_vpn_command(eipconfig=eipconfig,
- providerconfig=provider,
- socket_host="/blah")
diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py
index 15ac812b..707967e0 100644
--- a/src/leap/bitmask/services/eip/vpnprocess.py
+++ b/src/leap/bitmask/services/eip/vpnprocess.py
@@ -27,7 +27,7 @@ import socket
from PySide import QtCore
from leap.bitmask.config.providerconfig import ProviderConfig
-from leap.bitmask.services.eip.vpnlaunchers import get_platform_launcher
+from leap.bitmask.services.eip import get_vpn_launcher
from leap.bitmask.services.eip.eipconfig import EIPConfig
from leap.bitmask.services.eip.udstelnet import UDSTelnet
from leap.bitmask.util import first
@@ -697,7 +697,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):
self._socket_host = socket_host
self._socket_port = socket_port
- self._launcher = get_platform_launcher()
+ self._launcher = get_vpn_launcher()
self._last_state = None
self._last_status = None
diff --git a/src/leap/bitmask/services/eip/windowsvpnlauncher.py b/src/leap/bitmask/services/eip/windowsvpnlauncher.py
new file mode 100644
index 00000000..3f1ed43b
--- /dev/null
+++ b/src/leap/bitmask/services/eip/windowsvpnlauncher.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+# windowsvpnlauncher.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/>.
+"""
+Windows VPN launcher implementation.
+"""
+import logging
+
+from leap.bitmask.services.eip.vpnlauncher import VPNLauncher
+from leap.common.check import leap_assert
+
+logger = logging.getLogger(__name__)
+
+
+class WindowsVPNLauncher(VPNLauncher):
+ """
+ VPN launcher for the Windows platform
+ """
+
+ OPENVPN_BIN = 'openvpn_leap.exe'
+
+ # XXX UPDOWN_FILES ... we do not have updown files defined yet!
+ # (and maybe we won't)
+ @classmethod
+ def get_vpn_command(kls, eipconfig, providerconfig, socket_host,
+ socket_port="9876", openvpn_verb=1):
+ """
+ Returns the Windows implementation for the vpn launching command.
+
+ Might raise:
+ OpenVPNNotFoundException,
+ VPNLauncherException.
+
+ :param eipconfig: eip configuration object
+ :type eipconfig: EIPConfig
+ :param providerconfig: provider specific configuration
+ :type providerconfig: ProviderConfig
+ :param socket_host: either socket path (unix) or socket IP
+ :type socket_host: str
+ :param socket_port: either string "unix" if it's a unix socket,
+ or port otherwise
+ :type socket_port: str
+ :param openvpn_verb: the openvpn verbosity wanted
+ :type openvpn_verb: int
+
+ :return: A VPN command ready to be launched.
+ :rtype: list
+ """
+ leap_assert(socket_port != "unix",
+ "We cannot use unix sockets in windows!")
+
+ # we use `super` in order to send the class to use
+ command = super(WindowsVPNLauncher, kls).get_vpn_command(
+ eipconfig, providerconfig, socket_host, socket_port, openvpn_verb)
+
+ return command
diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
index cac91440..7968dd6a 100644
--- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py
+++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
@@ -14,27 +14,28 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
"""
Soledad bootstrapping
"""
-
import logging
import os
import socket
+from ssl import SSLError
+
from PySide import QtCore
from u1db import errors as u1db_errors
from leap.bitmask.config import flags
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.crypto.srpauth import SRPAuth
+from leap.bitmask.services import download_service_config
from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper
from leap.bitmask.services.soledad.soledadconfig import SoledadConfig
-from leap.bitmask.util.request_helpers import get_content
+from leap.bitmask.util import is_file, is_empty_file
from leap.bitmask.util import get_path_prefix
-from leap.common.check import leap_assert, leap_assert_type
-from leap.common.files import get_mtime
+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
@@ -42,17 +43,28 @@ from leap.soledad.client import Soledad
logger = logging.getLogger(__name__)
+# TODO these exceptions could be moved to soledad itself
+# after settling this down.
+
+class SoledadSyncError(Exception):
+ message = "Error while syncing Soledad"
+
+
+class SoledadInitError(Exception):
+ message = "Error while initializing Soledad"
+
+
class SoledadBootstrapper(AbstractBootstrapper):
"""
Soledad init procedure
"""
-
SOLEDAD_KEY = "soledad"
KEYMANAGER_KEY = "keymanager"
PUBKEY_KEY = "user[public_key]"
MAX_INIT_RETRIES = 10
+ MAX_SYNC_RETRIES = 10
# All dicts returned are of the form
# {"passed": bool, "error": str}
@@ -68,6 +80,7 @@ class SoledadBootstrapper(AbstractBootstrapper):
self._soledad_config = None
self._keymanager = None
self._download_if_needed = False
+
self._user = ""
self._password = ""
self._srpauth = None
@@ -109,68 +122,169 @@ class SoledadBootstrapper(AbstractBootstrapper):
"""
self._soledad_retries += 1
+ def _get_db_paths(self, uuid):
+ """
+ Returns the secrets and local db paths needed for soledad
+ initialization
+
+ :param uuid: uuid for user
+ :type uuid: str
+
+ :return: a tuple with secrets, local_db paths
+ :rtype: tuple
+ """
+ prefix = os.path.join(get_path_prefix(), "leap", "soledad")
+ secrets = "%s/%s.secret" % (prefix, uuid)
+ local_db = "%s/%s.db" % (prefix, uuid)
+
+ # We remove an empty file if found to avoid complains
+ # about the db not being properly initialized
+ if is_file(local_db) and is_empty_file(local_db):
+ try:
+ os.remove(local_db)
+ except OSError:
+ logger.warning("Could not remove empty file %s"
+ % local_db)
+ return secrets, local_db
+
# initialization
def load_and_sync_soledad(self):
"""
Once everthing is in the right place, we instantiate and sync
Soledad
-
- :param srp_auth: SRPAuth object used
- :type srp_auth: SRPAuth
"""
- srp_auth = self.srpauth
- uuid = srp_auth.get_uid()
+ # TODO this method is still too large
+ uuid = self.srpauth.get_uid()
+ token = self.srpauth.get_token()
- prefix = os.path.join(get_path_prefix(), "leap", "soledad")
- secrets_path = "%s/%s.secret" % (prefix, uuid)
- local_db_path = "%s/%s.db" % (prefix, uuid)
+ secrets_path, local_db_path = self._get_db_paths(uuid)
# TODO: Select server based on timezone (issue #3308)
server_dict = self._soledad_config.get_hosts()
- if server_dict.keys():
- selected_server = server_dict[server_dict.keys()[0]]
- server_url = "https://%s:%s/user-%s" % (
- selected_server["hostname"],
- selected_server["port"],
- uuid)
+ if not server_dict.keys():
+ # XXX raise more specific exception, and catch it properly!
+ raise Exception("No soledad server found")
+
+ selected_server = server_dict[server_dict.keys()[0]]
+ server_url = "https://%s:%s/user-%s" % (
+ selected_server["hostname"],
+ selected_server["port"],
+ uuid)
+ logger.debug("Using soledad server url: %s" % (server_url,))
- logger.debug("Using soledad server url: %s" % (server_url,))
+ cert_file = self._provider_config.get_ca_cert_path()
- cert_file = self._provider_config.get_ca_cert_path()
+ logger.debug('local_db:%s' % (local_db_path,))
+ logger.debug('secrets_path:%s' % (secrets_path,))
- # TODO: If selected server fails, retry with another host
- # (issue #3309)
+ try:
+ self._try_soledad_init(
+ uuid, secrets_path, local_db_path,
+ server_url, cert_file, token)
+ except:
+ # re-raise the exceptions from try_init,
+ # we're currently handling the retries from the
+ # soledad-launcher in the gui.
+ raise
+
+ leap_check(self._soledad is not None,
+ "Null soledad, error while initializing")
+
+ # and now, let's sync
+ sync_tries = self.MAX_SYNC_RETRIES
+ while sync_tries > 0:
try:
- self._soledad = Soledad(
- uuid,
- self._password.encode("utf-8"),
- secrets_path=secrets_path,
- local_db_path=local_db_path,
- server_url=server_url,
- cert_file=cert_file,
- auth_token=srp_auth.get_token())
- self._soledad.sync()
-
- # XXX All these errors should be handled by soledad itself,
- # and return a subclass of SoledadInitializationFailed
- except socket.timeout:
- logger.debug("SOLEDAD TIMED OUT...")
- self.soledad_timeout.emit()
- except socket.error as exc:
- logger.error("Socket error while initializing soledad")
- self.soledad_failed.emit()
- except u1db_errors.Unauthorized:
- logger.error("Error while initializing soledad "
- "(unauthorized).")
- self.soledad_failed.emit()
- except Exception as exc:
- logger.error("Unhandled error while initializating "
+ self._try_soledad_sync()
+
+ # at this point, sometimes the client
+ # gets stuck and does not progress to
+ # the _gen_key step. XXX investigate.
+ logger.debug("Soledad has been synced.")
+ # so long, and thanks for all the fish
+ return
+ except SoledadSyncError:
+ # maybe it's my connection, but I'm getting
+ # ssl handshake timeouts and read errors quite often.
+ # A particularly big sync is a disaster.
+ # This deserves further investigation, maybe the
+ # retry strategy can be pushed to u1db, or at least
+ # it's something worthy to talk about with the
+ # ubuntu folks.
+ sync_tries -= 1
+ continue
+
+ # reached bottom, failed to sync
+ # and there's nothing we can do...
+ self.soledad_failed.emit()
+ raise SoledadSyncError()
+
+ def _try_soledad_init(self, uuid, secrets_path, local_db_path,
+ server_url, cert_file, auth_token):
+ """
+ Tries to initialize soledad.
+
+ :param uuid: user identifier
+ :param secrets_path: path to secrets file
+ :param local_db_path: path to local db file
+ :param server_url: soledad server uri
+ :param cert_file: path to the certificate of the ca used
+ to validate the SSL certificate used by the remote
+ soledad server.
+ :type cert_file: str
+ :param auth token: auth token
+ :type auth_token: str
+ """
+ # TODO: If selected server fails, retry with another host
+ # (issue #3309)
+ try:
+ self._soledad = Soledad(
+ uuid,
+ self._password.encode("utf-8"),
+ secrets_path=secrets_path,
+ local_db_path=local_db_path,
+ server_url=server_url,
+ cert_file=cert_file,
+ auth_token=auth_token)
+
+ # XXX All these errors should be handled by soledad itself,
+ # and return a subclass of SoledadInitializationFailed
+
+ # recoverable, will guarantee retries
+ except socket.timeout:
+ logger.debug("SOLEDAD initialization TIMED OUT...")
+ self.soledad_timeout.emit()
+ except socket.error as exc:
+ logger.error("Socket error while initializing soledad")
+ self.soledad_timeout.emit()
+
+ # unrecoverable
+ except u1db_errors.Unauthorized:
+ logger.error("Error while initializing soledad "
+ "(unauthorized).")
+ self.soledad_failed.emit()
+ except Exception as exc:
+ logger.exception("Unhandled error while initializating "
"soledad: %r" % (exc,))
- raise
- else:
- raise Exception("No soledad server found")
+ self.soledad_failed.emit()
+
+ def _try_soledad_sync(self):
+ """
+ Tries to sync soledad.
+ Raises SoledadSyncError if not successful.
+ """
+ try:
+ logger.error("trying to sync soledad....")
+ self._soledad.sync()
+ except SSLError as exc:
+ logger.error("%r" % (exc,))
+ raise SoledadSyncError("Failed to sync soledad")
+ except Exception as exc:
+ logger.exception("Unhandled error while syncing"
+ "soledad: %r" % (exc,))
+ self.soledad_failed.emit()
+ raise SoledadSyncError("Failed to sync soledad")
def _download_config(self):
"""
@@ -179,86 +293,57 @@ class SoledadBootstrapper(AbstractBootstrapper):
leap_assert(self._provider_config,
"We need a provider configuration!")
-
logger.debug("Downloading Soledad config for %s" %
(self._provider_config.get_domain(),))
self._soledad_config = SoledadConfig()
-
- headers = {}
- mtime = get_mtime(
- os.path.join(get_path_prefix(), "leap", "providers",
- self._provider_config.get_domain(),
- "soledad-service.json"))
-
- if self._download_if_needed and mtime:
- headers['if-modified-since'] = mtime
-
- api_version = self._provider_config.get_api_version()
-
- # there is some confusion with this uri,
- config_uri = "%s/%s/config/soledad-service.json" % (
- self._provider_config.get_api_uri(),
- api_version)
- logger.debug('Downloading soledad config from: %s' % config_uri)
-
- # TODO factor out this srpauth protected get (make decorator)
- srp_auth = self.srpauth
- session_id = srp_auth.get_session_id()
- cookies = None
- if session_id:
- cookies = {"_session_id": session_id}
-
- res = self._session.get(config_uri,
- verify=self._provider_config
- .get_ca_cert_path(),
- headers=headers,
- cookies=cookies)
- res.raise_for_status()
-
- self._soledad_config.set_api_version(api_version)
-
- # Not modified
- if res.status_code == 304:
- logger.debug("Soledad definition has not been modified")
- self._soledad_config.load(
- os.path.join(
- "leap", "providers",
- self._provider_config.get_domain(),
- "soledad-service.json"))
- else:
- soledad_definition, mtime = get_content(res)
-
- self._soledad_config.load(data=soledad_definition, mtime=mtime)
- self._soledad_config.save(["leap",
- "providers",
- self._provider_config.get_domain(),
- "soledad-service.json"])
-
+ download_service_config(
+ self._provider_config,
+ self._soledad_config,
+ self._session,
+ self._download_if_needed)
+
+ # soledad config is ok, let's proceed to load and sync soledad
+ # XXX but honestly, this is a pretty strange entry point for that.
+ # it feels like it should be the other way around:
+ # load_and_sync, and from there, if needed, call download_config
self.load_and_sync_soledad()
- def _gen_key(self, _):
+ def _get_gpg_bin_path(self):
"""
- Generates the key pair if needed, uploads it to the webapp and
- nickserver
+ Returns the path to gpg binary.
+ :returns: the gpg binary path
+ :rtype: str
"""
- leap_assert(self._provider_config,
- "We need a provider configuration!")
-
- address = "%s@%s" % (self._user, self._provider_config.get_domain())
-
- logger.debug("Retrieving key for %s" % (address,))
-
- srp_auth = self.srpauth
-
- # TODO: use which implementation with known paths
# TODO: Fix for Windows
- gpgbin = "/usr/bin/gpg"
-
+ gpgbin = None
if flags.STANDALONE:
- gpgbin = os.path.join(get_path_prefix(),
- "..", "apps", "mail", "gpg")
-
+ gpgbin = os.path.join(
+ get_path_prefix(), "..", "apps", "mail", "gpg")
+ else:
+ try:
+ gpgbin_options = which("gpg")
+ # gnupg checks that the path to the binary is not a
+ # symlink, so we need to filter those and come up with
+ # just one option.
+ for opt in gpgbin_options:
+ if not os.path.islink(opt):
+ gpgbin = opt
+ break
+ except IndexError as e:
+ logger.debug("Couldn't find the gpg binary!")
+ logger.exception(e)
+ leap_check(gpgbin is not None, "Could not find gpg binary")
+ return gpgbin
+
+ def _init_keymanager(self, address):
+ """
+ Initializes the keymanager.
+ :param address: the address to initialize the keymanager with.
+ :type address: str
+ """
+ srp_auth = self.srpauth
+ logger.debug('initializing keymanager...')
self._keymanager = KeyManager(
address,
"https://nicknym.%s:6425" % (self._provider_config.get_domain(),),
@@ -269,15 +354,46 @@ class SoledadBootstrapper(AbstractBootstrapper):
api_uri=self._provider_config.get_api_uri(),
api_version=self._provider_config.get_api_version(),
uid=srp_auth.get_uid(),
- gpgbinary=gpgbin)
+ gpgbinary=self._get_gpg_bin_path())
+
+ def _gen_key(self, _):
+ """
+ Generates the key pair if needed, uploads it to the webapp and
+ nickserver
+ """
+ leap_assert(self._provider_config is not None,
+ "We need a provider configuration!")
+ leap_assert(self._soledad is not None,
+ "We need a non-null soledad to generate keys")
+
+ address = "%s@%s" % (self._user, self._provider_config.get_domain())
+ self._init_keymanager(address)
+ logger.debug("Retrieving key for %s" % (address,))
+
try:
- self._keymanager.get_key(address, openpgp.OpenPGPKey,
- private=True, fetch_remote=False)
+ self._keymanager.get_key(
+ address, openpgp.OpenPGPKey, private=True, fetch_remote=False)
+ return
except KeyNotFound:
logger.debug("Key not found. Generating key for %s" % (address,))
+
+ # generate key
+ try:
self._keymanager.gen_key(openpgp.OpenPGPKey)
+ except Exception as exc:
+ logger.error("error while generating key!")
+ logger.exception(exc)
+ raise
+
+ # send key
+ try:
self._keymanager.send_key(openpgp.OpenPGPKey)
- logger.debug("Key generated successfully.")
+ except Exception as exc:
+ logger.error("error while sending key!")
+ logger.exception(exc)
+ raise
+
+ logger.debug("Key generated successfully.")
def run_soledad_setup_checks(self,
provider_config,
diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py
index f762a350..b58e6e3b 100644
--- a/src/leap/bitmask/util/__init__.py
+++ b/src/leap/bitmask/util/__init__.py
@@ -62,3 +62,17 @@ def update_modification_ts(path):
"""
os.utime(path, None)
return get_modification_ts(path)
+
+
+def is_file(path):
+ """
+ Returns True if the path exists and is a file.
+ """
+ return os.path.isfile(path)
+
+
+def is_empty_file(path):
+ """
+ Returns True if the file at path is empty.
+ """
+ return os.stat(path).st_size is 0
diff --git a/src/leap/bitmask/util/constants.py b/src/leap/bitmask/util/constants.py
index 63f6b1f7..e6a6bdce 100644
--- a/src/leap/bitmask/util/constants.py
+++ b/src/leap/bitmask/util/constants.py
@@ -16,4 +16,4 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
SIGNUP_TIMEOUT = 5
-REQUEST_TIMEOUT = 10
+REQUEST_TIMEOUT = 15