summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKali Kaneko <kali@leap.se>2013-12-23 02:35:28 -0400
committerKali Kaneko <kali@leap.se>2013-12-23 02:35:28 -0400
commitf056eaa85fb3cf23555b23631050976d9800b704 (patch)
tree693ff2c168f5ad702ccfe810cacb8b974a58563f /src
parent4425bc74d6bb679f143538027f28602bfe973425 (diff)
parentbdc638e6fc3dadaaf9c60a19f4e850c5450cfc3e (diff)
Merge branch 'develop' into debian-0.5.0-rc
Diffstat (limited to 'src')
-rw-r--r--src/leap/bitmask/app.py10
-rw-r--r--src/leap/bitmask/backend.py381
-rw-r--r--src/leap/bitmask/config/flags.py2
-rw-r--r--src/leap/bitmask/gui/advanced_key_management.py22
-rw-r--r--src/leap/bitmask/gui/eip_status.py4
-rw-r--r--src/leap/bitmask/gui/mainwindow.py209
-rw-r--r--src/leap/bitmask/gui/preferenceswindow.py3
-rw-r--r--src/leap/bitmask/gui/ui/advanced_key_management.ui79
-rw-r--r--src/leap/bitmask/gui/ui/eip_status.ui66
-rw-r--r--src/leap/bitmask/gui/ui/login.ui4
-rw-r--r--src/leap/bitmask/gui/ui/mainwindow.ui199
-rw-r--r--src/leap/bitmask/gui/wizard.py78
-rw-r--r--src/leap/bitmask/provider/providerbootstrapper.py38
-rw-r--r--src/leap/bitmask/provider/tests/test_providerbootstrapper.py3
-rw-r--r--src/leap/bitmask/services/abstractbootstrapper.py33
-rw-r--r--src/leap/bitmask/services/eip/darwinvpnlauncher.py2
-rw-r--r--src/leap/bitmask/services/mail/imap.py12
-rw-r--r--src/leap/bitmask/services/mail/repair.py234
-rw-r--r--src/leap/bitmask/services/soledad/soledadbootstrapper.py58
-rw-r--r--src/leap/bitmask/util/leap_argparse.py11
20 files changed, 1065 insertions, 383 deletions
diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py
index 3bb9c8c3..b16a51aa 100644
--- a/src/leap/bitmask/app.py
+++ b/src/leap/bitmask/app.py
@@ -54,7 +54,9 @@ 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.bitmask.platform_init import IS_WIN
+from leap.bitmask.services.mail.repair import repair_account
from leap.common.events import server as event_server
+from leap.mail import __version__ as MAIL_VERSION
import codecs
codecs.register(lambda name: codecs.lookup('utf-8')
@@ -170,12 +172,18 @@ def main():
if opts.version:
print "Bitmask version: %s" % (VERSION,)
+ print "leap.mail version: %s" % (MAIL_VERSION,)
+ sys.exit(0)
+
+ if opts.acct_to_repair:
+ repair_account(opts.acct_to_repair)
sys.exit(0)
standalone = opts.standalone
bypass_checks = getattr(opts, 'danger', False)
debug = opts.debug
logfile = opts.log_file
+ mail_logfile = opts.mail_log_file
openvpn_verb = opts.openvpn_verb
try:
@@ -191,6 +199,7 @@ def main():
from leap.bitmask.config import flags
from leap.common.config.baseconfig import BaseConfig
flags.STANDALONE = standalone
+ flags.MAIL_LOGFILE = mail_logfile
BaseConfig.standalone = standalone
logger = add_logger_handlers(debug, logfile)
@@ -217,6 +226,7 @@ def main():
logger.info('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
logger.info('Bitmask version %s', VERSION)
+ logger.info('leap.mail version %s', MAIL_VERSION)
logger.info('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
logger.info('Starting app')
diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py
new file mode 100644
index 00000000..8a289a79
--- /dev/null
+++ b/src/leap/bitmask/backend.py
@@ -0,0 +1,381 @@
+# -*- coding: utf-8 -*-
+# backend.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/>.
+"""
+Backend for everything
+"""
+import logging
+import os
+
+from Queue import Queue, Empty
+
+from twisted.internet import threads, defer
+from twisted.internet.task import LoopingCall
+from twisted.python import log
+
+import zope.interface
+
+from leap.bitmask.config.providerconfig import ProviderConfig
+from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper
+
+# Frontend side
+from PySide import QtCore
+
+logger = logging.getLogger(__name__)
+
+
+class ILEAPComponent(zope.interface.Interface):
+ """
+ Interface that every component for the backend should comply to
+ """
+
+ key = zope.interface.Attribute("Key id for this component")
+
+
+class ILEAPService(ILEAPComponent):
+ """
+ Interface that every Service needs to implement
+ """
+
+ def start(self):
+ """
+ Starts the service.
+ """
+ pass
+
+ def stop(self):
+ """
+ Stops the service.
+ """
+ pass
+
+ def terminate(self):
+ """
+ Terminates the service, not necessarily in a nice way.
+ """
+ pass
+
+ def status(self):
+ """
+ Returns a json object with the current status for the service.
+
+ :rtype: object (list, str, dict)
+ """
+ # XXX: Use a namedtuple or a specific object instead of a json
+ # object, since parsing it will be problematic otherwise.
+ # It has to be something easily serializable though.
+ pass
+
+ def set_configs(self, keyval):
+ """
+ Sets the config parameters for this Service.
+
+ :param keyval: values to configure
+ :type keyval: dict, {str: str}
+ """
+ pass
+
+ def get_configs(self, keys):
+ """
+ Returns the configuration values for the list of keys.
+
+ :param keys: keys to retrieve
+ :type keys: list of str
+
+ :rtype: dict, {str: str}
+ """
+ pass
+
+
+class Provider(object):
+ """
+ Interfaces with setup and bootstrapping operations for a provider
+ """
+
+ zope.interface.implements(ILEAPComponent)
+
+ PROBLEM_SIGNAL = "prov_problem_with_provider"
+
+ def __init__(self, signaler=None, bypass_checks=False):
+ """
+ Constructor for the Provider component
+
+ :param signaler: Object in charge of handling communication
+ back to the frontend
+ :type signaler: Signaler
+ :param bypass_checks: Set to true if the app should bypass
+ first round of checks for CA
+ certificates at bootstrap
+ :type bypass_checks: bool
+ """
+ object.__init__(self)
+ self.key = "provider"
+ self._provider_bootstrapper = ProviderBootstrapper(signaler,
+ bypass_checks)
+ self._download_provider_defer = None
+ self._provider_config = ProviderConfig()
+
+ def setup_provider(self, provider):
+ """
+ Initiates the setup for a provider
+
+ :param provider: URL for the provider
+ :type provider: unicode
+ """
+ log.msg("Setting up provider %s..." % (provider.encode("idna"),))
+ pb = self._provider_bootstrapper
+ d = pb.run_provider_select_checks(provider, download_if_needed=True)
+ self._download_provider_defer = d
+ return d
+
+ def bootstrap(self, provider):
+ """
+ Second stage of bootstrapping for a provider.
+
+ :param provider: URL for the provider
+ :type provider: unicode
+ """
+
+ d = None
+
+ # If there's no loaded provider or
+ # we want to connect to other provider...
+ if (not self._provider_config.loaded() or
+ self._provider_config.get_domain() != provider):
+ self._provider_config.load(
+ os.path.join("leap", "providers",
+ provider, "provider.json"))
+
+ if self._provider_config.loaded():
+ d = self._provider_bootstrapper.run_provider_setup_checks(
+ self._provider_config,
+ download_if_needed=True)
+ else:
+ if self._signaler is not None:
+ self._signaler.signal(self.PROBLEM_SIGNAL)
+ logger.error("Could not load provider configuration.")
+ self._login_widget.set_enabled(True)
+
+ if d is None:
+ d = defer.Deferred()
+ return d
+
+
+class Signaler(QtCore.QObject):
+ """
+ Signaler object, handles converting string commands to Qt signals.
+
+ This is intended for the separation in frontend/backend, this will
+ live in the frontend.
+ """
+
+ # Signals for the ProviderBootstrapper
+ # These will only exist in the frontend
+ prov_name_resolution = QtCore.Signal(object)
+ prov_https_connection = QtCore.Signal(object)
+ prov_download_provider_info = QtCore.Signal(object)
+
+ prov_download_ca_cert = QtCore.Signal(object)
+ prov_check_ca_fingerprint = QtCore.Signal(object)
+ prov_check_api_certificate = QtCore.Signal(object)
+
+ prov_problem_with_provider = QtCore.Signal(object)
+
+ # These will exist both in the backend and the front end.
+ # The frontend might choose to not "interpret" all the signals
+ # from the backend, but the backend needs to have all the signals
+ # it's going to emit defined here
+ PROV_NAME_RESOLUTION_KEY = "prov_name_resolution"
+ PROV_HTTPS_CONNECTION_KEY = "prov_https_connection"
+ PROV_DOWNLOAD_PROVIDER_INFO_KEY = "prov_download_provider_info"
+ PROV_DOWNLOAD_CA_CERT_KEY = "prov_download_ca_cert"
+ PROV_CHECK_CA_FINGERPRINT_KEY = "prov_check_ca_fingerprint"
+ PROV_CHECK_API_CERTIFICATE_KEY = "prov_check_api_certificate"
+ PROV_PROV_PROBLEM_WITH_PROVIER_KEY = "prov_problem_with_provider"
+
+ def __init__(self):
+ """
+ Constructor for the Signaler
+ """
+ QtCore.QObject.__init__(self)
+ self._signals = {}
+
+ signals = [
+ self.PROV_NAME_RESOLUTION_KEY,
+ self.PROV_HTTPS_CONNECTION_KEY,
+ self.PROV_DOWNLOAD_PROVIDER_INFO_KEY,
+ self.PROV_DOWNLOAD_CA_CERT_KEY,
+ self.PROV_CHECK_CA_FINGERPRINT_KEY,
+ self.PROV_CHECK_API_CERTIFICATE_KEY,
+ self.PROV_PROV_PROBLEM_WITH_PROVIER_KEY
+ ]
+
+ for sig in signals:
+ self._signals[sig] = getattr(self, sig)
+
+ def signal(self, key, data=None):
+ """
+ Emits a Qt signal based on the key provided, with the data if provided.
+
+ :param key: string identifying the signal to emit
+ :type key: str
+ :param data: object to send with the data
+ :type data: object
+
+ NOTE: The data object will be a serialized str in the backend,
+ and an unserialized object in the frontend, but for now we
+ just care about objects.
+ """
+ # Right now it emits Qt signals. The backend version of this
+ # will do zmq.send_multipart, and the frontend version will be
+ # similar to this
+ log.msg("Signaling %s :: %s" % (key, data))
+ try:
+ self._signals[key].emit(data)
+ except KeyError:
+ log.msg("Unknown key for signal %s!" % (key,))
+
+
+class Backend(object):
+ """
+ Backend for everything, the UI should only use this class.
+ """
+
+ PASSED_KEY = "passed"
+ ERROR_KEY = "error"
+
+ def __init__(self, bypass_checks=False):
+ """
+ Constructor for the backend.
+ """
+ object.__init__(self)
+
+ # Components map for the commands received
+ self._components = {}
+
+ # Ongoing defers that will be cancelled at stop time
+ self._ongoing_defers = []
+
+ # Signaler object to translate commands into Qt signals
+ self._signaler = Signaler()
+
+ # Component registration
+ self._register(Provider(self._signaler, bypass_checks))
+
+ # We have a looping call on a thread executing all the
+ # commands in queue. Right now this queue is an actual Queue
+ # object, but it'll become the zmq recv_multipart queue
+ self._lc = LoopingCall(threads.deferToThread, self._worker)
+
+ # Temporal call_queue for worker, will be replaced with
+ # recv_multipart os something equivalent in the loopingcall
+ self._call_queue = Queue()
+
+ @property
+ def signaler(self):
+ """
+ Public signaler access to let the UI connect to its signals.
+ """
+ return self._signaler
+
+ def start(self):
+ """
+ Starts the looping call
+ """
+ log.msg("Starting worker...")
+ self._lc.start(0.01)
+
+ def stop(self):
+ """
+ Stops the looping call and tries to cancel all the defers.
+ """
+ log.msg("Stopping worker...")
+ self._lc.stop()
+ while len(self._ongoing_defers) > 0:
+ d = self._ongoing_defers.pop()
+ d.cancel()
+
+ def _register(self, component):
+ """
+ Registers a component in this backend
+
+ :param component: Component to register
+ :type component: any object that implements ILEAPComponent
+ """
+ # TODO: assert that the component implements the interfaces
+ # expected
+ try:
+ self._components[component.key] = component
+ except Exception:
+ log.msg("There was a problem registering %s" % (component,))
+ log.err()
+
+ def _signal_back(self, _, signal):
+ """
+ Helper method to signal back (callback like behavior) to the
+ UI that an operation finished.
+
+ :param signal: signal name
+ :type signal: str
+ """
+ self._signaler.signal(signal)
+
+ def _worker(self):
+ """
+ Worker method, called from a different thread and as a part of
+ a looping call
+ """
+ try:
+ # this'll become recv_multipart
+ cmd = self._call_queue.get(block=False)
+
+ # cmd is: component, method, signalback, *args
+ func = getattr(self._components[cmd[0]], cmd[1])
+ d = func(*cmd[3:])
+ # A call might not have a callback signal, but if it does,
+ # we add it to the chain
+ if cmd[2] is not None:
+ d.addCallbacks(self._signal_back, log.err, cmd[2])
+ d.addCallbacks(self._done_action, log.err,
+ callbackKeywords={"d": d})
+ d.addErrback(log.err)
+ self._ongoing_defers.append(d)
+ except Empty:
+ # If it's just empty we don't have anything to do.
+ pass
+ except Exception:
+ # But we log the rest
+ log.err()
+
+ def _done_action(self, _, d):
+ """
+ Remover of the defer once it's done
+
+ :param d: defer to remove
+ :type d: twisted.internet.defer.Deferred
+ """
+ self._ongoing_defers.remove(d)
+
+ # XXX: Temporal interface until we migrate to zmq
+ # We simulate the calls to zmq.send_multipart. Once we separate
+ # this in two processes, the methods bellow can be changed to
+ # send_multipart and this backend class will be really simple.
+
+ def setup_provider(self, provider):
+ self._call_queue.put(("provider", "setup_provider", None, provider))
+
+ def provider_bootstrap(self, provider):
+ self._call_queue.put(("provider", "bootstrap", None, provider))
diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py
index 98395def..ba1b65b9 100644
--- a/src/leap/bitmask/config/flags.py
+++ b/src/leap/bitmask/config/flags.py
@@ -30,3 +30,5 @@ WARNING: You should NOT use this kind of flags unless you're sure of what
# - search for binaries inside the bundled app instead of the system ones.
# e.g.: openvpn, gpg
STANDALONE = False
+
+MAIL_LOGFILE = None
diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py
index 2c0fa034..8f15719d 100644
--- a/src/leap/bitmask/gui/advanced_key_management.py
+++ b/src/leap/bitmask/gui/advanced_key_management.py
@@ -50,7 +50,8 @@ class AdvancedKeyManagement(QtGui.QWidget):
# if Soledad is not started yet
if sameProxiedObjects(soledad, None):
- self.ui.container.setEnabled(False)
+ self.ui.gbMyKeyPair.setEnabled(False)
+ self.ui.gbStoredPublicKeys.setEnabled(False)
msg = self.tr("<span style='color:#0000FF;'>NOTE</span>: "
"To use this, you need to enable/start {0}.")
msg = msg.format(get_service_display_name(MX_SERVICE))
@@ -79,6 +80,12 @@ class AdvancedKeyManagement(QtGui.QWidget):
self.ui.pbImportKeys.clicked.connect(self._import_keys)
self.ui.pbExportKeys.clicked.connect(self._export_keys)
+ # Stretch columns to content
+ self.ui.twPublicKeys.horizontalHeader().setResizeMode(
+ 0, QtGui.QHeaderView.Stretch)
+
+ self._list_keys()
+
def _import_keys(self):
"""
Imports the user's key pair.
@@ -183,3 +190,16 @@ class AdvancedKeyManagement(QtGui.QWidget):
return
else:
logger.debug('Export canceled by the user.')
+
+ def _list_keys(self):
+ """
+ Loads all the public keys stored in the local db to the keys table.
+ """
+ keys = self._keymanager.get_all_keys_in_local_db()
+
+ keys_table = self.ui.twPublicKeys
+ for key in keys:
+ row = keys_table.rowCount()
+ keys_table.insertRow(row)
+ keys_table.setItem(row, 0, QtGui.QTableWidgetItem(key.address))
+ keys_table.setItem(row, 1, QtGui.QTableWidgetItem(key.key_id))
diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py
index 4b4d360f..92bb623e 100644
--- a/src/leap/bitmask/gui/eip_status.py
+++ b/src/leap/bitmask/gui/eip_status.py
@@ -41,8 +41,8 @@ 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"
+ RATE_STR = "%1.2f KB/s"
+ TOTAL_STR = "%1.2f Kb"
eip_connection_connected = QtCore.Signal()
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index 929919ac..96aa8074 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -18,9 +18,9 @@
Main window for Bitmask.
"""
import logging
-import os
from PySide import QtCore, QtGui
+from functools import partial
from twisted.internet import threads
from zope.proxy import ProxyBase, setProxiedObject
@@ -42,9 +42,10 @@ from leap.bitmask.gui.systray import SysTray
from leap.bitmask import provider
from leap.bitmask.platform_init import IS_WIN, IS_MAC
from leap.bitmask.platform_init.initializers import init_platform
-from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper
-from leap.bitmask.services import get_service_display_name, EIP_SERVICE
+from leap.bitmask import backend
+
+from leap.bitmask.services import get_service_display_name
from leap.bitmask.services.mail import conductor as mail_conductor
@@ -138,6 +139,9 @@ class MainWindow(QtGui.QMainWindow):
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
+ self._backend = backend.Backend(bypass_checks)
+ self._backend.start()
+
self._settings = LeapSettings()
self._login_widget = LoginWidget(
@@ -180,7 +184,10 @@ class MainWindow(QtGui.QMainWindow):
# This is loaded only once, there's a bug when doing that more
# than once
- self._provider_config = ProviderConfig()
+ # XXX HACK!! But we need it as long as we are using
+ # provider_config in here
+ self._provider_config = (
+ self._backend._components["provider"]._provider_config)
# Used for automatic start of EIP
self._provisional_provider_config = ProviderConfig()
self._eip_config = eipconfig.EIPConfig()
@@ -191,25 +198,7 @@ class MainWindow(QtGui.QMainWindow):
self._srp_auth = None
self._logged_user = None
- # This thread is always running, although it's quite
- # lightweight when it's done setting up provider
- # configuration and certificate.
- self._provider_bootstrapper = ProviderBootstrapper(bypass_checks)
-
- # Intermediate stages, only do something if there was an error
- self._provider_bootstrapper.name_resolution.connect(
- self._intermediate_stage)
- self._provider_bootstrapper.https_connection.connect(
- self._intermediate_stage)
- self._provider_bootstrapper.download_ca_cert.connect(
- self._intermediate_stage)
-
- # Important stages, loads the provider config and checks
- # certificates
- self._provider_bootstrapper.download_provider_info.connect(
- self._load_provider_config)
- self._provider_bootstrapper.check_api_certificate.connect(
- self._provider_config_loaded)
+ self._backend_connect()
# This thread is similar to the provider bootstrapper
self._eip_bootstrapper = EIPBootstrapper()
@@ -248,6 +237,9 @@ class MainWindow(QtGui.QMainWindow):
self._soledad_bootstrapper.soledad_failed.connect(
self._mail_status.set_soledad_failed)
+ self.ui.action_preferences.triggered.connect(self._show_preferences)
+ self.ui.action_eip_preferences.triggered.connect(
+ self._show_eip_preferences)
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)
@@ -279,8 +271,9 @@ class MainWindow(QtGui.QMainWindow):
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)
+ # disable buttons for now, may come back later.
+ # self.ui.btnPreferences.clicked.connect(self._show_preferences)
+ # self.ui.btnEIPPreferences.clicked.connect(self._show_eip_preferences)
self._enabled_services = []
@@ -345,7 +338,9 @@ class MainWindow(QtGui.QMainWindow):
if self._first_run():
self._wizard_firstrun = True
- self._wizard = Wizard(bypass_checks=bypass_checks)
+ self._backend_disconnect()
+ self._wizard = Wizard(backend=self._backend,
+ bypass_checks=bypass_checks)
# Give this window time to finish init and then show the wizard
QtCore.QTimer.singleShot(1, self._launch_wizard)
self._wizard.accepted.connect(self._finish_init)
@@ -355,6 +350,47 @@ class MainWindow(QtGui.QMainWindow):
# so this has to be done after eip_machine is started
self._finish_init()
+ def _backend_connect(self):
+ """
+ Helper to connect to backend signals
+ """
+ self._backend.signaler.prov_name_resolution.connect(
+ self._intermediate_stage)
+ self._backend.signaler.prov_https_connection.connect(
+ self._intermediate_stage)
+ self._backend.signaler.prov_download_ca_cert.connect(
+ self._intermediate_stage)
+
+ self._backend.signaler.prov_download_provider_info.connect(
+ self._load_provider_config)
+ self._backend.signaler.prov_check_api_certificate.connect(
+ self._provider_config_loaded)
+
+ # Only used at login, no need to disconnect this like we do
+ # with the other
+ self._backend.signaler.prov_problem_with_provider.connect(
+ partial(self._login_widget.set_status,
+ self.tr("Unable to login: Problem with provider")))
+
+ def _backend_disconnect(self):
+ """
+ Helper to disconnect from backend signals.
+
+ Some signals are emitted from the wizard, and we want to
+ ignore those.
+ """
+ self._backend.signaler.prov_name_resolution.disconnect(
+ self._intermediate_stage)
+ self._backend.signaler.prov_https_connection.disconnect(
+ self._intermediate_stage)
+ self._backend.signaler.prov_download_ca_cert.disconnect(
+ self._intermediate_stage)
+
+ self._backend.signaler.prov_download_provider_info.disconnect(
+ self._load_provider_config)
+ self._backend.signaler.prov_check_api_certificate.disconnect(
+ self._provider_config_loaded)
+
def _rejected_wizard(self):
"""
SLOT
@@ -375,6 +411,7 @@ class MainWindow(QtGui.QMainWindow):
# This happens if the user finishes the provider
# setup but does not register
self._wizard = None
+ self._backend_connect()
self._finish_init()
def _launch_wizard(self):
@@ -390,7 +427,9 @@ class MainWindow(QtGui.QMainWindow):
there.
"""
if self._wizard is None:
- self._wizard = Wizard(bypass_checks=self._bypass_checks)
+ self._backend_disconnect()
+ self._wizard = Wizard(backend=self._backend,
+ bypass_checks=self._bypass_checks)
self._wizard.accepted.connect(self._finish_init)
self._wizard.rejected.connect(self._wizard.close)
@@ -467,22 +506,61 @@ class MainWindow(QtGui.QMainWindow):
"""
SLOT
TRIGGERS:
- self.ui.btnPreferences.clicked
+ self.ui.btnPreferences.clicked (disabled for now)
+ self.ui.action_preferences
Displays the preferences window.
"""
- preferences_window = PreferencesWindow(
+ preferences = PreferencesWindow(
self, self._srp_auth, self._provider_config, self._soledad,
self._login_widget.get_selected_provider())
- self.soledad_ready.connect(preferences_window.set_soledad_ready)
- preferences_window.show()
+ self.soledad_ready.connect(preferences.set_soledad_ready)
+ preferences.show()
+ preferences.preferences_saved.connect(self._update_eip_enabled_status)
+
+ def _update_eip_enabled_status(self):
+ """
+ SLOT
+ TRIGGER:
+ PreferencesWindow.preferences_saved
+
+ Enable or disable the EIP start/stop actions and stop EIP if the user
+ disabled that service.
+
+ :returns: if the eip actions were enabled or disabled
+ :rtype: bool
+ """
+ settings = self._settings
+ default_provider = settings.get_defaultprovider()
+ enabled_services = []
+ if default_provider is not None:
+ enabled_services = settings.get_enabled_services(default_provider)
+
+ eip_enabled = False
+ if EIP_SERVICE in enabled_services:
+ should_autostart = settings.get_autostart_eip()
+ if should_autostart and default_provider is not None:
+ self._eip_status.enable_eip_start()
+ self._eip_status.set_eip_status("")
+ eip_enabled = True
+ else:
+ # we don't have an usable provider
+ # so the user needs to log in first
+ self._eip_status.disable_eip_start()
+ else:
+ self._stop_eip()
+ self._eip_status.disable_eip_start()
+ self._eip_status.set_eip_status(self.tr("Disabled"))
+
+ return eip_enabled
def _show_eip_preferences(self):
"""
SLOT
TRIGGERS:
self.ui.btnEIPPreferences.clicked
+ self.ui.action_eip_preferences (disabled for now)
Displays the EIP preferences window.
"""
@@ -585,6 +663,7 @@ class MainWindow(QtGui.QMainWindow):
self.eip_needs_login.emit()
self._wizard = None
+ self._backend_connect()
else:
self._try_autostart_eip()
@@ -813,14 +892,12 @@ class MainWindow(QtGui.QMainWindow):
# XXX should rename this provider, name clash.
provider = self._login_widget.get_selected_provider()
- pb = self._provider_bootstrapper
- d = pb.run_provider_select_checks(provider, download_if_needed=True)
- self._download_provider_defer = d
+ self._backend.setup_provider(provider)
def _load_provider_config(self, data):
"""
SLOT
- TRIGGER: self._provider_bootstrapper.download_provider_info
+ TRIGGER: self._backend.signaler.prov_download_provider_info
Once the provider config has been downloaded, this loads the
self._provider_config instance with it and starts the second
@@ -830,31 +907,13 @@ class MainWindow(QtGui.QMainWindow):
run_provider_select_checks
:type data: dict
"""
- if data[self._provider_bootstrapper.PASSED_KEY]:
- # XXX should rename this provider, name clash.
- provider = self._login_widget.get_selected_provider()
-
- # If there's no loaded provider or
- # we want to connect to other provider...
- if (not self._provider_config.loaded() or
- self._provider_config.get_domain() != provider):
- self._provider_config.load(
- os.path.join("leap", "providers",
- provider, "provider.json"))
-
- if self._provider_config.loaded():
- self._provider_bootstrapper.run_provider_setup_checks(
- self._provider_config,
- download_if_needed=True)
- else:
- self._login_widget.set_status(
- self.tr("Unable to login: Problem with provider"))
- logger.error("Could not load provider configuration.")
- self._login_widget.set_enabled(True)
+ if data[self._backend.PASSED_KEY]:
+ selected_provider = self._login_widget.get_selected_provider()
+ self._backend.provider_bootstrap(selected_provider)
else:
self._login_widget.set_status(
self.tr("Unable to login: Problem with provider"))
- logger.error(data[self._provider_bootstrapper.ERROR_KEY])
+ logger.error(data[self._backend.ERROR_KEY])
self._login_widget.set_enabled(True)
def _login(self):
@@ -896,14 +955,14 @@ class MainWindow(QtGui.QMainWindow):
def _provider_config_loaded(self, data):
"""
SLOT
- TRIGGER: self._provider_bootstrapper.check_api_certificate
+ TRIGGER: self._backend.signaler.prov_check_api_certificate
Once the provider configuration is loaded, this starts the SRP
authentication
"""
leap_assert(self._provider_config, "We need a provider config!")
- if data[self._provider_bootstrapper.PASSED_KEY]:
+ if data[self._backend.PASSED_KEY]:
username = self._login_widget.get_user()
password = self._login_widget.get_password()
@@ -921,7 +980,7 @@ class MainWindow(QtGui.QMainWindow):
else:
self._login_widget.set_status(
"Unable to login: Problem with provider")
- logger.error(data[self._provider_bootstrapper.ERROR_KEY])
+ logger.error(data[self._backend.ERROR_KEY])
self._login_widget.set_enabled(True)
def _authentication_finished(self, ok, message):
@@ -956,7 +1015,7 @@ class MainWindow(QtGui.QMainWindow):
"""
self._login_widget.logged_in()
- self.ui.lblLoginProvider.setText(self._provider_config.get_name())
+ self.ui.lblLoginProvider.setText(self._provider_config.get_domain())
self._enabled_services = self._settings.get_enabled_services(
self._provider_config.get_domain())
@@ -1177,21 +1236,10 @@ class MainWindow(QtGui.QMainWindow):
"""
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()
+ if not self._update_eip_enabled_status():
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 = settings.get_enabled_services(
default_provider)
@@ -1506,11 +1554,11 @@ class MainWindow(QtGui.QMainWindow):
This is used for intermediate bootstrapping stages, in case
they fail.
"""
- passed = data[self._provider_bootstrapper.PASSED_KEY]
+ passed = data[self._backend.PASSED_KEY]
if not passed:
self._login_widget.set_status(
self.tr("Unable to connect: Problem with provider"))
- logger.error(data[self._provider_bootstrapper.ERROR_KEY])
+ logger.error(data[self._backend.ERROR_KEY])
self._already_started_eip = False
# end of EIP methods ---------------------------------------------
@@ -1583,21 +1631,21 @@ class MainWindow(QtGui.QMainWindow):
"""
SLOT
TRIGGERS:
- self._provider_bootstrapper.name_resolution
- self._provider_bootstrapper.https_connection
- self._provider_bootstrapper.download_ca_cert
+ self._backend.signaler.prov_name_resolution
+ self._backend.signaler.prov_https_connection
+ self._backend.signaler.prov_download_ca_cert
self._eip_bootstrapper.download_config
If there was a problem, displays it, otherwise it does nothing.
This is used for intermediate bootstrapping stages, in case
they fail.
"""
- passed = data[self._provider_bootstrapper.PASSED_KEY]
+ passed = data[self._backend.PASSED_KEY]
if not passed:
self._login_widget.set_enabled(True)
self._login_widget.set_status(
self.tr("Unable to connect: Problem with provider"))
- logger.error(data[self._provider_bootstrapper.ERROR_KEY])
+ logger.error(data[self._backend.ERROR_KEY])
#
# window handling methods
@@ -1687,6 +1735,7 @@ class MainWindow(QtGui.QMainWindow):
# Set this in case that the app is hidden
QtGui.QApplication.setQuitOnLastWindowClosed(True)
+ self._backend.stop()
self._cleanup_and_quit()
self._really_quit = True
diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py
index b4bddef2..517a90c4 100644
--- a/src/leap/bitmask/gui/preferenceswindow.py
+++ b/src/leap/bitmask/gui/preferenceswindow.py
@@ -42,6 +42,8 @@ class PreferencesWindow(QtGui.QDialog):
"""
Window that displays the preferences.
"""
+ preferences_saved = QtCore.Signal()
+
def __init__(self, parent, srp_auth, provider_config, soledad, domain):
"""
:param parent: parent object of the PreferencesWindow.
@@ -369,6 +371,7 @@ class PreferencesWindow(QtGui.QDialog):
"Services settings for provider '{0}' saved.".format(provider))
logger.debug(msg)
self._set_providers_services_status(msg, success=True)
+ self.preferences_saved.emit()
def _get_provider_config(self, domain):
"""
diff --git a/src/leap/bitmask/gui/ui/advanced_key_management.ui b/src/leap/bitmask/gui/ui/advanced_key_management.ui
index d61aa87e..1112670f 100644
--- a/src/leap/bitmask/gui/ui/advanced_key_management.ui
+++ b/src/leap/bitmask/gui/ui/advanced_key_management.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>431</width>
- <height>188</height>
+ <width>504</width>
+ <height>546</height>
</rect>
</property>
<property name="windowTitle">
@@ -17,10 +17,13 @@
<iconset resource="../../../../../data/resources/mainwindow.qrc">
<normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset>
</property>
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="0" column="0">
- <widget class="QWidget" name="container" native="true">
- <layout class="QGridLayout" name="gridLayout_2">
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="1" column="0" colspan="2">
+ <widget class="QGroupBox" name="gbMyKeyPair">
+ <property name="title">
+ <string>My key pair</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
@@ -90,20 +93,7 @@
</property>
</widget>
</item>
- <item row="3" column="1">
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="4" column="0" colspan="2">
+ <item row="3" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<widget class="QPushButton" name="pbExportKeys">
@@ -135,9 +125,56 @@
</layout>
</item>
</layout>
+ <zorder>leKeyID</zorder>
+ <zorder>leUser</zorder>
+ <zorder>leFingerprint</zorder>
+ <zorder>label_3</zorder>
+ <zorder>label_5</zorder>
+ <zorder>label</zorder>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QGroupBox" name="gbStoredPublicKeys">
+ <property name="title">
+ <string>Stored public keys</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QTableWidget" name="twPublicKeys">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="textElideMode">
+ <enum>Qt::ElideRight</enum>
+ </property>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string>Email</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Key ID</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
- <item row="1" column="0">
+ <item row="3" column="0" colspan="2">
<widget class="QLabel" name="lblStatus">
<property name="text">
<string/>
diff --git a/src/leap/bitmask/gui/ui/eip_status.ui b/src/leap/bitmask/gui/ui/eip_status.ui
index d078ca0c..64821ad6 100644
--- a/src/leap/bitmask/gui/ui/eip_status.ui
+++ b/src/leap/bitmask/gui/ui/eip_status.ui
@@ -25,6 +25,9 @@
</property>
<item>
<layout class="QGridLayout" name="gridLayout">
+ <property name="verticalSpacing">
+ <number>0</number>
+ </property>
<item row="0" column="2">
<widget class="QPushButton" name="btnEipStartStop">
<property name="text">
@@ -75,13 +78,6 @@
<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>
@@ -124,12 +120,6 @@
</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>
@@ -146,16 +136,6 @@
<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">
@@ -175,25 +155,18 @@
<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="styleSheet">
- <string notr="true">text-align: left;</string>
- </property>
<property name="text">
<string>0.0 KB/s</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
+ <property name="icon">
+ <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/down-arrow.png</pixmap>
+ </property>
</widget>
</item>
<item>
@@ -206,22 +179,12 @@
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>10</width>
+ <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">
@@ -242,25 +205,18 @@
<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="styleSheet">
- <string notr="true">text-align: left;</string>
- </property>
<property name="text">
<string>0.0 KB/s</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
+ <property name="icon">
+ <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/up-arrow.png</pixmap>
+ </property>
</widget>
</item>
<item>
@@ -271,7 +227,7 @@
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
- <height>20</height>
+ <height>0</height>
</size>
</property>
</spacer>
diff --git a/src/leap/bitmask/gui/ui/login.ui b/src/leap/bitmask/gui/ui/login.ui
index e7ca1652..7e8f9daf 100644
--- a/src/leap/bitmask/gui/ui/login.ui
+++ b/src/leap/bitmask/gui/ui/login.ui
@@ -215,7 +215,7 @@
<number>0</number>
</property>
<property name="bottomMargin">
- <number>24</number>
+ <number>0</number>
</property>
<item row="1" column="1">
<spacer name="horizontalSpacer">
@@ -255,7 +255,7 @@
<widget class="QLabel" name="lblLoginStatus">
<property name="styleSheet">
<string notr="true">color: rgb(132, 132, 132);
-font: 75 12pt &quot;Lucida Grande&quot;;</string>
+font: 75 12pt;</string>
</property>
<property name="text">
<string/>
diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui
index 3b83788e..ce05f8f3 100644
--- a/src/leap/bitmask/gui/ui/mainwindow.ui
+++ b/src/leap/bitmask/gui/ui/mainwindow.ui
@@ -85,108 +85,8 @@
<property name="margin">
<number>0</number>
</property>
- <item>
- <widget class="QWidget" name="eipWidget" native="true">
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <property name="spacing">
- <number>0</number>
- </property>
- <property name="margin">
- <number>0</number>
- </property>
- <item>
- <widget class="QFrame" name="frame_2">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="styleSheet">
- <string notr="true">QFrame{background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(160, 160, 160, 128), stop:1 rgba(255, 255, 255, 0));}</string>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <property name="leftMargin">
- <number>24</number>
- </property>
- <property name="rightMargin">
- <number>24</number>
- </property>
- <item>
- <widget class="QLabel" name="label_2">
- <property name="font">
- <font>
- <pointsize>16</pointsize>
- <weight>75</weight>
- <bold>true</bold>
- </font>
- </property>
- <property name="styleSheet">
- <string notr="true">background-color: rgba(255, 255, 255, 0);</string>
- </property>
- <property name="text">
- <string>Encrypted Internet</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="btnEIPPreferences">
- <property name="maximumSize">
- <size>
- <width>48</width>
- <height>20</height>
- </size>
- </property>
- <property name="styleSheet">
- <string notr="true"/>
- </property>
- <property name="text">
- <string/>
- </property>
- <property name="icon">
- <iconset resource="../../../../../data/resources/mainwindow.qrc">
- <normaloff>:/images/black/32/gear.png</normaloff>:/images/black/32/gear.png</iconset>
- </property>
- <property name="autoDefault">
- <bool>false</bool>
- </property>
- <property name="default">
- <bool>false</bool>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <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>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="Line" name="line">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
+
+ <!-- LOGIN -->
<item>
<widget class="QFrame" name="frame">
<property name="sizePolicy">
@@ -199,9 +99,7 @@
<bool>false</bool>
</property>
<property name="styleSheet">
- <string notr="true">QFrame{
-background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(160, 160, 160, 128), stop:1 rgba(255, 255, 255, 0));
-}</string>
+ <string notr="true">background-color: rgba(0,0,0,20); border-bottom: 1px solid rgba(0,0,0,30);</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
@@ -214,36 +112,15 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb
<widget class="QLabel" name="lblLoginProvider">
<property name="font">
<font>
- <pointsize>16</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
- <string notr="true">background-color: rgba(255, 255, 255, 0);</string>
- </property>
- <property name="text">
- <string>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="styleSheet">
- <string notr="true"/>
+ <string notr="true">background-color: rgba(255, 255, 255, 0); border: none;</string>
</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>
+ <string>Please Log In</string>
</property>
</widget>
</item>
@@ -257,13 +134,54 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb
</property>
</layout>
</item>
+
<item>
- <widget class="Line" name="line_2">
+ <widget class="Line" name="lineUnderLogin">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
+
+ <!-- EIP -->
+ <item>
+ <widget class="QWidget" name="eipWidget" native="true">
+ <layout class="QVBoxLayout" name="eipVerticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <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>
+ </layout>
+ </widget>
+ </item>
+
+ <item>
+ <widget class="Line" name="lineUnderEIP">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+
+ <!-- EMAIL -->
<item>
<widget class="QWidget" name="mailWidget" native="true">
<layout class="QVBoxLayout" name="verticalLayout_3">
@@ -286,6 +204,15 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb
</layout>
</widget>
</item>
+
+ <item>
+ <widget class="Line" name="lineUnderEmail">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+
<item>
<spacer name="verticalSpacer">
<property name="orientation">
@@ -390,6 +317,9 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb
<addaction name="action_create_new_account"/>
<addaction name="action_advanced_key_management"/>
<addaction name="separator"/>
+ <addaction name="action_preferences"/>
+ <addaction name="action_eip_preferences"/>
+ <addaction name="separator"/>
<addaction name="action_quit"/>
</widget>
<widget class="QMenu" name="menuHelp">
@@ -404,10 +334,17 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb
<addaction name="menuFile"/>
<addaction name="menuHelp"/>
</widget>
- <widget class="QStatusBar" name="statusbar"/>
<action name="action_preferences">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Account Preferences...</string>
+ </property>
+ </action>
+ <action name="action_eip_preferences">
<property name="text">
- <string>Preferences...</string>
+ <string>Internet Preferences...</string>
</property>
</action>
<action name="action_quit">
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index 5f5224ae..ec007110 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -30,7 +30,6 @@ from twisted.internet import threads
from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.crypto.srpregister import SRPRegister
-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
@@ -55,12 +54,15 @@ class Wizard(QtGui.QWizard):
BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$"
- def __init__(self, bypass_checks=False):
+ def __init__(self, backend, bypass_checks=False):
"""
Constructor for the main Wizard.
+ :param backend: Backend being used
+ :type backend: Backend
:param bypass_checks: Set to true if the app should bypass
- first round of checks for CA certificates at bootstrap
+ first round of checks for CA
+ certificates at bootstrap
:type bypass_checks: bool
"""
QtGui.QWizard.__init__(self)
@@ -68,6 +70,8 @@ class Wizard(QtGui.QWizard):
self.ui = Ui_Wizard()
self.ui.setupUi(self)
+ self._backend = backend
+
self.setPixmap(QtGui.QWizard.LogoPixmap,
QtGui.QPixmap(":/images/mask-icon.png"))
@@ -86,23 +90,25 @@ class Wizard(QtGui.QWizard):
self.ui.btnCheck.clicked.connect(self._check_provider)
self.ui.lnProvider.returnPressed.connect(self._check_provider)
- self._provider_bootstrapper = ProviderBootstrapper(bypass_checks)
- self._provider_bootstrapper.name_resolution.connect(
+ self._backend.signaler.prov_name_resolution.connect(
self._name_resolution)
- self._provider_bootstrapper.https_connection.connect(
+ self._backend.signaler.prov_https_connection.connect(
self._https_connection)
- self._provider_bootstrapper.download_provider_info.connect(
+ self._backend.signaler.prov_download_provider_info.connect(
self._download_provider_info)
- self._provider_bootstrapper.download_ca_cert.connect(
+ self._backend.signaler.prov_download_ca_cert.connect(
self._download_ca_cert)
- self._provider_bootstrapper.check_ca_fingerprint.connect(
+ self._backend.signaler.prov_check_ca_fingerprint.connect(
self._check_ca_fingerprint)
- self._provider_bootstrapper.check_api_certificate.connect(
+ self._backend.signaler.prov_check_api_certificate.connect(
self._check_api_certificate)
self._domain = None
- self._provider_config = ProviderConfig()
+ # HACK!! We need provider_config for the time being, it'll be
+ # removed
+ self._provider_config = (
+ self._backend._components["provider"]._provider_config)
# We will store a reference to the defers for eventual use
# (eg, to cancel them) but not doing anything with them right now.
@@ -385,8 +391,8 @@ class Wizard(QtGui.QWizard):
self._domain = self.ui.lnProvider.text()
self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON)
- self._provider_select_defer = self._provider_bootstrapper.\
- run_provider_select_checks(self._domain)
+ self._provider_select_defer = self._backend.\
+ setup_provider(self._domain)
def _skip_provider_checks(self, skip):
"""
@@ -423,8 +429,8 @@ class Wizard(QtGui.QWizard):
:param complete_page: page id to complete
:type complete_page: int
"""
- passed = data[self._provider_bootstrapper.PASSED_KEY]
- error = data[self._provider_bootstrapper.ERROR_KEY]
+ passed = data[self._backend.PASSED_KEY]
+ error = data[self._backend.ERROR_KEY]
if passed:
label.setPixmap(self.OK_ICON)
if complete:
@@ -437,13 +443,13 @@ class Wizard(QtGui.QWizard):
def _name_resolution(self, data):
"""
SLOT
- TRIGGER: self._provider_bootstrapper.name_resolution
+ TRIGGER: self._backend.signaler.prov_name_resolution
Sets the status for the name resolution check
"""
self._complete_task(data, self.ui.lblNameResolution)
status = ""
- passed = data[self._provider_bootstrapper.PASSED_KEY]
+ passed = data[self._backend.PASSED_KEY]
if not passed:
status = self.tr("<font color='red'><b>Non-existent "
"provider</b></font>")
@@ -456,16 +462,16 @@ class Wizard(QtGui.QWizard):
def _https_connection(self, data):
"""
SLOT
- TRIGGER: self._provider_bootstrapper.https_connection
+ TRIGGER: self._backend.signaler.prov_https_connection
Sets the status for the https connection check
"""
self._complete_task(data, self.ui.lblHTTPS)
status = ""
- passed = data[self._provider_bootstrapper.PASSED_KEY]
+ passed = data[self._backend.PASSED_KEY]
if not passed:
status = self.tr("<font color='red'><b>%s</b></font>") \
- % (data[self._provider_bootstrapper.ERROR_KEY])
+ % (data[self._backend.ERROR_KEY])
self.ui.lblProviderSelectStatus.setText(status)
else:
self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON)
@@ -475,7 +481,7 @@ class Wizard(QtGui.QWizard):
def _download_provider_info(self, data):
"""
SLOT
- TRIGGER: self._provider_bootstrapper.download_provider_info
+ TRIGGER: self._backend.signaler.prov_download_provider_info
Sets the status for the provider information download
check. Since this check is the last of this set, it also
@@ -490,14 +496,14 @@ class Wizard(QtGui.QWizard):
self._provider_checks_ok = True
else:
new_data = {
- self._provider_bootstrapper.PASSED_KEY: False,
- self._provider_bootstrapper.ERROR_KEY:
+ self._backend.PASSED_KEY: False,
+ self._backend.ERROR_KEY:
self.tr("Unable to load provider configuration")
}
self._complete_task(new_data, self.ui.lblProviderInfo)
status = ""
- if not data[self._provider_bootstrapper.PASSED_KEY]:
+ if not data[self._backend.PASSED_KEY]:
status = self.tr("<font color='red'><b>Not a valid provider"
"</b></font>")
self.ui.lblProviderSelectStatus.setText(status)
@@ -507,31 +513,31 @@ class Wizard(QtGui.QWizard):
def _download_ca_cert(self, data):
"""
SLOT
- TRIGGER: self._provider_bootstrapper.download_ca_cert
+ TRIGGER: self._backend.signaler.prov_download_ca_cert
Sets the status for the download of the CA certificate check
"""
self._complete_task(data, self.ui.lblDownloadCaCert)
- passed = data[self._provider_bootstrapper.PASSED_KEY]
+ passed = data[self._backend.PASSED_KEY]
if passed:
self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON)
def _check_ca_fingerprint(self, data):
"""
SLOT
- TRIGGER: self._provider_bootstrapper.check_ca_fingerprint
+ TRIGGER: self._backend.signaler.prov_check_ca_fingerprint
Sets the status for the CA fingerprint check
"""
self._complete_task(data, self.ui.lblCheckCaFpr)
- passed = data[self._provider_bootstrapper.PASSED_KEY]
+ passed = data[self._backend.PASSED_KEY]
if passed:
self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON)
def _check_api_certificate(self, data):
"""
SLOT
- TRIGGER: self._provider_bootstrapper.check_api_certificate
+ TRIGGER: self._backend.signaler.prov_check_api_certificate
Sets the status for the API certificate check. Also finishes
the provider bootstrapper thread since it's not needed anymore
@@ -597,6 +603,7 @@ class Wizard(QtGui.QWizard):
Prepares the pages when they appear
"""
if pageId == self.SELECT_PROVIDER_PAGE:
+ self._clear_register_widgets()
skip = self.ui.rbExistingProvider.isChecked()
if not self._provider_checks_ok:
self._enable_check()
@@ -611,8 +618,8 @@ class Wizard(QtGui.QWizard):
sub_title = sub_title.format(self._provider_config.get_name())
self.page(pageId).setSubTitle(sub_title)
self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON)
- self._provider_setup_defer = self._provider_bootstrapper.\
- run_provider_setup_checks(self._provider_config)
+ self._provider_setup_defer = self._backend.\
+ provider_bootstrap(self._domain)
if pageId == self.PRESENT_PROVIDER_PAGE:
self.page(pageId).setSubTitle(self.tr("Description of services "
@@ -670,3 +677,12 @@ class Wizard(QtGui.QWizard):
return self.SERVICES_PAGE
return QtGui.QWizard.nextId(self)
+
+ def _clear_register_widgets(self):
+ """
+ Clears the widgets that my be filled and a possible error message.
+ """
+ self._set_register_status("")
+ self.ui.lblUser.setText("")
+ self.ui.lblPassword.setText("")
+ self.ui.lblPassword2.setText("")
diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py
index f5a2003f..947ba0c9 100644
--- a/src/leap/bitmask/provider/providerbootstrapper.py
+++ b/src/leap/bitmask/provider/providerbootstrapper.py
@@ -24,8 +24,6 @@ import sys
import requests
-from PySide import QtCore
-
from leap.bitmask.config.providerconfig import ProviderConfig, MissingCACert
from leap.bitmask.util.request_helpers import get_content
from leap.bitmask import util
@@ -61,25 +59,19 @@ class ProviderBootstrapper(AbstractBootstrapper):
If a check fails, the subsequent checks are not executed
"""
- # All dicts returned are of the form
- # {"passed": bool, "error": str}
- name_resolution = QtCore.Signal(dict)
- https_connection = QtCore.Signal(dict)
- download_provider_info = QtCore.Signal(dict)
-
- download_ca_cert = QtCore.Signal(dict)
- check_ca_fingerprint = QtCore.Signal(dict)
- check_api_certificate = QtCore.Signal(dict)
-
- def __init__(self, bypass_checks=False):
+ def __init__(self, signaler=None, bypass_checks=False):
"""
Constructor for provider bootstrapper object
+ :param signaler: Signaler object used to receive notifications
+ from the backend
+ :type signaler: Signaler
:param bypass_checks: Set to true if the app should bypass
- first round of checks for CA certificates at bootstrap
+ first round of checks for CA
+ certificates at bootstrap
:type bypass_checks: bool
"""
- AbstractBootstrapper.__init__(self, bypass_checks)
+ AbstractBootstrapper.__init__(self, signaler, bypass_checks)
self._domain = None
self._provider_config = None
@@ -238,9 +230,11 @@ class ProviderBootstrapper(AbstractBootstrapper):
self._download_if_needed = download_if_needed
cb_chain = [
- (self._check_name_resolution, self.name_resolution),
- (self._check_https, self.https_connection),
- (self._download_provider_info, self.download_provider_info)
+ (self._check_name_resolution,
+ self._signaler.PROV_NAME_RESOLUTION_KEY),
+ (self._check_https, self._signaler.PROV_HTTPS_CONNECTION_KEY),
+ (self._download_provider_info,
+ self._signaler.PROV_DOWNLOAD_PROVIDER_INFO_KEY)
]
return self.addCallbackChain(cb_chain)
@@ -367,9 +361,11 @@ class ProviderBootstrapper(AbstractBootstrapper):
self._download_if_needed = download_if_needed
cb_chain = [
- (self._download_ca_cert, self.download_ca_cert),
- (self._check_ca_fingerprint, self.check_ca_fingerprint),
- (self._check_api_certificate, self.check_api_certificate)
+ (self._download_ca_cert, self._signaler.PROV_DOWNLOAD_CA_CERT_KEY),
+ (self._check_ca_fingerprint,
+ self._signaler.PROV_CHECK_CA_FINGERPRINT_KEY),
+ (self._check_api_certificate,
+ self._signaler.PROV_CHECK_API_CERTIFICATE_KEY)
]
return self.addCallbackChain(cb_chain)
diff --git a/src/leap/bitmask/provider/tests/test_providerbootstrapper.py b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py
index 88a4ff0b..d8336fec 100644
--- a/src/leap/bitmask/provider/tests/test_providerbootstrapper.py
+++ b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py
@@ -42,6 +42,7 @@ 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.backend import Signaler
from leap.bitmask import util
from leap.common.files import mkdir_p
from leap.common.testing.https_server import where
@@ -50,7 +51,7 @@ from leap.common.testing.basetest import BaseLeapTest
class ProviderBootstrapperTest(BaseLeapTest):
def setUp(self):
- self.pb = ProviderBootstrapper()
+ self.pb = ProviderBootstrapper(Signaler())
def tearDown(self):
pass
diff --git a/src/leap/bitmask/services/abstractbootstrapper.py b/src/leap/bitmask/services/abstractbootstrapper.py
index 6d4d319b..3bee8e01 100644
--- a/src/leap/bitmask/services/abstractbootstrapper.py
+++ b/src/leap/bitmask/services/abstractbootstrapper.py
@@ -25,6 +25,8 @@ import requests
from functools import partial
from PySide import QtCore
+
+from twisted.python import log
from twisted.internet import threads
from leap.common.check import leap_assert, leap_assert_type
@@ -40,10 +42,13 @@ class AbstractBootstrapper(QtCore.QObject):
PASSED_KEY = "passed"
ERROR_KEY = "error"
- def __init__(self, bypass_checks=False):
+ def __init__(self, signaler=None, bypass_checks=False):
"""
Constructor for the abstract bootstrapper
+ :param signaler: Signaler object used to receive notifications
+ from the backend
+ :type signaler: Signaler
:param bypass_checks: Set to true if the app should bypass
first round of checks for CA
certificates at bootstrap
@@ -71,6 +76,7 @@ class AbstractBootstrapper(QtCore.QObject):
self._bypass_checks = bypass_checks
self._signal_to_emit = None
self._err_msg = None
+ self._signaler = signaler
def _gui_errback(self, failure):
"""
@@ -89,10 +95,20 @@ class AbstractBootstrapper(QtCore.QObject):
err_msg = self._err_msg \
if self._err_msg is not None \
else str(failure.value)
- self._signal_to_emit.emit({
+ data = {
self.PASSED_KEY: False,
self.ERROR_KEY: err_msg
- })
+ }
+ # TODO: Remove this check when all the bootstrappers are
+ # in the backend form
+ if isinstance(self._signal_to_emit, basestring):
+ if self._signaler is not None:
+ self._signaler.signal(self._signal_to_emit, data)
+ else:
+ logger.warning("Tried to notify but no signaler found")
+ else:
+ self._signal_to_emit.emit(data)
+ log.err(failure)
failure.trap(Exception)
def _errback(self, failure, signal=None):
@@ -127,8 +143,15 @@ class AbstractBootstrapper(QtCore.QObject):
:param signal: Signal to emit if it fails here first
:type signal: QtCore.SignalInstance
"""
- if signal:
- signal.emit({self.PASSED_KEY: True, self.ERROR_KEY: ""})
+ if signal is not None:
+ data = {self.PASSED_KEY: True, self.ERROR_KEY: ""}
+ if isinstance(signal, basestring):
+ if self._signaler is not None:
+ self._signaler.signal(signal, data)
+ else:
+ logger.warning("Tried to notify but no signaler found")
+ else:
+ signal.emit(data)
def _callback_threader(self, cb, res, *args, **kwargs):
return threads.deferToThread(cb, res, *args, **kwargs)
diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py
index fe3fe4c1..a03bfc44 100644
--- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py
+++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py
@@ -95,7 +95,7 @@ class DarwinVPNLauncher(VPNLauncher):
resources_path = os.path.abspath(
os.path.join(os.getcwd(), "../../Contents/Resources"))
- return os.path.join(resources_path, "leap-client.tiff")
+ return os.path.join(resources_path, "bitmask.tiff")
@classmethod
def get_cocoasudo_ovpn_cmd(kls):
diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py
index 2667f156..5db18cb9 100644
--- a/src/leap/bitmask/services/mail/imap.py
+++ b/src/leap/bitmask/services/mail/imap.py
@@ -19,10 +19,10 @@ Initialization of imap service
"""
import logging
import os
-#import sys
+import sys
from leap.mail.imap.service import imap
-#from twisted.python import log
+from twisted.python import log
logger = logging.getLogger(__name__)
@@ -58,15 +58,15 @@ def start_imap_service(*args, **kwargs):
:returns: twisted.internet.task.LoopingCall instance
"""
+ from leap.bitmask.config import flags
logger.debug('Launching imap service')
override_period = get_mail_check_period()
if override_period:
kwargs['check_period'] = override_period
- # Uncomment the next two lines to get a separate debugging log
- # TODO handle this by a separate flag.
- #log.startLogging(open('/tmp/leap-imap.log', 'w'))
- #log.startLogging(sys.stdout)
+ if flags.MAIL_LOGFILE:
+ log.startLogging(open(flags.MAIL_LOGFILE, 'w'))
+ log.startLogging(sys.stdout)
return imap.run_service(*args, **kwargs)
diff --git a/src/leap/bitmask/services/mail/repair.py b/src/leap/bitmask/services/mail/repair.py
new file mode 100644
index 00000000..767df1ef
--- /dev/null
+++ b/src/leap/bitmask/services/mail/repair.py
@@ -0,0 +1,234 @@
+# -*- coding: utf-8 -*-
+# repair.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/>.
+"""
+Utils for repairing mailbox indexes.
+"""
+import logging
+import getpass
+import os
+
+from collections import defaultdict
+
+from leap.bitmask.config.providerconfig import ProviderConfig
+from leap.bitmask.crypto.srpauth import SRPAuth
+from leap.bitmask.util import get_path_prefix
+from leap.bitmask.services.soledad.soledadbootstrapper import get_db_paths
+
+from leap.mail.imap.server import SoledadBackedAccount
+from leap.soledad.client import Soledad
+
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.DEBUG)
+
+
+def initialize_soledad(uuid, email, passwd,
+ secrets, localdb,
+ gnupg_home, tempdir):
+ """
+ Initializes soledad by hand
+
+ :param email: ID for the user
+ :param gnupg_home: path to home used by gnupg
+ :param tempdir: path to temporal dir
+ :rtype: Soledad instance
+ """
+ # XXX TODO unify with an authoritative source of mocks
+ # for soledad (or partial initializations).
+ # This is copied from the imap tests.
+
+ server_url = "http://provider"
+ cert_file = ""
+
+ class Mock(object):
+ def __init__(self, return_value=None):
+ self._return = return_value
+
+ def __call__(self, *args, **kwargs):
+ return self._return
+
+ class MockSharedDB(object):
+
+ get_doc = Mock()
+ put_doc = Mock()
+ lock = Mock(return_value=('atoken', 300))
+ unlock = Mock(return_value=True)
+
+ def __call__(self):
+ return self
+
+ Soledad._shared_db = MockSharedDB()
+ soledad = Soledad(
+ uuid,
+ passwd,
+ secrets,
+ localdb,
+ server_url,
+ cert_file)
+
+ return soledad
+
+
+class MBOXPlumber(object):
+ """
+ An class that can fix things inside a soledadbacked account.
+ The idea is to gather in this helper different fixes for mailboxes
+ that can be invoked when data migration in the client is needed.
+ """
+
+ def __init__(self, userid, passwd):
+ """
+ Initializes the plumber with all that's needed to authenticate
+ against the provider.
+
+ :param userid: user identifier, foo@bar
+ :type userid: basestring
+ :param passwd: the soledad passphrase
+ :type passwd: basestring
+ """
+ self.userid = userid
+ self.passwd = passwd
+ user, provider = userid.split('@')
+ self.user = user
+ self.sol = None
+ provider_config_path = os.path.join(
+ get_path_prefix(),
+ "leap", "providers",
+ provider, "provider.json")
+ provider_config = ProviderConfig()
+ loaded = provider_config.load(provider_config_path)
+ if not loaded:
+ print "could not load provider config!"
+ return self.exit()
+
+ self.srp = SRPAuth(provider_config)
+ self.srp.authentication_finished.connect(self.repair_account)
+
+ def start_auth(self):
+ """
+ returns the user identifier for a given provider.
+
+ :param provider: the provider to which we authenticate against.
+ """
+ print "Authenticating with provider..."
+ self.d = self.srp.authenticate(self.user, self.passwd)
+
+ def repair_account(self, *args):
+ """
+ Gets the user id for this account.
+ """
+ print "Got authenticated."
+ self.uid = self.srp.get_uid()
+ if not self.uid:
+ print "Got BAD UID from provider!"
+ return self.exit()
+ print "UID: %s" % (self.uid)
+
+ secrets, localdb = get_db_paths(self.uid)
+
+ self.sol = initialize_soledad(
+ self.uid, self.userid, self.passwd,
+ secrets, localdb, "/tmp", "/tmp")
+
+ self.acct = SoledadBackedAccount(self.userid, self.sol)
+ for mbox_name in self.acct.mailboxes:
+ self.repair_mbox(mbox_name)
+ print "done."
+ self.exit()
+
+ def repair_mbox(self, mbox_name):
+ """
+ Repairs indexes for a given mbox
+
+ :param mbox_name: mailbox to repair
+ :type mbox_name: basestring
+ """
+ print
+ print "REPAIRING INDEXES FOR MAILBOX %s" % (mbox_name,)
+ print "----------------------------------------------"
+ mbox = self.acct.getMailbox(mbox_name)
+ len_mbox = mbox.getMessageCount()
+ print "There are %s messages" % (len_mbox,)
+
+ last_ok = True if mbox.last_uid == len_mbox else False
+ uids_iter = (doc.content['uid'] for doc in mbox.messages.get_all())
+ dupes = self._has_dupes(uids_iter)
+ if last_ok and not dupes:
+ print "Mbox does not need repair."
+ return
+
+ msgs = mbox.messages.get_all()
+ for zindex, doc in enumerate(msgs):
+ mindex = zindex + 1
+ old_uid = doc.content['uid']
+ doc.content['uid'] = mindex
+ self.sol.put_doc(doc)
+ print "%s -> %s (%s)" % (mindex, doc.content['uid'], old_uid)
+
+ old_last_uid = mbox.last_uid
+ mbox.last_uid = len_mbox
+ print "LAST UID: %s (%s)" % (mbox.last_uid, old_last_uid)
+
+ def _has_dupes(self, sequence):
+ """
+ Returns True if the given sequence of ints has duplicates.
+
+ :param sequence: a sequence of ints
+ :type sequence: sequence
+ :rtype: bool
+ """
+ d = defaultdict(lambda: 0)
+ for uid in sequence:
+ d[uid] += 1
+ if d[uid] != 1:
+ return True
+ return False
+
+ def exit(self):
+ from twisted.internet import reactor
+ self.d.cancel()
+ if self.sol:
+ self.sol.close()
+ try:
+ reactor.stop()
+ except Exception:
+ pass
+ return
+
+
+def repair_account(userid):
+ """
+ Starts repair process for a given account.
+ :param userid: the user id (email-like)
+ """
+ from twisted.internet import reactor
+ passwd = unicode(getpass.getpass("Passphrase: "))
+
+ # go mario!
+ plumber = MBOXPlumber(userid, passwd)
+ reactor.callLater(1, plumber.start_auth)
+ reactor.run()
+
+
+if __name__ == "__main__":
+ import sys
+
+ logging.basicConfig()
+
+ if len(sys.argv) != 2:
+ print "Usage: repair <username>"
+ sys.exit(1)
+ repair_account(sys.argv[1])
diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
index d078ae96..3ab62b2e 100644
--- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py
+++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
@@ -59,6 +59,33 @@ class SoledadInitError(Exception):
message = "Error while initializing Soledad"
+def get_db_paths(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
+
+
class SoledadBootstrapper(AbstractBootstrapper):
"""
Soledad init procedure
@@ -127,31 +154,6 @@ 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):
@@ -163,7 +165,7 @@ class SoledadBootstrapper(AbstractBootstrapper):
uuid = self.srpauth.get_uid()
token = self.srpauth.get_token()
- secrets_path, local_db_path = self._get_db_paths(uuid)
+ secrets_path, local_db_path = get_db_paths(uuid)
# TODO: Select server based on timezone (issue #3308)
server_dict = self._soledad_config.get_hosts()
@@ -300,6 +302,10 @@ class SoledadBootstrapper(AbstractBootstrapper):
except SSLError as exc:
logger.error("%r" % (exc,))
raise SoledadSyncError("Failed to sync soledad")
+ except u1db_errors.InvalidGeneration as exc:
+ logger.error("%r" % (exc,))
+ raise SoledadSyncError("u1db: InvalidGeneration")
+
except Exception as exc:
logger.exception("Unhandled error while syncing "
"soledad: %r" % (exc,))
diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py
index e8a9fda9..6703b600 100644
--- a/src/leap/bitmask/util/leap_argparse.py
+++ b/src/leap/bitmask/util/leap_argparse.py
@@ -41,6 +41,11 @@ Launches Bitmask""", epilog=epilog)
action="store", dest="log_file",
#type=argparse.FileType('w'),
help='optional log file')
+ parser.add_argument('-m', '--mail-logfile',
+ metavar="MAIL LOG FILE", nargs='?',
+ action="store", dest="mail_log_file",
+ #type=argparse.FileType('w'),
+ help='optional log file for email')
parser.add_argument('--openvpn-verbosity', nargs='?',
type=int,
action="store", dest="openvpn_verb",
@@ -51,6 +56,12 @@ Launches Bitmask""", epilog=epilog)
'searching')
parser.add_argument('-V', '--version', action="store_true",
help='Displays Bitmask version and exits')
+ parser.add_argument('-r', '--repair-mailboxes', metavar="user@provider",
+ nargs='?',
+ action="store", dest="acct_to_repair",
+ help='Repair mailboxes for a given account. '
+ 'Use when upgrading versions after a schema '
+ 'change.')
# Not in use, we might want to reintroduce them.
#parser.add_argument('-i', '--no-provider-checks',