summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKali Kaneko <kali@leap.se>2013-09-12 14:29:32 +0200
committerKali Kaneko <kali@leap.se>2013-09-12 14:29:32 +0200
commitb1a2ec9a672d6f097d56d039d164918508f72ceb (patch)
tree0e259ada6c41b5e660f2e64188238acf4594f140 /src
parent4951157e3740b42ecef6775c8927b912a6ee8870 (diff)
parent9568093138c85212e15d50ade5d5fc7dcec9ff6e (diff)
Merge branch 'master' into debian
Conflicts: pkg/requirements.pip setup.py
Diffstat (limited to 'src')
-rw-r--r--src/leap/bitmask/__init__.py75
-rw-r--r--src/leap/bitmask/app.py13
-rw-r--r--src/leap/bitmask/config/leapsettings.py76
-rw-r--r--src/leap/bitmask/config/providerconfig.py10
-rw-r--r--src/leap/bitmask/config/tests/test_leapsettings.py71
-rw-r--r--src/leap/bitmask/gui/loggerwindow.py3
-rw-r--r--src/leap/bitmask/gui/mainwindow.py167
-rw-r--r--src/leap/bitmask/gui/preferenceswindow.py337
-rw-r--r--src/leap/bitmask/gui/statuspanel.py55
-rw-r--r--src/leap/bitmask/gui/ui/mainwindow.ui2
-rw-r--r--src/leap/bitmask/gui/ui/preferences.ui211
-rw-r--r--src/leap/bitmask/gui/ui/wizard.ui93
-rw-r--r--src/leap/bitmask/gui/wizard.py69
-rw-r--r--src/leap/bitmask/platform_init/locks.py4
-rw-r--r--src/leap/bitmask/services/__init__.py39
-rw-r--r--src/leap/bitmask/services/eip/eipconfig.py43
-rw-r--r--src/leap/bitmask/services/eip/vpnlaunchers.py83
-rw-r--r--src/leap/bitmask/services/soledad/soledadbootstrapper.py3
-rw-r--r--src/leap/bitmask/util/__init__.py52
-rw-r--r--src/leap/bitmask/util/leap_argparse.py6
-rw-r--r--src/leap/bitmask/util/leap_log_handler.py3
-rw-r--r--src/leap/bitmask/util/log_silencer.py95
-rw-r--r--src/leap/bitmask/util/password.py58
-rw-r--r--src/leap/bitmask/util/polkit_agent.py52
-rw-r--r--src/leap/bitmask/util/reqs.txt14
-rw-r--r--src/leap/bitmask/util/requirement_checker.py5
-rw-r--r--src/leap/bitmask/util/tests/test_is_release_version.py2
27 files changed, 1188 insertions, 453 deletions
diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py
index e69de29b..ebdd53c4 100644
--- a/src/leap/bitmask/__init__.py
+++ b/src/leap/bitmask/__init__.py
@@ -0,0 +1,75 @@
+# -*- 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/>.
+"""
+Init file for leap.bitmask
+
+Initializes version and app info.
+"""
+import re
+
+from pkg_resources import parse_version
+
+from leap.bitmask.util import first
+
+
+def _is_release_version(version):
+ """
+ Helper to determine whether a version is a final release or not.
+ The release needs to be of the form: w.x.y.z containing only numbers
+ and dots.
+
+ :param version: the version string
+ :type version: str
+ :returns: if the version is a release version or not.
+ :rtype: bool
+ """
+ parsed_version = parse_version(version)
+ not_number = 0
+ for x in parsed_version:
+ try:
+ int(x)
+ except:
+ not_number += 1
+
+ return not_number == 1
+
+
+__version__ = "unknown"
+IS_RELEASE_VERSION = False
+
+__short_version__ = "unknown"
+
+try:
+ from leap.bitmask._version import get_versions
+ __version__ = get_versions()['version']
+ IS_RELEASE_VERSION = _is_release_version(__version__)
+ del get_versions
+except ImportError:
+ #running on a tree that has not run
+ #the setup.py setver
+ pass
+
+__appname__ = "unknown"
+try:
+ from leap._appname import __appname__
+except ImportError:
+ #running on a tree that has not run
+ #the setup.py setver
+ pass
+
+__short_version__ = first(re.findall('\d\.\d\.\d', __version__))
+__full_version__ = __appname__ + '/' + str(__version__)
diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py
index 6ffa1d25..158f1afe 100644
--- a/src/leap/bitmask/app.py
+++ b/src/leap/bitmask/app.py
@@ -25,6 +25,7 @@ from functools import partial
from PySide import QtCore, QtGui
from leap.bitmask.util import leap_argparse
+from leap.bitmask.util import log_silencer
from leap.bitmask.util.leap_log_handler import LeapLogHandler
from leap.bitmask.util.streamtologger import StreamToLogger
from leap.common.events import server as event_server
@@ -51,7 +52,7 @@ def install_qtreactor(logger):
logger.debug("Qt4 reactor installed")
-def add_logger_handlers(debug=False, logfile=None):
+def add_logger_handlers(debug=False, logfile=None, standalone=False):
"""
Create the logger and attach the handlers.
@@ -71,6 +72,7 @@ def add_logger_handlers(debug=False, logfile=None):
# Create logger and formatter
logger = logging.getLogger(name='leap')
logger.setLevel(level)
+
log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
formatter = logging.Formatter(log_format)
@@ -78,12 +80,16 @@ def add_logger_handlers(debug=False, logfile=None):
console = logging.StreamHandler()
console.setLevel(level)
console.setFormatter(formatter)
+
+ silencer = log_silencer.SelectiveSilencerFilter(standalone=standalone)
+ console.addFilter(silencer)
logger.addHandler(console)
logger.debug('Console handler plugged!')
# LEAP custom handler
leap_handler = LeapLogHandler()
leap_handler.setLevel(level)
+ leap_handler.addFilter(silencer)
logger.addHandler(leap_handler)
logger.debug('Leap handler plugged!')
@@ -93,6 +99,7 @@ def add_logger_handlers(debug=False, logfile=None):
fileh = logging.FileHandler(logfile)
fileh.setLevel(logging.DEBUG)
fileh.setFormatter(formatter)
+ fileh.addFilter(silencer)
logger.addHandler(fileh)
logger.debug('File handler plugged!')
@@ -149,13 +156,13 @@ def main():
from leap.bitmask.gui.mainwindow import MainWindow
from leap.bitmask.platform_init import IS_MAC
from leap.bitmask.platform_init.locks import we_are_the_one_and_only
- from leap.bitmask.util import __version__ as VERSION
+ from leap.bitmask import __version__ as VERSION
from leap.bitmask.util.requirement_checker import check_requirements
# pylint: avoid unused import
assert(locale_rc)
- logger = add_logger_handlers(debug, logfile)
+ logger = add_logger_handlers(debug, logfile, standalone)
replace_stdout_stderr_with_logging(logger)
if not we_are_the_one_and_only():
diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py
index 35010280..7d8b5977 100644
--- a/src/leap/bitmask/config/leapsettings.py
+++ b/src/leap/bitmask/config/leapsettings.py
@@ -24,7 +24,7 @@ import logging
from PySide import QtCore
from leap.common.check import leap_assert, leap_assert_type
-from leap.common.config.prefixers import get_platform_prefixer
+from leap.common.config import get_path_prefix
logger = logging.getLogger(__name__)
@@ -66,20 +66,22 @@ class LeapSettings(object):
REMEMBER_KEY = "RememberUserAndPass"
DEFAULTPROVIDER_KEY = "DefaultProvider"
ALERTMISSING_KEY = "AlertMissingScripts"
+ GATEWAY_KEY = "Gateway"
+
+ # values
+ GATEWAY_AUTOMATIC = "Automatic"
def __init__(self, standalone=False):
"""
Constructor
- :param standalone: parameter used to define the location of
- the config
+ :param standalone: parameter used to define the location of the config.
:type standalone: bool
"""
+ self._path_prefix = get_path_prefix(standalone=standalone)
+ settings_path = os.path.join(self._path_prefix,
+ "leap", self.CONFIG_NAME)
- settings_path = os.path.join(get_platform_prefixer()
- .get_path_prefix(standalone=standalone),
- "leap",
- self.CONFIG_NAME)
self._settings = QtCore.QSettings(settings_path,
QtCore.QSettings.IniFormat)
@@ -119,6 +121,58 @@ class LeapSettings(object):
leap_assert(windowstate, "We need a window state")
self._settings.setValue(self.WINDOWSTATE_KEY, windowstate)
+ def get_configured_providers(self):
+ """
+ Returns the configured providers based on the file structure in the
+ settings directory.
+
+ :rtype: list of str
+ """
+ # TODO: check which providers have a valid certificate among
+ # other things, not just the directories
+ providers = []
+ try:
+ providers_path = os.path.join(self._path_prefix,
+ "leap", "providers")
+ providers = os.listdir(providers_path)
+ except Exception as e:
+ logger.debug("Error listing providers, assume there are none. %r"
+ % (e,))
+
+ return providers
+
+ def get_selected_gateway(self, provider):
+ """
+ Returns the configured gateway for the given provider.
+
+ :param provider: provider domain
+ :type provider: str
+
+ :rtype: str
+ """
+ leap_assert(len(provider) > 0, "We need a nonempty provider")
+ gateway_key = "{0}/{1}".format(provider, self.GATEWAY_KEY)
+ gateway = self._settings.value(gateway_key, self.GATEWAY_AUTOMATIC)
+
+ return gateway
+
+ def set_selected_gateway(self, provider, gateway):
+ """
+ Saves the configured gateway for the given provider
+
+ :param provider: provider domain
+ :type provider: str
+
+ :param gateway: gateway to use as default
+ :type gateway: str
+ """
+
+ leap_assert(len(provider) > 0, "We need a nonempty provider")
+ leap_assert_type(gateway, (str, unicode))
+
+ gateway_key = "{0}/{1}".format(provider, self.GATEWAY_KEY)
+ self._settings.setValue(gateway_key, gateway)
+
def get_enabled_services(self, provider):
"""
Returns a list of enabled services for the given provider
@@ -151,8 +205,12 @@ class LeapSettings(object):
leap_assert(len(provider) > 0, "We need a nonempty provider")
leap_assert_type(services, list)
- self._settings.setValue("%s/Services" % (provider,),
- services)
+ key = "{0}/Services".format(provider)
+ if not services:
+ # if there are no enabled services we don't need that key
+ self._settings.remove(key)
+ else:
+ self._settings.setValue(key, services)
def get_user(self):
"""
diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py
index c65932be..a7808399 100644
--- a/src/leap/bitmask/config/providerconfig.py
+++ b/src/leap/bitmask/config/providerconfig.py
@@ -24,6 +24,7 @@ import os
from leap.bitmask.config.provider_spec import leap_provider_spec
from leap.common.check import leap_check
from leap.common.config.baseconfig import BaseConfig, LocalizedKey
+from leap.bitmask.services import get_service_display_name
logger = logging.getLogger(__name__)
@@ -130,9 +131,11 @@ class ProviderConfig(BaseConfig):
Returns a string with the available services in the current
provider, ready to be shown to the user.
"""
- services_str = ", ".join(self.get_services())
- services_str = services_str.replace(
- "openvpn", "Encrypted Internet")
+ services = []
+ for service in self.get_services():
+ services.append(get_service_display_name(service))
+
+ services_str = ", ".join(services)
return services_str
def get_ca_cert_path(self, about_to_download=False):
@@ -216,3 +219,4 @@ if __name__ == "__main__":
print provider.get_languages()
print provider.get_name()
print provider.get_services()
+ print provider.get_services_string()
diff --git a/src/leap/bitmask/config/tests/test_leapsettings.py b/src/leap/bitmask/config/tests/test_leapsettings.py
new file mode 100644
index 00000000..18166923
--- /dev/null
+++ b/src/leap/bitmask/config/tests/test_leapsettings.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+# test_leapsettings.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/>.
+
+"""
+Tests for leapsettings module.
+"""
+
+try:
+ import unittest2 as unittest
+except ImportError:
+ import unittest
+
+import os
+import mock
+
+from leap.common.testing.basetest import BaseLeapTest
+from leap.bitmask.config.leapsettings import LeapSettings
+
+
+class LeapSettingsTest(BaseLeapTest):
+ """Tests for LeapSettings"""
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def test_get_configured_providers(self):
+ """
+ Test that the config file IS NOT stored under the CWD.
+ """
+ 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()
+ args, kwargs = os_listdir.call_args
+ config_dir = args[0]
+ self.assertFalse(config_dir.startswith(os.getcwd()))
+ self.assertFalse(config_dir.endswith('config'))
+
+ def test_get_configured_providers_in_bundle(self):
+ """
+ Test that the config file IS stored under the CWD.
+ """
+ self._leapsettings = LeapSettings(standalone=True)
+ with mock.patch('os.listdir') as os_listdir:
+ # use this method only to spy where LeapSettings is looking for
+ self._leapsettings.get_configured_providers()
+ args, kwargs = os_listdir.call_args
+ config_dir = args[0]
+ self.assertTrue(config_dir.startswith(os.getcwd()))
+ self.assertFalse(config_dir.endswith('config'))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py
index 9b4ba55d..ece4cad6 100644
--- a/src/leap/bitmask/gui/loggerwindow.py
+++ b/src/leap/bitmask/gui/loggerwindow.py
@@ -146,7 +146,8 @@ class LoggerWindow(QtGui.QDialog):
Lets the user save the current log to a file
"""
fileName, filtr = QtGui.QFileDialog.getSaveFileName(
- self, self.tr("Save As"))
+ self, self.tr("Save As"),
+ options=QtGui.QFileDialog.DontUseNativeDialog)
if fileName:
try:
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index c832887a..0950462b 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -14,9 +14,8 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
"""
-Main window for the leap client
+Main window for Bitmask.
"""
import logging
import os
@@ -59,7 +58,7 @@ from leap.bitmask.services.eip.vpnlaunchers import \
EIPNoPolkitAuthAgentAvailable
from leap.bitmask.services.eip.vpnlaunchers import EIPNoTunKextLoaded
-from leap.bitmask.util import __version__ as VERSION
+from leap.bitmask import __version__ as VERSION
from leap.bitmask.util.keyring_helpers import has_keyring
from leap.bitmask.util.leap_log_handler import LeapLogHandler
@@ -102,6 +101,7 @@ class MainWindow(QtGui.QMainWindow):
raise_window = QtCore.Signal([])
soledad_ready = QtCore.Signal([])
mail_client_logged_in = QtCore.Signal([])
+ logout = QtCore.Signal([])
# We use this flag to detect abnormal terminations
user_stopped_eip = False
@@ -254,14 +254,20 @@ class MainWindow(QtGui.QMainWindow):
self._action_eip_provider = QtGui.QAction(
self.tr("No default provider"), self)
self._action_eip_provider.setEnabled(False)
+
self._action_eip_status = QtGui.QAction(
self.tr("Encrypted internet is OFF"),
self)
self._action_eip_status.setEnabled(False)
-
self._status_panel.set_action_eip_status(
self._action_eip_status)
+ self._action_mail_status = QtGui.QAction(
+ self.tr("Encrypted Mail is OFF"), self)
+ self._action_mail_status.setEnabled(False)
+ self._status_panel.set_action_mail_status(
+ self._action_mail_status)
+
self._action_eip_startstop = QtGui.QAction(
self.tr("Turn OFF"), self)
self._action_eip_startstop.triggered.connect(
@@ -270,6 +276,9 @@ class MainWindow(QtGui.QMainWindow):
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._action_visible = QtGui.QAction(self.tr("Hide Main Window"), self)
self._action_visible.triggered.connect(self._toggle_visible)
@@ -284,26 +293,26 @@ class MainWindow(QtGui.QMainWindow):
# Services signals/slots connection
self.new_updates.connect(self._react_to_new_updates)
self.soledad_ready.connect(self._start_imap_service)
+ self.soledad_ready.connect(self._set_soledad_ready)
self.mail_client_logged_in.connect(self._fetch_incoming_mail)
+ self.logout.connect(self._stop_imap_service)
+ self.logout.connect(self._stop_smtp_service)
################################# end Qt Signals connection ########
- # Enable the password change when soledad is ready
- self.soledad_ready.connect(
- partial(self.ui.btnPreferences.setEnabled, True))
-
init_platform()
self._wizard = None
self._wizard_firstrun = False
self._logger_window = None
- self._preferences_window = None
self._bypass_checks = bypass_checks
self._soledad = None
+ self._soledad_ready = False
self._keymanager = None
+ self._smtp_service = None
self._imap_service = None
self._login_defer = None
@@ -419,7 +428,26 @@ class MainWindow(QtGui.QMainWindow):
Displays the preferences window.
"""
- PreferencesWindow(self, self._srp_auth, self._soledad).show()
+ preferences_window = PreferencesWindow(
+ self, self._srp_auth, self._settings, self._standalone)
+
+ if self._soledad_ready:
+ preferences_window.set_soledad_ready(self._soledad)
+ else:
+ self.soledad_ready.connect(
+ lambda: preferences_window.set_soledad_ready(self._soledad))
+
+ preferences_window.show()
+
+ def _set_soledad_ready(self):
+ """
+ SLOT
+ TRIGGERS:
+ self.soledad_ready
+
+ It sets the soledad object as ready to use.
+ """
+ self._soledad_ready = True
def _uncheck_logger_button(self):
"""
@@ -491,7 +519,8 @@ class MainWindow(QtGui.QMainWindow):
"""
# XXX: May be this can be divided into two methods?
- self._login_widget.set_providers(self._configured_providers())
+ providers = self._settings.get_configured_providers()
+ self._login_widget.set_providers(providers)
self._show_systray()
self.show()
if IS_MAC:
@@ -589,10 +618,8 @@ class MainWindow(QtGui.QMainWindow):
self._systray.setVisible(True)
return
- # Placeholder actions
- # They are temporary to display the tray as designed
- preferences_action = QtGui.QAction(self.tr("Preferences"), self)
- preferences_action.setEnabled(False)
+ # Placeholder action
+ # It is temporary to display the tray as designed
help_action = QtGui.QAction(self.tr("Help"), self)
help_action.setEnabled(False)
@@ -602,8 +629,9 @@ class MainWindow(QtGui.QMainWindow):
systrayMenu.addAction(self._action_eip_provider)
systrayMenu.addAction(self._action_eip_status)
systrayMenu.addAction(self._action_eip_startstop)
+ systrayMenu.addAction(self._action_mail_status)
systrayMenu.addSeparator()
- systrayMenu.addAction(preferences_action)
+ systrayMenu.addAction(self._action_preferences)
systrayMenu.addAction(help_action)
systrayMenu.addSeparator()
systrayMenu.addAction(self.ui.action_log_out)
@@ -736,34 +764,14 @@ class MainWindow(QtGui.QMainWindow):
QtGui.QMainWindow.closeEvent(self, e)
- def _configured_providers(self):
- """
- Returns the available providers based on the file structure
-
- :rtype: list
- """
-
- # TODO: check which providers have a valid certificate among
- # other things, not just the directories
- providers = []
- try:
- providers = os.listdir(
- os.path.join(self._provider_config.get_path_prefix(),
- "leap",
- "providers"))
- except Exception as e:
- logger.debug("Error listing providers, assume there are none. %r"
- % (e,))
-
- return providers
-
def _first_run(self):
"""
Returns True if there are no configured providers. False otherwise
:rtype: bool
"""
- has_provider_on_disk = len(self._configured_providers()) != 0
+ providers = self._settings.get_configured_providers()
+ has_provider_on_disk = len(providers) != 0
is_proper_provider = self._settings.get_properprovider()
return not (has_provider_on_disk and is_proper_provider)
@@ -891,6 +899,8 @@ class MainWindow(QtGui.QMainWindow):
logger.debug("Cancelling login defer.")
self._login_defer.cancel()
+ self._login_widget.set_status(self.tr("Log in cancelled by the user."))
+
def _provider_config_loaded(self, data):
"""
SLOT
@@ -912,7 +922,7 @@ class MainWindow(QtGui.QMainWindow):
self._srp_auth.logout_finished.connect(
self._done_logging_out)
- # TODO: Add errback!
+ # TODO Add errback!
self._login_defer = self._srp_auth.authenticate(username, password)
else:
self._login_widget.set_status(
@@ -979,7 +989,7 @@ class MainWindow(QtGui.QMainWindow):
"""
passed = data[self._soledad_bootstrapper.PASSED_KEY]
if not passed:
- # TODO: display in the GUI:
+ # TODO display in the GUI:
# should pass signal to a slot in status_panel
# that sets the global status
logger.error("Soledad failed to start: %s" %
@@ -1040,13 +1050,13 @@ class MainWindow(QtGui.QMainWindow):
True)
else:
if self._enabled_services.count(self.MX_SERVICE) > 0:
- pass # TODO: show MX status
+ pass # TODO show MX status
#self._status_panel.set_eip_status(
# self.tr("%s does not support MX") %
# (self._provider_config.get_domain(),),
# error=True)
else:
- pass # TODO: show MX status
+ pass # TODO show MX status
#self._status_panel.set_eip_status(
# self.tr("MX is disabled"))
@@ -1073,25 +1083,43 @@ class MainWindow(QtGui.QMainWindow):
logger.debug("Done bootstrapping SMTP")
hosts = self._smtp_config.get_hosts()
- # TODO: handle more than one host and define how to choose
+ # TODO handle more than one host and define how to choose
if len(hosts) > 0:
hostname = hosts.keys()[0]
logger.debug("Using hostname %s for SMTP" % (hostname,))
host = hosts[hostname][self.IP_KEY].encode("utf-8")
port = hosts[hostname][self.PORT_KEY]
- # TODO: pick local smtp port in a better way
- # TODO: Make the encrypted_only configurable
+ # TODO move the start to _start_smtp_service
+
+ # TODO Make the encrypted_only configurable
+ # TODO pick local smtp port in a better way
+ # TODO remove hard-coded port and let leap.mail set
+ # the specific default.
from leap.mail.smtp import setup_smtp_relay
client_cert = self._eip_config.get_client_cert_path(
self._provider_config)
- setup_smtp_relay(port=2013,
- keymanager=self._keymanager,
- smtp_host=host,
- smtp_port=port,
- smtp_cert=client_cert,
- smtp_key=client_cert,
- encrypted_only=False)
+ self._smtp_service = setup_smtp_relay(
+ port=2013,
+ keymanager=self._keymanager,
+ smtp_host=host,
+ smtp_port=port,
+ smtp_cert=client_cert,
+ smtp_key=client_cert,
+ encrypted_only=False)
+
+ def _stop_smtp_service(self):
+ """
+ SLOT
+ TRIGGERS:
+ self.logout
+ """
+ # There is a subtle difference here:
+ # we are stopping the factory for the smtp service here,
+ # but in the imap case we are just stopping the fetcher.
+ if self._smtp_service is not None:
+ logger.debug('Stopping smtp service.')
+ self._smtp_service.doStop()
###################################################################
# Service control methods: imap
@@ -1100,7 +1128,7 @@ class MainWindow(QtGui.QMainWindow):
"""
SLOT
TRIGGERS:
- soledad_ready
+ self.soledad_ready
"""
if self._provider_config.provides_mx() and \
self._enabled_services.count(self.MX_SERVICE) > 0:
@@ -1109,17 +1137,6 @@ class MainWindow(QtGui.QMainWindow):
self._imap_service = imap.start_imap_service(
self._soledad,
self._keymanager)
- else:
- if self._enabled_services.count(self.MX_SERVICE) > 0:
- pass # TODO: show MX status
- #self._status_panel.set_eip_status(
- # self.tr("%s does not support MX") %
- # (self._provider_config.get_domain(),),
- # error=True)
- else:
- pass # TODO: show MX status
- #self._status_panel.set_eip_status(
- # self.tr("MX is disabled"))
def _on_mail_client_logged_in(self, req):
"""
@@ -1131,13 +1148,27 @@ class MainWindow(QtGui.QMainWindow):
"""
SLOT
TRIGGERS:
- mail_client_logged_in
+ self.mail_client_logged_in
"""
# TODO have a mutex over fetch operation.
if self._imap_service:
logger.debug('Client connected, fetching mail...')
self._imap_service.fetch()
+ def _stop_imap_service(self):
+ """
+ SLOT
+ TRIGGERS:
+ self.logout
+ """
+ # There is a subtle difference here:
+ # we are just stopping the fetcher here,
+ # but in the smtp case we are stopping the factory.
+ # We should homogenize both services.
+ if self._imap_service is not None:
+ logger.debug('Stopping imap service.')
+ self._imap_service.stop()
+
# end service control methods (imap)
###################################################################
@@ -1149,7 +1180,8 @@ class MainWindow(QtGui.QMainWindow):
:rtype: tuple (str, str) (host, port)
"""
- # TODO: make this properly multiplatform
+ # TODO make this properly multiplatform
+ # TODO get this out of gui/
if platform.system() == "Windows":
host = "localhost"
@@ -1402,6 +1434,7 @@ class MainWindow(QtGui.QMainWindow):
# XXX: If other defers are doing authenticated stuff, this
# might conflict with those. CHECK!
threads.deferToThread(self._srp_auth.logout)
+ self.logout.emit()
def _done_logging_out(self, ok, message):
"""
@@ -1576,7 +1609,7 @@ class MainWindow(QtGui.QMainWindow):
"""
Cleanup and tidely close the main window before quitting.
"""
- # TODO: separate the shutting down of services from the
+ # TODO separate the shutting down of services from the
# UI stuff.
self._cleanup_and_quit()
diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py
index 67448768..1becfb18 100644
--- a/src/leap/bitmask/gui/preferenceswindow.py
+++ b/src/leap/bitmask/gui/preferenceswindow.py
@@ -18,14 +18,20 @@
"""
Preferences window
"""
+import os
import logging
from functools import partial
-from PySide import QtGui
+from PySide import QtCore, QtGui
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.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__)
@@ -37,58 +43,62 @@ class PreferencesWindow(QtGui.QDialog):
WEAK_PASSWORDS = ("123456", "qweasd", "qwerty", "password")
- def __init__(self, parent, srp_auth, soledad):
+ def __init__(self, parent, srp_auth, leap_settings, standalone):
"""
:param parent: parent object of the PreferencesWindow.
:parent type: QWidget
:param srp_auth: SRPAuth object configured in the main app.
:type srp_auth: SRPAuth
- :param soledad: Soledad object configured in the main app.
- :type soledad: Soledad
+ :param standalone: If True, the application is running as standalone
+ and the preferences dialog should display some
+ messages according to this.
+ :type standalone: bool
"""
QtGui.QDialog.__init__(self, parent)
+ self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic")
self._srp_auth = srp_auth
- self._soledad = soledad
+ self._settings = leap_settings
+ self._standalone = standalone
+ self._soledad = None
# Load UI
self.ui = Ui_Preferences()
self.ui.setupUi(self)
self.ui.lblPasswordChangeStatus.setVisible(False)
+ self.ui.lblProvidersServicesStatus.setVisible(False)
+ self.ui.lblProvidersGatewayStatus.setVisible(False)
+
+ self._selected_services = set()
# Connections
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)
- def _basic_password_checks(self, username, password, password2):
- """
- Performs basic password checks to avoid really easy passwords.
+ self.ui.cbGateways.currentIndexChanged[unicode].connect(
+ lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False))
- :param username: username provided at the registrarion form
- :type username: str
- :param password: password from the registration form
- :type password: str
- :param password2: second password from the registration form
- :type password: str
+ if not self._settings.get_configured_providers():
+ self.ui.gbEnabledServices.setEnabled(False)
+ else:
+ self._add_configured_providers()
- :returns: True and empty message if all the checks pass,
- False and an error message otherwise
- :rtype: tuple(bool, str)
+ def set_soledad_ready(self, soledad):
"""
- message = None
-
- if message is None and password != password2:
- message = self.tr("Passwords don't match")
+ SLOT
+ TRIGGERS:
+ parent.soledad_ready
- if message is None and len(password) < 6:
- message = self.tr("Password too short")
+ It sets the soledad object as ready to use.
- if message is None and password in self.WEAK_PASSWORDS:
- message = self.tr("Password too easy")
-
- if message is None and username == password:
- message = self.tr("Password equal to username")
-
- return message is None, message
+ :param soledad: Soledad object configured in the main app.
+ :type soledad: Soledad
+ """
+ self._soledad = soledad
+ self.ui.gbPasswordChange.setEnabled(True)
def _set_password_change_status(self, status, error=False, success=False):
"""
@@ -116,7 +126,7 @@ class PreferencesWindow(QtGui.QDialog):
:type disable: bool
"""
if disable:
- self._set_password_change_disable(self.tr("Changing password..."))
+ self._set_password_change_status(self.tr("Changing password..."))
self.ui.leCurrentPassword.setEnabled(not disable)
self.ui.leNewPassword.setEnabled(not disable)
@@ -125,6 +135,10 @@ class PreferencesWindow(QtGui.QDialog):
def _change_password(self):
"""
+ SLOT
+ TRIGGERS:
+ self.ui.pbChangePassword.clicked
+
Changes the user's password if the inputboxes are correctly filled.
"""
username = self._srp_auth.get_username()
@@ -132,8 +146,7 @@ class PreferencesWindow(QtGui.QDialog):
new_password = self.ui.leNewPassword.text()
new_password2 = self.ui.leNewPassword2.text()
- ok, msg = self._basic_password_checks(
- username, new_password, new_password2)
+ ok, msg = basic_password_checks(username, new_password, new_password2)
if not ok:
self._set_changing_password(False)
@@ -165,7 +178,7 @@ class PreferencesWindow(QtGui.QDialog):
self._set_password_change_status(
self.tr("Password changed successfully."), success=True)
- self._clear_inputs()
+ self._clear_password_inputs()
self._set_changing_password(False)
def _change_password_problem(self, failure):
@@ -187,10 +200,264 @@ class PreferencesWindow(QtGui.QDialog):
self._set_changing_password(False)
failure.trap(Exception)
- def _clear_inputs(self):
+ def _clear_password_inputs(self):
"""
Clear the contents of the inputs.
"""
self.ui.leCurrentPassword.setText("")
self.ui.leNewPassword.setText("")
self.ui.leNewPassword2.setText("")
+
+ def _set_providers_services_status(self, status, success=False):
+ """
+ Sets the status label for the password 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
+ """
+ if success:
+ status = "<font color='green'><b>%s</b></font>" % (status,)
+
+ 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):
+ """
+ SLOT
+ TRIGGER: service_checkbox.stateChanged
+ Adds the service to the state if the state is checked, removes
+ it otherwise
+
+ :param service: service to handle
+ :type service: str
+ :param state: state of the checkbox
+ :type state: int
+ """
+ if state == QtCore.Qt.Checked:
+ self._selected_services = \
+ self._selected_services.union(set([service]))
+ else:
+ self._selected_services = \
+ self._selected_services.difference(set([service]))
+
+ # We hide the maybe-visible status label after a change
+ self.ui.lblProvidersServicesStatus.setVisible(False)
+
+ def _populate_services(self, domain):
+ """
+ SLOT
+ TRIGGERS:
+ self.ui.cbProvidersServices.currentIndexChanged[unicode]
+
+ Loads the services that the provider provides into the UI for
+ the user to enable or disable.
+
+ :param domain: the domain of the provider to load services from.
+ :type domain: str
+ """
+ # We hide the maybe-visible status label after a change
+ self.ui.lblProvidersServicesStatus.setVisible(False)
+
+ if not domain:
+ return
+
+ provider_config = self._get_provider_config(domain)
+ if provider_config is None:
+ return
+
+ # set the proper connection for the 'save' button
+ try:
+ self.ui.pbSaveServices.clicked.disconnect()
+ except RuntimeError:
+ pass # Signal was not connected
+
+ save_services = partial(self._save_enabled_services, domain)
+ self.ui.pbSaveServices.clicked.connect(save_services)
+
+ services = get_supported(provider_config.get_services())
+ services_conf = self._settings.get_enabled_services(domain)
+
+ # discard changes if other provider is selected
+ self._selected_services = set()
+
+ # from: http://stackoverflow.com/a/13103617/687989
+ # remove existing checkboxes
+ layout = self.ui.vlServices
+ for i in reversed(range(layout.count())):
+ layout.itemAt(i).widget().setParent(None)
+
+ # add one checkbox per service and set the current configured value
+ for service in services:
+ try:
+ checkbox = QtGui.QCheckBox(self)
+ service_label = get_service_display_name(
+ service, self._standalone)
+ checkbox.setText(service_label)
+
+ self.ui.vlServices.addWidget(checkbox)
+ checkbox.stateChanged.connect(
+ partial(self._service_selection_changed, service))
+
+ checkbox.setChecked(service in services_conf)
+ except ValueError:
+ logger.error("Something went wrong while trying to "
+ "load service %s" % (service,))
+
+ def _save_enabled_services(self, provider):
+ """
+ SLOT
+ TRIGGERS:
+ self.ui.pbSaveServices.clicked
+
+ Saves the new enabled services settings to the configuration file.
+
+ :param provider: the provider config that we need to save.
+ :type provider: str
+ """
+ services = list(self._selected_services)
+ self._settings.set_enabled_services(provider, services)
+
+ msg = self.tr(
+ "Services settings for provider '{0}' saved.".format(provider))
+ logger.debug(msg)
+ self._set_providers_services_status(msg, success=True)
+
+ 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 _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/statuspanel.py b/src/leap/bitmask/gui/statuspanel.py
index 9352eb04..3a91f08e 100644
--- a/src/leap/bitmask/gui/statuspanel.py
+++ b/src/leap/bitmask/gui/statuspanel.py
@@ -356,6 +356,16 @@ class StatusPanelWidget(QtGui.QWidget):
leap_assert_type(action_eip_status, QtGui.QAction)
self._action_eip_status = action_eip_status
+ 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.
@@ -538,6 +548,27 @@ class StatusPanelWidget(QtGui.QWidget):
def set_provider(self, provider):
self.ui.lblProvider.setText(provider)
+ def _set_mail_status(self, status, ready=False):
+ """
+ Sets the Encrypted 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)
+
+ tray_status = self.tr('Encrypted Mail is OFF')
+
+ icon = QtGui.QPixmap(self.MAIL_OFF_ICON)
+ if ready:
+ icon = QtGui.QPixmap(self.MAIL_ON_ICON)
+ tray_status = self.tr('Encrypted Mail is ON')
+
+ self.ui.lblMailIcon.setPixmap(icon)
+ self._action_mail_status.setText(tray_status)
+
def _mail_handle_soledad_events(self, req):
"""
Callback for ...
@@ -557,7 +588,7 @@ class StatusPanelWidget(QtGui.QWidget):
:param req: Request type
:type req: leap.common.events.events_pb2.SignalRequest
"""
- self.ui.lblMailStatus.setText(self.tr("Starting..."))
+ self._set_mail_status(self.tr("Starting..."))
ext_status = ""
@@ -591,7 +622,12 @@ class StatusPanelWidget(QtGui.QWidget):
:param req: Request type
:type req: leap.common.events.events_pb2.SignalRequest
"""
- self.ui.lblMailStatus.setText(self.tr("Starting..."))
+ # 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 = ""
@@ -639,14 +675,11 @@ class StatusPanelWidget(QtGui.QWidget):
ext_status = self.tr("SMTP has started...")
self._smtp_started = True
if self._smtp_started and self._imap_started:
- self.ui.lblMailStatus.setText(self.tr("ON"))
- self.ui.lblMailIcon.setPixmap(QtGui.QPixmap(self.MAIL_ON_ICON))
- self.ui.lblMailIcon.setPixmap(
- QtGui.QPixmap(":/images/mail-locked.png"))
+ 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.ui.lblMailStatus.setText(self.tr("Failed"))
+ self._set_mail_status(self.tr("Failed"))
else:
leap_assert(False,
"Don't know how to handle this state: %s"
@@ -679,19 +712,17 @@ class StatusPanelWidget(QtGui.QWidget):
ext_status = self.tr("IMAP has started...")
self._imap_started = True
if self._smtp_started and self._imap_started:
- self.ui.lblMailStatus.setText(self.tr("ON"))
- self.ui.lblMailIcon.setPixmap(QtGui.QPixmap(self.MAIL_ON_ICON))
+ 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.ui.lblMailStatus.setText(self.tr("Failed"))
+ 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.ui.lblMailStatus.setText(self.tr("ON"))
- self.ui.lblMailIcon.setPixmap(QtGui.QPixmap(self.MAIL_ON_ICON))
+ self._set_mail_status(self.tr("ON"), ready=True)
else:
leap_assert(False,
"Don't know how to handle this state: %s"
diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui
index 834a562e..17837642 100644
--- a/src/leap/bitmask/gui/ui/mainwindow.ui
+++ b/src/leap/bitmask/gui/ui/mainwindow.ui
@@ -217,7 +217,7 @@
<item>
<widget class="QPushButton" name="btnPreferences">
<property name="enabled">
- <bool>false</bool>
+ <bool>true</bool>
</property>
<property name="text">
<string>Preferences</string>
diff --git a/src/leap/bitmask/gui/ui/preferences.ui b/src/leap/bitmask/gui/ui/preferences.ui
index 8c63ccad..e66a2d68 100644
--- a/src/leap/bitmask/gui/ui/preferences.ui
+++ b/src/leap/bitmask/gui/ui/preferences.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>451</width>
- <height>267</height>
+ <width>503</width>
+ <height>529</height>
</rect>
</property>
<property name="windowTitle">
@@ -17,9 +17,149 @@
<iconset resource="../../../../../data/resources/mainwindow.qrc">
<normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="5" column="0">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="0">
+ <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="1" colspan="2">
+ <widget class="QComboBox" name="cbProvidersGateway">
+ <item>
+ <property name="text">
+ <string>&lt;Select provider&gt;</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <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="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>
+ <item row="5" column="2">
+ <widget class="QPushButton" name="pbSaveGateway">
+ <property name="text">
+ <string>Save this provider settings</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" 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>
+ </layout>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QGroupBox" name="gbEnabledServices">
+ <property name="title">
+ <string>Enabled services</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="3" column="1">
+ <widget class="QPushButton" name="pbSaveServices">
+ <property name="text">
+ <string>Save this provider settings</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QGroupBox" name="gbServices">
+ <property name="title">
+ <string>Services</string>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="0" column="0">
+ <layout class="QVBoxLayout" name="vlServices"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="cbProvidersServices">
+ <item>
+ <property name="text">
+ <string>&lt;Select provider&gt;</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Select provider:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QLabel" name="lblProvidersServicesStatus">
+ <property name="text">
+ <string>&lt; Providers Services Status &gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </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>
@@ -98,69 +238,6 @@
</layout>
</widget>
</item>
- <item>
- <widget class="QGroupBox" name="gbGatewaySelector">
- <property name="enabled">
- <bool>false</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>cbProviders</cstring>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QComboBox" name="cbProviders">
- <item>
- <property name="text">
- <string>&lt;Select provider&gt;</string>
- </property>
- </item>
- </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">
- <widget class="QComboBox" name="comboBox">
- <item>
- <property name="text">
- <string>Automatic</string>
- </property>
- </item>
- </widget>
- </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>
diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui
index 3b8f1215..2a412784 100644
--- a/src/leap/bitmask/gui/ui/wizard.ui
+++ b/src/leap/bitmask/gui/ui/wizard.ui
@@ -183,7 +183,7 @@
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
- <string>Can we stablish a secure connection?</string>
+ <string>Can we establish a secure connection?</string>
</property>
</widget>
</item>
@@ -740,97 +740,6 @@
</item>
</layout>
</widget>
- <widget class="QWizardPage" name="finish_page">
- <property name="title">
- <string>Congratulations!</string>
- </property>
- <property name="subTitle">
- <string>You have successfully configured Bitmask.</string>
- </property>
- <attribute name="pageId">
- <string notr="true">6</string>
- </attribute>
- <layout class="QGridLayout" name="gridLayout_10">
- <item row="1" column="0">
- <spacer name="horizontalSpacer_4">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="0" column="1">
- <spacer name="verticalSpacer_9">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="1">
- <widget class="QLabel" name="label_23">
- <property name="text">
- <string/>
- </property>
- <property name="pixmap">
- <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/mask-icon.png</pixmap>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QLabel" name="label_25">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string/>
- </property>
- <property name="pixmap">
- <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/Globe.png</pixmap>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <spacer name="verticalSpacer_10">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="3">
- <spacer name="horizontalSpacer_5">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
</widget>
<customwidgets>
<customwidget>
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index ed6c1da0..e004e6cf 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -32,8 +32,9 @@ from leap.bitmask.crypto.srpregister import SRPRegister
from leap.bitmask.util.privilege_policies import is_missing_policy_permissions
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_supported
+from leap.bitmask.services import get_service_display_name, get_supported
from ui_wizard import Ui_Wizard
@@ -51,7 +52,6 @@ class Wizard(QtGui.QWizard):
SETUP_PROVIDER_PAGE = 3
REGISTER_USER_PAGE = 4
SERVICES_PAGE = 5
- FINISH_PAGE = 6
WEAK_PASSWORDS = ("123456", "qweasd", "qwerty",
"password")
@@ -83,23 +83,6 @@ class Wizard(QtGui.QWizard):
self.ERROR_ICON = QtGui.QPixmap(":/images/Dialog-error.png")
self.OK_ICON = QtGui.QPixmap(":/images/Dialog-accept.png")
- # Correspondence for services and their name to display
- EIP_LABEL = self.tr("Encrypted Internet")
- MX_LABEL = self.tr("Encrypted Mail")
-
- if self._is_need_eip_password_warning():
- EIP_LABEL += " " + self.tr(
- "(will need admin password to start)")
-
- self.SERVICE_DISPLAY = [
- EIP_LABEL,
- MX_LABEL
- ]
- self.SERVICE_CONFIG = [
- "openvpn",
- "mx"
- ]
-
self._selected_services = set()
self._shown_services = set()
@@ -160,7 +143,7 @@ class Wizard(QtGui.QWizard):
self.page(self.REGISTER_USER_PAGE).setButtonText(
QtGui.QWizard.CommitButton, self.tr("&Next >"))
- self.page(self.FINISH_PAGE).setButtonText(
+ self.page(self.SERVICES_PAGE).setButtonText(
QtGui.QWizard.FinishButton, self.tr("Connect"))
# XXX: Temporary removal for enrollment policy
@@ -199,41 +182,6 @@ class Wizard(QtGui.QWizard):
"""
self.ui.lblPassword2.setFocus()
- def _basic_password_checks(self, username, password, password2):
- """
- Performs basic password checks to avoid really easy passwords.
-
- :param username: username provided at the registrarion form
- :type username: str
- :param password: password from the registration form
- :type password: str
- :param password2: second password from the registration form
- :type password: str
-
- :return: returns True if all the checks pass, False otherwise
- :rtype: bool
- """
- message = None
-
- if message is None and password != password2:
- message = self.tr("Passwords don't match")
-
- if message is None and len(password) < 6:
- message = self.tr("Password too short")
-
- if message is None and password in self.WEAK_PASSWORDS:
- message = self.tr("Password too easy")
-
- if message is None and username == password:
- message = self.tr("Password equal to username")
-
- if message is not None:
- self._set_register_status(message, error=True)
- self._focus_password()
- return False
-
- return True
-
def _register(self):
"""
Performs the registration based on the values provided in the form
@@ -244,7 +192,8 @@ class Wizard(QtGui.QWizard):
password = self.ui.lblPassword.text()
password2 = self.ui.lblPassword2.text()
- if self._basic_password_checks(username, password, password2):
+ ok, msg = basic_password_checks(username, password, password2)
+ if ok:
register = SRPRegister(provider_config=self._provider_config)
register.registration_finished.connect(
self._registration_finished)
@@ -258,6 +207,8 @@ class Wizard(QtGui.QWizard):
self._password = password
self._set_register_status(self.tr("Starting registration..."))
else:
+ self._set_register_status(msg, error=True)
+ self._focus_password()
self.ui.btnRegister.setEnabled(True)
def _set_registration_fields_visibility(self, visible):
@@ -538,8 +489,10 @@ class Wizard(QtGui.QWizard):
try:
if service not in self._shown_services:
checkbox = QtGui.QCheckBox(self)
- service_index = self.SERVICE_CONFIG.index(service)
- checkbox.setText(self.SERVICE_DISPLAY[service_index])
+ service_label = get_service_display_name(
+ service, self.standalone)
+ checkbox.setText(service_label)
+
self.ui.serviceListLayout.addWidget(checkbox)
checkbox.stateChanged.connect(
partial(self._service_selection_changed, service))
diff --git a/src/leap/bitmask/platform_init/locks.py b/src/leap/bitmask/platform_init/locks.py
index ecfe3b1f..34f884dc 100644
--- a/src/leap/bitmask/platform_init/locks.py
+++ b/src/leap/bitmask/platform_init/locks.py
@@ -185,7 +185,7 @@ if platform_init.IS_WIN:
Creates a lock based on the atomic nature of mkdir on Windows
system calls.
"""
- LOCKBASE = os.path.join(gettempdir(), "leap-client-lock")
+ LOCKBASE = os.path.join(gettempdir(), "bitmask-lock")
def __init__(self):
"""
@@ -353,7 +353,7 @@ def we_are_the_one_and_only():
_sys = platform.system()
if _sys in ("Linux", "Darwin"):
- locker = UnixLock('/tmp/leap-client.lock')
+ locker = UnixLock('/tmp/bitmask.lock')
locker.get_lock()
we_are_the_one = locker.locked_by_us
if not we_are_the_one:
diff --git a/src/leap/bitmask/services/__init__.py b/src/leap/bitmask/services/__init__.py
index 253359cd..339f9cc6 100644
--- a/src/leap/bitmask/services/__init__.py
+++ b/src/leap/bitmask/services/__init__.py
@@ -17,9 +17,48 @@
"""
Services module.
"""
+from PySide import QtCore
+from leap.bitmask.util.privilege_policies import is_missing_policy_permissions
+
DEPLOYED = ["openvpn", "mx"]
+def get_service_display_name(service, standalone=False):
+ """
+ Returns the name to display of the given service.
+ If there is no configured name for that service, then returns the same
+ parameter
+
+ :param service: the 'machine' service name
+ :type service: str
+ :param standalone: True if the app is running in a standalone mode, used
+ to display messages according that.
+ :type standalone: bool
+
+ :rtype: str
+ """
+ # qt translator method helper
+ _tr = QtCore.QObject().tr
+
+ # Correspondence for services and their name to display
+ EIP_LABEL = _tr("Encrypted Internet")
+ MX_LABEL = _tr("Encrypted Mail")
+
+ service_display = {
+ "openvpn": EIP_LABEL,
+ "mx": MX_LABEL
+ }
+
+ # If we need to add a warning about eip needing
+ # administrative permissions to start. That can be either
+ # because we are running in standalone mode, or because we could
+ # not find the needed privilege escalation mechanisms being operative.
+ if standalone or is_missing_policy_permissions():
+ EIP_LABEL += " " + _tr("(will need admin password to start)")
+
+ return service_display.get(service, service)
+
+
def get_supported(services):
"""
Returns a list of the available services.
diff --git a/src/leap/bitmask/services/eip/eipconfig.py b/src/leap/bitmask/services/eip/eipconfig.py
index 843e7397..1cb7419e 100644
--- a/src/leap/bitmask/services/eip/eipconfig.py
+++ b/src/leap/bitmask/services/eip/eipconfig.py
@@ -62,11 +62,12 @@ class VPNGatewaySelector(object):
self._eipconfig = eipconfig
- def get_gateways(self):
+ def get_gateways_list(self):
"""
- Returns the 4 best gateways, sorted by timezone proximity.
+ Returns the existing gateways, sorted by timezone proximity.
- :rtype: list of IPv4Address or IPv6Address object.
+ :rtype: list of tuples (location, ip)
+ (str, IPv4Address or IPv6Address object)
"""
gateways_timezones = []
locations = self._eipconfig.get_locations()
@@ -77,19 +78,35 @@ class VPNGatewaySelector(object):
gateway_distance = 99 # if hasn't location -> should go last
if gateway_location is not None:
- gw_offset = int(locations[gateway['location']]['timezone'])
+ timezone = locations[gateway['location']]['timezone']
+ gateway_name = locations[gateway['location']].get('name', None)
+ if gateway_name is not None:
+ gateway_location = gateway_name
+
+ gw_offset = int(timezone)
if gw_offset in self.equivalent_timezones:
gw_offset = self.equivalent_timezones[gw_offset]
gateway_distance = self._get_timezone_distance(gw_offset)
ip = self._eipconfig.get_gateway_ip(idx)
- gateways_timezones.append((ip, gateway_distance))
+ gateways_timezones.append((ip, gateway_distance, gateway_location))
- gateways_timezones = sorted(gateways_timezones,
- key=lambda gw: gw[1])[:4]
+ gateways_timezones = sorted(gateways_timezones, key=lambda gw: gw[1])
+
+ gateways = []
+ for ip, distance, location in gateways_timezones:
+ gateways.append((location, ip))
+
+ return gateways
- gateways = [ip for ip, dist in gateways_timezones]
+ def get_gateways(self):
+ """
+ Returns the 4 best gateways, sorted by timezone proximity.
+
+ :rtype: list of IPv4Address or IPv6Address object.
+ """
+ gateways = [ip for location, ip in self.get_gateways_list()][:4]
return gateways
def _get_timezone_distance(self, offset):
@@ -124,7 +141,7 @@ class VPNGatewaySelector(object):
if time.daylight:
local_offset = time.altzone
- return local_offset / 3600
+ return -local_offset / 3600
class EIPConfig(BaseConfig):
@@ -246,7 +263,8 @@ if __name__ == "__main__":
console.setFormatter(formatter)
logger.addHandler(console)
- eipconfig = EIPConfig('1')
+ eipconfig = EIPConfig()
+ eipconfig.set_api_version('1')
try:
eipconfig.get_clusters()
@@ -255,9 +273,14 @@ if __name__ == "__main__":
print "Safe value getting is working"
if eipconfig.load("leap/providers/bitmask.net/eip-service.json"):
+ print "EIPConfig methods"
print eipconfig.get_clusters()
print eipconfig.get_gateways()
print eipconfig.get_locations()
print eipconfig.get_openvpn_configuration()
print eipconfig.get_serial()
print eipconfig.get_version()
+ print "VPNGatewaySelector methods"
+ gws = VPNGatewaySelector(eipconfig)
+ print gws.get_gateways()
+ print gws.get_gateways_list()
diff --git a/src/leap/bitmask/services/eip/vpnlaunchers.py b/src/leap/bitmask/services/eip/vpnlaunchers.py
index f8c51ad8..a50da8b9 100644
--- a/src/leap/bitmask/services/eip/vpnlaunchers.py
+++ b/src/leap/bitmask/services/eip/vpnlaunchers.py
@@ -23,8 +23,8 @@ import logging
import getpass
import os
import platform
-import subprocess
import stat
+import subprocess
try:
import grp
except ImportError:
@@ -32,6 +32,9 @@ except ImportError:
from abc import ABCMeta, abstractmethod
from functools import partial
+from time import sleep
+
+from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector
@@ -217,19 +220,23 @@ def _is_auth_agent_running():
return any(is_running)
-def _try_to_launch_agent():
+def _try_to_launch_agent(standalone=False):
"""
Tries to launch a polkit daemon.
"""
- opts = [
- "/usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1",
- # XXX add kde thing here
- ]
- for cmd in opts:
- try:
- subprocess.Popen([cmd], shell=True)
- except:
- pass
+ env = None
+ if 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):
@@ -313,7 +320,8 @@ class LinuxVPNLauncher(VPNLauncher):
"""
if _is_pkexec_in_system():
if not _is_auth_agent_running():
- _try_to_launch_agent()
+ _try_to_launch_agent(ProviderConfig.standalone)
+ sleep(0.5)
if _is_auth_agent_running():
pkexec_possibilities = which(kls.PKEXEC_BIN)
leap_assert(len(pkexec_possibilities) > 0,
@@ -414,14 +422,22 @@ class LinuxVPNLauncher(VPNLauncher):
if openvpn_verb is not None:
args += ['--verb', '%d' % (openvpn_verb,)]
- gateway_selector = VPNGatewaySelector(eipconfig)
- gateways = gateway_selector.get_gateways()
+ gateways = []
+ leap_settings = LeapSettings(ProviderConfig.standalone)
+ 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: {}".format(', '.join(gateways)))
+ logger.debug("Using gateways ips: {0}".format(', '.join(gateways)))
for gw in gateways:
args += ['--remote', gw, '1194', 'udp']
@@ -669,11 +685,22 @@ class DarwinVPNLauncher(VPNLauncher):
if openvpn_verb is not None:
args += ['--verb', '%d' % (openvpn_verb,)]
- gateway_selector = VPNGatewaySelector(eipconfig)
- gateways = gateway_selector.get_gateways()
+ gateways = []
+ leap_settings = LeapSettings(ProviderConfig.standalone)
+ 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: {gw}".format(
- gw=', '.join(gateways)))
+ logger.debug("Using gateways ips: {0}".format(', '.join(gateways)))
for gw in gateways:
args += ['--remote', gw, '1194', 'udp']
@@ -841,10 +868,22 @@ class WindowsVPNLauncher(VPNLauncher):
if openvpn_verb is not None:
args += ['--verb', '%d' % (openvpn_verb,)]
- gateway_selector = VPNGatewaySelector(eipconfig)
- gateways = gateway_selector.get_gateways()
+ gateways = []
+ leap_settings = LeapSettings(ProviderConfig.standalone)
+ 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: {}".format(', '.join(gateways)))
+ logger.debug("Using gateways ips: {0}".format(', '.join(gateways)))
for gw in gateways:
args += ['--remote', gw, '1194', 'udp']
diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
index 2419fc0d..3bbfea85 100644
--- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py
+++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
@@ -159,8 +159,7 @@ class SoledadBootstrapper(AbstractBootstrapper):
self.soledad_timeout.emit()
except socket.error as exc:
logger.error("Socket error while initializing soledad")
- if exc.errno in (111, ):
- self.soledad_failed.emit()
+ self.soledad_failed.emit()
except u1db_errors.Unauthorized:
logger.error("Error while initializing soledad "
"(unauthorized).")
diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py
index ce8323cd..78efcb6e 100644
--- a/src/leap/bitmask/util/__init__.py
+++ b/src/leap/bitmask/util/__init__.py
@@ -15,59 +15,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
-Initializes version and app info, plus some small and handy functions.
+Some small and handy functions.
"""
import datetime
import os
-from pkg_resources import parse_version
-
-
-def _is_release_version(version):
- """
- Helper to determine whether a version is a final release or not.
- The release needs to be of the form: w.x.y.z containing only numbers
- and dots.
-
- :param version: the version string
- :type version: str
- :returns: if the version is a release version or not.
- :rtype: bool
- """
- parsed_version = parse_version(version)
- not_number = 0
- for x in parsed_version:
- try:
- int(x)
- except:
- not_number += 1
-
- return not_number == 1
-
-
-__version__ = "unknown"
-IS_RELEASE_VERSION = False
-
-try:
- from leap.bitmask._version import get_versions
- __version__ = get_versions()['version']
- IS_RELEASE_VERSION = _is_release_version(__version__)
- del get_versions
-except ImportError:
- #running on a tree that has not run
- #the setup.py setver
- pass
-
-__appname__ = "unknown"
-try:
- from leap._appname import __appname__
-except ImportError:
- #running on a tree that has not run
- #the setup.py setver
- pass
-
-__full_version__ = __appname__ + '/' + str(__version__)
-
def first(things):
"""
@@ -75,7 +27,7 @@ def first(things):
"""
try:
return things[0]
- except TypeError:
+ except (IndexError, TypeError):
return None
diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py
index 71f5163d..bc21a9cf 100644
--- a/src/leap/bitmask/util/leap_argparse.py
+++ b/src/leap/bitmask/util/leap_argparse.py
@@ -14,10 +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/>.
-
+"""
+Parses the command line arguments passed to the application.
+"""
import argparse
-from leap.bitmask.util import IS_RELEASE_VERSION
+from leap.bitmask import IS_RELEASE_VERSION
def build_parser():
diff --git a/src/leap/bitmask/util/leap_log_handler.py b/src/leap/bitmask/util/leap_log_handler.py
index 9adb21a5..98924c12 100644
--- a/src/leap/bitmask/util/leap_log_handler.py
+++ b/src/leap/bitmask/util/leap_log_handler.py
@@ -90,6 +90,9 @@ class HandlerAdapter(object):
def setLevel(self, *args, **kwargs):
return self._handler.setLevel(*args, **kwargs)
+ def addFilter(self, *args, **kwargs):
+ return self._handler.addFilter(*args, **kwargs)
+
def handle(self, *args, **kwargs):
return self._handler.handle(*args, **kwargs)
diff --git a/src/leap/bitmask/util/log_silencer.py b/src/leap/bitmask/util/log_silencer.py
new file mode 100644
index 00000000..09aa2cff
--- /dev/null
+++ b/src/leap/bitmask/util/log_silencer.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+# log_silencer.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/>.
+"""
+Filter for leap logs.
+"""
+import logging
+import os
+import re
+
+from leap.common.config import get_path_prefix
+
+
+class SelectiveSilencerFilter(logging.Filter):
+ """
+ Configurable filter for root leap logger.
+
+ If you want to ignore components from the logging, just add them,
+ one by line, to ~/.config/leap/leap.dev.conf
+ """
+ # TODO we can augment this by properly parsing the log-silencer file
+ # and having different sections: ignore, levels, ...
+
+ # TODO use ConfigParser to unify sections [log-ignore] [log-debug] etc
+
+ CONFIG_NAME = "leap.dev.conf"
+
+ # Components to be completely silenced in the main bitmask logs.
+ # You probably should think twice before adding a component to
+ # the tuple below. Only very well tested components should go here, and
+ # only in those cases in which we gain more from silencing them than from
+ # having their logs into the main log file that the user will likely send
+ # to us.
+ SILENCER_RULES = (
+ 'leap.common.events',
+ )
+
+ def __init__(self, standalone=False):
+ """
+ Tries to load silencer rules from the default path,
+ or load from the SILENCER_RULES tuple if not found.
+ """
+ self.standalone = standalone
+ self.rules = None
+ if os.path.isfile(self._rules_path):
+ self.rules = self._load_rules()
+ if not self.rules:
+ self.rules = self.SILENCER_RULES
+
+ @property
+ def _rules_path(self):
+ """
+ The configuration file for custom ignore rules.
+ """
+ return os.path.join(
+ get_path_prefix(standalone=self.standalone),
+ "leap", self.CONFIG_NAME)
+
+ def _load_rules(self):
+ """
+ Loads a list of paths to be ignored from the logging.
+ """
+ lines = open(self._rules_path).readlines()
+ return map(lambda line: re.sub('\s', '', line),
+ lines)
+
+ def filter(self, record):
+ """
+ Implements the filter functionality for this Filter
+
+ :param record: the record to be examined
+ :type record: logging.LogRecord
+ :returns: a bool indicating whether the record should be logged or not.
+ :rtype: bool
+ """
+ if not self.rules:
+ return True
+ logger_path = record.name
+ for path in self.rules:
+ if logger_path.startswith(path):
+ return False
+ return True
diff --git a/src/leap/bitmask/util/password.py b/src/leap/bitmask/util/password.py
new file mode 100644
index 00000000..73659f0d
--- /dev/null
+++ b/src/leap/bitmask/util/password.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+# password.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/>.
+
+"""
+Password utilities
+"""
+from PySide import QtCore
+
+WEAK_PASSWORDS = ("123456", "qweasd", "qwerty", "password")
+
+
+def basic_password_checks(username, password, password2):
+ """
+ Performs basic password checks to avoid really easy passwords.
+
+ :param username: username provided at the registrarion form
+ :type username: str
+ :param password: password from the registration form
+ :type password: str
+ :param password2: second password from the registration form
+ :type password: str
+
+ :returns: True and empty message if all the checks pass,
+ False and an error message otherwise
+ :rtype: tuple(bool, str)
+ """
+ # translation helper
+ _tr = QtCore.QObject().tr
+
+ message = None
+
+ if message is None and password != password2:
+ message = _tr("Passwords don't match")
+
+ if message is None and len(password) < 6:
+ message = _tr("Password too short")
+
+ if message is None and password in WEAK_PASSWORDS:
+ message = _tr("Password too easy")
+
+ if message is None and username == password:
+ message = _tr("Password equal to username")
+
+ return message is None, message
diff --git a/src/leap/bitmask/util/polkit_agent.py b/src/leap/bitmask/util/polkit_agent.py
new file mode 100644
index 00000000..6fda2f88
--- /dev/null
+++ b/src/leap/bitmask/util/polkit_agent.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# polkit_agent.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/>.
+"""
+Daemonizes polkit authentication agent.
+"""
+import logging
+import subprocess
+
+import daemon
+
+logger = logging.getLogger(__name__)
+
+AUTH_FILE = "polkit-%s-authentication-agent-1"
+BASE_PATH_GNO = "/usr/lib/policykit-1-gnome/"
+BASE_PATH_KDE = "/usr/lib/kde4/libexec/"
+GNO_PATH = BASE_PATH_GNO + AUTH_FILE % ("gnome",)
+KDE_PATH = BASE_PATH_KDE + AUTH_FILE % ("kde",)
+
+
+def _launch_agent():
+ logger.debug('Launching polkit auth agent')
+ try:
+ subprocess.call(GNO_PATH)
+ except Exception as exc:
+ logger.error('Exception while running polkit authentication agent '
+ '%s' % (exc,))
+ # XXX fix KDE launch. See: #3755
+ #try:
+ #subprocess.call(KDE_PATH)
+ #except Exception as exc:
+
+
+def launch():
+ with daemon.DaemonContext():
+ _launch_agent()
+
+if __name__ == "__main__":
+ launch()
diff --git a/src/leap/bitmask/util/reqs.txt b/src/leap/bitmask/util/reqs.txt
deleted file mode 100644
index 0bcf85dc..00000000
--- a/src/leap/bitmask/util/reqs.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-requests
-srp>=1.0.2
-pyopenssl
-keyring
-python-dateutil
-psutil
-ipaddr
-twisted
-qt4reactor
-python-gnupg
-leap.common>=0.2.5
-leap.soledad>=0.1.0
-mock
-oauth \ No newline at end of file
diff --git a/src/leap/bitmask/util/requirement_checker.py b/src/leap/bitmask/util/requirement_checker.py
index 1d9b9923..37e8e693 100644
--- a/src/leap/bitmask/util/requirement_checker.py
+++ b/src/leap/bitmask/util/requirement_checker.py
@@ -51,8 +51,9 @@ def get_requirements():
# if we are running from the package
if not develop:
- requires_file_name = os.path.join('leap', 'util', 'reqs.txt')
- dist_name = Requirement.parse('leap-client')
+ requires_file_name = os.path.join(
+ 'leap', 'bitmask', 'util', 'reqs.txt')
+ dist_name = Requirement.parse('leap.bitmask')
try:
with resource_stream(dist_name, requires_file_name) as stream:
diff --git a/src/leap/bitmask/util/tests/test_is_release_version.py b/src/leap/bitmask/util/tests/test_is_release_version.py
index 088ec66d..0a0093da 100644
--- a/src/leap/bitmask/util/tests/test_is_release_version.py
+++ b/src/leap/bitmask/util/tests/test_is_release_version.py
@@ -19,7 +19,7 @@ tests for _is_release_version function
"""
import unittest
-from leap.bitmask.util import _is_release_version as is_release_version
+from leap.bitmask import _is_release_version as is_release_version
from leap.common.testing.basetest import BaseLeapTest