From 6f93bd407cb9f36a7ff58e607ebdd069868888ae Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 23 Dec 2013 15:37:52 -0400 Subject: add hash specifically in about dialog. --- src/leap/bitmask/__init__.py | 1 + src/leap/bitmask/_appname.py | 1 + src/leap/bitmask/gui/mainwindow.py | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 src/leap/bitmask/_appname.py (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/__init__.py b/src/leap/bitmask/__init__.py index a4642e27..c844beb1 100644 --- a/src/leap/bitmask/__init__.py +++ b/src/leap/bitmask/__init__.py @@ -56,6 +56,7 @@ __short_version__ = "unknown" try: from leap.bitmask._version import get_versions __version__ = get_versions()['version'] + __version_hash__ = get_versions()['full'] IS_RELEASE_VERSION = _is_release_version(__version__) del get_versions except ImportError: diff --git a/src/leap/bitmask/_appname.py b/src/leap/bitmask/_appname.py new file mode 100644 index 00000000..82e8bd43 --- /dev/null +++ b/src/leap/bitmask/_appname.py @@ -0,0 +1 @@ +__appname__ = "bitmask" diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 96aa8074..51456473 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -25,6 +25,7 @@ from twisted.internet import threads from zope.proxy import ProxyBase, setProxiedObject from leap.bitmask import __version__ as VERSION +from leap.bitmask import __version_hash__ as VERSION_HASH from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpauth import SRPAuth @@ -829,7 +830,7 @@ class MainWindow(QtGui.QMainWindow): """ QtGui.QMessageBox.about( self, self.tr("About Bitmask - %s") % (VERSION,), - self.tr("Version: %s
" + self.tr("Version: %s (%s)
" "
" "Bitmask is the Desktop client application for " "the LEAP platform, supporting encrypted internet " @@ -842,7 +843,7 @@ class MainWindow(QtGui.QMainWindow): "and widely available.
" "
" "More about LEAP" - "") % (VERSION,)) + "") % (VERSION, VERSION_HASH[:10])) def changeEvent(self, e): """ -- cgit v1.2.3 From 17b19d82439143193af8b8eefcfa05f20d011c89 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 26 Dec 2013 12:37:57 -0400 Subject: comments here and there --- src/leap/bitmask/gui/eip_status.py | 6 +++--- src/leap/bitmask/gui/mainwindow.py | 10 ++++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 92bb623e..19942d9d 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -248,10 +248,10 @@ class EIPStatusWidget(QtGui.QWidget): Triggered when a default provider_config has not been found. Disables the start button and adds instructions to the user. """ - logger.debug('Hiding EIP start button') + #logger.debug('Hiding EIP start button') # you might be tempted to change this for a .setEnabled(False). # it won't work. it's under the claws of the state machine. - # probably the best thing would be to make a transitional + # probably the best thing would be to make a conditional # transition there, but that's more involved. self.eip_button.hide() msg = self.tr("You must login to use {0}".format(self._service_name)) @@ -272,7 +272,7 @@ class EIPStatusWidget(QtGui.QWidget): Triggered after a successful login. Enables the start button. """ - logger.debug('Showing EIP start button') + #logger.debug('Showing EIP start button') self.eip_button.show() # Restore the eip action menu diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 51456473..c05d65fe 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -20,6 +20,7 @@ Main window for Bitmask. import logging from PySide import QtCore, QtGui +from datetime import datetime from functools import partial from twisted.internet import threads from zope.proxy import ProxyBase, setProxiedObject @@ -828,10 +829,13 @@ class MainWindow(QtGui.QMainWindow): Display the About Bitmask dialog """ + today = datetime.now().date() + greet = ("Happy New 1984!... or not ;)

" + if today.month == 1 and today.day < 15 else "") QtGui.QMessageBox.about( self, self.tr("About Bitmask - %s") % (VERSION,), self.tr("Version: %s (%s)
" - "
" + "
%s" "Bitmask is the Desktop client application for " "the LEAP platform, supporting encrypted internet " "proxy, secure email, and secure chat (coming soon).
" @@ -843,7 +847,7 @@ class MainWindow(QtGui.QMainWindow): "and widely available.
" "
" "More about LEAP" - "") % (VERSION, VERSION_HASH[:10])) + "") % (VERSION, VERSION_HASH[:10], greet)) def changeEvent(self, e): """ @@ -1070,6 +1074,8 @@ class MainWindow(QtGui.QMainWindow): logger.debug("Retrying soledad connection.") if self._soledad_bootstrapper.should_retry_initialization(): self._soledad_bootstrapper.increment_retries_count() + # XXX should cancel the existing socket --- this + # is avoiding a clean termination. threads.deferToThread( self._soledad_bootstrapper.load_and_sync_soledad) else: -- cgit v1.2.3 From d508f41a6f0ae1f16ee2c0f28b6fb2484d487d4f Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 27 Dec 2013 09:52:18 -0400 Subject: update mail path after refactor --- src/leap/bitmask/services/mail/repair.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/mail/repair.py b/src/leap/bitmask/services/mail/repair.py index 767df1ef..30571adf 100644 --- a/src/leap/bitmask/services/mail/repair.py +++ b/src/leap/bitmask/services/mail/repair.py @@ -28,7 +28,7 @@ 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.mail.imap.account import SoledadBackedAccount from leap.soledad.client import Soledad logger = logging.getLogger(__name__) @@ -145,11 +145,11 @@ class MBOXPlumber(object): self.acct = SoledadBackedAccount(self.userid, self.sol) for mbox_name in self.acct.mailboxes: - self.repair_mbox(mbox_name) + self.repair_mbox_uids(mbox_name) print "done." self.exit() - def repair_mbox(self, mbox_name): + def repair_mbox_uids(self, mbox_name): """ Repairs indexes for a given mbox @@ -176,7 +176,8 @@ class MBOXPlumber(object): old_uid = doc.content['uid'] doc.content['uid'] = mindex self.sol.put_doc(doc) - print "%s -> %s (%s)" % (mindex, doc.content['uid'], old_uid) + if mindex != old_uid: + print "%s -> %s (%s)" % (mindex, doc.content['uid'], old_uid) old_last_uid = mbox.last_uid mbox.last_uid = len_mbox -- cgit v1.2.3 From 5e3adedc6c4de23fe8ef5ee99f3c88977c420329 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 6 Jan 2014 18:35:51 -0300 Subject: Disconnect signals before closing the wizard. [Closes #4817] --- src/leap/bitmask/gui/mainwindow.py | 5 +++-- src/leap/bitmask/gui/wizard.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index c05d65fe..1fe2cfca 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -414,7 +414,8 @@ class MainWindow(QtGui.QMainWindow): # setup but does not register self._wizard = None self._backend_connect() - self._finish_init() + if self._wizard_firstrun: + self._finish_init() def _launch_wizard(self): """ @@ -433,7 +434,7 @@ class MainWindow(QtGui.QMainWindow): 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) + self._wizard.rejected.connect(self._rejected_wizard) self.setVisible(False) # Do NOT use exec_, it will use a child event loop! diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index ec007110..b99e8db6 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -686,3 +686,26 @@ class Wizard(QtGui.QWizard): self.ui.lblUser.setText("") self.ui.lblPassword.setText("") self.ui.lblPassword2.setText("") + + def closeEvent(self, event): + """ + This method is called when the wizard dialog is closed. + We disconnect all the backend signals in here. + """ + try: + # disconnect backend signals + self._backend.signaler.prov_name_resolution.disconnect( + self._name_resolution) + self._backend.signaler.prov_https_connection.disconnect( + self._https_connection) + self._backend.signaler.prov_download_provider_info.disconnect( + self._download_provider_info) + + self._backend.signaler.prov_download_ca_cert.disconnect( + self._download_ca_cert) + self._backend.signaler.prov_check_ca_fingerprint.disconnect( + self._check_ca_fingerprint) + self._backend.signaler.prov_check_api_certificate.disconnect( + self._check_api_certificate) + except RuntimeError: + pass # Signal was not connected -- cgit v1.2.3 From 257b42bf0baf923c1437778cd99d0a9cbf69637d Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 7 Jan 2014 12:40:26 -0200 Subject: Make Soledad wait for EIP before starting (#4885). --- src/leap/bitmask/gui/mainwindow.py | 57 ++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 17 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index c05d65fe..01e72597 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -105,6 +105,9 @@ class MainWindow(QtGui.QMainWindow): # We use this flag to detect abnormal terminations user_stopped_eip = False + # We give EIP some time to come up before starting soledad anyway + EIP_TIMEOUT = 60000 # in milliseconds + def __init__(self, quit_callback, openvpn_verb=1, bypass_checks=False): @@ -179,6 +182,8 @@ class MainWindow(QtGui.QMainWindow): self._eip_status.eip_connection_connected.connect( self._on_eip_connected) + self._eip_status.eip_connection_connected.connect( + self._maybe_run_soledad_setup_checks) self.eip_needs_login.connect( self._eip_status.disable_eip_start) self.eip_needs_login.connect( @@ -195,6 +200,7 @@ class MainWindow(QtGui.QMainWindow): self._eip_config = eipconfig.EIPConfig() self._already_started_eip = False + self._already_started_soledad = False # This is created once we have a valid provider config self._srp_auth = None @@ -1026,22 +1032,27 @@ class MainWindow(QtGui.QMainWindow): self._provider_config.get_domain()) # TODO separate UI from logic. - # TODO soledad should check if we want to run only over EIP. if self._provider_config.provides_mx() and \ self._enabled_services.count(MX_SERVICE) > 0: self._mail_status.about_to_start() + else: + self._mail_status.set_disabled() + + self._maybe_start_eip() + def _maybe_run_soledad_setup_checks(self): + """ + """ + # TODO soledad should check if we want to run only over EIP. + if self._already_started_soledad is False \ + and self._logged_user is not None: + self._already_started_soledad = True self._soledad_bootstrapper.run_soledad_setup_checks( self._provider_config, self._login_widget.get_user(), self._login_widget.get_password(), download_if_needed=True) - else: - self._mail_status.set_disabled() - # XXX the config should be downloaded from the start_eip - # method. - self._download_eip_config() ################################################################### # Service control methods: soledad @@ -1257,7 +1268,7 @@ class MainWindow(QtGui.QMainWindow): # it adds some delay. # Maybe if it's the first run in a session, # or we can try only if it fails. - self._download_eip_config() + self._maybe_start_eip() else: # XXX: Display a proper message to the user self.eip_needs_login.emit() @@ -1489,9 +1500,10 @@ class MainWindow(QtGui.QMainWindow): # eip boostrapping, config etc... - def _download_eip_config(self): + def _maybe_start_eip(self): """ - Starts the EIP bootstrapping sequence + Start the EIP bootstrapping sequence if the client is configured to + do so. """ leap_assert(self._eip_bootstrapper, "We need an eip bootstrapper!") @@ -1508,14 +1520,22 @@ class MainWindow(QtGui.QMainWindow): provider_config, download_if_needed=True) self._already_started_eip = True - elif not self._already_started_eip: - if self._enabled_services.count(EIP_SERVICE) > 0: - self._eip_status.set_eip_status( - self.tr("Not supported"), - error=True) - else: - self._eip_status.disable_eip_start() - self._eip_status.set_eip_status(self.tr("Disabled")) + # we want to start soledad anyway after a certain timeout if eip + # fails to come up + QtCore.QTimer.singleShot( + self.EIP_TIMEOUT, + self._maybe_run_soledad_setup_checks) + else: + if not self._already_started_eip: + if self._enabled_services.count(EIP_SERVICE) > 0: + self._eip_status.set_eip_status( + self.tr("Not supported"), + error=True) + else: + self._eip_status.disable_eip_start() + self._eip_status.set_eip_status(self.tr("Disabled")) + # eip will not start, so we start soledad anyway + self._maybe_run_soledad_setup_checks() def _finish_eip_bootstrap(self, data): """ @@ -1606,6 +1626,9 @@ class MainWindow(QtGui.QMainWindow): self._soledad_bootstrapper.cancel_bootstrap() setProxiedObject(self._soledad, None) + # reset soledad status flag + self._already_started_soledad = False + # XXX: If other defers are doing authenticated stuff, this # might conflict with those. CHECK! threads.deferToThread(self._srp_auth.logout) -- cgit v1.2.3 From 80a77efe595215da3ae836771f9dfc5cffd1cfd3 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 8 Jan 2014 23:33:13 -0400 Subject: Flag imap session for closing. Fixes: #4925 This will need a more serious investigation, since we're not shutting down all the imap-related objects, but will do for now to prevent access to different accounts while a session is initiated in the MUA. (tested with thunderbird). --- src/leap/bitmask/services/mail/conductor.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index addf9bef..875b98ea 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -102,6 +102,7 @@ class IMAPControl(object): # Stop listening on the IMAP port self.imap_port.stopListening() # Stop the protocol + self.imap_factory.theAccount.closed = True self.imap_factory.doStop() def fetch_incoming_mail(self): -- cgit v1.2.3 From 74397ee78ab7f01cb622b0e06b3de901a3604f0b Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 7 Jan 2014 18:25:21 -0300 Subject: Warn the user if is using an old app version. [Closes #4636] --- src/leap/bitmask/backend.py | 11 ++++++++++- src/leap/bitmask/gui/mainwindow.py | 15 +++++++++++++++ src/leap/bitmask/provider/__init__.py | 22 ++++++++++++++++++++++ src/leap/bitmask/provider/providerbootstrapper.py | 17 +++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 8a289a79..705a85be 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -194,6 +194,8 @@ class Signaler(QtCore.QObject): prov_problem_with_provider = QtCore.Signal(object) + prov_unsupported_client = 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 @@ -205,6 +207,7 @@ class Signaler(QtCore.QObject): 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" + PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client" def __init__(self): """ @@ -220,7 +223,8 @@ class Signaler(QtCore.QObject): 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 + self.PROV_PROV_PROBLEM_WITH_PROVIER_KEY, + self.PROV_UNSUPPORTED_CLIENT ] for sig in signals: @@ -243,6 +247,11 @@ class Signaler(QtCore.QObject): # will do zmq.send_multipart, and the frontend version will be # similar to this log.msg("Signaling %s :: %s" % (key, data)) + + # for some reason emitting 'None' gives a segmentation fault. + if data is None: + data = '' + try: self._signals[key].emit(data) except KeyError: diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index f954006d..69cb4169 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -380,6 +380,9 @@ class MainWindow(QtGui.QMainWindow): partial(self._login_widget.set_status, self.tr("Unable to login: Problem with provider"))) + self._backend.signaler.prov_unsupported_client.connect( + self._needs_update) + def _backend_disconnect(self): """ Helper to disconnect from backend signals. @@ -856,6 +859,18 @@ class MainWindow(QtGui.QMainWindow): "More about LEAP" "") % (VERSION, VERSION_HASH[:10], greet)) + def _needs_update(self): + """ + Display a warning dialog to inform the user that the app needs update. + """ + url = "https://dl.bitmask.net/" + msg = self.tr( + "The current client version is not supported " + "by this provider.
" + "Please update to latest version.

" + "You can get the latest version from {0}").format(url) + QtGui.QMessageBox.warning(self, self.tr("Update Needed"), msg) + def changeEvent(self, e): """ Reimplements the changeEvent method to minimize to tray diff --git a/src/leap/bitmask/provider/__init__.py b/src/leap/bitmask/provider/__init__.py index 53587d65..68f3ded0 100644 --- a/src/leap/bitmask/provider/__init__.py +++ b/src/leap/bitmask/provider/__init__.py @@ -18,6 +18,10 @@ Module initialization for leap.bitmask.provider """ import os + +from distutils.version import LooseVersion + +from leap.bitmask import __version__ as BITMASK_VERSION from leap.common.check import leap_assert @@ -32,3 +36,21 @@ def get_provider_path(domain): """ leap_assert(domain is not None, "get_provider_path: We need a domain") return os.path.join("leap", "providers", domain, "provider.json") + + +class SupportedClient(object): + """ + Class responsible of checking for client compatibility. + """ + + @classmethod + def supports(self, minimum_version): + """ + :param minimum_version: the version number of the client that + we need to check. + :type minimum_version: str + + :returns: True if that version is supported or False otherwise. + :return type: bool + """ + return LooseVersion(minimum_version) <= LooseVersion(BITMASK_VERSION) diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index 947ba0c9..695b1593 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -30,6 +30,7 @@ from leap.bitmask import util from leap.bitmask.util.constants import REQUEST_TIMEOUT from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.provider.supportedapis import SupportedAPIs +from leap.bitmask.provider import SupportedClient from leap.common import ca_bundle from leap.common.certs import get_digest from leap.common.files import check_and_fix_urw_only, get_mtime, mkdir_p @@ -45,6 +46,14 @@ class UnsupportedProviderAPI(Exception): pass +class UnsupportedClientVersionError(Exception): + """ + Raised when attempting to use a provider with an older + client than supported. + """ + pass + + class WrongFingerprint(Exception): """ Raised when a fingerprint comparison does not match. @@ -59,6 +68,8 @@ class ProviderBootstrapper(AbstractBootstrapper): If a check fails, the subsequent checks are not executed """ + MIN_CLIENT_VERSION = 'x-minimum-client-version' + def __init__(self, signaler=None, bypass_checks=False): """ Constructor for provider bootstrapper object @@ -187,6 +198,8 @@ class ProviderBootstrapper(AbstractBootstrapper): res.raise_for_status() logger.debug("Request status code: {0}".format(res.status_code)) + min_client_version = res.headers.get(self.MIN_CLIENT_VERSION, '0') + # Not modified if res.status_code == 304: logger.debug("Provider definition has not been modified") @@ -194,6 +207,10 @@ class ProviderBootstrapper(AbstractBootstrapper): # end refactor, more or less... # XXX Watch out, have to check the supported api yet. else: + if not SupportedClient.supports(min_client_version): + self._signaler.signal(self._signaler.PROV_UNSUPPORTED_CLIENT) + raise UnsupportedClientVersionError() + provider_definition, mtime = get_content(res) provider_config = ProviderConfig() -- cgit v1.2.3 From 504936617069b7dcba497ba6daf630769c36d4fd Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 7 Jan 2014 18:47:26 -0300 Subject: Move a provider problem to a separate signal. - Some code cleanup - Fix typos --- src/leap/bitmask/backend.py | 4 +- src/leap/bitmask/gui/mainwindow.py | 50 ++++++++++------------- src/leap/bitmask/provider/providerbootstrapper.py | 2 + 3 files changed, 26 insertions(+), 30 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 705a85be..0dfc6b21 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -206,7 +206,7 @@ class Signaler(QtCore.QObject): 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" + PROV_PROBLEM_WITH_PROVIDER_KEY = "prov_problem_with_provider" PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client" def __init__(self): @@ -223,7 +223,7 @@ class Signaler(QtCore.QObject): 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, + self.PROV_PROBLEM_WITH_PROVIDER_KEY, self.PROV_UNSUPPORTED_CLIENT ] diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 69cb4169..1bbce8d4 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -21,7 +21,6 @@ import logging from PySide import QtCore, QtGui from datetime import datetime -from functools import partial from twisted.internet import threads from zope.proxy import ProxyBase, setProxiedObject @@ -362,26 +361,19 @@ class MainWindow(QtGui.QMainWindow): """ 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) + sig = self._backend.signaler + sig.prov_name_resolution.connect(self._intermediate_stage) + sig.prov_https_connection.connect(self._intermediate_stage) + sig.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) + sig.prov_download_provider_info.connect(self._load_provider_config) + sig.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"))) + sig.prov_problem_with_provider.connect(self._login_problem_provider) - self._backend.signaler.prov_unsupported_client.connect( - self._needs_update) + sig.prov_unsupported_client.connect(self._needs_update) def _backend_disconnect(self): """ @@ -390,17 +382,13 @@ class MainWindow(QtGui.QMainWindow): 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) + sig = self._backend.signaler + sig.prov_name_resolution.disconnect(self._intermediate_stage) + sig.prov_https_connection.disconnect(self._intermediate_stage) + sig.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) + sig.prov_download_provider_info.disconnect(self._load_provider_config) + sig.prov_check_api_certificate.disconnect(self._provider_config_loaded) def _rejected_wizard(self): """ @@ -938,11 +926,17 @@ class MainWindow(QtGui.QMainWindow): 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._backend.ERROR_KEY]) self._login_widget.set_enabled(True) + def _login_problem_provider(self): + """ + Warns the user about a problem with the provider during login. + """ + self._login_widget.set_status( + self.tr("Unable to login: Problem with provider")) + self._login_widget.set_enabled(True) + def _login(self): """ SLOT diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index 695b1593..c02b4a05 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -228,6 +228,8 @@ class ProviderBootstrapper(AbstractBootstrapper): 'Found: {1}.').format(api_supported, api_version) logger.error(error) + self._signaler.signal( + self._signaler.PROV_PROBLEM_WITH_PROVIDER_KEY) raise UnsupportedProviderAPI(error) def run_provider_select_checks(self, domain, download_if_needed=False): -- cgit v1.2.3 From fc0663054c4f76c1103291c334bb60c0dff1280b Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 7 Jan 2014 18:58:06 -0300 Subject: Refactor provider utilities. --- src/leap/bitmask/provider/__init__.py | 37 +++++++++++++-------- src/leap/bitmask/provider/providerbootstrapper.py | 9 +++-- src/leap/bitmask/provider/supportedapis.py | 38 ---------------------- .../provider/tests/test_providerbootstrapper.py | 10 +++--- 4 files changed, 32 insertions(+), 62 deletions(-) delete mode 100644 src/leap/bitmask/provider/supportedapis.py (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/provider/__init__.py b/src/leap/bitmask/provider/__init__.py index 68f3ded0..ca6426f4 100644 --- a/src/leap/bitmask/provider/__init__.py +++ b/src/leap/bitmask/provider/__init__.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ -Module initialization for leap.bitmask.provider +Provider utilities. """ import os @@ -25,6 +25,10 @@ from leap.bitmask import __version__ as BITMASK_VERSION from leap.common.check import leap_assert +# The currently supported API versions by the client. +SUPPORTED_APIS = ["1"] + + def get_provider_path(domain): """ Returns relative path for provider config. @@ -38,19 +42,24 @@ def get_provider_path(domain): return os.path.join("leap", "providers", domain, "provider.json") -class SupportedClient(object): +def supports_api(api_version): """ - Class responsible of checking for client compatibility. + :param api_version: the version number of the api that we need to check + :type api_version: str + + :returns: if that version is supported or not. + :return type: bool """ + return api_version in SUPPORTED_APIS + - @classmethod - def supports(self, minimum_version): - """ - :param minimum_version: the version number of the client that - we need to check. - :type minimum_version: str - - :returns: True if that version is supported or False otherwise. - :return type: bool - """ - return LooseVersion(minimum_version) <= LooseVersion(BITMASK_VERSION) +def supports_client(minimum_version): + """ + :param minimum_version: the version number of the client that + we need to check. + :type minimum_version: str + + :returns: True if that version is supported or False otherwise. + :return type: bool + """ + return LooseVersion(minimum_version) <= LooseVersion(BITMASK_VERSION) diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index c02b4a05..e00dd646 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -29,8 +29,7 @@ from leap.bitmask.util.request_helpers import get_content from leap.bitmask import util from leap.bitmask.util.constants import REQUEST_TIMEOUT from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper -from leap.bitmask.provider.supportedapis import SupportedAPIs -from leap.bitmask.provider import SupportedClient +from leap.bitmask import provider from leap.common import ca_bundle from leap.common.certs import get_digest from leap.common.files import check_and_fix_urw_only, get_mtime, mkdir_p @@ -207,7 +206,7 @@ class ProviderBootstrapper(AbstractBootstrapper): # end refactor, more or less... # XXX Watch out, have to check the supported api yet. else: - if not SupportedClient.supports(min_client_version): + if not provider.supports_client(min_client_version): self._signaler.signal(self._signaler.PROV_UNSUPPORTED_CLIENT) raise UnsupportedClientVersionError() @@ -219,10 +218,10 @@ class ProviderBootstrapper(AbstractBootstrapper): domain, "provider.json"]) api_version = provider_config.get_api_version() - if SupportedAPIs.supports(api_version): + if provider.supports_api(api_version): logger.debug("Provider definition has been modified") else: - api_supported = ', '.join(SupportedAPIs.SUPPORTED_APIS) + api_supported = ', '.join(provider.SUPPORTED_APIS) error = ('Unsupported provider API version. ' 'Supported versions are: {0}. ' 'Found: {1}.').format(api_supported, api_version) diff --git a/src/leap/bitmask/provider/supportedapis.py b/src/leap/bitmask/provider/supportedapis.py deleted file mode 100644 index 3e650ba2..00000000 --- a/src/leap/bitmask/provider/supportedapis.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -# supportedapis.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 . - -""" -API Support check. -""" - - -class SupportedAPIs(object): - """ - Class responsible of checking for API compatibility. - """ - SUPPORTED_APIS = ["1"] - - @classmethod - def supports(self, api_version): - """ - :param api_version: the version number of the api that we need to check - :type api_version: str - - :returns: if that version is supported or not. - :return type: bool - """ - return api_version in self.SUPPORTED_APIS diff --git a/src/leap/bitmask/provider/tests/test_providerbootstrapper.py b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py index d8336fec..6cf3e469 100644 --- a/src/leap/bitmask/provider/tests/test_providerbootstrapper.py +++ b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py @@ -36,17 +36,17 @@ from nose.twistedtools import deferred, reactor from twisted.internet import threads from requests.models import Response +from leap.bitmask import provider +from leap.bitmask import util +from leap.bitmask.backend import Signaler from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.tests import fake_provider from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper from leap.bitmask.provider.providerbootstrapper import UnsupportedProviderAPI from leap.bitmask.provider.providerbootstrapper import WrongFingerprint -from leap.bitmask.provider.supportedapis import SupportedAPIs -from leap.bitmask.backend import Signaler -from leap.bitmask import util from leap.common.files import mkdir_p -from leap.common.testing.https_server import where from leap.common.testing.basetest import BaseLeapTest +from leap.common.testing.https_server import where class ProviderBootstrapperTest(BaseLeapTest): @@ -489,7 +489,7 @@ class ProviderBootstrapperActiveTest(unittest.TestCase): 'leap.bitmask.config.providerconfig.ProviderConfig.get_ca_cert_path', lambda x: where('cacert.pem')) def test_download_provider_info_unsupported_api(self): - self._setup_provider_config_with(SupportedAPIs.SUPPORTED_APIS[0], + self._setup_provider_config_with(provider.SUPPORTED_APIS[0], tempfile.mkdtemp()) self._setup_providerbootstrapper(False) self._produce_dummy_provider_json() -- cgit v1.2.3 From f4a0747e216fb651628490f7849d2e4c9c6d8092 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 7 Jan 2014 19:06:49 -0300 Subject: Warn the user on incompatible api error. - Add a proper signal for the incompatible api error. - Warn the user of an incompatible api. --- src/leap/bitmask/backend.py | 5 ++++- src/leap/bitmask/gui/mainwindow.py | 11 +++++++++++ src/leap/bitmask/provider/providerbootstrapper.py | 3 +-- 3 files changed, 16 insertions(+), 3 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 0dfc6b21..6b29d4b3 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -195,6 +195,7 @@ class Signaler(QtCore.QObject): prov_problem_with_provider = QtCore.Signal(object) prov_unsupported_client = QtCore.Signal(object) + prov_unsupported_api = QtCore.Signal(object) # These will exist both in the backend and the front end. # The frontend might choose to not "interpret" all the signals @@ -208,6 +209,7 @@ class Signaler(QtCore.QObject): PROV_CHECK_API_CERTIFICATE_KEY = "prov_check_api_certificate" PROV_PROBLEM_WITH_PROVIDER_KEY = "prov_problem_with_provider" PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client" + PROV_UNSUPPORTED_API = "prov_unsupported_api" def __init__(self): """ @@ -224,7 +226,8 @@ class Signaler(QtCore.QObject): self.PROV_CHECK_CA_FINGERPRINT_KEY, self.PROV_CHECK_API_CERTIFICATE_KEY, self.PROV_PROBLEM_WITH_PROVIDER_KEY, - self.PROV_UNSUPPORTED_CLIENT + self.PROV_UNSUPPORTED_CLIENT, + self.PROV_UNSUPPORTED_API ] for sig in signals: diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 1bbce8d4..8c512ad2 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -374,6 +374,7 @@ class MainWindow(QtGui.QMainWindow): sig.prov_problem_with_provider.connect(self._login_problem_provider) sig.prov_unsupported_client.connect(self._needs_update) + sig.prov_unsupported_api.connect(self._incompatible_api) def _backend_disconnect(self): """ @@ -859,6 +860,16 @@ class MainWindow(QtGui.QMainWindow): "You can get the latest version from {0}").format(url) QtGui.QMessageBox.warning(self, self.tr("Update Needed"), msg) + def _incompatible_api(self): + """ + Display a warning dialog to inform the user that the provider has an + incompatible API. + """ + msg = self.tr( + "This provider is not compatible with the client.

" + "Error: API version incompatible.") + QtGui.QMessageBox.warning(self, self.tr("Incompatible Provider"), msg) + def changeEvent(self, e): """ Reimplements the changeEvent method to minimize to tray diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index e00dd646..52ebacd2 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -227,8 +227,7 @@ class ProviderBootstrapper(AbstractBootstrapper): 'Found: {1}.').format(api_supported, api_version) logger.error(error) - self._signaler.signal( - self._signaler.PROV_PROBLEM_WITH_PROVIDER_KEY) + self._signaler.signal(self._signaler.PROV_UNSUPPORTED_API) raise UnsupportedProviderAPI(error) def run_provider_select_checks(self, domain, download_if_needed=False): -- cgit v1.2.3 From caeb3f95236005ffd1260394688344459208e9a6 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 8 Jan 2014 13:35:35 -0300 Subject: Replace version check with a better one. --- src/leap/bitmask/provider/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/provider/__init__.py b/src/leap/bitmask/provider/__init__.py index ca6426f4..87f9a8b7 100644 --- a/src/leap/bitmask/provider/__init__.py +++ b/src/leap/bitmask/provider/__init__.py @@ -19,7 +19,7 @@ Provider utilities. """ import os -from distutils.version import LooseVersion +from pkg_resources import parse_version from leap.bitmask import __version__ as BITMASK_VERSION from leap.common.check import leap_assert @@ -62,4 +62,4 @@ def supports_client(minimum_version): :returns: True if that version is supported or False otherwise. :return type: bool """ - return LooseVersion(minimum_version) <= LooseVersion(BITMASK_VERSION) + return parse_version(minimum_version) <= parse_version(BITMASK_VERSION) -- cgit v1.2.3 From c9644f1bdbd26e3c54d8849433d8cfb9fc05a3e7 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 8 Jan 2014 18:22:21 -0300 Subject: Use short version since we don't care of suffix. This way we support client '0.5.0-rc3' if the provider supports '0.5.0'. --- src/leap/bitmask/provider/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/provider/__init__.py b/src/leap/bitmask/provider/__init__.py index 87f9a8b7..89ff5d95 100644 --- a/src/leap/bitmask/provider/__init__.py +++ b/src/leap/bitmask/provider/__init__.py @@ -21,7 +21,7 @@ import os from pkg_resources import parse_version -from leap.bitmask import __version__ as BITMASK_VERSION +from leap.bitmask import __short_version__ as BITMASK_VERSION from leap.common.check import leap_assert -- cgit v1.2.3 From 785380dfa7b3f6205b52be2a90afa9b0afa04c5e Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 9 Jan 2014 14:24:33 -0300 Subject: Add flag to disable version check. --- src/leap/bitmask/app.py | 3 ++ src/leap/bitmask/config/flags.py | 9 ++++++ src/leap/bitmask/provider/providerbootstrapper.py | 34 +++++++++++++---------- src/leap/bitmask/util/leap_argparse.py | 8 ++++++ 4 files changed, 39 insertions(+), 15 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index b16a51aa..d50743d6 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -200,6 +200,9 @@ def main(): from leap.common.config.baseconfig import BaseConfig flags.STANDALONE = standalone flags.MAIL_LOGFILE = mail_logfile + flags.APP_VERSION_CHECK = opts.app_version_check + flags.API_VERSION_CHECK = opts.api_version_check + BaseConfig.standalone = standalone logger = add_logger_handlers(debug, logfile) diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py index ba1b65b9..b1576c32 100644 --- a/src/leap/bitmask/config/flags.py +++ b/src/leap/bitmask/config/flags.py @@ -32,3 +32,12 @@ WARNING: You should NOT use this kind of flags unless you're sure of what STANDALONE = False MAIL_LOGFILE = None + +# The APP/API version check flags are used to provide a way to skip +# that checks. +# This can be used for: +# - allow the use of a client that is not compatible with a provider. +# - use a development version of the client with an older version number +# since it's not released yet, and it is compatible with a newer provider. +APP_VERSION_CHECK = True +API_VERSION_CHECK = True diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index 52ebacd2..531d255e 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -24,6 +24,7 @@ import sys import requests +from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig, MissingCACert from leap.bitmask.util.request_helpers import get_content from leap.bitmask import util @@ -206,9 +207,11 @@ class ProviderBootstrapper(AbstractBootstrapper): # end refactor, more or less... # XXX Watch out, have to check the supported api yet. else: - if not provider.supports_client(min_client_version): - self._signaler.signal(self._signaler.PROV_UNSUPPORTED_CLIENT) - raise UnsupportedClientVersionError() + if flags.APP_VERSION_CHECK: + if not provider.supports_client(min_client_version): + self._signaler.signal( + self._signaler.PROV_UNSUPPORTED_CLIENT) + raise UnsupportedClientVersionError() provider_definition, mtime = get_content(res) @@ -217,18 +220,19 @@ class ProviderBootstrapper(AbstractBootstrapper): provider_config.save(["leap", "providers", domain, "provider.json"]) - api_version = provider_config.get_api_version() - if provider.supports_api(api_version): - logger.debug("Provider definition has been modified") - else: - api_supported = ', '.join(provider.SUPPORTED_APIS) - error = ('Unsupported provider API version. ' - 'Supported versions are: {0}. ' - 'Found: {1}.').format(api_supported, api_version) - - logger.error(error) - self._signaler.signal(self._signaler.PROV_UNSUPPORTED_API) - raise UnsupportedProviderAPI(error) + if flags.API_VERSION_CHECK: + api_version = provider_config.get_api_version() + if provider.supports_api(api_version): + logger.debug("Provider definition has been modified") + else: + api_supported = ', '.join(provider.SUPPORTED_APIS) + error = ('Unsupported provider API version. ' + 'Supported versions are: {0}. ' + 'Found: {1}.').format(api_supported, api_version) + + logger.error(error) + self._signaler.signal(self._signaler.PROV_UNSUPPORTED_API) + raise UnsupportedProviderAPI(error) def run_provider_select_checks(self, domain, download_if_needed=False): """ diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index 6703b600..280573f1 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -62,6 +62,14 @@ Launches Bitmask""", epilog=epilog) help='Repair mailboxes for a given account. ' 'Use when upgrading versions after a schema ' 'change.') + parser.add_argument('-N', '--no-app-version-check', default=True, + action="store_false", dest="app_version_check", + help='Skip the app version compatibility check with ' + 'the provider.') + parser.add_argument('-M', '--no-api-version-check', default=True, + action="store_false", dest="api_version_check", + help='Skip the api version compatibility check with ' + 'the provider.') # Not in use, we might want to reintroduce them. #parser.add_argument('-i', '--no-provider-checks', -- cgit v1.2.3 From 2bd96c2f0a3c5b6d4a80fe450dfe84bf524ca722 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 9 Jan 2014 19:01:00 -0400 Subject: add --offline flag and tidy up arguments by sections --- src/leap/bitmask/util/leap_argparse.py | 63 ++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 26 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index 280573f1..fb92f141 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -27,41 +27,29 @@ def build_parser(): All the options for the leap arg parser Some of these could be switched on only if debug flag is present! """ - epilog = "Copyright 2012-2013 The LEAP Encryption Access Project" + epilog = "Copyright 2012-2014 The LEAP Encryption Access Project" parser = argparse.ArgumentParser(description=""" -Launches Bitmask""", epilog=epilog) +Launches the Bitmask client.""", epilog=epilog) parser.add_argument('-d', '--debug', action="store_true", - help=("Launches Bitmask in debug mode, writing debug" - "info to stdout")) - if not IS_RELEASE_VERSION: - help_text = "Bypasses the certificate check for bootstrap" - parser.add_argument('--danger', action="store_true", help=help_text) + help=("Launches Bitmask in debug mode, writing debug " + "info to stdout.")) + parser.add_argument('-V', '--version', action="store_true", + help='Displays Bitmask version and exits.') + # files parser.add_argument('-l', '--logfile', metavar="LOG FILE", nargs='?', action="store", dest="log_file", - #type=argparse.FileType('w'), - help='optional log file') + 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", - help='verbosity level for openvpn logs [1-6]') + help='Optional log file for email.') + + # flags parser.add_argument('-s', '--standalone', action="store_true", - help='Makes Bitmask use standalone' - 'directories for configuration and binary' - '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.') + help='Makes Bitmask use standalone ' + 'directories for configuration and binary ' + 'searching.') parser.add_argument('-N', '--no-app-version-check', default=True, action="store_false", dest="app_version_check", help='Skip the app version compatibility check with ' @@ -71,6 +59,29 @@ Launches Bitmask""", epilog=epilog) help='Skip the api version compatibility check with ' 'the provider.') + # openvpn options + parser.add_argument('--openvpn-verbosity', nargs='?', + type=int, + action="store", dest="openvpn_verb", + help='Verbosity level for openvpn logs [1-6]') + + # mail stuff + parser.add_argument('-o', '--offline', action="store_true", + help='Starts Bitmask in offline mode: will not ' + 'try to sync with remote replicas for email.') + 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.') + + if not IS_RELEASE_VERSION: + help_text = ("Bypasses the certificate check during provider " + "bootstraping, for debugging development servers. " + "Use at your own risk!") + parser.add_argument('--danger', action="store_true", help=help_text) + # Not in use, we might want to reintroduce them. #parser.add_argument('-i', '--no-provider-checks', #action="store_true", default=False, -- cgit v1.2.3 From cbdda58f1e5f74f37489f3b4b67616bd19d6715d Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 9 Jan 2014 19:06:44 -0400 Subject: add offline flag --- src/leap/bitmask/app.py | 5 ++++- src/leap/bitmask/config/flags.py | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index d50743d6..e8423fd5 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # app.py -# Copyright (C) 2013 LEAP +# Copyright (C) 2013, 2014 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 @@ -168,6 +168,7 @@ def main(): """ Starts the main event loop and launches the main window. """ + # TODO move boilerplate outa here! _, opts = leap_argparse.init_leapc_args() if opts.version: @@ -180,6 +181,7 @@ def main(): sys.exit(0) standalone = opts.standalone + offline = opts.offline bypass_checks = getattr(opts, 'danger', False) debug = opts.debug logfile = opts.log_file @@ -199,6 +201,7 @@ def main(): from leap.bitmask.config import flags from leap.common.config.baseconfig import BaseConfig flags.STANDALONE = standalone + flags.OFFLINE = offline flags.MAIL_LOGFILE = mail_logfile flags.APP_VERSION_CHECK = opts.app_version_check flags.API_VERSION_CHECK = opts.api_version_check diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py index b1576c32..82501fb2 100644 --- a/src/leap/bitmask/config/flags.py +++ b/src/leap/bitmask/config/flags.py @@ -41,3 +41,7 @@ MAIL_LOGFILE = None # since it's not released yet, and it is compatible with a newer provider. APP_VERSION_CHECK = True API_VERSION_CHECK = True + +# Offline mode? +# Used for skipping soledad bootstrapping/syncs. +OFFLINE = False -- cgit v1.2.3 From a1db341a39ec336ab62e89280f9bfb315420bfb5 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sat, 11 Jan 2014 23:10:09 -0400 Subject: offline mode This will skip: * srp authentication with server * remote soledad configuration * keymanager sending key to server * imap fetches. Its main goal is to help us while debugging imap accounts, by cutting almost all communication with server. It will break havoc if you use it without having local keys configured. So, basically, use with care. --- src/leap/bitmask/config/leapsettings.py | 34 ++- src/leap/bitmask/crypto/srpauth.py | 41 ++-- src/leap/bitmask/crypto/tests/test_srpauth.py | 8 +- src/leap/bitmask/gui/login.py | 24 +- src/leap/bitmask/gui/mainwindow.py | 134 ++++++++--- src/leap/bitmask/provider/providerbootstrapper.py | 2 + src/leap/bitmask/services/mail/conductor.py | 13 +- src/leap/bitmask/services/mail/repair.py | 14 +- .../services/soledad/soledadbootstrapper.py | 252 ++++++++++++++++----- src/leap/bitmask/util/__init__.py | 13 ++ 10 files changed, 410 insertions(+), 125 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index c524425e..91ff83a8 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # leapsettings.py -# Copyright (C) 2013 LEAP +# Copyright (C) 2013, 2014 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 @@ -14,9 +14,8 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - """ -QSettings abstraction +QSettings abstraction. """ import os import logging @@ -70,6 +69,7 @@ class LeapSettings(object): GATEWAY_KEY = "Gateway" PINNED_KEY = "Pinned" SKIPFIRSTRUN_KEY = "SkipFirstRun" + UUIDFORUSER_KEY = "%s/%s_uuid" # values GATEWAY_AUTOMATIC = "Automatic" @@ -353,3 +353,31 @@ class LeapSettings(object): """ leap_assert_type(skip, bool) self._settings.setValue(self.SKIPFIRSTRUN_KEY, skip) + + def get_uuid(self, username): + """ + Gets the uuid for a given username. + + :param username: the full user identifier in the form user@provider + :type username: basestring + """ + leap_assert("@" in username, + "Expected username in the form user@provider") + user, provider = username.split('@') + return self._settings.value( + self.UUIDFORUSER_KEY % (provider, user), "") + + def set_uuid(self, username, value): + """ + Sets the uuid for a given username. + + :param username: the full user identifier in the form user@provider + :type username: basestring + :param value: the uuid to save + :type value: basestring + """ + leap_assert("@" in username, + "Expected username in the form user@provider") + user, provider = username.split('@') + leap_assert(len(value) > 0, "We cannot save an empty uuid") + self._settings.setValue(self.UUIDFORUSER_KEY % (provider, user), value) diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 85b9b003..bdd38db2 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -31,6 +31,7 @@ from requests.adapters import HTTPAdapter from PySide import QtCore from twisted.internet import threads +from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.util import request_helpers as reqhelper from leap.bitmask.util.compat import requests_has_max_retries from leap.bitmask.util.constants import REQUEST_TIMEOUT @@ -147,6 +148,7 @@ class SRPAuth(QtCore.QObject): "We need a provider config to authenticate") self._provider_config = provider_config + self._settings = LeapSettings() # **************************************************** # # Dependency injection helpers, override this for more @@ -161,8 +163,8 @@ class SRPAuth(QtCore.QObject): self._session_id = None self._session_id_lock = QtCore.QMutex() - self._uid = None - self._uid_lock = QtCore.QMutex() + self._uuid = None + self._uuid_lock = QtCore.QMutex() self._token = None self._token_lock = QtCore.QMutex() @@ -394,24 +396,24 @@ class SRPAuth(QtCore.QObject): """ try: M2 = json_content.get("M2", None) - uid = json_content.get("id", None) + uuid = json_content.get("id", None) token = json_content.get("token", None) except Exception as e: logger.error(e) raise SRPAuthBadDataFromServer("Something went wrong with the " "login") - self.set_uid(uid) + self.set_uuid(uuid) self.set_token(token) - if M2 is None or self.get_uid() is None: + if M2 is None or self.get_uuid() is None: logger.error("Something went wrong. Content = %r" % (json_content,)) raise SRPAuthBadDataFromServer(self.tr("Problem getting data " "from server")) events_signal( - proto.CLIENT_UID, content=uid, + proto.CLIENT_UID, content=uuid, reqcbk=lambda req, res: None) # make the rpc call async return M2 @@ -475,7 +477,7 @@ class SRPAuth(QtCore.QObject): :param new_password: the new password for the user :type new_password: str """ - leap_assert(self.get_uid() is not None) + leap_assert(self.get_uuid() is not None) if current_password != self._password: raise SRPAuthBadUserOrPassword @@ -483,7 +485,7 @@ class SRPAuth(QtCore.QObject): url = "%s/%s/users/%s.json" % ( self._provider_config.get_api_uri(), self._provider_config.get_api_version(), - self.get_uid()) + self.get_uuid()) salt, verifier = self._srp.create_salted_verification_key( self._username.encode('utf-8'), new_password.encode('utf-8'), @@ -580,7 +582,7 @@ class SRPAuth(QtCore.QObject): raise else: self.set_session_id(None) - self.set_uid(None) + self.set_uuid(None) self.set_token(None) # Also reset the session self._session = self._fetcher.session() @@ -594,13 +596,16 @@ class SRPAuth(QtCore.QObject): QtCore.QMutexLocker(self._session_id_lock) return self._session_id - def set_uid(self, uid): - QtCore.QMutexLocker(self._uid_lock) - self._uid = uid + def set_uuid(self, uuid): + QtCore.QMutexLocker(self._uuid_lock) + full_uid = "%s@%s" % ( + self._username, self._provider_config.get_domain()) + self._settings.set_uuid(full_uid, uuid) + self._uuid = uuid - def get_uid(self): - QtCore.QMutexLocker(self._uid_lock) - return self._uid + def get_uuid(self): + QtCore.QMutexLocker(self._uuid_lock) + return self._uuid def set_token(self, token): QtCore.QMutexLocker(self._token_lock) @@ -676,7 +681,7 @@ class SRPAuth(QtCore.QObject): :rtype: str or None """ - if self.get_uid() is None: + if self.get_uuid() is None: return None return self.__instance._username @@ -705,8 +710,8 @@ class SRPAuth(QtCore.QObject): def get_session_id(self): return self.__instance.get_session_id() - def get_uid(self): - return self.__instance.get_uid() + def get_uuid(self): + return self.__instance.get_uuid() def get_token(self): return self.__instance.get_token() diff --git a/src/leap/bitmask/crypto/tests/test_srpauth.py b/src/leap/bitmask/crypto/tests/test_srpauth.py index e63c1385..511a12ed 100644 --- a/src/leap/bitmask/crypto/tests/test_srpauth.py +++ b/src/leap/bitmask/crypto/tests/test_srpauth.py @@ -520,9 +520,9 @@ class SRPAuthTestCase(unittest.TestCase): m2 = self.auth_backend._extract_data(test_data) self.assertEqual(m2, test_m2) - self.assertEqual(self.auth_backend.get_uid(), test_uid) - self.assertEqual(self.auth_backend.get_uid(), - self.auth.get_uid()) + self.assertEqual(self.auth_backend.get_uuid(), test_uid) + self.assertEqual(self.auth_backend.get_uuid(), + self.auth.get_uuid()) self.assertEqual(self.auth_backend.get_token(), test_token) self.assertEqual(self.auth_backend.get_token(), self.auth.get_token()) @@ -691,7 +691,7 @@ class SRPAuthTestCase(unittest.TestCase): old_session = self.auth_backend._session self.auth_backend.logout() self.assertIsNone(self.auth_backend.get_session_id()) - self.assertIsNone(self.auth_backend.get_uid()) + self.assertIsNone(self.auth_backend.get_uuid()) self.assertNotEqual(old_session, self.auth_backend._session) d = threads.deferToThread(wrapper) diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index b21057f0..d0cb20b1 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -19,12 +19,13 @@ Login widget implementation """ import logging -import keyring - from PySide import QtCore, QtGui from ui_login import Ui_LoginWidget +from leap.bitmask.config import flags +from leap.bitmask.util import make_address from leap.bitmask.util.keyring_helpers import has_keyring +from leap.bitmask.util.keyring_helpers import get_keyring from leap.common.check import leap_assert_type logger = logging.getLogger(__name__) @@ -304,14 +305,15 @@ class LoginWidget(QtGui.QWidget): if self.get_remember() and has_keyring(): # in the keyring and in the settings # we store the value 'usename@provider' - username_domain = (username + '@' + provider).encode("utf8") + full_user_id = make_address(username, provider).encode("utf8") try: + keyring = get_keyring() keyring.set_password(self.KEYRING_KEY, - username_domain, + full_user_id, password.encode("utf8")) # Only save the username if it was saved correctly in # the keyring - self._settings.set_user(username_domain) + self._settings.set_user(full_user_id) except Exception as e: logger.exception("Problem saving data to keyring. %r" % (e,)) @@ -323,15 +325,20 @@ class LoginWidget(QtGui.QWidget): """ self.ui.login_widget.hide() self.ui.logged_widget.show() - self.ui.lblUser.setText("%s@%s" % (self.get_user(), - self.get_selected_provider())) + self.ui.lblUser.setText(make_address( + self.get_user(), self.get_selected_provider())) self.set_login_status("") - self.logged_in_signal.emit() + + if flags.OFFLINE is False: + self.logged_in_signal.emit() def logged_out(self): """ Sets the widgets to the logged out state """ + # TODO consider "logging out offline" too... + # how that would be ??? + self.ui.login_widget.show() self.ui.logged_widget.hide() @@ -396,6 +403,7 @@ class LoginWidget(QtGui.QWidget): saved_password = None try: + keyring = get_keyring() saved_password = keyring.get_password(self.KEYRING_KEY, saved_user .encode("utf8")) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 8c512ad2..18ef56e5 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # mainwindow.py -# Copyright (C) 2013 LEAP +# Copyright (C) 2013, 2014 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 @@ -26,6 +26,7 @@ from zope.proxy import ProxyBase, setProxiedObject from leap.bitmask import __version__ as VERSION from leap.bitmask import __version_hash__ as VERSION_HASH +from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpauth import SRPAuth @@ -68,6 +69,7 @@ from leap.bitmask.services.eip.darwinvpnlauncher import EIPNoTunKextLoaded from leap.bitmask.services.soledad.soledadbootstrapper import \ SoledadBootstrapper +from leap.bitmask.util import make_address from leap.bitmask.util.keyring_helpers import has_keyring from leap.bitmask.util.leap_log_handler import LeapLogHandler @@ -95,6 +97,7 @@ class MainWindow(QtGui.QMainWindow): # Signals eip_needs_login = QtCore.Signal([]) + offline_mode_bypass_login = QtCore.Signal([]) new_updates = QtCore.Signal(object) raise_window = QtCore.Signal([]) soledad_ready = QtCore.Signal([]) @@ -137,12 +140,11 @@ class MainWindow(QtGui.QMainWindow): # end register leap events #################################### self._quit_callback = quit_callback - self._updates_content = "" + # setup UI self.ui = Ui_MainWindow() self.ui.setupUi(self) - self._backend = backend.Backend(bypass_checks) self._backend.start() @@ -183,6 +185,9 @@ class MainWindow(QtGui.QMainWindow): self._on_eip_connected) self._eip_status.eip_connection_connected.connect( self._maybe_run_soledad_setup_checks) + self.offline_mode_bypass_login.connect( + self._maybe_run_soledad_setup_checks) + self.eip_needs_login.connect( self._eip_status.disable_eip_start) self.eip_needs_login.connect( @@ -204,6 +209,7 @@ class MainWindow(QtGui.QMainWindow): # This is created once we have a valid provider config self._srp_auth = None self._logged_user = None + self._logged_in_offline = False self._backend_connect() @@ -239,6 +245,8 @@ class MainWindow(QtGui.QMainWindow): self._soledad_intermediate_stage) self._soledad_bootstrapper.gen_key.connect( self._soledad_bootstrapped_stage) + self._soledad_bootstrapper.local_only_ready.connect( + self._soledad_bootstrapped_stage) self._soledad_bootstrapper.soledad_timeout.connect( self._retry_soledad_connection) self._soledad_bootstrapper.soledad_failed.connect( @@ -284,8 +292,9 @@ class MainWindow(QtGui.QMainWindow): self._enabled_services = [] - self._center_window() + # last minute UI manipulations + self._center_window() self.ui.lblNewUpdates.setVisible(False) self.ui.btnMore.setVisible(False) ######################################### @@ -294,6 +303,8 @@ class MainWindow(QtGui.QMainWindow): self.ui.btnMore.resize(0, 0) ######################################### self.ui.btnMore.clicked.connect(self._updates_details) + if flags.OFFLINE is True: + self._set_label_offline() # Services signals/slots connection self.new_updates.connect(self._react_to_new_updates) @@ -706,6 +717,19 @@ class MainWindow(QtGui.QMainWindow): self.ui.eipWidget.setVisible(EIP_SERVICE in services) self.ui.mailWidget.setVisible(MX_SERVICE in services) + def _set_label_offline(self): + """ + Set the login label to reflect offline status. + """ + if self._logged_in_offline: + provider = "" + else: + provider = self.ui.lblLoginProvider.text() + + self.ui.lblLoginProvider.setText( + provider + + self.tr(" (offline mode)")) + # # systray # @@ -917,7 +941,6 @@ class MainWindow(QtGui.QMainWindow): """ # XXX should rename this provider, name clash. provider = self._login_widget.get_selected_provider() - self._backend.setup_provider(provider) def _load_provider_config(self, data): @@ -930,7 +953,7 @@ class MainWindow(QtGui.QMainWindow): part of the bootstrapping sequence :param data: result from the last stage of the - run_provider_select_checks + run_provider_select_checks :type data: dict """ if data[self._backend.PASSED_KEY]: @@ -959,10 +982,21 @@ class MainWindow(QtGui.QMainWindow): start the SRP authentication, and as the last step bootstrapping the EIP service """ - leap_assert(self._provider_config, "We need a provider config") - - if self._login_widget.start_login(): - self._download_provider_config() + # TODO most of this could ve handled by the login widget, + # but we'd have to move lblLoginProvider into the widget itself, + # instead of having it as a top-level attribute. + if flags.OFFLINE is True: + logger.debug("OFFLINE mode! bypassing remote login") + # TODO reminder, we're not handling logout for offline + # mode. + self._login_widget.logged_in() + self._logged_in_offline = True + self._set_label_offline() + self.offline_mode_bypass_login.emit() + else: + leap_assert(self._provider_config, "We need a provider config") + if self._login_widget.start_login(): + self._download_provider_config() def _cancel_login(self): """ @@ -1033,8 +1067,8 @@ class MainWindow(QtGui.QMainWindow): self._logged_user = self._login_widget.get_user() user = self._logged_user domain = self._provider_config.get_domain() - userid = "%s@%s" % (user, domain) - self._mail_conductor.userid = userid + full_user_id = make_address(user, domain) + self._mail_conductor.userid = full_user_id self._login_defer = None self._start_eip_bootstrap() else: @@ -1047,7 +1081,8 @@ class MainWindow(QtGui.QMainWindow): """ self._login_widget.logged_in() - self.ui.lblLoginProvider.setText(self._provider_config.get_domain()) + provider = self._provider_config.get_domain() + self.ui.lblLoginProvider.setText(provider) self._enabled_services = self._settings.get_enabled_services( self._provider_config.get_domain()) @@ -1063,17 +1098,42 @@ class MainWindow(QtGui.QMainWindow): def _maybe_run_soledad_setup_checks(self): """ + Conditionally start Soledad. """ - # TODO soledad should check if we want to run only over EIP. - if self._already_started_soledad is False \ - and self._logged_user is not None: - self._already_started_soledad = True - self._soledad_bootstrapper.run_soledad_setup_checks( - self._provider_config, - self._login_widget.get_user(), - self._login_widget.get_password(), - download_if_needed=True) + # TODO split. + if self._already_started_soledad is True: + return + + username = self._login_widget.get_user() + password = unicode(self._login_widget.get_password()) + provider_domain = self._login_widget.get_selected_provider() + + sb = self._soledad_bootstrapper + if flags.OFFLINE is True: + provider_domain = self._login_widget.get_selected_provider() + sb._password = password + + self._provisional_provider_config.load( + provider.get_provider_path(provider_domain)) + + full_user_id = make_address(username, provider_domain) + uuid = self._settings.get_uuid(full_user_id) + self._mail_conductor.userid = full_user_id + + if uuid is None: + # We don't need more visibility at the moment, + # this is mostly for internal use/debug for now. + logger.warning("Sorry! Log-in at least one time.") + return + fun = sb.load_offline_soledad + fun(full_user_id, password, uuid) + else: + provider_config = self._provider_config + if self._logged_user is not None: + fun = sb.run_soledad_setup_checks + fun(provider_config, username, password, + download_if_needed=True) ################################################################### # Service control methods: soledad @@ -1119,6 +1179,7 @@ class MainWindow(QtGui.QMainWindow): SLOT TRIGGERS: self._soledad_bootstrapper.gen_key + self._soledad_bootstrapper.local_only_ready If there was a problem, displays it, otherwise it does nothing. This is used for intermediate bootstrapping stages, in case @@ -1160,6 +1221,10 @@ class MainWindow(QtGui.QMainWindow): TRIGGERS: self.soledad_ready """ + if flags.OFFLINE is True: + logger.debug("not starting smtp in offline mode") + return + # TODO for simmetry, this should be called start_smtp_service # (and delegate all the checks to the conductor) if self._provider_config.provides_mx() and \ @@ -1191,9 +1256,24 @@ class MainWindow(QtGui.QMainWindow): TRIGGERS: self.soledad_ready """ + # TODO in the OFFLINE mode we should also modify the rules + # in the mail state machine so it shows that imap is active + # (but not smtp since it's not yet ready for offline use) + start_fun = self._mail_conductor.start_imap_service + if flags.OFFLINE is True: + provider_domain = self._login_widget.get_selected_provider() + self._provider_config.load( + provider.get_provider_path(provider_domain)) + provides_mx = self._provider_config.provides_mx() + + if flags.OFFLINE is True and provides_mx: + start_fun() + return + + enabled_services = self._enabled_services if self._provider_config.provides_mx() and \ - self._enabled_services.count(MX_SERVICE) > 0: - self._mail_conductor.start_imap_service() + enabled_services.count(MX_SERVICE) > 0: + start_fun() def _on_mail_client_logged_in(self, req): """ @@ -1423,8 +1503,9 @@ class MainWindow(QtGui.QMainWindow): if self._logged_user: self._eip_status.set_provider( - "%s@%s" % (self._logged_user, - self._get_best_provider_config().get_domain())) + make_address( + self._logged_user, + self._get_best_provider_config().get_domain())) self._eip_status.eip_stopped() @QtCore.Slot() @@ -1643,7 +1724,6 @@ class MainWindow(QtGui.QMainWindow): Starts the logout sequence """ - self._soledad_bootstrapper.cancel_bootstrap() setProxiedObject(self._soledad, None) diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index 531d255e..2a66b78c 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -208,6 +208,7 @@ class ProviderBootstrapper(AbstractBootstrapper): # XXX Watch out, have to check the supported api yet. else: if flags.APP_VERSION_CHECK: + # TODO split if not provider.supports_client(min_client_version): self._signaler.signal( self._signaler.PROV_UNSUPPORTED_CLIENT) @@ -221,6 +222,7 @@ class ProviderBootstrapper(AbstractBootstrapper): domain, "provider.json"]) if flags.API_VERSION_CHECK: + # TODO split api_version = provider_config.get_api_version() if provider.supports_api(api_version): logger.debug("Provider definition has been modified") diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index addf9bef..c16801d4 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -72,6 +72,8 @@ class IMAPControl(object): """ Starts imap service. """ + from leap.bitmask.config import flags + logger.debug('Starting imap service') leap_assert(sameProxiedObjects(self._soledad, None) is not True, @@ -81,12 +83,17 @@ class IMAPControl(object): "We need a non-null keymanager for initializing imap " "service") + offline = flags.OFFLINE self.imap_service, self.imap_port, \ self.imap_factory = imap.start_imap_service( self._soledad, self._keymanager, - userid=self.userid) - self.imap_service.start_loop() + userid=self.userid, + offline=offline) + + if offline is False: + logger.debug("Starting loop") + self.imap_service.start_loop() def stop_imap_service(self): """ @@ -339,7 +346,7 @@ class MailConductor(IMAPControl, SMTPControl): self._mail_machine = None self._mail_connection = mail_connection.MailConnection() - self.userid = None + self._userid = None @property def userid(self): diff --git a/src/leap/bitmask/services/mail/repair.py b/src/leap/bitmask/services/mail/repair.py index 30571adf..660e9f11 100644 --- a/src/leap/bitmask/services/mail/repair.py +++ b/src/leap/bitmask/services/mail/repair.py @@ -131,16 +131,20 @@ class MBOXPlumber(object): 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!" + + # XXX this won't be needed anymore after we keep a local + # cache of user uuids, so we'll be able to pick it from + # there. + self.uuid = self.srp.get_uuid() + if not self.uuid: + print "Got BAD UUID from provider!" return self.exit() - print "UID: %s" % (self.uid) + print "UUID: %s" % (self.uuid) secrets, localdb = get_db_paths(self.uid) self.sol = initialize_soledad( - self.uid, self.userid, self.passwd, + self.uuid, self.userid, self.passwd, secrets, localdb, "/tmp", "/tmp") self.acct = SoledadBackedAccount(self.userid, self.sol) diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 3ab62b2e..5351bcd2 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -28,15 +28,13 @@ from PySide import QtCore from u1db import errors as u1db_errors from zope.proxy import sameProxiedObjects -from twisted.internet.threads import deferToThread - from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.services import download_service_config from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.services.soledad.soledadconfig import SoledadConfig -from leap.bitmask.util import is_file, is_empty_file +from leap.bitmask.util import first, is_file, is_empty_file, make_address from leap.bitmask.util import get_path_prefix from leap.bitmask.platform_init import IS_WIN from leap.common.check import leap_assert, leap_assert_type, leap_check @@ -47,10 +45,44 @@ from leap.soledad.client import Soledad, BootstrapSequenceError logger = logging.getLogger(__name__) +""" +These mocks are replicated from imap tests and the repair utility. +They are needed for the moment to knock out the remote capabilities of soledad +during the use of the offline mode. + +They should not be needed after we allow a null remote initialization in the +soledad client, and a switch to remote sync-able mode during runtime. +""" + + +class Mock(object): + """ + A generic simple mock class + """ + def __init__(self, return_value=None): + self._return = return_value + + def __call__(self, *args, **kwargs): + return self._return + + +class MockSharedDB(object): + """ + Mocked SharedDB object to replace in soledad before + instantiating it in offline mode. + """ + get_doc = Mock() + put_doc = Mock() + lock = Mock(return_value=('atoken', 300)) + unlock = Mock(return_value=True) + + def __call__(self): + return self # TODO these exceptions could be moved to soledad itself # after settling this down. + class SoledadSyncError(Exception): message = "Error while syncing Soledad" @@ -61,7 +93,7 @@ class SoledadInitError(Exception): def get_db_paths(uuid): """ - Returns the secrets and local db paths needed for soledad + Return the secrets and local db paths needed for soledad initialization :param uuid: uuid for user @@ -88,7 +120,7 @@ def get_db_paths(uuid): class SoledadBootstrapper(AbstractBootstrapper): """ - Soledad init procedure + Soledad init procedure. """ SOLEDAD_KEY = "soledad" KEYMANAGER_KEY = "keymanager" @@ -102,6 +134,7 @@ class SoledadBootstrapper(AbstractBootstrapper): # {"passed": bool, "error": str} download_config = QtCore.Signal(dict) gen_key = QtCore.Signal(dict) + local_only_ready = QtCore.Signal(dict) soledad_timeout = QtCore.Signal() soledad_failed = QtCore.Signal() @@ -115,6 +148,9 @@ class SoledadBootstrapper(AbstractBootstrapper): self._user = "" self._password = "" + self._address = "" + self._uuid = "" + self._srpauth = None self._soledad = None @@ -130,6 +166,8 @@ class SoledadBootstrapper(AbstractBootstrapper): @property def srpauth(self): + if flags.OFFLINE is True: + return None leap_assert(self._provider_config is not None, "We need a provider config") return SRPAuth(self._provider_config) @@ -141,7 +179,7 @@ class SoledadBootstrapper(AbstractBootstrapper): def should_retry_initialization(self): """ - Returns True if we should retry the initialization. + Return True if we should retry the initialization. """ logger.debug("current retries: %s, max retries: %s" % ( self._soledad_retries, @@ -150,47 +188,94 @@ class SoledadBootstrapper(AbstractBootstrapper): def increment_retries_count(self): """ - Increments the count of initialization retries. + Increment the count of initialization retries. """ self._soledad_retries += 1 # initialization - def load_and_sync_soledad(self): + def load_offline_soledad(self, username, password, uuid): """ - Once everthing is in the right place, we instantiate and sync - Soledad + Instantiate Soledad for offline use. + + :param username: full user id (user@provider) + :type username: basestring + :param password: the soledad passphrase + :type password: unicode + :param uuid: the user uuid + :type uuid: basestring """ - # TODO this method is still too large - uuid = self.srpauth.get_uid() - token = self.srpauth.get_token() + print "UUID ", uuid + self._address = username + self._uuid = uuid + return self.load_and_sync_soledad(uuid, offline=True) + + def _get_soledad_local_params(self, uuid, offline=False): + """ + Return the locals parameters needed for the soledad initialization. + + :param uuid: the uuid of the user, used in offline mode. + :type uuid: unicode, or None. + :return: secrets_path, local_db_path, token + :rtype: tuple + """ + # in the future, when we want to be able to switch to + # online mode, this should be a proxy object too. + # Same for server_url below. + + if offline is False: + token = self.srpauth.get_token() + else: + token = "" secrets_path, local_db_path = get_db_paths(uuid) - # TODO: Select server based on timezone (issue #3308) - server_dict = self._soledad_config.get_hosts() + logger.debug('secrets_path:%s' % (secrets_path,)) + logger.debug('local_db:%s' % (local_db_path,)) + return (secrets_path, local_db_path, token) - if not server_dict.keys(): - # XXX raise more specific exception, and catch it properly! - raise Exception("No soledad server found") + def _get_soledad_server_params(self, uuid, offline): + """ + Return the remote parameters needed for the soledad initialization. - selected_server = server_dict[server_dict.keys()[0]] - server_url = "https://%s:%s/user-%s" % ( - selected_server["hostname"], - selected_server["port"], - uuid) - logger.debug("Using soledad server url: %s" % (server_url,)) + :param uuid: the uuid of the user, used in offline mode. + :type uuid: unicode, or None. + :return: server_url, cert_file + :rtype: tuple + """ + if uuid is None: + uuid = self.srpauth.get_uuid() + + if offline is True: + server_url = "http://localhost:9999/" + cert_file = "" + else: + server_url = self._pick_server(uuid) + cert_file = self._provider_config.get_ca_cert_path() - cert_file = self._provider_config.get_ca_cert_path() + return server_url, cert_file - logger.debug('local_db:%s' % (local_db_path,)) - logger.debug('secrets_path:%s' % (secrets_path,)) + def load_and_sync_soledad(self, uuid=None, offline=False): + """ + Once everthing is in the right place, we instantiate and sync + Soledad + + :param uuid: the uuid of the user, used in offline mode. + :type uuid: unicode, or None. + :param offline: whether to instantiate soledad for offline use. + :type offline: bool + """ + local_param = self._get_soledad_local_params(uuid, offline) + remote_param = self._get_soledad_server_params(uuid, offline) + + secrets_path, local_db_path, token = local_param + server_url, cert_file = remote_param try: self._try_soledad_init( uuid, secrets_path, local_db_path, server_url, cert_file, token) - except: + except Exception: # re-raise the exceptions from try_init, # we're currently handling the retries from the # soledad-launcher in the gui. @@ -198,11 +283,40 @@ class SoledadBootstrapper(AbstractBootstrapper): leap_assert(not sameProxiedObjects(self._soledad, None), "Null soledad, error while initializing") - self._do_soledad_sync() + + if flags.OFFLINE is True: + self._init_keymanager(self._address) + self.local_only_ready.emit({self.PASSED_KEY: True}) + else: + self._do_soledad_sync() + + def _pick_server(self, uuid): + """ + Choose a soledad server to sync against. + + :param uuid: the uuid for the user. + :type uuid: unicode + :returns: the server url + :rtype: unicode + """ + # TODO: Select server based on timezone (issue #3308) + server_dict = self._soledad_config.get_hosts() + + if not server_dict.keys(): + # XXX raise more specific exception, and catch it properly! + raise Exception("No soledad server found") + + selected_server = server_dict[first(server_dict.keys())] + server_url = "https://%s:%s/user-%s" % ( + selected_server["hostname"], + selected_server["port"], + uuid) + logger.debug("Using soledad server url: %s" % (server_url,)) + return server_url def _do_soledad_sync(self): """ - Does several retries to get an initial soledad sync. + Do several retries to get an initial soledad sync. """ # and now, let's sync sync_tries = self.MAX_SYNC_RETRIES @@ -231,7 +345,7 @@ class SoledadBootstrapper(AbstractBootstrapper): def _try_soledad_init(self, uuid, secrets_path, local_db_path, server_url, cert_file, auth_token): """ - Tries to initialize soledad. + Try to initialize soledad. :param uuid: user identifier :param secrets_path: path to secrets file @@ -247,6 +361,10 @@ class SoledadBootstrapper(AbstractBootstrapper): # TODO: If selected server fails, retry with another host # (issue #3309) encoding = sys.getfilesystemencoding() + + # XXX We should get a flag in soledad itself + if flags.OFFLINE is True: + Soledad._shared_db = MockSharedDB() try: self._soledad = Soledad( uuid, @@ -281,7 +399,7 @@ class SoledadBootstrapper(AbstractBootstrapper): self.soledad_failed.emit() raise except u1db_errors.HTTPError as exc: - logger.exception("Error whie initializing soledad " + logger.exception("Error while initializing soledad " "(HTTPError)") self.soledad_failed.emit() raise @@ -293,7 +411,7 @@ class SoledadBootstrapper(AbstractBootstrapper): def _try_soledad_sync(self): """ - Tries to sync soledad. + Try to sync soledad. Raises SoledadSyncError if not successful. """ try: @@ -313,7 +431,7 @@ class SoledadBootstrapper(AbstractBootstrapper): def _download_config(self): """ - Downloads the Soledad config for the given provider + Download the Soledad config for the given provider """ leap_assert(self._provider_config, @@ -332,11 +450,14 @@ class SoledadBootstrapper(AbstractBootstrapper): # XXX but honestly, this is a pretty strange entry point for that. # it feels like it should be the other way around: # load_and_sync, and from there, if needed, call download_config - self.load_and_sync_soledad() + + uuid = self.srpauth.get_uuid() + self.load_and_sync_soledad(uuid) def _get_gpg_bin_path(self): """ - Returns the path to gpg binary. + Return the path to gpg binary. + :returns: the gpg binary path :rtype: str """ @@ -364,38 +485,54 @@ class SoledadBootstrapper(AbstractBootstrapper): def _init_keymanager(self, address): """ - Initializes the keymanager. + Initialize the keymanager. + :param address: the address to initialize the keymanager with. :type address: str """ srp_auth = self.srpauth logger.debug('initializing keymanager...') - try: - self._keymanager = KeyManager( + + if flags.OFFLINE is True: + args = (address, "https://localhost", self._soledad) + kwargs = { + "session_id": "", + "ca_cert_path": "", + "api_uri": "", + "api_version": "", + "uid": self._uuid, + "gpgbinary": self._get_gpg_bin_path() + } + else: + args = ( address, "https://nicknym.%s:6425" % ( self._provider_config.get_domain(),), - self._soledad, - #token=srp_auth.get_token(), # TODO: enable token usage - session_id=srp_auth.get_session_id(), - ca_cert_path=self._provider_config.get_ca_cert_path(), - api_uri=self._provider_config.get_api_uri(), - api_version=self._provider_config.get_api_version(), - uid=srp_auth.get_uid(), - gpgbinary=self._get_gpg_bin_path()) + self._soledad + ) + kwargs = { + "session_id": srp_auth.get_session_id(), + "ca_cert_path": self._provider_config.get_ca_cert_path(), + "api_uri": self._provider_config.get_api_uri(), + "api_version": self._provider_config.get_api_version(), + "uid": srp_auth.get_uuid(), + "gpgbinary": self._get_gpg_bin_path() + } + try: + self._keymanager = KeyManager(*args, **kwargs) except Exception as exc: logger.exception(exc) raise - logger.debug('sending key to server...') - - # make sure key is in server - try: - self._keymanager.send_key(openpgp.OpenPGPKey) - except Exception as exc: - logger.error("Error sending key to server.") - logger.exception(exc) - # but we do not raise + if flags.OFFLINE is False: + # make sure key is in server + logger.debug('sending key to server...') + try: + self._keymanager.send_key(openpgp.OpenPGPKey) + except Exception as exc: + logger.error("Error sending key to server.") + logger.exception(exc) + # but we do not raise def _gen_key(self, _): """ @@ -407,7 +544,8 @@ class SoledadBootstrapper(AbstractBootstrapper): leap_assert(self._soledad is not None, "We need a non-null soledad to generate keys") - address = "%s@%s" % (self._user, self._provider_config.get_domain()) + address = make_address( + self._user, self._provider_config.get_domain()) self._init_keymanager(address) logger.debug("Retrieving key for %s" % (address,)) diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py index b58e6e3b..85676d51 100644 --- a/src/leap/bitmask/util/__init__.py +++ b/src/leap/bitmask/util/__init__.py @@ -76,3 +76,16 @@ def is_empty_file(path): Returns True if the file at path is empty. """ return os.stat(path).st_size is 0 + + +def make_address(user, provider): + """ + Return a full identifier for an user, as a email-like + identifier. + + :param user: the username + :type user: basestring + :param provider: the provider domain + :type provider: basestring + """ + return "%s@%s" % (user, provider) -- cgit v1.2.3 From 5dc5dc816f2ba50920dd3a5908824c244c8d9390 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sat, 11 Jan 2014 23:10:53 -0400 Subject: workaround for using keyring inside a virtualenv (securestorage backend) This is related to research #4190 and #4083. I didn't resolve them, but I ended understanding a bit more what kind of issues can we be having with those. This workaround is more than anything a cleanup on that for future work, and is making me able to test the client much more nicely inside a virtualenv (where the default keyring selecting was the plaintext one). For this to work inside a virtualenv, one have to install SecureStorage, which in turns depends on python-dbus, which is basically uninstallable using pip inside a venv. So, symlinking to the rescue! it needs gi/repository and pyobject to be symlinked too. but at least you don't have to type the pass every time. I don't know what should be do in the long term with this issue. Some of us were not too fond on using the keyrings at all. We don't have many options among all the keyring backends, and sadly many of them depend on PyCrypto. So probably roll our own backend. Yay. --- src/leap/bitmask/util/keyring_helpers.py | 43 ++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 8 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/util/keyring_helpers.py b/src/leap/bitmask/util/keyring_helpers.py index 4b3eb57f..b202d47e 100644 --- a/src/leap/bitmask/util/keyring_helpers.py +++ b/src/leap/bitmask/util/keyring_helpers.py @@ -31,18 +31,45 @@ OBSOLETE_KEYRINGS = [ PlaintextKeyring ] +canuse = lambda kr: kr is not None and kr.__class__ not in OBSOLETE_KEYRINGS + + +def _get_keyring_with_fallback(): + """ + Get the default keyring, and if obsolete try to pick SecretService keyring + if available. + + This is a workaround for the cases in which the keyring module chooses + an insecure keyring by default (ie, inside a virtualenv). + """ + kr = keyring.get_keyring() + if not canuse(kr): + try: + kr_klass = keyring.backends.SecretService + kr = kr_klass.Keyring() + except AttributeError: + logger.warning("Keyring cannot find SecretService Backend") + logger.debug("Selected keyring: %s" % (kr.__class__,)) + if not canuse(kr): + logger.debug("Not using default keyring since it is obsolete") + return kr + def has_keyring(): """ - Returns whether we have an useful keyring to use. + Return whether we have an useful keyring to use. :rtype: bool """ - kr = keyring.get_keyring() - klass = kr.__class__ - logger.debug("Selected keyring: %s" % (klass,)) + kr = _get_keyring_with_fallback() + return canuse(kr) + - canuse = kr is not None and klass not in OBSOLETE_KEYRINGS - if not canuse: - logger.debug("Not using this keyring since it is obsolete") - return canuse +def get_keyring(): + """ + Return an usable keyring. + + :rtype: keyringBackend or None + """ + kr = _get_keyring_with_fallback() + return kr if canuse(kr) else None -- cgit v1.2.3 From 6cc752e403e08795273270f637ed212270d2eaef Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sat, 11 Jan 2014 23:17:06 -0400 Subject: beautify link to downloads (make it a link) --- src/leap/bitmask/gui/mainwindow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 18ef56e5..83aa47a9 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -881,7 +881,8 @@ class MainWindow(QtGui.QMainWindow): "The current client version is not supported " "by this provider.
" "Please update to latest version.

" - "You can get the latest version from {0}").format(url) + "You can get the latest version from " + "{1}").format(url, url) QtGui.QMessageBox.warning(self, self.tr("Update Needed"), msg) def _incompatible_api(self): -- cgit v1.2.3 From 82586b4717851e882bdd0d378191595bf9ef0535 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sun, 12 Jan 2014 21:22:51 -0400 Subject: make plumber use the cached uuid instead of authenticating --- src/leap/bitmask/services/mail/repair.py | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/mail/repair.py b/src/leap/bitmask/services/mail/repair.py index 660e9f11..e921e207 100644 --- a/src/leap/bitmask/services/mail/repair.py +++ b/src/leap/bitmask/services/mail/repair.py @@ -23,8 +23,8 @@ import os from collections import defaultdict +from leap.bitmask.config.leapsettings import LeapSettings 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 @@ -104,6 +104,8 @@ class MBOXPlumber(object): user, provider = userid.split('@') self.user = user self.sol = None + self._settings = LeapSettings() + provider_config_path = os.path.join( get_path_prefix(), "leap", "providers", @@ -114,34 +116,17 @@ class MBOXPlumber(object): 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." - - # XXX this won't be needed anymore after we keep a local - # cache of user uuids, so we'll be able to pick it from - # there. - self.uuid = self.srp.get_uuid() + self.uuid = self._settings.get_uuid(self.userid) if not self.uuid: - print "Got BAD UUID from provider!" + print "Cannot get UUID from settings. Log in at least once." return self.exit() print "UUID: %s" % (self.uuid) - secrets, localdb = get_db_paths(self.uid) + secrets, localdb = get_db_paths(self.uuid) self.sol = initialize_soledad( self.uuid, self.userid, self.passwd, @@ -168,7 +153,7 @@ class MBOXPlumber(object): 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()) + uids_iter = mbox.messages.all_msg_iter() dupes = self._has_dupes(uids_iter) if last_ok and not dupes: print "Mbox does not need repair." @@ -204,7 +189,6 @@ class MBOXPlumber(object): def exit(self): from twisted.internet import reactor - self.d.cancel() if self.sol: self.sol.close() try: @@ -224,7 +208,7 @@ def repair_account(userid): # go mario! plumber = MBOXPlumber(userid, passwd) - reactor.callLater(1, plumber.start_auth) + reactor.callLater(1, plumber.repair_account) reactor.run() -- cgit v1.2.3 From 36d634ad980bd260a3d93f8005725bc2dc3527f7 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sun, 12 Jan 2014 21:28:21 -0400 Subject: rename repair to plumber to suit more generic functionality --- src/leap/bitmask/app.py | 2 +- src/leap/bitmask/services/mail/plumber.py | 228 ++++++++++++++++++++++++++++++ src/leap/bitmask/services/mail/repair.py | 223 ----------------------------- src/leap/bitmask/util/leap_argparse.py | 5 + 4 files changed, 234 insertions(+), 224 deletions(-) create mode 100644 src/leap/bitmask/services/mail/plumber.py delete mode 100644 src/leap/bitmask/services/mail/repair.py (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index e8423fd5..12d1dadd 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -54,7 +54,7 @@ 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.bitmask.services.mail.plumber import repair_account from leap.common.events import server as event_server from leap.mail import __version__ as MAIL_VERSION diff --git a/src/leap/bitmask/services/mail/plumber.py b/src/leap/bitmask/services/mail/plumber.py new file mode 100644 index 00000000..4ecbc361 --- /dev/null +++ b/src/leap/bitmask/services/mail/plumber.py @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- +# plumber.py +# Copyright (C) 2013, 2014 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 . +""" +Utils for manipulating local mailboxes. +""" +import logging +import getpass +import os + +from collections import defaultdict + +from leap.bitmask.config.leapsettings import LeapSettings +from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.util import get_path_prefix +from leap.bitmask.services.soledad.soledadbootstrapper import get_db_paths + +from leap.mail.imap.account 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 + self._settings = LeapSettings() + + 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() + + def repair_account(self, *args): + """ + Gets the user id for this account. + """ + self.uuid = self._settings.get_uuid(self.userid) + if not self.uuid: + print "Cannot get UUID from settings. Log in at least once." + return self.exit() + print "UUID: %s" % (self.uuid) + + secrets, localdb = get_db_paths(self.uuid) + + self.sol = initialize_soledad( + self.uuid, 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_uids(mbox_name) + print "done." + self.exit() + + def repair_mbox_uids(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 = mbox.messages.all_msg_iter() + 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) + if mindex != old_uid: + 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 + 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.repair_account) + reactor.run() + + +if __name__ == "__main__": + import sys + + logging.basicConfig() + + if len(sys.argv) != 3: + print "Usage: plumber [repair|import] " + sys.exit(1) + + # this would be better with a dict if it grows + if sys.argv[1] == "repair": + repair_account(sys.argv[2]) + if sys.argv[1] == "import": + print "Not implemented yet." diff --git a/src/leap/bitmask/services/mail/repair.py b/src/leap/bitmask/services/mail/repair.py deleted file mode 100644 index e921e207..00000000 --- a/src/leap/bitmask/services/mail/repair.py +++ /dev/null @@ -1,223 +0,0 @@ -# -*- 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 . -""" -Utils for repairing mailbox indexes. -""" -import logging -import getpass -import os - -from collections import defaultdict - -from leap.bitmask.config.leapsettings import LeapSettings -from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.util import get_path_prefix -from leap.bitmask.services.soledad.soledadbootstrapper import get_db_paths - -from leap.mail.imap.account 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 - self._settings = LeapSettings() - - 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() - - def repair_account(self, *args): - """ - Gets the user id for this account. - """ - self.uuid = self._settings.get_uuid(self.userid) - if not self.uuid: - print "Cannot get UUID from settings. Log in at least once." - return self.exit() - print "UUID: %s" % (self.uuid) - - secrets, localdb = get_db_paths(self.uuid) - - self.sol = initialize_soledad( - self.uuid, 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_uids(mbox_name) - print "done." - self.exit() - - def repair_mbox_uids(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 = mbox.messages.all_msg_iter() - 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) - if mindex != old_uid: - 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 - 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.repair_account) - reactor.run() - - -if __name__ == "__main__": - import sys - - logging.basicConfig() - - if len(sys.argv) != 2: - print "Usage: repair " - sys.exit(1) - repair_account(sys.argv[1]) diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index fb92f141..dd0f40f7 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -75,6 +75,11 @@ Launches the Bitmask client.""", epilog=epilog) help='Repair mailboxes for a given account. ' 'Use when upgrading versions after a schema ' 'change.') + parser.add_argument('--import-maildir', metavar="/path/to/Maildir", + nargs='?', + action="store", dest="maildir", + help='Import the given maildir. Use with the --mdir ' + 'flag to import to folders other than INBOX.') if not IS_RELEASE_VERSION: help_text = ("Bypasses the certificate check during provider " -- cgit v1.2.3 From 7f9fa030ed44a7db6ced5b359c49dadc0a781b8a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sun, 12 Jan 2014 22:56:31 -0400 Subject: able to import maildir --- src/leap/bitmask/app.py | 10 ++- src/leap/bitmask/services/mail/plumber.py | 114 +++++++++++++++++++++++++++--- src/leap/bitmask/util/__init__.py | 27 ++++++- src/leap/bitmask/util/leap_argparse.py | 17 +++-- 4 files changed, 147 insertions(+), 21 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 12d1dadd..d8d1d38a 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -54,7 +54,7 @@ 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.plumber import repair_account +from leap.bitmask.services.mail import plumber from leap.common.events import server as event_server from leap.mail import __version__ as MAIL_VERSION @@ -176,9 +176,13 @@ def main(): print "leap.mail version: %s" % (MAIL_VERSION,) sys.exit(0) - if opts.acct_to_repair: - repair_account(opts.acct_to_repair) + if opts.repair: + plumber.repair_account(opts.acct) sys.exit(0) + if opts.import_maildir and opts.acct: + plumber.import_maildir(opts.acct, opts.import_maildir) + sys.exit(0) + # XXX catch when import is used w/o acct standalone = opts.standalone offline = opts.offline diff --git a/src/leap/bitmask/services/mail/plumber.py b/src/leap/bitmask/services/mail/plumber.py index 4ecbc361..49514655 100644 --- a/src/leap/bitmask/services/mail/plumber.py +++ b/src/leap/bitmask/services/mail/plumber.py @@ -22,10 +22,13 @@ import getpass import os from collections import defaultdict +from functools import partial + +from twisted.internet import defer from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.util import get_path_prefix +from leap.bitmask.util import flatten, get_path_prefix from leap.bitmask.services.soledad.soledadbootstrapper import get_db_paths from leap.mail.imap.account import SoledadBackedAccount @@ -89,20 +92,23 @@ class MBOXPlumber(object): that can be invoked when data migration in the client is needed. """ - def __init__(self, userid, passwd): + def __init__(self, userid, passwd, mdir=None): """ - Initializes the plumber with all that's needed to authenticate + Initialize 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 + :param mdir: a path to a maildir to import + :type mdir: str or None """ self.userid = userid self.passwd = passwd user, provider = userid.split('@') self.user = user + self.mdir = mdir self.sol = None self._settings = LeapSettings() @@ -116,9 +122,9 @@ class MBOXPlumber(object): print "could not load provider config!" return self.exit() - def repair_account(self, *args): + def _init_local_soledad(self): """ - Gets the user id for this account. + Initialize local Soledad instance. """ self.uuid = self._settings.get_uuid(self.userid) if not self.uuid: @@ -131,8 +137,16 @@ class MBOXPlumber(object): self.sol = initialize_soledad( self.uuid, self.userid, self.passwd, secrets, localdb, "/tmp", "/tmp") - self.acct = SoledadBackedAccount(self.userid, self.sol) + # + # Account repairing + # + + def repair_account(self, *args): + """ + Repair mbox uids for all mboxes in this account. + """ + self._init_local_soledad() for mbox_name in self.acct.mailboxes: self.repair_mbox_uids(mbox_name) print "done." @@ -140,7 +154,7 @@ class MBOXPlumber(object): def repair_mbox_uids(self, mbox_name): """ - Repairs indexes for a given mbox + Repair indexes for a given mbox. :param mbox_name: mailbox to repair :type mbox_name: basestring @@ -159,6 +173,7 @@ class MBOXPlumber(object): print "Mbox does not need repair." return + # XXX CHANGE? ---- msgs = mbox.messages.get_all() for zindex, doc in enumerate(msgs): mindex = zindex + 1 @@ -174,7 +189,7 @@ class MBOXPlumber(object): def _has_dupes(self, sequence): """ - Returns True if the given sequence of ints has duplicates. + Return True if the given sequence of ints has duplicates. :param sequence: a sequence of ints :type sequence: sequence @@ -187,6 +202,71 @@ class MBOXPlumber(object): return True return False + # + # Maildir import + # + def import_mail(self, mail_filename): + """ + Import a single mail into a mailbox. + + :param mbox: the Mailbox instance to save in. + :type mbox: SoledadMailbox + :param mail_filename: the filename to the mail file to save + :type mail_filename: basestring + :return: a deferred + """ + def saved(_): + print "message added" + + with open(mail_filename) as f: + mail_string = f.read() + uid = self._mbox.getUIDNext() + print "saving with UID: %s" % uid + d = self._mbox.messages.add_msg(mail_string, uid=uid) + return d + + def import_maildir(self, mbox_name="INBOX"): + """ + Import all mails in a maildir. + + We will process all subfolders as beloging + to the same mailbox (cur, new, tmp). + """ + # TODO parse hierarchical subfolders into + # inferior mailboxes. + + if not os.path.isdir(self.mdir): + print "ERROR: maildir path does not exist." + return + + self._init_local_soledad() + mbox = self.acct.getMailbox(mbox_name) + self._mbox = mbox + len_mbox = mbox.getMessageCount() + + mail_files_g = flatten( + map(partial(os.path.join, f), files) + for f, _, files in os.walk(self.mdir)) + + # we only coerce the generator to give the + # len, but we could skip than and inform at the end. + mail_files = list(mail_files_g) + print "Got %s mails to import into %s (%s)" % ( + len(mail_files), mbox_name, len_mbox) + + def all_saved(_): + print "all messages imported" + + deferreds = [] + for f_name in mail_files: + deferreds.append(self.import_mail(f_name)) + d1 = defer.gatherResults(deferreds, consumeErrors=False) + d1.addCallback(all_saved) + d1.addCallback(self._cbExit) + + def _cbExit(self, ignored): + return self.exit() + def exit(self): from twisted.internet import reactor if self.sol: @@ -200,7 +280,8 @@ class MBOXPlumber(object): def repair_account(userid): """ - Starts repair process for a given account. + Start repair process for a given account. + :param userid: the user id (email-like) """ from twisted.internet import reactor @@ -212,6 +293,21 @@ def repair_account(userid): reactor.run() +def import_maildir(userid, maildir_path): + """ + Start import-maildir 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, mdir=maildir_path) + reactor.callLater(1, plumber.import_maildir) + reactor.run() + + if __name__ == "__main__": import sys diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py index 85676d51..c35be99e 100644 --- a/src/leap/bitmask/util/__init__.py +++ b/src/leap/bitmask/util/__init__.py @@ -18,19 +18,23 @@ Some small and handy functions. """ import datetime +import itertools import os from leap.bitmask.config import flags from leap.common.config import get_path_prefix as common_get_path_prefix - -def get_path_prefix(): - return common_get_path_prefix(flags.STANDALONE) +# functional goodies for a healthier life: +# We'll give your money back if it does not alleviate the eye strain, at least. def first(things): """ Return the head of a collection. + + :param things: a sequence to extract the head from. + :type things: sequence + :return: object, or None """ try: return things[0] @@ -38,6 +42,23 @@ def first(things): return None +def flatten(things): + """ + Return a generator iterating through a flattened sequence. + + :param things: a nested sequence, eg, a list of lists. + :type things: sequence + :rtype: generator + """ + return itertools.chain(*things) + + +# leap repetitive chores + +def get_path_prefix(): + return common_get_path_prefix(flags.STANDALONE) + + def get_modification_ts(path): """ Gets modification time of a file. diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index dd0f40f7..56bf26dc 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -69,17 +69,22 @@ Launches the Bitmask client.""", epilog=epilog) parser.add_argument('-o', '--offline', action="store_true", help='Starts Bitmask in offline mode: will not ' 'try to sync with remote replicas for email.') - parser.add_argument('-r', '--repair-mailboxes', metavar="user@provider", + + parser.add_argument('--acct', metavar="user@provider", nargs='?', - action="store", dest="acct_to_repair", + action="store", dest="acct", + help='Manipulate mailboxes for this account') + parser.add_argument('-r', '--repair-mailboxes', default=False, + action="store_true", dest="repair", help='Repair mailboxes for a given account. ' 'Use when upgrading versions after a schema ' - 'change.') + 'change. Use with --acct') parser.add_argument('--import-maildir', metavar="/path/to/Maildir", nargs='?', - action="store", dest="maildir", - help='Import the given maildir. Use with the --mdir ' - 'flag to import to folders other than INBOX.') + action="store", dest="import_maildir", + help='Import the given maildir. Use with the ' + '--to-mbox flag to import to folders other ' + 'than INBOX. Use with --acct') if not IS_RELEASE_VERSION: help_text = ("Bypasses the certificate check during provider " -- cgit v1.2.3 From 24e7c4f505d92164187c8afe038b24c67e2bdedc Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 13 Jan 2014 14:20:33 -0300 Subject: Use set_status for all messages. Remove set_login_status since it's a remaining code after split the login in a separated widget. [Closes #4942] --- src/leap/bitmask/gui/login.py | 17 --------------- src/leap/bitmask/gui/mainwindow.py | 5 ++--- src/leap/bitmask/gui/ui/login.ui | 43 ++++++++++++++------------------------ 3 files changed, 18 insertions(+), 47 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index d0cb20b1..8a7c6996 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -327,7 +327,6 @@ class LoginWidget(QtGui.QWidget): self.ui.logged_widget.show() self.ui.lblUser.setText(make_address( self.get_user(), self.get_selected_provider())) - self.set_login_status("") if flags.OFFLINE is False: self.logged_in_signal.emit() @@ -346,22 +345,6 @@ class LoginWidget(QtGui.QWidget): self.set_enabled(True) self.set_status("", error=False) - def set_login_status(self, msg, error=False): - """ - Sets the status label for the logged in state. - - :param msg: status message - :type msg: str or unicode - :param error: if the status is an erroneous one, then set this - to True - :type error: bool - """ - leap_assert_type(error, bool) - if error: - msg = "%s" % (msg,) - self.ui.lblLoginStatus.setText(msg) - self.ui.lblLoginStatus.show() - def start_logout(self): """ Sets the widgets to the logging out state diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 83aa47a9..ddaa085c 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1754,9 +1754,8 @@ class MainWindow(QtGui.QMainWindow): self._mail_status.mail_state_disabled() else: - self._login_widget.set_login_status( - self.tr("Something went wrong with the logout."), - error=True) + self._login_widget.set_status( + self.tr("Something went wrong with the logout.")) def _intermediate_stage(self, data): # TODO this method name is confusing as hell. diff --git a/src/leap/bitmask/gui/ui/login.ui b/src/leap/bitmask/gui/ui/login.ui index 7e8f9daf..f5725d5a 100644 --- a/src/leap/bitmask/gui/ui/login.ui +++ b/src/leap/bitmask/gui/ui/login.ui @@ -217,26 +217,6 @@ 0 - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Logout - - - @@ -251,17 +231,26 @@ - - - - color: rgb(132, 132, 132); -font: 75 12pt; - + + - + Logout + + + + Qt::Horizontal + + + + 40 + 20 + + + + -- cgit v1.2.3 From fca0c934d33e11445a0c9cc58aa655c8a429bfce Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 13 Jan 2014 13:22:39 -0400 Subject: Reorder initialization calls. * Refactor opt checking, paving the way for separation into commands module. * Do not hijack the stdout for commands, reduce clutter. --- src/leap/bitmask/app.py | 55 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 14 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index d8d1d38a..941ba4f8 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -80,7 +80,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, replace_stdout=True): """ Create the logger and attach the handlers. @@ -117,6 +117,9 @@ def add_logger_handlers(debug=False, logfile=None): else: using_coloredlog = True + if using_coloredlog: + replace_stdout = False + silencer = log_silencer.SelectiveSilencerFilter() console.addFilter(silencer) logger.addHandler(console) @@ -139,7 +142,7 @@ def add_logger_handlers(debug=False, logfile=None): logger.addHandler(fileh) logger.debug('File handler plugged!') - if not using_coloredlog: + if replace_stdout: replace_stdout_stderr_with_logging(logger) return logger @@ -164,18 +167,22 @@ def replace_stdout_stderr_with_logging(logger): log.startLogging(sys.stdout) -def main(): +def do_display_version(opts): """ - Starts the main event loop and launches the main window. + Display version and exit. """ - # TODO move boilerplate outa here! - _, opts = leap_argparse.init_leapc_args() - + # TODO move to a different module: commands? if opts.version: print "Bitmask version: %s" % (VERSION,) print "leap.mail version: %s" % (MAIL_VERSION,) sys.exit(0) + +def do_mail_plumbing(opts): + """ + Analize options and do mailbox plumbing if requested. + """ + # TODO move to a different module: commands? if opts.repair: plumber.repair_account(opts.acct) sys.exit(0) @@ -184,6 +191,15 @@ def main(): sys.exit(0) # XXX catch when import is used w/o acct + +def main(): + """ + Starts the main event loop and launches the main window. + """ + # TODO move boilerplate outa here! + _, opts = leap_argparse.init_leapc_args() + do_display_version(opts) + standalone = opts.standalone offline = opts.offline bypass_checks = getattr(opts, 'danger', False) @@ -192,12 +208,6 @@ def main(): mail_logfile = opts.mail_log_file openvpn_verb = opts.openvpn_verb - try: - event_server.ensure_server(event_server.SERVER_PORT) - except Exception as e: - # We don't even have logger configured in here - print "Could not ensure server: %r" % (e,) - ############################################################# # Given how paths and bundling works, we need to delay the imports # of certain parts that depend on this path settings. @@ -212,9 +222,25 @@ def main(): BaseConfig.standalone = standalone - logger = add_logger_handlers(debug, logfile) + replace_stdout = True + if opts.repair or opts.import_maildir: + # We don't want too much clutter on the comand mode + # this could be more generic with a Command class. + replace_stdout = False + logger = add_logger_handlers(debug, logfile, replace_stdout) + + # ok, we got logging in place, we can satisfy mail plumbing requests + # and show logs there. it normally will exit there if we got that path. + do_mail_plumbing(opts) + + try: + event_server.ensure_server(event_server.SERVER_PORT) + except Exception as e: + # We don't even have logger configured in here + print "Could not ensure server: %r" % (e,) # And then we import all the other stuff + # I think it's safe to import at the top by now -- kali from leap.bitmask.gui import locale_rc from leap.bitmask.gui import twisted_main from leap.bitmask.gui.mainwindow import MainWindow @@ -225,6 +251,7 @@ def main(): # pylint: avoid unused import assert(locale_rc) + # TODO move to a different module: commands? if not we_are_the_one_and_only(): # Bitmask is already running logger.warning("Tried to launch more than one instance " -- cgit v1.2.3 From cd1ddb919b535800cee96d38a908f31f93f85e29 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 14 Jan 2014 11:07:45 -0300 Subject: Add an extra string for the singular 'email' word. Client should say 1 unread email, not emails. [Closes #4952] --- src/leap/bitmask/gui/mail_status.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 3c933c9a..6db17427 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -363,9 +363,13 @@ class MailStatusWidget(QtGui.QWidget): if req.event == proto.IMAP_UNREAD_MAIL: if self._started: - if req.content != "0": - self._set_mail_status(self.tr("%s Unread Emails") % - (req.content,), ready=2) + count = req.content + if count != "0": + status = self.tr("{0} Unread Emails").format(count) + if count == "1": + status = self.tr("1 Unread Email") + + self._set_mail_status(status, ready=2) else: self._set_mail_status("", ready=2) elif req.event == proto.IMAP_SERVICE_STARTED: -- cgit v1.2.3 From 9bfaaded5adf66a129fb35c2e561a4e435a9179d Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 14 Jan 2014 13:59:14 -0300 Subject: Add dialog with instructions to configure mail. [Closes #4530] --- src/leap/bitmask/gui/mainwindow.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index ddaa085c..3f41900b 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -81,6 +81,8 @@ from leap.common.check import leap_assert from leap.common.events import register from leap.common.events import events_pb2 as proto +from leap.mail.imap.service.imap import IMAP_PORT + from ui_mainwindow import Ui_MainWindow logger = logging.getLogger(__name__) @@ -259,6 +261,7 @@ class MainWindow(QtGui.QMainWindow): self.ui.action_quit.triggered.connect(self.quit) self.ui.action_wizard.triggered.connect(self._launch_wizard) self.ui.action_show_logs.triggered.connect(self._show_logger_window) + self.ui.action_help.triggered.connect(self._help) self.ui.action_create_new_account.triggered.connect( self._launch_wizard) @@ -872,6 +875,33 @@ class MainWindow(QtGui.QMainWindow): "More about LEAP" "") % (VERSION, VERSION_HASH[:10], greet)) + def _help(self): + """ + SLOT + TRIGGERS: self.ui.action_help.triggered + + Display the Bitmask help dialog. + """ + # TODO: don't hardcode! + smtp_port = 2013 + + url = ("bitmask addon") + + msg = ( + "Instructions to use mail:
" + "If you use Thunderbird you can use the Bitmask extension helper. " + "Search for 'Bitmask' in the add-on manager or download it " + "from: {0}.

" + "You can configure bitmask manually with this options:
" + "" + " Incoming -> IMAP, port: {1}
" + " Outgoing -> SMTP, port: {2}
" + " Username -> your bitmask username.
" + " Password -> leave it empty." + "
").format(url, IMAP_PORT, smtp_port) + QtGui.QMessageBox.about(self, self.tr("Bitmask Help"), msg) + def _needs_update(self): """ Display a warning dialog to inform the user that the app needs update. -- cgit v1.2.3 From 9dbbfeebbb4bb7c445e01f94533e88e514fd6821 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 17 Jan 2014 11:22:35 -0400 Subject: defend against keyring errors on certain settings, like a virtualenv with symlinks, I'm getting errors after a suspend, related to a error to connect to the dbus socket. wrapping all of it in a conditional we avoid that kind of error. --- src/leap/bitmask/util/keyring_helpers.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/util/keyring_helpers.py b/src/leap/bitmask/util/keyring_helpers.py index b202d47e..ee2d7a1c 100644 --- a/src/leap/bitmask/util/keyring_helpers.py +++ b/src/leap/bitmask/util/keyring_helpers.py @@ -19,19 +19,23 @@ Keyring helpers. """ import logging -import keyring +try: + import keyring + from keyring.backends.file import EncryptedKeyring, PlaintextKeyring + OBSOLETE_KEYRINGS = [ + EncryptedKeyring, + PlaintextKeyring + ] + canuse = lambda kr: (kr is not None + and kr.__class__ not in OBSOLETE_KEYRINGS) -from keyring.backends.file import EncryptedKeyring, PlaintextKeyring - -logger = logging.getLogger(__name__) +except Exception: + # Problems when importing keyring! It might be a problem binding to the + # dbus socket, or stuff like that. + keyring = None -OBSOLETE_KEYRINGS = [ - EncryptedKeyring, - PlaintextKeyring -] - -canuse = lambda kr: kr is not None and kr.__class__ not in OBSOLETE_KEYRINGS +logger = logging.getLogger(__name__) def _get_keyring_with_fallback(): @@ -42,6 +46,8 @@ def _get_keyring_with_fallback(): This is a workaround for the cases in which the keyring module chooses an insecure keyring by default (ie, inside a virtualenv). """ + if not keyring: + return None kr = keyring.get_keyring() if not canuse(kr): try: @@ -61,6 +67,8 @@ def has_keyring(): :rtype: bool """ + if not keyring: + return False kr = _get_keyring_with_fallback() return canuse(kr) @@ -71,5 +79,7 @@ def get_keyring(): :rtype: keyringBackend or None """ + if not keyring: + return False kr = _get_keyring_with_fallback() return kr if canuse(kr) else None -- cgit v1.2.3 From 01208db08da7cb48ee6239c917fceb2d6846228d Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 20 Jan 2014 11:36:41 -0300 Subject: Disable Advanced Key Manager import. Closes #4877. --- src/leap/bitmask/gui/advanced_key_management.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py index 8f15719d..cbc8c3e3 100644 --- a/src/leap/bitmask/gui/advanced_key_management.py +++ b/src/leap/bitmask/gui/advanced_key_management.py @@ -48,6 +48,9 @@ class AdvancedKeyManagement(QtGui.QWidget): self.ui = Ui_AdvancedKeyManagement() self.ui.setupUi(self) + # XXX: Temporarily disable the key import. + self.ui.pbImportKeys.setVisible(False) + # if Soledad is not started yet if sameProxiedObjects(soledad, None): self.ui.gbMyKeyPair.setEnabled(False) @@ -57,12 +60,13 @@ class AdvancedKeyManagement(QtGui.QWidget): msg = msg.format(get_service_display_name(MX_SERVICE)) self.ui.lblStatus.setText(msg) return - else: - msg = self.tr( - "WARNING:
" - "This is an experimental feature, you can lose access to " - "existing e-mails.") - self.ui.lblStatus.setText(msg) + # XXX: since import is disabled this is no longer a dangerous feature. + # else: + # msg = self.tr( + # "WARNING:
" + # "This is an experimental feature, you can lose access to " + # "existing e-mails.") + # self.ui.lblStatus.setText(msg) self._keymanager = keymanager self._soledad = soledad -- cgit v1.2.3 From 4426635373a4f59249f4da17b581cea3ee7f33d8 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 22 Jan 2014 14:58:38 -0300 Subject: Don't use an empty password to configure email. - Also add translation support for the help message. [Closes #4985] --- src/leap/bitmask/gui/mainwindow.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 3f41900b..ffedfa1c 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -888,7 +888,7 @@ class MainWindow(QtGui.QMainWindow): url = ("bitmask addon") - msg = ( + msg = self.tr( "Instructions to use mail:
" "If you use Thunderbird you can use the Bitmask extension helper. " "Search for 'Bitmask' in the add-on manager or download it " @@ -898,7 +898,8 @@ class MainWindow(QtGui.QMainWindow): " Incoming -> IMAP, port: {1}
" " Outgoing -> SMTP, port: {2}
" " Username -> your bitmask username.
" - " Password -> leave it empty." + " Password -> does not matter, use any text. " + " Just don't leave it empty and don't use your account's password." "").format(url, IMAP_PORT, smtp_port) QtGui.QMessageBox.about(self, self.tr("Bitmask Help"), msg) -- cgit v1.2.3 From 1221160b11a32164c966cb1130bb3e0d74866d98 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 22 Jan 2014 17:18:08 -0300 Subject: Select by default the use of an existing provider. [Closes #4488] --- src/leap/bitmask/gui/ui/wizard.ui | 73 ++++++++++++++++++++------------------- src/leap/bitmask/gui/wizard.py | 4 +++ 2 files changed, 42 insertions(+), 35 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui index cf591470..1ddd854f 100644 --- a/src/leap/bitmask/gui/ui/wizard.ui +++ b/src/leap/bitmask/gui/ui/wizard.ui @@ -269,7 +269,20 @@ Configure or select a provider - + + + + + + + https:// + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + Configure new provider: @@ -279,30 +292,20 @@ - - + + - Use existing one: + Check - - + + - https:// - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Use existing one: - - - - - - - - - Check + + false @@ -820,8 +823,8 @@ setFocus() - 167 - 192 + 174 + 174 265 @@ -836,12 +839,12 @@ setFocus() - 171 - 164 + 174 + 227 - 246 - 164 + 425 + 254 @@ -852,12 +855,12 @@ setDisabled(bool) - 169 - 196 + 174 + 174 - 327 - 163 + 454 + 254 @@ -868,8 +871,8 @@ setDisabled(bool) - 169 - 162 + 174 + 227 269 @@ -884,12 +887,12 @@ setDisabled(bool) - 154 - 193 + 169 + 174 - 498 - 170 + 520 + 255 diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index b99e8db6..d5940c52 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -205,6 +205,10 @@ class Wizard(QtGui.QWizard): random.shuffle(pinned) # don't prioritize alphabetically self.ui.cbProviders.addItems(pinned) + # We have configured providers, so by default we select the + # 'Use existing provider' option. + self.ui.rbExistingProvider.setChecked(True) + def get_domain(self): return self._domain -- cgit v1.2.3 From d4e53f6cef9ba2b476cc8308f132d8af20e79156 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 23 Jan 2014 17:07:02 -0300 Subject: Replace provider hardcoded path with helper. Also reorder some imports and remove unused ones. --- src/leap/bitmask/backend.py | 6 ++---- src/leap/bitmask/config/providerconfig.py | 10 ++++------ src/leap/bitmask/gui/preferenceswindow.py | 7 ++----- src/leap/bitmask/gui/wizard.py | 9 +++------ src/leap/bitmask/provider/providerbootstrapper.py | 15 ++++++++------- src/leap/bitmask/services/mail/plumber.py | 11 +++++------ 6 files changed, 24 insertions(+), 34 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 6b29d4b3..df79381c 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -18,7 +18,6 @@ Backend for everything """ import logging -import os from Queue import Queue, Empty @@ -29,6 +28,7 @@ from twisted.python import log import zope.interface from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.provider import get_provider_path from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper # Frontend side @@ -155,9 +155,7 @@ class Provider(object): # 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")) + self._provider_config.load(get_provider_path(provider)) if self._provider_config.loaded(): d = self._provider_bootstrapper.run_provider_setup_checks( diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py index e80b2337..2ebe05ce 100644 --- a/src/leap/bitmask/config/providerconfig.py +++ b/src/leap/bitmask/config/providerconfig.py @@ -21,11 +21,12 @@ Provider configuration import logging import os -from leap.common.check import leap_check -from leap.common.config.baseconfig import BaseConfig, LocalizedKey +from leap.bitmask import provider from leap.bitmask.config.provider_spec import leap_provider_spec from leap.bitmask.services import get_service_display_name from leap.bitmask.util import get_path_prefix +from leap.common.check import leap_check +from leap.common.config.baseconfig import BaseConfig, LocalizedKey logger = logging.getLogger(__name__) @@ -55,10 +56,7 @@ class ProviderConfig(BaseConfig): :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): + if not provider_config.load(provider.get_provider_path(domain)): provider_config = None return provider_config diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 517a90c4..b2cc2236 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -18,7 +18,6 @@ """ Preferences window """ -import os import logging from functools import partial @@ -26,6 +25,7 @@ from functools import partial from PySide import QtCore, QtGui from zope.proxy import sameProxiedObjects +from leap.bitmask.provider import get_provider_path from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.gui.ui_preferences import Ui_Preferences from leap.soledad.client import NoStorageSecret @@ -383,10 +383,7 @@ class PreferencesWindow(QtGui.QDialog): :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): + if not provider_config.load(get_provider_path(domain)): provider_config = None return provider_config diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index d5940c52..baf1ab8b 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -17,7 +17,6 @@ """ First run wizard """ -import os import logging import json import random @@ -30,10 +29,11 @@ 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 import get_provider_path from leap.bitmask.services import get_service_display_name, get_supported -from leap.bitmask.util.request_helpers import get_content from leap.bitmask.util.keyring_helpers import has_keyring from leap.bitmask.util.password import basic_password_checks +from leap.bitmask.util.request_helpers import get_content from ui_wizard import Ui_Wizard @@ -491,10 +491,7 @@ class Wizard(QtGui.QWizard): check. Since this check is the last of this set, it also completes the page if passed """ - if self._provider_config.load(os.path.join("leap", - "providers", - self._domain, - "provider.json")): + if self._provider_config.load(get_provider_path(self._domain)): self._complete_task(data, self.ui.lblProviderInfo, True, self.SELECT_PROVIDER_PAGE) self._provider_checks_ok = True diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index 2a66b78c..654d1790 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -24,17 +24,18 @@ import sys import requests +from leap.bitmask import provider +from leap.bitmask import util from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig, MissingCACert -from leap.bitmask.util.request_helpers import get_content -from leap.bitmask import util -from leap.bitmask.util.constants import REQUEST_TIMEOUT +from leap.bitmask.provider import get_provider_path from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper -from leap.bitmask import provider +from leap.bitmask.util.constants import REQUEST_TIMEOUT +from leap.bitmask.util.request_helpers import get_content from leap.common import ca_bundle from leap.common.certs import get_digest -from leap.common.files import check_and_fix_urw_only, get_mtime, mkdir_p from leap.common.check import leap_assert, leap_assert_type, leap_check +from leap.common.files import check_and_fix_urw_only, get_mtime, mkdir_p logger = logging.getLogger(__name__) @@ -166,8 +167,8 @@ class ProviderBootstrapper(AbstractBootstrapper): headers = {} domain = self._domain.encode(sys.getfilesystemencoding()) provider_json = os.path.join(util.get_path_prefix(), - "leap", "providers", domain, - "provider.json") + get_provider_path(domain)) + mtime = get_mtime(provider_json) if self._download_if_needed and mtime: diff --git a/src/leap/bitmask/services/mail/plumber.py b/src/leap/bitmask/services/mail/plumber.py index 49514655..07a2608c 100644 --- a/src/leap/bitmask/services/mail/plumber.py +++ b/src/leap/bitmask/services/mail/plumber.py @@ -17,8 +17,8 @@ """ Utils for manipulating local mailboxes. """ -import logging import getpass +import logging import os from collections import defaultdict @@ -28,8 +28,9 @@ from twisted.internet import defer from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.util import flatten, get_path_prefix +from leap.bitmask.provider import get_provider_path from leap.bitmask.services.soledad.soledadbootstrapper import get_db_paths +from leap.bitmask.util import flatten, get_path_prefix from leap.mail.imap.account import SoledadBackedAccount from leap.soledad.client import Soledad @@ -112,10 +113,8 @@ class MBOXPlumber(object): self.sol = None self._settings = LeapSettings() - provider_config_path = os.path.join( - get_path_prefix(), - "leap", "providers", - provider, "provider.json") + provider_config_path = os.path.join(get_path_prefix(), + get_provider_path(provider)) provider_config = ProviderConfig() loaded = provider_config.load(provider_config_path) if not loaded: -- cgit v1.2.3 From f4f0930ab7ce749b09d7ab12e26cb14e3085d54e Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 24 Jan 2014 11:29:09 -0300 Subject: Handle wizard close correctly. Closes #4986. - Refactor connection and disconnection from backend. --- src/leap/bitmask/gui/wizard.py | 49 +++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 25 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index baf1ab8b..e1bed6b8 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -70,8 +70,6 @@ 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")) @@ -90,19 +88,8 @@ class Wizard(QtGui.QWizard): self.ui.btnCheck.clicked.connect(self._check_provider) self.ui.lnProvider.returnPressed.connect(self._check_provider) - self._backend.signaler.prov_name_resolution.connect( - self._name_resolution) - self._backend.signaler.prov_https_connection.connect( - self._https_connection) - self._backend.signaler.prov_download_provider_info.connect( - self._download_provider_info) - - self._backend.signaler.prov_download_ca_cert.connect( - self._download_ca_cert) - self._backend.signaler.prov_check_ca_fingerprint.connect( - self._check_ca_fingerprint) - self._backend.signaler.prov_check_api_certificate.connect( - self._check_api_certificate) + self._backend = backend + self._backend_connect() self._domain = None # HACK!! We need provider_config for the time being, it'll be @@ -172,6 +159,7 @@ class Wizard(QtGui.QWizard): self._provider_setup_ok = False self.ui.lnProvider.setText('') self.ui.grpCheckProvider.setVisible(False) + self._backend_disconnect() def _load_configured_providers(self): """ @@ -688,25 +676,36 @@ class Wizard(QtGui.QWizard): self.ui.lblPassword.setText("") self.ui.lblPassword2.setText("") - def closeEvent(self, event): + def _backend_connect(self): + """ + Connects all the backend signals with the wizard. + """ + sig = self._backend.signaler + sig.prov_name_resolution.connect(self._name_resolution) + sig.prov_https_connection.connect(self._https_connection) + sig.prov_download_provider_info.connect(self._download_provider_info) + + sig.prov_download_ca_cert.connect(self._download_ca_cert) + sig.prov_check_ca_fingerprint.connect(self._check_ca_fingerprint) + sig.prov_check_api_certificate.connect(self._check_api_certificate) + + def _backend_disconnect(self): """ This method is called when the wizard dialog is closed. We disconnect all the backend signals in here. """ + sig = self._backend.signaler try: # disconnect backend signals - self._backend.signaler.prov_name_resolution.disconnect( - self._name_resolution) - self._backend.signaler.prov_https_connection.disconnect( - self._https_connection) - self._backend.signaler.prov_download_provider_info.disconnect( + sig.prov_name_resolution.disconnect(self._name_resolution) + sig.prov_https_connection.disconnect(self._https_connection) + sig.prov_download_provider_info.disconnect( self._download_provider_info) - self._backend.signaler.prov_download_ca_cert.disconnect( - self._download_ca_cert) - self._backend.signaler.prov_check_ca_fingerprint.disconnect( + sig.prov_download_ca_cert.disconnect(self._download_ca_cert) + sig.prov_check_ca_fingerprint.disconnect( self._check_ca_fingerprint) - self._backend.signaler.prov_check_api_certificate.disconnect( + sig.prov_check_api_certificate.disconnect( self._check_api_certificate) except RuntimeError: pass # Signal was not connected -- cgit v1.2.3 From ed40a4a46a19c587aee6a65d576842410bc83aa2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 22 Jan 2014 00:41:28 -0400 Subject: make plumber use memory store --- src/leap/bitmask/services/mail/plumber.py | 36 +++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 9 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/mail/plumber.py b/src/leap/bitmask/services/mail/plumber.py index 07a2608c..c16a1fed 100644 --- a/src/leap/bitmask/services/mail/plumber.py +++ b/src/leap/bitmask/services/mail/plumber.py @@ -33,6 +33,8 @@ from leap.bitmask.services.soledad.soledadbootstrapper import get_db_paths from leap.bitmask.util import flatten, get_path_prefix from leap.mail.imap.account import SoledadBackedAccount +from leap.mail.imap.memorystore import MemoryStore +from leap.mail.imap.soledadstore import SoledadStore from leap.soledad.client import Soledad logger = logging.getLogger(__name__) @@ -128,7 +130,8 @@ class MBOXPlumber(object): self.uuid = self._settings.get_uuid(self.userid) if not self.uuid: print "Cannot get UUID from settings. Log in at least once." - return self.exit() + return False + print "UUID: %s" % (self.uuid) secrets, localdb = get_db_paths(self.uuid) @@ -136,7 +139,13 @@ class MBOXPlumber(object): self.sol = initialize_soledad( self.uuid, self.userid, self.passwd, secrets, localdb, "/tmp", "/tmp") - self.acct = SoledadBackedAccount(self.userid, self.sol) + memstore = MemoryStore( + permanent_store=SoledadStore(self.sol), + write_period=5) + self.acct = SoledadBackedAccount(self.userid, self.sol, + memstore=memstore) + return True + # # Account repairing # @@ -145,7 +154,10 @@ class MBOXPlumber(object): """ Repair mbox uids for all mboxes in this account. """ - self._init_local_soledad() + init = self._init_local_soledad() + if not init: + return self.exit() + for mbox_name in self.acct.mailboxes: self.repair_mbox_uids(mbox_name) print "done." @@ -219,9 +231,10 @@ class MBOXPlumber(object): with open(mail_filename) as f: mail_string = f.read() - uid = self._mbox.getUIDNext() - print "saving with UID: %s" % uid - d = self._mbox.messages.add_msg(mail_string, uid=uid) + #uid = self._mbox.getUIDNext() + #print "saving with UID: %s" % uid + d = self._mbox.messages.add_msg( + mail_string, notify_on_disk=True) return d def import_maildir(self, mbox_name="INBOX"): @@ -238,7 +251,10 @@ class MBOXPlumber(object): print "ERROR: maildir path does not exist." return - self._init_local_soledad() + init = self._init_local_soledad() + if not init: + return self.exit() + mbox = self.acct.getMailbox(mbox_name) self._mbox = mbox len_mbox = mbox.getMessageCount() @@ -259,6 +275,8 @@ class MBOXPlumber(object): deferreds = [] for f_name in mail_files: deferreds.append(self.import_mail(f_name)) + print "deferreds: ", deferreds + d1 = defer.gatherResults(deferreds, consumeErrors=False) d1.addCallback(all_saved) d1.addCallback(self._cbExit) @@ -268,9 +286,9 @@ class MBOXPlumber(object): def exit(self): from twisted.internet import reactor - if self.sol: - self.sol.close() try: + if self.sol: + self.sol.close() reactor.stop() except Exception: pass -- cgit v1.2.3 From 80a3983b5d3562906fcc81e8b196810d7d5a82cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 31 Jan 2014 12:30:53 -0300 Subject: Only sync soledad at account creation, otherwise defer to thread --- src/leap/bitmask/services/soledad/soledadbootstrapper.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 5351bcd2..f7217af6 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -26,6 +26,7 @@ from ssl import SSLError from PySide import QtCore from u1db import errors as u1db_errors +from twisted.internet import threads from zope.proxy import sameProxiedObjects from leap.bitmask.config import flags @@ -288,7 +289,18 @@ class SoledadBootstrapper(AbstractBootstrapper): self._init_keymanager(self._address) self.local_only_ready.emit({self.PASSED_KEY: True}) else: - self._do_soledad_sync() + try: + address = make_address( + self._user, self._provider_config.get_domain()) + self._init_keymanager(address) + self._keymanager.get_key( + address, openpgp.OpenPGPKey, + private=True, fetch_remote=False) + threads.deferToThread(self._do_soledad_sync) + except KeyNotFound: + logger.debug("Key not found. Generating key for %s" % + (address,)) + self._do_soledad_sync() def _pick_server(self, uuid): """ @@ -546,7 +558,6 @@ class SoledadBootstrapper(AbstractBootstrapper): address = make_address( self._user, self._provider_config.get_domain()) - self._init_keymanager(address) logger.debug("Retrieving key for %s" % (address,)) try: -- cgit v1.2.3 From 403c681e3d420479747407ae77d5c157c0677dd9 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 31 Jan 2014 13:03:08 -0300 Subject: Improve logs readability - levelname: after time, fixed width, left aligned - Line number before name and with fixed width - function with () --- src/leap/bitmask/app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 941ba4f8..a702ab3a 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -101,8 +101,9 @@ def add_logger_handlers(debug=False, logfile=None, replace_stdout=True): logger = logging.getLogger(name='leap') logger.setLevel(level) - log_format = ('%(asctime)s - %(name)s:%(funcName)s:L#%(lineno)s ' - '- %(levelname)s - %(message)s') + # levelname length == 8, since 'CRITICAL' is the longest + log_format = ('%(asctime)s - %(levelname)-8s - ' + 'L#%(lineno)-4s : %(name)s:%(funcName)s() - %(message)s') formatter = logging.Formatter(log_format) # Console handler -- cgit v1.2.3 From 2ea1ffae0423355e626145dcd30d52fdc1b2fee6 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 3 Feb 2014 12:49:03 -0300 Subject: Use the new log format in the GUI too. - Unify location for log format. --- src/leap/bitmask/app.py | 8 ++------ src/leap/bitmask/util/__init__.py | 5 +++++ src/leap/bitmask/util/leap_log_handler.py | 7 +++---- 3 files changed, 10 insertions(+), 10 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index a702ab3a..1146d1d0 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -50,7 +50,7 @@ from PySide import QtCore, QtGui from leap.bitmask import __version__ as VERSION from leap.bitmask.util import leap_argparse -from leap.bitmask.util import log_silencer +from leap.bitmask.util import log_silencer, LOG_FORMAT from leap.bitmask.util.leap_log_handler import LeapLogHandler from leap.bitmask.util.streamtologger import StreamToLogger from leap.bitmask.platform_init import IS_WIN @@ -100,11 +100,7 @@ def add_logger_handlers(debug=False, logfile=None, replace_stdout=True): # Create logger and formatter logger = logging.getLogger(name='leap') logger.setLevel(level) - - # levelname length == 8, since 'CRITICAL' is the longest - log_format = ('%(asctime)s - %(levelname)-8s - ' - 'L#%(lineno)-4s : %(name)s:%(funcName)s() - %(message)s') - formatter = logging.Formatter(log_format) + formatter = logging.Formatter(LOG_FORMAT) # Console handler try: diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py index c35be99e..2b2cd874 100644 --- a/src/leap/bitmask/util/__init__.py +++ b/src/leap/bitmask/util/__init__.py @@ -28,6 +28,11 @@ from leap.common.config import get_path_prefix as common_get_path_prefix # We'll give your money back if it does not alleviate the eye strain, at least. +# levelname length == 8, since 'CRITICAL' is the longest +LOG_FORMAT = ('%(asctime)s - %(levelname)-8s - ' + 'L#%(lineno)-4s : %(name)s:%(funcName)s() - %(message)s') + + def first(things): """ Return the head of a collection. diff --git a/src/leap/bitmask/util/leap_log_handler.py b/src/leap/bitmask/util/leap_log_handler.py index 1ab62331..807e53d4 100644 --- a/src/leap/bitmask/util/leap_log_handler.py +++ b/src/leap/bitmask/util/leap_log_handler.py @@ -21,6 +21,8 @@ import logging from PySide import QtCore +from leap.bitmask.util import LOG_FORMAT + class LogHandler(logging.Handler): """ @@ -52,10 +54,7 @@ class LogHandler(logging.Handler): :param logging_level: the debug level to define the color. :type logging_level: str. """ - log_format = ('%(asctime)s - %(name)s:%(funcName)s:L#%(lineno)s ' - '- %(levelname)s - %(message)s') - formatter = logging.Formatter(log_format) - + formatter = logging.Formatter(LOG_FORMAT) return formatter def emit(self, logRecord): -- cgit v1.2.3 From d25b720cae45b87538985ead37e03e1a83bf7fa0 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 4 Feb 2014 11:22:13 -0300 Subject: Update menu name in wizard. [Closes 4984] --- src/leap/bitmask/gui/ui/wizard.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui index 1ddd854f..d9b8499e 100644 --- a/src/leap/bitmask/gui/ui/wizard.ui +++ b/src/leap/bitmask/gui/ui/wizard.ui @@ -59,7 +59,7 @@ - <html><head/><body><p>Now we will guide you through some configuration that is needed before you can connect for the first time.</p><p>If you ever need to modify these options again, you can find the wizard in the <span style=" font-style:italic;">'Settings'</span> menu from the main window.</p><p>Do you want to <span style=" font-weight:600;">sign up</span> for a new account, or <span style=" font-weight:600;">log in</span> with an already existing username?</p></body></html> + <html><head/><body><p>Now we will guide you through some configuration that is needed before you can connect for the first time.</p><p>If you ever need to modify these options again, you can find the wizard in the <span style=" font-style:italic;">'Bitmask -&gt; Create new account...'</span> menu from the main window.</p><p>Do you want to <span style=" font-weight:600;">sign up</span> for a new account, or <span style=" font-weight:600;">log in</span> with an already existing username?</p></body></html> Qt::RichText -- cgit v1.2.3 From d0499461511541ea3ca3b0b1bb3f0f5c7f900c86 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 5 Feb 2014 20:13:31 -0400 Subject: fix typos --- src/leap/bitmask/gui/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index ffedfa1c..86fc1022 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -893,7 +893,7 @@ class MainWindow(QtGui.QMainWindow): "If you use Thunderbird you can use the Bitmask extension helper. " "Search for 'Bitmask' in the add-on manager or download it " "from: {0}.

" - "You can configure bitmask manually with this options:
" + "You can configure Bitmask manually with these options:
" "" " Incoming -> IMAP, port: {1}
" " Outgoing -> SMTP, port: {2}
" -- cgit v1.2.3 From 7e8c64770da710c4f9807d7789bae87fa44f4026 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 6 Feb 2014 16:27:14 -0300 Subject: Take care of None value for the uuid. Receiving a None value was raising an exception that didn't show up since was trapped inside the srpauth:logout method. [Closes #4995] [Closes #5071] --- src/leap/bitmask/config/leapsettings.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index 91ff83a8..13a1e99e 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -372,12 +372,16 @@ class LeapSettings(object): Sets the uuid for a given username. :param username: the full user identifier in the form user@provider - :type username: basestring - :param value: the uuid to save - :type value: basestring + :type username: str or unicode + :param value: the uuid to save or None to remove it + :type value: str or unicode or None """ leap_assert("@" in username, "Expected username in the form user@provider") user, provider = username.split('@') - leap_assert(len(value) > 0, "We cannot save an empty uuid") - self._settings.setValue(self.UUIDFORUSER_KEY % (provider, user), value) + key = self.UUIDFORUSER_KEY % (provider, user) + if value is None: + self._settings.remove(key) + else: + leap_assert(len(value) > 0, "We cannot save an empty uuid") + self._settings.setValue(key, value) -- cgit v1.2.3 From e4c80d78567d79ff0ad4be8e15629e9ed93259bb Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 6 Feb 2014 15:39:14 -0200 Subject: Ensure IMAP flushes data to disk before quitting. Closes #5095. --- src/leap/bitmask/gui/mainwindow.py | 14 +++++++++++--- src/leap/bitmask/services/mail/conductor.py | 14 ++++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 86fc1022..db24a1c8 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -19,6 +19,7 @@ Main window for Bitmask. """ import logging +from threading import Condition from PySide import QtCore, QtGui from datetime import datetime from twisted.internet import threads @@ -112,6 +113,9 @@ class MainWindow(QtGui.QMainWindow): # We give EIP some time to come up before starting soledad anyway EIP_TIMEOUT = 60000 # in milliseconds + # We give each service some time to come to a halt before forcing quit + SERVICE_STOP_TIMEOUT = 20 + def __init__(self, quit_callback, openvpn_verb=1, bypass_checks=False): @@ -1330,8 +1334,13 @@ class MainWindow(QtGui.QMainWindow): TRIGGERS: self.logout """ + cv = Condition() + cv.acquire() # TODO call stop_mail_service - self._mail_conductor.stop_imap_service() + threads.deferToThread(self._mail_conductor.stop_imap_service, cv) + # and wait for it to be stopped + logger.debug('Waiting for imap service to stop.') + cv.wait(self.SERVICE_STOP_TIMEOUT) # end service control methods (imap) @@ -1857,7 +1866,7 @@ class MainWindow(QtGui.QMainWindow): """ logger.debug('About to quit, doing cleanup...') - self._mail_conductor.stop_imap_service() + self._stop_imap_service() if self._srp_auth is not None: if self._srp_auth.get_session_id() is not None or \ @@ -1899,7 +1908,6 @@ class MainWindow(QtGui.QMainWindow): self._backend.stop() self._cleanup_and_quit() - self._really_quit = True if self._wizard: diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index fc53923c..f5892468 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -95,9 +95,13 @@ class IMAPControl(object): logger.debug("Starting loop") self.imap_service.start_loop() - def stop_imap_service(self): + def stop_imap_service(self, cv): """ Stops imap service (fetcher, factory and port). + + :param cv: A condition variable to which we can signal when imap + indeed stops. + :type cv: threading.Condition """ self.imap_connection.qtsigs.disconnecting_signal.emit() # TODO We should homogenize both services. @@ -110,7 +114,13 @@ class IMAPControl(object): self.imap_port.stopListening() # Stop the protocol self.imap_factory.theAccount.closed = True - self.imap_factory.doStop() + self.imap_factory.doStop(cv) + else: + # main window does not have to wait because there's no service to + # be stopped, so we release the condition variable + cv.acquire() + cv.notify() + cv.release() def fetch_incoming_mail(self): """ -- cgit v1.2.3 From 61420592e80d5446f8a5fba317316beeaff287e2 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 7 Feb 2014 16:07:26 -0300 Subject: Pep8 fixes, remove commented ipdb, group imports. --- src/leap/bitmask/crypto/tests/fake_provider.py | 1 - src/leap/bitmask/gui/mainwindow.py | 3 ++- src/leap/bitmask/platform_init/locks.py | 2 -- src/leap/bitmask/services/soledad/soledadbootstrapper.py | 8 ++++---- 4 files changed, 6 insertions(+), 8 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/crypto/tests/fake_provider.py b/src/leap/bitmask/crypto/tests/fake_provider.py index 54af485d..b8cdbb12 100755 --- a/src/leap/bitmask/crypto/tests/fake_provider.py +++ b/src/leap/bitmask/crypto/tests/fake_provider.py @@ -280,7 +280,6 @@ class FakeSession(Resource): if HAMK is None: print '[server] verification failed!!!' raise Exception("Authentication failed!") - #import ipdb;ipdb.set_trace() assert svr.authenticated() print "***" diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index db24a1c8..c078e3f4 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -20,8 +20,9 @@ Main window for Bitmask. import logging from threading import Condition -from PySide import QtCore, QtGui from datetime import datetime + +from PySide import QtCore, QtGui from twisted.internet import threads from zope.proxy import ProxyBase, setProxiedObject diff --git a/src/leap/bitmask/platform_init/locks.py b/src/leap/bitmask/platform_init/locks.py index 34f884dc..78ebf4cd 100644 --- a/src/leap/bitmask/platform_init/locks.py +++ b/src/leap/bitmask/platform_init/locks.py @@ -83,8 +83,6 @@ if platform_init.IS_UNIX: flock(self._fd, LOCK_EX | LOCK_NB) except IOError as exc: # could not get the lock - #import ipdb; ipdb.set_trace() - if exc.args[0] in (errno.EDEADLK, errno.EAGAIN): # errno 11 or 35 # Resource temporarily unavailable diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index f7217af6..9a292b18 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -508,10 +508,10 @@ class SoledadBootstrapper(AbstractBootstrapper): if flags.OFFLINE is True: args = (address, "https://localhost", self._soledad) kwargs = { - "session_id": "", - "ca_cert_path": "", - "api_uri": "", - "api_version": "", + "session_id": "", + "ca_cert_path": "", + "api_uri": "", + "api_version": "", "uid": self._uuid, "gpgbinary": self._get_gpg_bin_path() } -- cgit v1.2.3 From 29902330067f564185d7b57a864be2099f8ea2e8 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 10 Feb 2014 14:29:09 -0300 Subject: Properly handle defer cancelling. - Fix issues related to "Cancel login does not work". - Move srpauth errback to mainwindow. - Add signal for provider setup cancel. - Add support to cancel the soledad defer. [Closes #4869] [Closes #4973] --- src/leap/bitmask/backend.py | 36 ++++++++--- src/leap/bitmask/crypto/srpauth.py | 13 ---- src/leap/bitmask/gui/mainwindow.py | 69 +++++++++++++++++----- src/leap/bitmask/services/abstractbootstrapper.py | 7 +++ .../services/soledad/soledadbootstrapper.py | 2 +- 5 files changed, 89 insertions(+), 38 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index df79381c..34457e34 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -141,6 +141,14 @@ class Provider(object): self._download_provider_defer = d return d + def cancel_setup_provider(self): + """ + Cancel the ongoing setup provider defer (if any). + """ + d = self._download_provider_defer + if d is not None: + d.cancel() + def bootstrap(self, provider): """ Second stage of bootstrapping for a provider. @@ -195,6 +203,8 @@ class Signaler(QtCore.QObject): prov_unsupported_client = QtCore.Signal(object) prov_unsupported_api = QtCore.Signal(object) + prov_cancelled_setup = 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 @@ -208,6 +218,7 @@ class Signaler(QtCore.QObject): PROV_PROBLEM_WITH_PROVIDER_KEY = "prov_problem_with_provider" PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client" PROV_UNSUPPORTED_API = "prov_unsupported_api" + PROV_CANCELLED_SETUP = "prov_cancelled_setup" def __init__(self): """ @@ -225,7 +236,8 @@ class Signaler(QtCore.QObject): self.PROV_CHECK_API_CERTIFICATE_KEY, self.PROV_PROBLEM_WITH_PROVIDER_KEY, self.PROV_UNSUPPORTED_CLIENT, - self.PROV_UNSUPPORTED_API + self.PROV_UNSUPPORTED_API, + self.PROV_CANCELLED_SETUP, ] for sig in signals: @@ -355,17 +367,20 @@ class Backend(object): # 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) + if d is not None: # d may be None if a defer chain is cancelled. + # 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 defer.CancelledError: + logger.debug("defer cancelled somewhere (CancelledError).") except Exception: # But we log the rest log.err() @@ -387,5 +402,8 @@ class Backend(object): def setup_provider(self, provider): self._call_queue.put(("provider", "setup_provider", None, provider)) + def cancel_setup_provider(self): + self._call_queue.put(("provider", "cancel_setup_provider", None)) + def provider_bootstrap(self, provider): self._call_queue.put(("provider", "bootstrap", None, provider)) diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index bdd38db2..1fb7b774 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -655,7 +655,6 @@ class SRPAuth(QtCore.QObject): username = username.lower() d = self.__instance.authenticate(username, password) d.addCallback(self._gui_notify) - d.addErrback(self._errback) return d def change_password(self, current_password, new_password): @@ -695,18 +694,6 @@ class SRPAuth(QtCore.QObject): logger.debug("Successful login!") self.authentication_finished.emit(True, self.tr("Succeeded")) - def _errback(self, failure): - """ - General errback for the whole login process. Will notify the - UI with the proper signal. - - :param failure: Failure object captured from a callback. - :type failure: twisted.python.failure.Failure - """ - logger.error("Error logging in %s" % (failure,)) - self.authentication_finished.emit(False, "%s" % (failure.value,)) - failure.trap(Exception) - def get_session_id(self): return self.__instance.get_session_id() diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index c078e3f4..6512ffce 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -23,8 +23,9 @@ from threading import Condition from datetime import datetime from PySide import QtCore, QtGui -from twisted.internet import threads from zope.proxy import ProxyBase, setProxiedObject +from twisted.internet import threads +from twisted.internet.defer import CancelledError from leap.bitmask import __version__ as VERSION from leap.bitmask import __version_hash__ as VERSION_HASH @@ -342,7 +343,7 @@ class MainWindow(QtGui.QMainWindow): self._keymanager = ProxyBase(None) self._login_defer = None - self._download_provider_defer = None + self._soledad_defer = None self._mail_conductor = mail_conductor.MailConductor( self._soledad, self._keymanager) @@ -395,6 +396,8 @@ class MainWindow(QtGui.QMainWindow): sig.prov_unsupported_client.connect(self._needs_update) sig.prov_unsupported_api.connect(self._incompatible_api) + sig.prov_cancelled_setup.connect(self._set_login_cancelled) + def _backend_disconnect(self): """ Helper to disconnect from backend signals. @@ -1035,6 +1038,25 @@ class MainWindow(QtGui.QMainWindow): if self._login_widget.start_login(): self._download_provider_config() + def _login_errback(self, failure): + """ + Error handler for the srpauth.authenticate method. + + :param failure: failure object that Twisted generates + :type failure: twisted.python.failure.Failure + """ + # NOTE: this behavior needs to be managed through the signaler, + # as we are doing with the prov_cancelled_setup signal. + # After we move srpauth to the backend, we need to update this. + logger.error("Error logging in, {0!r}".format(failure)) + if failure.check(CancelledError): + logger.debug("Defer cancelled.") + failure.trap(Exception) + self._set_login_cancelled() + else: + self._login_widget.set_status(str(failure.value)) + self._login_widget.set_enabled(True) + def _cancel_login(self): """ SLOT @@ -1043,17 +1065,29 @@ class MainWindow(QtGui.QMainWindow): Stops the login sequence. """ - logger.debug("Cancelling log in.") + logger.debug("Cancelling setup provider defer.") + self._backend.cancel_setup_provider() - if self._download_provider_defer: - logger.debug("Cancelling download provider defer.") - self._download_provider_defer.cancel() - - if self._login_defer: + if self._login_defer is not None: logger.debug("Cancelling login defer.") self._login_defer.cancel() + if self._soledad_defer is not None: + logger.debug("Cancelling soledad defer.") + self._soledad_defer.cancel() + + def _set_login_cancelled(self): + """ + SLOT + TRIGGERS: + Signaler.prov_cancelled_setup fired by + self._backend.cancel_setup_provider() + + This method re-enables the login widget and display a message for + the cancelled operation. + """ self._login_widget.set_status(self.tr("Log in cancelled by the user.")) + self._login_widget.set_enabled(True) def _provider_config_loaded(self, data): """ @@ -1080,6 +1114,7 @@ class MainWindow(QtGui.QMainWindow): # TODO Add errback! self._login_defer = self._srp_auth.authenticate(username, password) + self._login_defer.addErrback(self._login_errback) else: self._login_widget.set_status( "Unable to login: Problem with provider") @@ -1168,8 +1203,8 @@ class MainWindow(QtGui.QMainWindow): provider_config = self._provider_config if self._logged_user is not None: - fun = sb.run_soledad_setup_checks - fun(provider_config, username, password, + self._soledad_defer = sb.run_soledad_setup_checks( + provider_config, username, password, download_if_needed=True) ################################################################### @@ -1243,6 +1278,7 @@ class MainWindow(QtGui.QMainWindow): # Ok, now soledad is ready, so we can allow other things that # depend on soledad to start. + self._soledad_defer = None # this will trigger start_imap_service # and start_smtp_boostrapping @@ -1814,9 +1850,9 @@ class MainWindow(QtGui.QMainWindow): """ passed = data[self._backend.PASSED_KEY] if not passed: + msg = self.tr("Unable to connect: Problem with provider") + self._login_widget.set_status(msg) self._login_widget.set_enabled(True) - self._login_widget.set_status( - self.tr("Unable to connect: Problem with provider")) logger.error(data[self._backend.ERROR_KEY]) # @@ -1888,9 +1924,12 @@ class MainWindow(QtGui.QMainWindow): logger.debug("Cancelling login defer.") self._login_defer.cancel() - if self._download_provider_defer: - logger.debug("Cancelling download provider defer.") - self._download_provider_defer.cancel() + logger.debug("Cancelling setup provider defer.") + self._backend.cancel_setup_provider() + + if self._soledad_defer is not None: + logger.debug("Cancelling soledad defer.") + self._soledad_defer.cancel() # TODO missing any more cancels? diff --git a/src/leap/bitmask/services/abstractbootstrapper.py b/src/leap/bitmask/services/abstractbootstrapper.py index 3bee8e01..fc6bd3e9 100644 --- a/src/leap/bitmask/services/abstractbootstrapper.py +++ b/src/leap/bitmask/services/abstractbootstrapper.py @@ -28,6 +28,7 @@ from PySide import QtCore from twisted.python import log from twisted.internet import threads +from twisted.internet.defer import CancelledError from leap.common.check import leap_assert, leap_assert_type @@ -91,6 +92,12 @@ class AbstractBootstrapper(QtCore.QObject): :param failure: failure object that Twisted generates :type failure: twisted.python.failure.Failure """ + if failure.check(CancelledError): + logger.debug("Defer cancelled.") + failure.trap(Exception) + self._signaler.signal(self._signaler.PROV_CANCELLED_SETUP) + return + if self._signal_to_emit: err_msg = self._err_msg \ if self._err_msg is not None \ diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 9a292b18..53846b31 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -617,4 +617,4 @@ class SoledadBootstrapper(AbstractBootstrapper): (self._gen_key, self.gen_key) ] - self.addCallbackChain(cb_chain) + return self.addCallbackChain(cb_chain) -- cgit v1.2.3 From d8ce7c3b9f8b0176f8ac617ee30072a86c52f695 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 12 Feb 2014 09:50:57 -0300 Subject: Handle closed db exception during sync. [Closes #5130] --- src/leap/bitmask/services/soledad/soledadbootstrapper.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 9a292b18..9e797042 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -20,6 +20,7 @@ Soledad bootstrapping import logging import os import socket +import sqlite3 import sys from ssl import SSLError @@ -348,6 +349,10 @@ class SoledadBootstrapper(AbstractBootstrapper): # ubuntu folks. sync_tries -= 1 continue + except Exception as e: + logger.exception("Unhandled error while syncing " + "soledad: %r" % (e,)) + break # reached bottom, failed to sync # and there's nothing we can do... @@ -435,7 +440,9 @@ class SoledadBootstrapper(AbstractBootstrapper): except u1db_errors.InvalidGeneration as exc: logger.error("%r" % (exc,)) raise SoledadSyncError("u1db: InvalidGeneration") - + except sqlite3.ProgrammingError as e: + logger.exception("%r" % (e,)) + raise except Exception as exc: logger.exception("Unhandled error while syncing " "soledad: %r" % (exc,)) -- cgit v1.2.3 From 7cfe280436db6ef1eb89281c23c71163df0ecdfa Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 13 Feb 2014 12:31:11 -0300 Subject: Preserve settings' uuid on logout. --- src/leap/bitmask/crypto/srpauth.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 1fb7b774..b46f0ea6 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -600,7 +600,8 @@ class SRPAuth(QtCore.QObject): QtCore.QMutexLocker(self._uuid_lock) full_uid = "%s@%s" % ( self._username, self._provider_config.get_domain()) - self._settings.set_uuid(full_uid, uuid) + if uuid is not None: # avoid removing the uuid from settings + self._settings.set_uuid(full_uid, uuid) self._uuid = uuid def get_uuid(self): -- cgit v1.2.3 From b4b9f10698902005c8d463d4bf3939d40c2a2783 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 17 Feb 2014 11:13:55 -0400 Subject: silence decorator --- src/leap/bitmask/util/log_silencer.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/util/log_silencer.py b/src/leap/bitmask/util/log_silencer.py index b9f69ad2..56b290e4 100644 --- a/src/leap/bitmask/util/log_silencer.py +++ b/src/leap/bitmask/util/log_silencer.py @@ -46,6 +46,7 @@ class SelectiveSilencerFilter(logging.Filter): # to us. SILENCER_RULES = ( 'leap.common.events', + 'leap.common.decorators', ) def __init__(self): -- cgit v1.2.3 From f56c549e590d5f091690666cb3538a38700635ee Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 17 Feb 2014 16:57:45 -0300 Subject: Update keymanager auth to interact with webapp v2 (Closes #5120). --- .../bitmask/services/soledad/soledadbootstrapper.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index b61d0d43..797508a7 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -287,13 +287,13 @@ class SoledadBootstrapper(AbstractBootstrapper): "Null soledad, error while initializing") if flags.OFFLINE is True: - self._init_keymanager(self._address) + self._init_keymanager(self._address, token) self.local_only_ready.emit({self.PASSED_KEY: True}) else: try: address = make_address( self._user, self._provider_config.get_domain()) - self._init_keymanager(address) + self._init_keymanager(address, token) self._keymanager.get_key( address, openpgp.OpenPGPKey, private=True, fetch_remote=False) @@ -502,12 +502,14 @@ class SoledadBootstrapper(AbstractBootstrapper): leap_check(gpgbin is not None, "Could not find gpg binary") return gpgbin - def _init_keymanager(self, address): + def _init_keymanager(self, address, token): """ Initialize the keymanager. :param address: the address to initialize the keymanager with. :type address: str + :param token: the auth token for accessing webapp. + :type token: str """ srp_auth = self.srpauth logger.debug('initializing keymanager...') @@ -515,7 +517,6 @@ class SoledadBootstrapper(AbstractBootstrapper): if flags.OFFLINE is True: args = (address, "https://localhost", self._soledad) kwargs = { - "session_id": "", "ca_cert_path": "", "api_uri": "", "api_version": "", @@ -530,7 +531,7 @@ class SoledadBootstrapper(AbstractBootstrapper): self._soledad ) kwargs = { - "session_id": srp_auth.get_session_id(), + "token": token, "ca_cert_path": self._provider_config.get_ca_cert_path(), "api_uri": self._provider_config.get_api_uri(), "api_version": self._provider_config.get_api_version(), @@ -539,15 +540,20 @@ class SoledadBootstrapper(AbstractBootstrapper): } try: self._keymanager = KeyManager(*args, **kwargs) + except KeyNotFound: + logger.debug('key for %s not found.' % address) except Exception as exc: logger.exception(exc) raise if flags.OFFLINE is False: # make sure key is in server - logger.debug('sending key to server...') + logger.debug('Trying to send key to server...') try: self._keymanager.send_key(openpgp.OpenPGPKey) + except KeyNotFound: + logger.debug('No key found for %s, will generate soon.' + % address) except Exception as exc: logger.error("Error sending key to server.") logger.exception(exc) -- cgit v1.2.3 From 388196b372f693b596cf96c6982d5b38ff0d8b56 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 19 Feb 2014 14:44:27 -0300 Subject: Catch ProgrammingError from pysqlcipher too. Related to #5130. --- src/leap/bitmask/services/soledad/soledadbootstrapper.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 797508a7..7aa86a02 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -20,15 +20,16 @@ Soledad bootstrapping import logging import os import socket -import sqlite3 import sys from ssl import SSLError +from sqlite3 import ProgrammingError as sqlite_ProgrammingError from PySide import QtCore from u1db import errors as u1db_errors from twisted.internet import threads from zope.proxy import sameProxiedObjects +from pysqlcipher.dbapi2 import ProgrammingError as sqlcipher_ProgrammingError from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig @@ -440,7 +441,7 @@ class SoledadBootstrapper(AbstractBootstrapper): except u1db_errors.InvalidGeneration as exc: logger.error("%r" % (exc,)) raise SoledadSyncError("u1db: InvalidGeneration") - except sqlite3.ProgrammingError as e: + except (sqlite_ProgrammingError, sqlcipher_ProgrammingError) as e: logger.exception("%r" % (e,)) raise except Exception as exc: -- cgit v1.2.3 From 2d45a0e1cb0791c7e94b5d2a33c498954055a38e Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 13 Feb 2014 11:02:31 -0300 Subject: Improve logout action and fix typo. Related to #5131. [Closes #4815] --- src/leap/bitmask/gui/login.py | 11 ++++++++++- src/leap/bitmask/gui/mainwindow.py | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index 8a7c6996..4a483c32 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -222,6 +222,15 @@ class LoginWidget(QtGui.QWidget): self._set_cancel(not enabled) + def set_logout_btn_enabled(self, enabled): + """ + Enables or disables the logout button. + + :param enabled: wether they should be enabled or not + :type enabled: bool + """ + self.ui.btnLogout.setEnabled(enabled) + def _set_cancel(self, enabled=False): """ Enables or disables the cancel action in the "log in" process. @@ -349,7 +358,7 @@ class LoginWidget(QtGui.QWidget): """ Sets the widgets to the logging out state """ - self.ui.btnLogout.setText(self.tr("Loggin out...")) + self.ui.btnLogout.setText(self.tr("Logging out...")) self.ui.btnLogout.setEnabled(False) def done_logout(self): diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 6512ffce..b14ca19e 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1112,7 +1112,6 @@ class MainWindow(QtGui.QMainWindow): self._srp_auth.logout_finished.connect( self._done_logging_out) - # TODO Add errback! self._login_defer = self._srp_auth.authenticate(username, password) self._login_defer.addErrback(self._login_errback) else: @@ -1143,6 +1142,13 @@ class MainWindow(QtGui.QMainWindow): self._mail_conductor.userid = full_user_id self._login_defer = None self._start_eip_bootstrap() + + # if soledad/mail is enabled: + if MX_SERVICE in self._enabled_services: + btn_enabled = self._login_widget.set_logout_btn_enabled + btn_enabled(False) + self.soledad_ready.connect(lambda: btn_enabled(True)) + self.soledad_failed.connect(lambda: btn_enabled(True)) else: self._login_widget.set_enabled(True) @@ -1804,6 +1810,9 @@ class MainWindow(QtGui.QMainWindow): """ self._soledad_bootstrapper.cancel_bootstrap() setProxiedObject(self._soledad, None) + if self._soledad_defer is not None: + logger.debug("Cancelling soledad defer.") + self._soledad_defer.cancel() # reset soledad status flag self._already_started_soledad = False -- cgit v1.2.3 From 0af54b404fb82c4c3c36c9252f3df77afe104ff1 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 24 Feb 2014 16:38:40 -0300 Subject: Connect with correct soledad_failed signal. --- src/leap/bitmask/gui/mainwindow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index b14ca19e..eeab7e2b 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1148,7 +1148,8 @@ class MainWindow(QtGui.QMainWindow): btn_enabled = self._login_widget.set_logout_btn_enabled btn_enabled(False) self.soledad_ready.connect(lambda: btn_enabled(True)) - self.soledad_failed.connect(lambda: btn_enabled(True)) + self._soledad_bootstrapper.soledad_failed.connect( + lambda: btn_enabled(True)) else: self._login_widget.set_enabled(True) -- cgit v1.2.3 From 2abe8f333f9b920b79d30903697f0ecd616d8c71 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 25 Feb 2014 22:01:55 -0400 Subject: fix phrasing of unread mail notification --- src/leap/bitmask/gui/mail_status.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 6db17427..1d5ff319 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -362,12 +362,17 @@ class MailStatusWidget(QtGui.QWidget): ext_status = None if req.event == proto.IMAP_UNREAD_MAIL: + # By now, the semantics of the UNREAD_MAIL event are + # limited to mails with the Unread flag *in the Inbox". + # We could make this configurable to include all unread mail + # or all unread mail in subscribed folders. if self._started: count = req.content if count != "0": - status = self.tr("{0} Unread Emails").format(count) + status = self.tr("{0} Unread Emails " + "in your Inbox").format(count) if count == "1": - status = self.tr("1 Unread Email") + status = self.tr("1 Unread Email in your Inbox") self._set_mail_status(status, ready=2) else: -- cgit v1.2.3 From 306b62e7dcf757c2143d5db4380c0be42fd1e16a Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 24 Feb 2014 18:37:49 -0300 Subject: Add pastebin api wrapper file. Currently installation through PyPI isn't working so I embedded here. Add reference in license file. --- src/leap/bitmask/util/pastebin.py | 814 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 814 insertions(+) create mode 100755 src/leap/bitmask/util/pastebin.py (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/util/pastebin.py b/src/leap/bitmask/util/pastebin.py new file mode 100755 index 00000000..21b8a0b7 --- /dev/null +++ b/src/leap/bitmask/util/pastebin.py @@ -0,0 +1,814 @@ +#!/usr/bin/env python + +############################################################################# +# Pastebin.py - Python 3.2 Pastebin API. +# Copyright (C) 2012 Ian Havelock +# +# 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 . +# + +############################################################################# + +# This software is a derivative work of: +# http://winappdbg.sourceforge.net/blog/pastebin.py + +############################################################################# + + +__ALL__ = ['delete_paste', 'user_details', 'trending', 'pastes_by_user', + 'generate_user_key', 'legacy_paste', 'paste', 'Pastebin', + 'PastebinError'] + +import urllib + + +class PastebinError(RuntimeError): + """Pastebin API error. + + The error message returned by the web application is stored as the Python + exception message.""" + + +class PastebinAPI(object): + """Pastebin API interaction object. + + Public functions: + + paste -- Pastes a user-specified file or string using the new API-key POST + method. + + legacy_paste -- Pastes a user-specified file or string using the old + anonymous POST method. + + generate_user_key -- Generates a session-key that is required for other + functions. + + pastes_by_user -- Returns all public pastes submitted by the specified + login credentials. + + trending -- Returns the top trending paste. + + user_details -- Returns details about the user for the specified API user + key. + + delete_paste -- Adds two numbers together and returns the result.""" + + # String to determine bad API requests + _bad_request = 'Bad API request' + + # Base domain name + _base_domain = 'pastebin.com' + + # Valid Pastebin URLs begin with this string (kinda obvious) + _prefix_url = 'http://%s/' % _base_domain + + # Valid Pastebin URLs with a custom subdomain begin with this string + _subdomain_url = 'http://%%s.%s/' % _base_domain + + # URL to the LEGACY POST API + _legacy_api_url = 'http://%s/api_public.php' % _base_domain + + # URL to the POST API + _api_url = 'http://%s/api/api_post.php' % _base_domain + + # URL to the login POST API + _api_login_url = 'http://%s/api/api_login.php' % _base_domain + + # Valid paste_expire_date values: Never, 10 minutes, 1 Hour, 1 Day, 1 Month + paste_expire_date = ('N', '10M', '1H', '1D', '1M') + + # Valid paste_expire_date values (0 = public, 1 = unlisted, 2 = private) + paste_private = ('public', 'unlisted', 'private') + + # Valid parse_format values + paste_format = ( + '4cs', # 4CS + '6502acme', # 6502 ACME Cross Assembler + '6502kickass', # 6502 Kick Assembler + '6502tasm', # 6502 TASM/64TASS + 'abap', # ABAP + 'actionscript', # ActionScript + 'actionscript3', # ActionScript 3 + 'ada', # Ada + 'algol68', # ALGOL 68 + 'apache', # Apache Log + 'applescript', # AppleScript + 'apt_sources', # APT Sources + 'asm', # ASM (NASM) + 'asp', # ASP + 'autoconf', # autoconf + 'autohotkey', # Autohotkey + 'autoit', # AutoIt + 'avisynth', # Avisynth + 'awk', # Awk + 'bascomavr', # BASCOM AVR + 'bash', # Bash + 'basic4gl', # Basic4GL + 'bibtex', # BibTeX + 'blitzbasic', # Blitz Basic + 'bnf', # BNF + 'boo', # BOO + 'bf', # BrainFuck + 'c', # C + 'c_mac', # C for Macs + 'cil', # C Intermediate Language + 'csharp', # C# + 'cpp', # C++ + 'cpp-qt', # C++ (with QT extensions) + 'c_loadrunner', # C: Loadrunner + 'caddcl', # CAD DCL + 'cadlisp', # CAD Lisp + 'cfdg', # CFDG + 'chaiscript', # ChaiScript + 'clojure', # Clojure + 'klonec', # Clone C + 'klonecpp', # Clone C++ + 'cmake', # CMake + 'cobol', # COBOL + 'coffeescript', # CoffeeScript + 'cfm', # ColdFusion + 'css', # CSS + 'cuesheet', # Cuesheet + 'd', # D + 'dcs', # DCS + 'delphi', # Delphi + 'oxygene', # Delphi Prism (Oxygene) + 'diff', # Diff + 'div', # DIV + 'dos', # DOS + 'dot', # DOT + 'e', # E + 'ecmascript', # ECMAScript + 'eiffel', # Eiffel + 'email', # Email + 'epc', # EPC + 'erlang', # Erlang + 'fsharp', # F# + 'falcon', # Falcon + 'fo', # FO Language + 'f1', # Formula One + 'fortran', # Fortran + 'freebasic', # FreeBasic + 'freeswitch', # FreeSWITCH + 'gambas', # GAMBAS + 'gml', # Game Maker + 'gdb', # GDB + 'genero', # Genero + 'genie', # Genie + 'gettext', # GetText + 'go', # Go + 'groovy', # Groovy + 'gwbasic', # GwBasic + 'haskell', # Haskell + 'hicest', # HicEst + 'hq9plus', # HQ9 Plus + 'html4strict', # HTML + 'html5', # HTML 5 + 'icon', # Icon + 'idl', # IDL + 'ini', # INI file + 'inno', # Inno Script + 'intercal', # INTERCAL + 'io', # IO + 'j', # J + 'java', # Java + 'java5', # Java 5 + 'javascript', # JavaScript + 'jquery', # jQuery + 'kixtart', # KiXtart + 'latex', # Latex + 'lb', # Liberty BASIC + 'lsl2', # Linden Scripting + 'lisp', # Lisp + 'llvm', # LLVM + 'locobasic', # Loco Basic + 'logtalk', # Logtalk + 'lolcode', # LOL Code + 'lotusformulas', # Lotus Formulas + 'lotusscript', # Lotus Script + 'lscript', # LScript + 'lua', # Lua + 'm68k', # M68000 Assembler + 'magiksf', # MagikSF + 'make', # Make + 'mapbasic', # MapBasic + 'matlab', # MatLab + 'mirc', # mIRC + 'mmix', # MIX Assembler + 'modula2', # Modula 2 + 'modula3', # Modula 3 + '68000devpac', # Motorola 68000 HiSoft Dev + 'mpasm', # MPASM + 'mxml', # MXML + 'mysql', # MySQL + 'newlisp', # newLISP + 'text', # None + 'nsis', # NullSoft Installer + 'oberon2', # Oberon 2 + 'objeck', # Objeck Programming Langua + 'objc', # Objective C + 'ocaml-brief', # OCalm Brief + 'ocaml', # OCaml + 'pf', # OpenBSD PACKET FILTER + 'glsl', # OpenGL Shading + 'oobas', # Openoffice BASIC + 'oracle11', # Oracle 11 + 'oracle8', # Oracle 8 + 'oz', # Oz + 'pascal', # Pascal + 'pawn', # PAWN + 'pcre', # PCRE + 'per', # Per + 'perl', # Perl + 'perl6', # Perl 6 + 'php', # PHP + 'php-brief', # PHP Brief + 'pic16', # Pic 16 + 'pike', # Pike + 'pixelbender', # Pixel Bender + 'plsql', # PL/SQL + 'postgresql', # PostgreSQL + 'povray', # POV-Ray + 'powershell', # Power Shell + 'powerbuilder', # PowerBuilder + 'proftpd', # ProFTPd + 'progress', # Progress + 'prolog', # Prolog + 'properties', # Properties + 'providex', # ProvideX + 'purebasic', # PureBasic + 'pycon', # PyCon + 'python', # Python + 'q', # q/kdb+ + 'qbasic', # QBasic + 'rsplus', # R + 'rails', # Rails + 'rebol', # REBOL + 'reg', # REG + 'robots', # Robots + 'rpmspec', # RPM Spec + 'ruby', # Ruby + 'gnuplot', # Ruby Gnuplot + 'sas', # SAS + 'scala', # Scala + 'scheme', # Scheme + 'scilab', # Scilab + 'sdlbasic', # SdlBasic + 'smalltalk', # Smalltalk + 'smarty', # Smarty + 'sql', # SQL + 'systemverilog', # SystemVerilog + 'tsql', # T-SQL + 'tcl', # TCL + 'teraterm', # Tera Term + 'thinbasic', # thinBasic + 'typoscript', # TypoScript + 'unicon', # Unicon + 'uscript', # UnrealScript + 'vala', # Vala + 'vbnet', # VB.NET + 'verilog', # VeriLog + 'vhdl', # VHDL + 'vim', # VIM + 'visualprolog', # Visual Pro Log + 'vb', # VisualBasic + 'visualfoxpro', # VisualFoxPro + 'whitespace', # WhiteSpace + 'whois', # WHOIS + 'winbatch', # Winbatch + 'xbasic', # XBasic + 'xml', # XML + 'xorg_conf', # Xorg Config + 'xpp', # XPP + 'yaml', # YAML + 'z80', # Z80 Assembler + 'zxbasic', # ZXBasic + ) + + def __init__(self): + pass + + def delete_paste(self, api_dev_key, api_user_key, api_paste_key): + """Delete the paste specified by the api_paste_key. + + Usage Example:: + from pastebin import PastebinAPI + x = PastebinAPI() + paste_to_delete = x.delete_paste( + '453a994e0e2f1efae07f8759e59e075b', + 'c57a18e6c0ae228cd4bd16fe36da381a', + 'WkgcTFtv') + print paste_to_delete + Paste Removed + + + @type api_dev_key: string + @param api_dev_key: The API Developer Key of a registered + U{http://pastebin.com} account. + + @type api_user_key: string + @param api_user_key: The API User Key of a U{http://pastebin.com} + registered user. + + @type api_paste_key: string + @param api_paste_key: The Paste Key of the paste to be deleted + (string after final / in + U{http://pastebin.com} URL). + + @rtype: string + @returns: A successful deletion returns 'Paste Removed'. + """ + + # Valid api developer key + argv = {'api_dev_key': str(api_dev_key)} + + # Requires pre-registered account + if api_user_key is not None: + argv['api_user_key'] = str(api_user_key) + + # Key of the paste to be deleted. + if api_paste_key is not None: + argv['api_paste_key'] = str(api_paste_key) + + # Valid API option - 'user_details' in this instance + argv['api_option'] = str('delete') + + # lets try to read the URL that we've just built. + request_string = urllib.urlopen(self._api_url, urllib.urlencode(argv)) + response = request_string.read() + + return response + + def user_details(self, api_dev_key, api_user_key): + """Return user details of the user specified by the api_user_key. + + + Usage Example:: + from pastebin import PastebinAPI + x = PastebinAPI() + details = x.user_details('453a994e0e2f1efae07f8759e59e075b', 'c57a18e6c0ae228cd4bd16fe36da381a') + print details + + MonkeyPuzzle + python + N + http://pastebin.com/i/guest.gif + 0 + + user@email.com + + 0 + + + + @type api_dev_key: string + @param api_dev_key: The API Developer Key of a registered + U{http://pastebin.com} account. + + @type api_user_key: string + @param api_user_key: The API User Key of a U{http://pastebin.com} + registered user. + + @rtype: string + @returns: Returns an XML string containing user information. + """ + + # Valid api developer key + argv = {'api_dev_key': str(api_dev_key)} + + # Requires pre-registered account to generate an api_user_key + # (see generate_user_key) + if api_user_key is not None: + argv['api_user_key'] = str(api_user_key) + + # Valid API option - 'user_details' in this instance + argv['api_option'] = str('userdetails') + + # lets try to read the URL that we've just built. + request_string = urllib.urlopen(self._api_url, urllib.urlencode(argv)) + response = request_string.read() + + # do some basic error checking here so we can gracefully handle any + # errors we are likely to encounter + if response.startswith(self._bad_request): + raise PastebinError(response) + + elif not response.startswith(''): + raise PastebinError(response) + + return response + + def trending(self, api_dev_key): + """Returns the top trending paste details. + + + Usage Example:: + from pastebin import PastebinAPI + x = PastebinAPI() + details = x.trending('453a994e0e2f1efae07f8759e59e075b') + print details + + jjMRFDH6 + 1333230838 + + 6416 + 0 + 0 + None + text + http://pastebin.com/jjMRFDH6 + 6384 + + + Note: Returns multiple trending pastes, not just 1. + + + @type api_dev_key: string + @param api_dev_key: The API Developer Key of a registered + U{http://pastebin.com} account. + + + @rtype: string + @return: Returns the string (XML formatted) containing the top + trending pastes. + """ + + # Valid api developer key + argv = {'api_dev_key': str(api_dev_key), 'api_option': str('trends')} + + # Valid API option - 'trends' is returns trending pastes + + # lets try to read the URL that we've just built. + request_string = urllib.urlopen(self._api_url, urllib.urlencode(argv)) + response = request_string.read() + + # do some basic error checking here so we can gracefully handle any + # errors we are likely to encounter + if response.startswith(self._bad_request): + raise PastebinError(response) + + elif not response.startswith(''): + raise PastebinError(response) + + return response + + def pastes_by_user(self, api_dev_key, api_user_key, results_limit=None): + """Returns all pastes for the provided api_user_key. + + + Usage Example:: + from pastebin import PastebinAPI + x = PastebinAPI() + details = x.user_details('453a994e0e2f1efae07f8759e59e075b', + 'c57a18e6c0ae228cd4bd16fe36da381a', + 100) + print details + + DLiSspYT + 1332714730 + Pastebin.py - Python 3.2 Pastebin.com API + 25300 + 0 + 0 + Python + python + http://pastebin.com/DLiSspYT + 70 + + + Note: Returns multiple pastes, not just 1. + + + @type api_dev_key: string + @param api_dev_key: The API Developer Key of a registered + U{http://pastebin.com} account. + + @type api_user_key: string + @param api_user_key: The API User Key of a U{http://pastebin.com} + registered user. + + @type results_limit: number + @param results_limit: The number of pastes to return between 1 - 1000. + + @rtype: string + @returns: Returns an XML string containing number of specified pastes + by user. + """ + + # Valid api developer key + argv = {'api_dev_key': str(api_dev_key)} + + # Requires pre-registered account + if api_user_key is not None: + argv['api_user_key'] = str(api_user_key) + + # Number of results to return - between 1 & 1000, default = 50 + if results_limit is None: + argv['api_results_limit'] = 50 + + if results_limit is not None: + if results_limit < 1: + argv['api_results_limit'] = 50 + elif results_limit > 1000: + argv['api_results_limit'] = 1000 + else: + argv['api_results_limit'] = int(results_limit) + + # Valid API option - 'paste' is default for new paste + argv['api_option'] = str('list') + + # lets try to read the URL that we've just built. + request_string = urllib.urlopen(self._api_url, urllib.urlencode(argv)) + response = request_string.read() + + # do some basic error checking here so we can gracefully handle any + # errors we are likely to encounter + if response.startswith(self._bad_request): + raise PastebinError(response) + + elif not response.startswith(''): + raise PastebinError(response) + + return response + + def generate_user_key(self, api_dev_key, username, password): + """Generate a user session key - needed for other functions. + + + Usage Example:: + from pastebin import PastebinAPI + x = PastebinAPI() + my_key = x.generate_user_key( + '453a994e0e2f1efae07f8759e59e075b', + 'MonkeyPuzzle', + '12345678') + print my_key + c57a18e6c0ae228cd4bd16fe36da381a + + + @type api_dev_key: string + @param api_dev_key: The API Developer Key of a registered + U{http://pastebin.com} account. + + @type username: string + @param username: The username of a registered U{http://pastebin.com} + account. + + @type password: string + @param password: The password of a registered U{http://pastebin.com} + account. + + @rtype: string + @returns: Session key (api_user_key) to allow authenticated + interaction to the API. + + """ + # Valid api developer key + argv = {'api_dev_key': str(api_dev_key)} + + # Requires pre-registered pastebin account + if username is not None: + argv['api_user_name'] = str(username) + + # Requires pre-registered pastebin account + if password is not None: + argv['api_user_password'] = str(password) + + # lets try to read the URL that we've just built. + data = urllib.urlencode(argv) + request_string = urllib.urlopen(self._api_login_url, data) + response = request_string.read() + + # do some basic error checking here so we can gracefully handle + # any errors we are likely to encounter + if response.startswith(self._bad_request): + raise PastebinError(response) + + return response + + def paste(self, api_dev_key, api_paste_code, + api_user_key=None, paste_name=None, paste_format=None, + paste_private=None, paste_expire_date=None): + + """Submit a code snippet to Pastebin using the new API. + + + Usage Example:: + from pastebin import PastebinAPI + x = PastebinAPI() + url = x.paste( + '453a994e0e2f1efae07f8759e59e075b' , + 'Snippet of code to paste goes here', + paste_name = 'title of paste', + api_user_key = 'c57a18e6c0ae228cd4bd16fe36da381a', + paste_format = 'python', + paste_private = 'unlisted', + paste_expire_date = '10M') + print url + http://pastebin.com/tawPUgqY + + + @type api_dev_key: string + @param api_dev_key: The API Developer Key of a registered + U{http://pastebin.com} account. + + @type api_paste_code: string + @param api_paste_code: The file or string to paste to body of the + U{http://pastebin.com} paste. + + @type api_user_key: string + @param api_user_key: The API User Key of a U{http://pastebin.com} + registered user. + If none specified, paste is made as a guest. + + @type paste_name: string + @param paste_name: (Optional) Title of the paste. + Default is to paste anonymously. + + @type paste_format: string + @param paste_format: (Optional) Programming language of the code being + pasted. This enables syntax highlighting when reading the code in + U{http://pastebin.com}. Default is no syntax highlighting (text is + just text and not source code). + + @type paste_private: string + @param paste_private: (Optional) C{'public'} if the paste is public + (visible by everyone), C{'unlisted'} if it's public but not + searchable. C{'private'} if the paste is private and not + searchable or indexed. + The Pastebin FAQ (U{http://pastebin.com/faq}) claims + private pastes are not indexed by search engines (aka Google). + + @type paste_expire_date: str + @param paste_expire_date: (Optional) Expiration date for the paste. + Once past this date the paste is deleted automatically. Valid + values are found in the L{PastebinAPI.paste_expire_date} class + member. + If not provided, the paste never expires. + + @rtype: string + @return: Returns the URL to the newly created paste. + """ + + # Valid api developer key + argv = {'api_dev_key': str(api_dev_key)} + + # Code snippet to submit + if api_paste_code is not None: + argv['api_paste_code'] = str(api_paste_code) + + # Valid API option - 'paste' is default for new paste + argv['api_option'] = str('paste') + + # API User Key + if api_user_key is not None: + argv['api_user_key'] = str(api_user_key) + elif api_user_key is None: + argv['api_user_key'] = str('') + + # Name of the poster + if paste_name is not None: + argv['api_paste_name'] = str(paste_name) + + # Syntax highlighting + if paste_format is not None: + paste_format = str(paste_format).strip().lower() + argv['api_paste_format'] = paste_format + + # Is the snippet private? + if paste_private is not None: + if paste_private == 'public': + argv['api_paste_private'] = int(0) + elif paste_private == 'unlisted': + argv['api_paste_private'] = int(1) + elif paste_private == 'private': + argv['api_paste_private'] = int(2) + + # Expiration for the snippet + if paste_expire_date is not None: + paste_expire_date = str(paste_expire_date).strip().upper() + argv['api_paste_expire_date'] = paste_expire_date + + # lets try to read the URL that we've just built. + request_string = urllib.urlopen(self._api_url, urllib.urlencode(argv)) + response = request_string.read() + + # do some basic error checking here so we can gracefully handle any + # errors we are likely to encounter + if response.startswith(self._bad_request): + raise PastebinError(response) + elif not response.startswith(self._prefix_url): + raise PastebinError(response) + + return response + + def legacy_paste(self, paste_code, + paste_name=None, paste_private=None, + paste_expire_date=None, paste_format=None): + """Unofficial python interface to the Pastebin legacy API. + + Unlike the official API, this one doesn't require an API key, so it's + virtually anonymous. + + + Usage Example:: + from pastebin import PastebinAPI + x = PastebinAPI() + url = x.legacy_paste('Snippet of code to paste goes here', + paste_name = 'title of paste', + paste_private = 'unlisted', + paste_expire_date = '10M', + paste_format = 'python') + print url + http://pastebin.com/tawPUgqY + + + @type paste_code: string + @param paste_code: The file or string to paste to body of the + U{http://pastebin.com} paste. + + @type paste_name: string + @param paste_name: (Optional) Title of the paste. + Default is to paste with no title. + + @type paste_private: string + @param paste_private: (Optional) C{'public'} if the paste is public + (visible by everyone), C{'unlisted'} if it's public but not + searchable. C{'private'} if the paste is private and not + searchable or indexed. + The Pastebin FAQ (U{http://pastebin.com/faq}) claims + private pastes are not indexed by search engines (aka Google). + + @type paste_expire_date: string + @param paste_expire_date: (Optional) Expiration date for the paste. + Once past this date the paste is deleted automatically. Valid + values are found in the L{PastebinAPI.paste_expire_date} class + member. + If not provided, the paste never expires. + + @type paste_format: string + @param paste_format: (Optional) Programming language of the code being + pasted. This enables syntax highlighting when reading the code in + U{http://pastebin.com}. Default is no syntax highlighting (text is + just text and not source code). + + @rtype: string + @return: Returns the URL to the newly created paste. + """ + + # Code snippet to submit + argv = {'paste_code': str(paste_code)} + + # Name of the poster + if paste_name is not None: + argv['paste_name'] = str(paste_name) + + # Is the snippet private? + if paste_private is not None: + argv['paste_private'] = int(bool(int(paste_private))) + + # Expiration for the snippet + if paste_expire_date is not None: + paste_expire_date = str(paste_expire_date).strip().upper() + argv['paste_expire_date'] = paste_expire_date + + # Syntax highlighting + if paste_format is not None: + paste_format = str(paste_format).strip().lower() + argv['paste_format'] = paste_format + + # lets try to read the URL that we've just built. + data = urllib.urlencode(argv) + request_string = urllib.urlopen(self._legacy_api_url, data) + response = request_string.read() + + # do some basic error checking here so we can gracefully handle any + # errors we are likely to encounter + if response.startswith(self._bad_request): + raise PastebinError(response) + elif not response.startswith(self._prefix_url): + raise PastebinError(response) + + return response + + +###################################################### + +delete_paste = PastebinAPI.delete_paste +user_details = PastebinAPI.user_details +trending = PastebinAPI.trending +pastes_by_user = PastebinAPI.pastes_by_user +generate_user_key = PastebinAPI.generate_user_key +legacy_paste = PastebinAPI.legacy_paste +paste = PastebinAPI.paste -- cgit v1.2.3 From 0f56f7c2c5ac1367e42694b34778d6e1b513feb7 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 25 Feb 2014 13:28:48 -0300 Subject: Add Pastebin button to logger window. Also add png image for the button and license information. --- src/leap/bitmask/gui/ui/loggerwindow.ui | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/ui/loggerwindow.ui b/src/leap/bitmask/gui/ui/loggerwindow.ui index 3de786f7..b19ed91a 100644 --- a/src/leap/bitmask/gui/ui/loggerwindow.ui +++ b/src/leap/bitmask/gui/ui/loggerwindow.ui @@ -6,8 +6,8 @@ 0 0 - 648 - 469 + 769 + 464
@@ -154,6 +154,17 @@
+ + + + Send to Pastebin.com + + + + :/images/pastebin.png:/images/pastebin.png + + +
-- cgit v1.2.3 From 4fddabec963015655a7129f1f95b95412b10ccf6 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 24 Feb 2014 18:38:39 -0300 Subject: Add pastebin support for upload logs. --- src/leap/bitmask/gui/loggerwindow.py | 61 ++++++++++++++++++++++++++++++++++++ src/leap/bitmask/util/constants.py | 1 + 2 files changed, 62 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py index 6ef58558..b32852c0 100644 --- a/src/leap/bitmask/gui/loggerwindow.py +++ b/src/leap/bitmask/gui/loggerwindow.py @@ -22,10 +22,13 @@ import logging import cgi from PySide import QtGui +from twisted.internet import threads from ui_loggerwindow import Ui_LoggerWindow +from leap.bitmask.util.constants import PASTEBIN_API_DEV_KEY from leap.bitmask.util.leap_log_handler import LeapLogHandler +from leap.bitmask.util.pastebin import PastebinAPI, PastebinError from leap.common.check import leap_assert, leap_assert_type logger = logging.getLogger(__name__) @@ -42,6 +45,9 @@ class LoggerWindow(QtGui.QDialog): :param handler: Custom handler that supports history and signal. :type handler: LeapLogHandler. """ + from twisted.internet import reactor + self.reactor = reactor + QtGui.QDialog.__init__(self) leap_assert(handler, "We need a handler for the logger window") leap_assert_type(handler, LeapLogHandler) @@ -59,8 +65,10 @@ class LoggerWindow(QtGui.QDialog): self.ui.btnCritical.toggled.connect(self._load_history) self.ui.leFilterBy.textEdited.connect(self._filter_by) self.ui.cbCaseInsensitive.stateChanged.connect(self._load_history) + self.ui.btnPastebin.clicked.connect(self._pastebin_this) self._current_filter = "" + self._current_history = "" # Load logging history and connect logger with the widget self._logging_handler = handler @@ -116,8 +124,13 @@ class LoggerWindow(QtGui.QDialog): self._set_logs_to_display() self.ui.txtLogHistory.clear() history = self._logging_handler.log_history + current_history = [] for line in history: self._add_log_line(line) + message = cgi.escape(line[LeapLogHandler.MESSAGE_KEY]) + current_history.append(message) + + self._current_history = "\n".join(current_history) def _set_logs_to_display(self): """ @@ -164,3 +177,51 @@ class LoggerWindow(QtGui.QDialog): logger.error("Error saving log file: %r" % (e, )) else: logger.debug('Log not saved!') + + def _pastebin_this(self): + """ + Send the current log history to pastebin.com and gives the user a link + to see it. + """ + def do_pastebin(): + """ + Send content to pastebin and return the link. + """ + content = self._current_history + pb = PastebinAPI() + link = pb.paste(PASTEBIN_API_DEV_KEY, content, + paste_name="Bitmask log", + paste_expire_date='1W') + return link + + def pastebin_ok(link): + """ + Callback handler for `do_pastebin`. + + :param link: the recently created pastebin link. + :type link: str + """ + msg = self.tr("Your pastebin link {0}") + msg = msg.format(link) + logger.debug(msg) + show_info = lambda: QtGui.QMessageBox.information( + self, self.tr("Pastebin OK"), msg) + self.reactor.callLater(0, show_info) + + def pastebin_err(failure): + """ + Errback handler for `do_pastebin`. + + :param failure: the failure that triggered the errback. + :type failure: twisted.python.failure.Failure + """ + logger.error(repr(failure)) + msg = self.tr("Sending logs to Pastebin failed!") + show_err = lambda: QtGui.QMessageBox.error( + self, self.tr("Pastebin Error"), msg) + self.reactor.callLater(0, show_err) + failure.trap(PastebinError) + + d = threads.deferToThread(do_pastebin) + d.addCallback(pastebin_ok) + d.addErrback(pastebin_err) diff --git a/src/leap/bitmask/util/constants.py b/src/leap/bitmask/util/constants.py index e6a6bdce..e7e72cc4 100644 --- a/src/leap/bitmask/util/constants.py +++ b/src/leap/bitmask/util/constants.py @@ -17,3 +17,4 @@ SIGNUP_TIMEOUT = 5 REQUEST_TIMEOUT = 15 +PASTEBIN_API_DEV_KEY = "09563100642af6085d641f749a1922b4" -- cgit v1.2.3 From 6650529db2f001718bb79eab34aee4d4830eb346 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 26 Feb 2014 11:30:15 -0300 Subject: Show sending status in button and return raw link. --- src/leap/bitmask/gui/loggerwindow.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py index b32852c0..9b6cef91 100644 --- a/src/leap/bitmask/gui/loggerwindow.py +++ b/src/leap/bitmask/gui/loggerwindow.py @@ -178,6 +178,21 @@ class LoggerWindow(QtGui.QDialog): else: logger.debug('Log not saved!') + def _set_pastebin_sending(self, sending): + """ + Define the status of the pastebin button. + Change the text and enable/disable according to the current action. + + :param sending: if we are sending to pastebin or not. + :type sending: bool + """ + if sending: + self.ui.btnPastebin.setText(self.tr("Sending to pastebin...")) + self.ui.btnPastebin.setEnabled(False) + else: + self.ui.btnPastebin.setText(self.tr("Send to Pastebin.com")) + self.ui.btnPastebin.setEnabled(True) + def _pastebin_this(self): """ Send the current log history to pastebin.com and gives the user a link @@ -192,6 +207,10 @@ class LoggerWindow(QtGui.QDialog): link = pb.paste(PASTEBIN_API_DEV_KEY, content, paste_name="Bitmask log", paste_expire_date='1W') + + # convert to 'raw' link + link = "http://pastebin.com/raw.php?i=" + link.split('/')[-1] + return link def pastebin_ok(link): @@ -206,6 +225,7 @@ class LoggerWindow(QtGui.QDialog): logger.debug(msg) show_info = lambda: QtGui.QMessageBox.information( self, self.tr("Pastebin OK"), msg) + self._set_pastebin_sending(False) self.reactor.callLater(0, show_info) def pastebin_err(failure): @@ -219,9 +239,11 @@ class LoggerWindow(QtGui.QDialog): msg = self.tr("Sending logs to Pastebin failed!") show_err = lambda: QtGui.QMessageBox.error( self, self.tr("Pastebin Error"), msg) + self._set_pastebin_sending(False) self.reactor.callLater(0, show_err) failure.trap(PastebinError) + self._set_pastebin_sending(True) d = threads.deferToThread(do_pastebin) d.addCallback(pastebin_ok) d.addErrback(pastebin_err) -- cgit v1.2.3 From ffec4eaf33b24565dec2fd1e2483e4edccecc887 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 26 Feb 2014 15:39:00 -0300 Subject: Fix wrong method name for pastebin error. --- src/leap/bitmask/gui/loggerwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py index 9b6cef91..c01642fc 100644 --- a/src/leap/bitmask/gui/loggerwindow.py +++ b/src/leap/bitmask/gui/loggerwindow.py @@ -237,7 +237,7 @@ class LoggerWindow(QtGui.QDialog): """ logger.error(repr(failure)) msg = self.tr("Sending logs to Pastebin failed!") - show_err = lambda: QtGui.QMessageBox.error( + show_err = lambda: QtGui.QMessageBox.critical( self, self.tr("Pastebin Error"), msg) self._set_pastebin_sending(False) self.reactor.callLater(0, show_err) -- cgit v1.2.3 From 75c9b86e605742f2850d8b50b2419d75129e7949 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 28 Feb 2014 14:47:44 -0300 Subject: Do version checks in the wizard too. When the user choose to use an existing provider we download the provider.json again so the version checks are executed. [Closes #5048] --- src/leap/bitmask/gui/ui/wizard.ui | 73 ++++++++++++++++++++++++--------------- src/leap/bitmask/gui/wizard.py | 11 +++--- 2 files changed, 50 insertions(+), 34 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui index d9b8499e..6c592522 100644 --- a/src/leap/bitmask/gui/ui/wizard.ui +++ b/src/leap/bitmask/gui/ui/wizard.ui @@ -269,20 +269,7 @@ Configure or select a provider - - - - - - - https:// - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - + Configure new provider: @@ -292,14 +279,27 @@ - - + + + + false + + + + + - Check + https:// + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + + + + Use existing one: @@ -309,7 +309,7 @@ - + https:// @@ -319,12 +319,29 @@ - - - - false - - + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Check + + + + @@ -859,8 +876,8 @@ 174 - 454 - 254 + 450 + 266 @@ -884,7 +901,7 @@ rbExistingProvider toggled(bool) btnCheck - setDisabled(bool) + setEnabled(bool) 169 diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index e1bed6b8..024b23bc 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -223,7 +223,7 @@ class Wizard(QtGui.QWizard): depending on the lnProvider content. """ enabled = len(self.ui.lnProvider.text()) != 0 - enabled = enabled and self.ui.rbNewProvider.isChecked() + enabled = enabled or self.ui.rbExistingProvider.isChecked() self.ui.btnCheck.setEnabled(enabled) if reset: @@ -367,8 +367,10 @@ class Wizard(QtGui.QWizard): Starts the checks for a given provider """ - if len(self.ui.lnProvider.text()) == 0: - return + if self.ui.rbNewProvider.isChecked(): + self._domain = self.ui.lnProvider.text() + else: + self._domain = self.ui.cbProviders.currentText() self._provider_checks_ok = False @@ -380,7 +382,6 @@ class Wizard(QtGui.QWizard): self.ui.btnCheck.setEnabled(False) self.ui.lnProvider.setEnabled(False) self.button(QtGui.QWizard.BackButton).clearFocus() - self._domain = self.ui.lnProvider.text() self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) self._provider_select_defer = self._backend.\ @@ -401,8 +402,6 @@ class Wizard(QtGui.QWizard): if skip: self._reset_provider_check() - self.page(self.SELECT_PROVIDER_PAGE).set_completed(skip) - self.button(QtGui.QWizard.NextButton).setEnabled(skip) self._use_existing_provider = skip def _complete_task(self, data, label, complete=False, complete_page=-1): -- cgit v1.2.3 From e90b36f9c1c2672c07e662d99e55dd87e5357711 Mon Sep 17 00:00:00 2001 From: KwadroNaut Date: Tue, 4 Mar 2014 17:32:20 +0100 Subject: =?UTF-8?q?double=20dot=20=E2=86=92=20ellipsis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/leap/bitmask/gui/mail_status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 1d5ff319..8da26f6d 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -213,7 +213,7 @@ class MailStatusWidget(QtGui.QWidget): self._service_name)) elif ready == 1: icon = self.CONNECTING_ICON - self._mx_status = self.tr('Starting..') + self._mx_status = self.tr('Starting…') tray_status = self.tr('Mail is starting') elif ready >= 2: icon = self.CONNECTED_ICON -- cgit v1.2.3 From f4893104fb402624f788273bc047aac63da673d7 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 6 Mar 2014 14:40:56 -0300 Subject: Select current provider as default in eip settings Set as selected default for the eip preferences window the item selented in the bitmask main window. [Closes #5153] --- src/leap/bitmask/gui/eip_preferenceswindow.py | 21 ++++++++++++++++----- src/leap/bitmask/gui/mainwindow.py | 3 ++- 2 files changed, 18 insertions(+), 6 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/eip_preferenceswindow.py b/src/leap/bitmask/gui/eip_preferenceswindow.py index 504d1cf1..dcaa8b1e 100644 --- a/src/leap/bitmask/gui/eip_preferenceswindow.py +++ b/src/leap/bitmask/gui/eip_preferenceswindow.py @@ -22,7 +22,7 @@ import os import logging from functools import partial -from PySide import QtGui +from PySide import QtCore, QtGui from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig @@ -37,10 +37,12 @@ class EIPPreferencesWindow(QtGui.QDialog): """ Window that displays the EIP preferences. """ - def __init__(self, parent): + def __init__(self, parent, domain): """ :param parent: parent object of the EIPPreferencesWindow. - :parent type: QWidget + :type parent: QWidget + :param domain: the selected by default domain. + :type domain: unicode """ QtGui.QDialog.__init__(self, parent) self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") @@ -59,7 +61,7 @@ class EIPPreferencesWindow(QtGui.QDialog): self.ui.cbGateways.currentIndexChanged[unicode].connect( lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False)) - self._add_configured_providers() + self._add_configured_providers(domain) def _set_providers_gateway_status(self, status, success=False, error=False): @@ -83,9 +85,12 @@ class EIPPreferencesWindow(QtGui.QDialog): self.ui.lblProvidersGatewayStatus.setVisible(True) self.ui.lblProvidersGatewayStatus.setText(status) - def _add_configured_providers(self): + def _add_configured_providers(self, domain=None): """ Add the client's configured providers to the providers combo boxes. + + :param domain: the domain to be selected by default. + :type domain: unicode """ self.ui.cbProvidersGateway.clear() providers = self._settings.get_configured_providers() @@ -100,6 +105,12 @@ class EIPPreferencesWindow(QtGui.QDialog): label = provider + self.tr(" (uninitialized)") self.ui.cbProvidersGateway.addItem(label, userData=provider) + # Select provider by name + if domain is not None: + provider_index = self.ui.cbProvidersGateway.findText( + domain, QtCore.Qt.MatchStartsWith) + self.ui.cbProvidersGateway.setCurrentIndex(provider_index) + def _save_selected_gateway(self, provider): """ SLOT diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index eeab7e2b..13e289fb 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -587,7 +587,8 @@ class MainWindow(QtGui.QMainWindow): Displays the EIP preferences window. """ - EIPPreferencesWindow(self).show() + domain = self._login_widget.get_selected_provider() + EIPPreferencesWindow(self, domain).show() # # updates -- cgit v1.2.3 From ce66b82ce46948b8c2a74a17a1d75d7510e77f7f Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 7 Mar 2014 12:56:11 -0400 Subject: add ability to set nice order via env var --- src/leap/bitmask/app.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 1146d1d0..2034bc33 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -236,6 +236,11 @@ def main(): # We don't even have logger configured in here print "Could not ensure server: %r" % (e,) + PLAY_NICE = os.environ.get("LEAP_NICE") + if PLAY_NICE and PLAY_NICE.isdigit(): + nice = os.nice(int(PLAY_NICE)) + logger.info("Setting NICE: %s" % nice) + # And then we import all the other stuff # I think it's safe to import at the top by now -- kali from leap.bitmask.gui import locale_rc -- cgit v1.2.3 From 17fcf4f3efd1c016bd0f93129b4bedfd8ca92309 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 7 Mar 2014 17:24:33 -0300 Subject: Run all the soledad bootstrap on error. Before this fix, if the soledad bootstrapping failed during the first download process, we retried since after that point which caused a failure. --- src/leap/bitmask/gui/mainwindow.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index eeab7e2b..6ef3920f 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1247,8 +1247,7 @@ class MainWindow(QtGui.QMainWindow): self._soledad_bootstrapper.increment_retries_count() # XXX should cancel the existing socket --- this # is avoiding a clean termination. - threads.deferToThread( - self._soledad_bootstrapper.load_and_sync_soledad) + self._maybe_run_soledad_setup_checks() else: logger.warning("Max number of soledad initialization " "retries reached.") -- cgit v1.2.3 From f274193b30cbe28f75a976b0fd5710f06c2c9c49 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 5 Mar 2014 17:02:09 -0300 Subject: Move all the defer cancelling to a method. Also add X_defer = None for the defers after being cancelled. --- src/leap/bitmask/gui/mainwindow.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index eeab7e2b..2c1d56d3 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1065,16 +1065,24 @@ class MainWindow(QtGui.QMainWindow): Stops the login sequence. """ - logger.debug("Cancelling setup provider defer.") + logger.debug("Cancelling log in.") + self._cancel_ongoing_defers() + + def _cancel_ongoing_defers(self): + """ + Cancel the running defers to avoid app blocking. + """ self._backend.cancel_setup_provider() if self._login_defer is not None: logger.debug("Cancelling login defer.") self._login_defer.cancel() + self._login_defer = None if self._soledad_defer is not None: logger.debug("Cancelling soledad defer.") self._soledad_defer.cancel() + self._soledad_defer = None def _set_login_cancelled(self): """ @@ -1811,9 +1819,8 @@ class MainWindow(QtGui.QMainWindow): """ self._soledad_bootstrapper.cancel_bootstrap() setProxiedObject(self._soledad, None) - if self._soledad_defer is not None: - logger.debug("Cancelling soledad defer.") - self._soledad_defer.cancel() + + self._cancel_ongoing_defers() # reset soledad status flag self._already_started_soledad = False @@ -1930,16 +1937,7 @@ class MainWindow(QtGui.QMainWindow): logger.debug('Terminating vpn') self._vpn.terminate(shutdown=True) - if self._login_defer: - logger.debug("Cancelling login defer.") - self._login_defer.cancel() - - logger.debug("Cancelling setup provider defer.") - self._backend.cancel_setup_provider() - - if self._soledad_defer is not None: - logger.debug("Cancelling soledad defer.") - self._soledad_defer.cancel() + self._cancel_ongoing_defers() # TODO missing any more cancels? -- cgit v1.2.3 From 2f46572ef137bb20c04f443a36361569b4b9e120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 7 Mar 2014 18:02:58 -0300 Subject: Remove qtreactor dependency --- src/leap/bitmask/app.py | 17 ++++++----------- src/leap/bitmask/gui/mainwindow.py | 1 + src/leap/bitmask/gui/twisted_main.py | 18 ------------------ 3 files changed, 7 insertions(+), 29 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 1146d1d0..a1f3a69f 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -58,6 +58,9 @@ from leap.bitmask.services.mail import plumber from leap.common.events import server as event_server from leap.mail import __version__ as MAIL_VERSION +from twisted.internet import reactor +from twisted.internet.task import LoopingCall + import codecs codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) @@ -74,12 +77,6 @@ def sigint_handler(*args, **kwargs): mainwindow.quit() -def install_qtreactor(logger): - import qt4reactor - qt4reactor.install() - logger.debug("Qt4 reactor installed") - - def add_logger_handlers(debug=False, logfile=None, replace_stdout=True): """ Create the logger and attach the handlers. @@ -274,9 +271,6 @@ def main(): app = QtGui.QApplication(sys.argv) - # install the qt4reactor. - install_qtreactor(logger) - # To test: # $ LANG=es ./app.py locale = QtCore.QLocale.system().name() @@ -319,8 +313,9 @@ def main(): #tx_app = leap_services() #assert(tx_app) - # Run main loop - twisted_main.start(app) + l = LoopingCall(QtCore.QCoreApplication.processEvents, 0, 10) + l.start(0.01) + reactor.run() if __name__ == "__main__": main() diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index eeab7e2b..9ee62e17 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -134,6 +134,7 @@ class MainWindow(QtGui.QMainWindow): :type bypass_checks: bool """ QtGui.QMainWindow.__init__(self) + self.menuBar().setNativeMenuBar(False) # register leap events ######################################## register(signal=proto.UPDATER_NEW_UPDATES, diff --git a/src/leap/bitmask/gui/twisted_main.py b/src/leap/bitmask/gui/twisted_main.py index e11af7bd..1e876c57 100644 --- a/src/leap/bitmask/gui/twisted_main.py +++ b/src/leap/bitmask/gui/twisted_main.py @@ -27,24 +27,6 @@ from twisted.internet import error logger = logging.getLogger(__name__) -def start(app): - """ - Start the mainloop. - - :param app: the main qt QApplication instance. - :type app: QtCore.QApplication - """ - from twisted.internet import reactor - logger.debug('starting twisted reactor') - - # this seems to be troublesome under some - # unidentified settings. - #reactor.run() - - reactor.runReturn() - app.exec_() - - def quit(app): """ Stop the mainloop. -- cgit v1.2.3 From 55fd2d680c1d1f8cdf3433bf408858b72d55178f Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 11 Mar 2014 12:47:28 -0300 Subject: Move error messages for the user to the GUI. --- src/leap/bitmask/crypto/srpauth.py | 60 +++++++++++++++----------------------- src/leap/bitmask/gui/mainwindow.py | 24 +++++++++++++-- 2 files changed, 45 insertions(+), 39 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index b46f0ea6..179520d5 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -171,9 +171,6 @@ class SRPAuth(QtCore.QObject): self._srp_user = None self._srp_a = None - # Error msg displayed if the username or the password is invalid - self._WRONG_USER_PASS = self.tr("Invalid username or password.") - # User credentials stored for password changing checks self._username = None self._password = None @@ -267,14 +264,11 @@ class SRPAuth(QtCore.QObject): # Clean up A value, we don't need it anymore self._srp_a = None except requests.exceptions.ConnectionError as e: - logger.error("No connection made (salt): %r" % - (e,)) - raise SRPAuthConnectionError("Could not establish a " - "connection") + logger.error("No connection made (salt): {0!r}".format(e)) + raise SRPAuthConnectionError() except Exception as e: logger.error("Unknown error: %r" % (e,)) - raise SRPAuthenticationError("Unknown error: %r" % - (e,)) + raise SRPAuthenticationError() content, mtime = reqhelper.get_content(init_session) @@ -283,23 +277,22 @@ class SRPAuth(QtCore.QObject): "Status code = %r. Content: %r" % (init_session.status_code, content)) if init_session.status_code == 422: - raise SRPAuthBadUserOrPassword(self._WRONG_USER_PASS) + logger.error("Invalid username or password.") + raise SRPAuthBadUserOrPassword() - raise SRPAuthBadStatusCode(self.tr("There was a problem with" - " authentication")) + logger.error("There was a problem with authentication.") + raise SRPAuthBadStatusCode() json_content = json.loads(content) salt = json_content.get("salt", None) B = json_content.get("B", None) if salt is None: - logger.error("No salt parameter sent") - raise SRPAuthNoSalt(self.tr("The server did not send " - "the salt parameter")) + logger.error("The server didn't send the salt parameter.") + raise SRPAuthNoSalt() if B is None: - logger.error("No B parameter sent") - raise SRPAuthNoB(self.tr("The server did not send " - "the B parameter")) + logger.error("The server didn't send the B parameter.") + raise SRPAuthNoB() return salt, B @@ -330,8 +323,7 @@ class SRPAuth(QtCore.QObject): unhex_B = self._safe_unhexlify(B) except (TypeError, ValueError) as e: logger.error("Bad data from server: %r" % (e,)) - raise SRPAuthBadDataFromServer( - self.tr("The data sent from the server had errors")) + raise SRPAuthBadDataFromServer() M = self._srp_user.process_challenge(unhex_salt, unhex_B) auth_url = "%s/%s/%s/%s" % (self._provider_config.get_api_uri(), @@ -352,13 +344,13 @@ class SRPAuth(QtCore.QObject): timeout=REQUEST_TIMEOUT) except requests.exceptions.ConnectionError as e: logger.error("No connection made (HAMK): %r" % (e,)) - raise SRPAuthConnectionError(self.tr("Could not connect to " - "the server")) + raise SRPAuthConnectionError() try: content, mtime = reqhelper.get_content(auth_result) except JSONDecodeError: - raise SRPAuthJSONDecodeError("Bad JSON content in auth result") + logger.error("Bad JSON content in auth result.") + raise SRPAuthJSONDecodeError() if auth_result.status_code == 422: error = "" @@ -372,14 +364,13 @@ class SRPAuth(QtCore.QObject): "received: %s", (content,)) logger.error("[%s] Wrong password (HAMK): [%s]" % (auth_result.status_code, error)) - raise SRPAuthBadUserOrPassword(self._WRONG_USER_PASS) + raise SRPAuthBadUserOrPassword() if auth_result.status_code not in (200,): logger.error("No valid response (HAMK): " "Status code = %s. Content = %r" % (auth_result.status_code, content)) - raise SRPAuthBadStatusCode(self.tr("Unknown error (%s)") % - (auth_result.status_code,)) + raise SRPAuthBadStatusCode() return json.loads(content) @@ -400,8 +391,7 @@ class SRPAuth(QtCore.QObject): token = json_content.get("token", None) except Exception as e: logger.error(e) - raise SRPAuthBadDataFromServer("Something went wrong with the " - "login") + raise SRPAuthBadDataFromServer() self.set_uuid(uuid) self.set_token(token) @@ -409,8 +399,7 @@ class SRPAuth(QtCore.QObject): if M2 is None or self.get_uuid() is None: logger.error("Something went wrong. Content = %r" % (json_content,)) - raise SRPAuthBadDataFromServer(self.tr("Problem getting data " - "from server")) + raise SRPAuthBadDataFromServer() events_signal( proto.CLIENT_UID, content=uuid, @@ -436,22 +425,19 @@ class SRPAuth(QtCore.QObject): unhex_M2 = self._safe_unhexlify(M2) except TypeError: logger.error("Bad data from server (HAWK)") - raise SRPAuthBadDataFromServer(self.tr("Bad data from server")) + raise SRPAuthBadDataFromServer() self._srp_user.verify_session(unhex_M2) if not self._srp_user.authenticated(): - logger.error("Auth verification failed") - raise SRPAuthVerificationFailed(self.tr("Auth verification " - "failed")) + logger.error("Auth verification failed.") + raise SRPAuthVerificationFailed() logger.debug("Session verified.") session_id = self._session.cookies.get(self.SESSION_ID_KEY, None) if not session_id: logger.error("Bad cookie from server (missing _session_id)") - raise SRPAuthNoSessionId(self.tr("Session cookie " - "verification " - "failed")) + raise SRPAuthNoSessionId() events_signal( proto.CLIENT_SESSION_ID, content=session_id, diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 004d135b..949dcb03 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -32,7 +32,10 @@ from leap.bitmask import __version_hash__ as VERSION_HASH from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig + +from leap.bitmask.crypto import srpauth from leap.bitmask.crypto.srpauth import SRPAuth + from leap.bitmask.gui.loggerwindow import LoggerWindow from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement from leap.bitmask.gui.login import LoginWidget @@ -1051,13 +1054,30 @@ class MainWindow(QtGui.QMainWindow): # as we are doing with the prov_cancelled_setup signal. # After we move srpauth to the backend, we need to update this. logger.error("Error logging in, {0!r}".format(failure)) + if failure.check(CancelledError): logger.debug("Defer cancelled.") failure.trap(Exception) self._set_login_cancelled() + return + elif failure.check(srpauth.SRPAuthBadUserOrPassword): + msg = self.tr("Invalid username or password.") + elif failure.check(srpauth.SRPAuthBadStatusCode, + srpauth.SRPAuthenticationError, + srpauth.SRPAuthVerificationFailed, + srpauth.SRPAuthNoSessionId, + srpauth.SRPAuthNoSalt, srpauth.SRPAuthNoB, + srpauth.SRPAuthBadDataFromServer, + srpauth.SRPAuthJSONDecodeError): + msg = self.tr("There was a server problem with authentication.") + elif failure.check(srpauth.SRPAuthConnectionError): + msg = self.tr("Could not establish a connection.") else: - self._login_widget.set_status(str(failure.value)) - self._login_widget.set_enabled(True) + # this shouldn't happen, but just in case. + msg = self.tr("Unknown error: {0!r}".format(failure.value)) + + self._login_widget.set_status(msg) + self._login_widget.set_enabled(True) def _cancel_login(self): """ -- cgit v1.2.3 From 5c2150723d87e8713bffe23d0fa897d1de088b3f Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 11 Mar 2014 13:07:38 -0300 Subject: Move login/logout success/error msgs to the GUI. Also refactor signals to be more granular and notify clearly if there was an error or not, then in the GUI we show the corresponding message. --- src/leap/bitmask/crypto/srpauth.py | 13 ++++--- src/leap/bitmask/gui/mainwindow.py | 75 +++++++++++++++++++------------------- 2 files changed, 45 insertions(+), 43 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 179520d5..7cf7e55a 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -604,8 +604,9 @@ class SRPAuth(QtCore.QObject): __instance = None - authentication_finished = QtCore.Signal(bool, str) - logout_finished = QtCore.Signal(bool, str) + authentication_finished = QtCore.Signal() + logout_ok = QtCore.Signal() + logout_error = QtCore.Signal() def __init__(self, provider_config): """ @@ -679,7 +680,7 @@ class SRPAuth(QtCore.QObject): :type _: IGNORED """ logger.debug("Successful login!") - self.authentication_finished.emit(True, self.tr("Succeeded")) + self.authentication_finished.emit() def get_session_id(self): return self.__instance.get_session_id() @@ -697,8 +698,10 @@ class SRPAuth(QtCore.QObject): """ try: self.__instance.logout() - self.logout_finished.emit(True, self.tr("Succeeded")) + logger.debug("Logout success") + self.logout_ok.emit() return True except Exception as e: - self.logout_finished.emit(False, "%s" % (e,)) + logger.debug("Logout error: {0!r}".format(e)) + self.logout_error.emit() return False diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 949dcb03..7cec4831 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1139,8 +1139,8 @@ class MainWindow(QtGui.QMainWindow): self._srp_auth = SRPAuth(self._provider_config) self._srp_auth.authentication_finished.connect( self._authentication_finished) - self._srp_auth.logout_finished.connect( - self._done_logging_out) + self._srp_auth.logout_ok.connect(self._logout_ok) + self._srp_auth.logout_error.connect(self._logout_error) self._login_defer = self._srp_auth.authenticate(username, password) self._login_defer.addErrback(self._login_errback) @@ -1150,7 +1150,7 @@ class MainWindow(QtGui.QMainWindow): logger.error(data[self._backend.ERROR_KEY]) self._login_widget.set_enabled(True) - def _authentication_finished(self, ok, message): + def _authentication_finished(self): """ SLOT TRIGGER: self._srp_auth.authentication_finished @@ -1158,30 +1158,23 @@ class MainWindow(QtGui.QMainWindow): Once the user is properly authenticated, try starting the EIP service """ - # In general we want to "filter" likely complicated error - # messages, but in this case, the messages make more sense as - # they come. Since they are "Unknown user" or "Unknown - # password" - self._login_widget.set_status(message, error=not ok) - - if ok: - self._logged_user = self._login_widget.get_user() - user = self._logged_user - domain = self._provider_config.get_domain() - full_user_id = make_address(user, domain) - self._mail_conductor.userid = full_user_id - self._login_defer = None - self._start_eip_bootstrap() - - # if soledad/mail is enabled: - if MX_SERVICE in self._enabled_services: - btn_enabled = self._login_widget.set_logout_btn_enabled - btn_enabled(False) - self.soledad_ready.connect(lambda: btn_enabled(True)) - self._soledad_bootstrapper.soledad_failed.connect( - lambda: btn_enabled(True)) - else: - self._login_widget.set_enabled(True) + self._login_widget.set_status(self.tr("Succeeded"), error=False) + + self._logged_user = self._login_widget.get_user() + user = self._logged_user + domain = self._provider_config.get_domain() + full_user_id = make_address(user, domain) + self._mail_conductor.userid = full_user_id + self._login_defer = None + self._start_eip_bootstrap() + + # if soledad/mail is enabled: + if MX_SERVICE in self._enabled_services: + btn_enabled = self._login_widget.set_logout_btn_enabled + btn_enabled(False) + self.soledad_ready.connect(lambda: btn_enabled(True)) + self._soledad_bootstrapper.soledad_failed.connect( + lambda: btn_enabled(True)) def _start_eip_bootstrap(self): """ @@ -1851,11 +1844,22 @@ class MainWindow(QtGui.QMainWindow): threads.deferToThread(self._srp_auth.logout) self.logout.emit() - def _done_logging_out(self, ok, message): - # TODO missing params in docstring + def _logout_error(self): """ SLOT - TRIGGER: self._srp_auth.logout_finished + TRIGGER: self._srp_auth.logout_error + + Inform the user about a logout error. + """ + self._login_widget.done_logout() + self.ui.lblLoginProvider.setText(self.tr("Login")) + self._login_widget.set_status( + self.tr("Something went wrong with the logout.")) + + def _logout_ok(self): + """ + SLOT + TRIGGER: self._srp_auth.logout_ok Switches the stackedWidget back to the login stage after logging out @@ -1863,14 +1867,9 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.done_logout() self.ui.lblLoginProvider.setText(self.tr("Login")) - if ok: - self._logged_user = None - self._login_widget.logged_out() - self._mail_status.mail_state_disabled() - - else: - self._login_widget.set_status( - self.tr("Something went wrong with the logout.")) + self._logged_user = None + self._login_widget.logged_out() + self._mail_status.mail_state_disabled() def _intermediate_stage(self, data): # TODO this method name is confusing as hell. -- cgit v1.2.3 From d724a65ca08d5b1c16da1f2ce3fd0b50bd519dec Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 10 Mar 2014 17:29:52 -0300 Subject: Check for DNS resolution after connecting EIP. [Closes #5301] --- src/leap/bitmask/gui/mainwindow.py | 52 +++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 7cec4831..758bff4e 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -18,13 +18,14 @@ Main window for Bitmask. """ import logging +import socket from threading import Condition from datetime import datetime from PySide import QtCore, QtGui from zope.proxy import ProxyBase, setProxiedObject -from twisted.internet import threads +from twisted.internet import reactor, threads from twisted.internet.defer import CancelledError from leap.bitmask import __version__ as VERSION @@ -1456,6 +1457,55 @@ class MainWindow(QtGui.QMainWindow): """ self._eip_connection.qtsigs.connected_signal.emit() + # check for connectivity + provider_config = self._get_best_provider_config() + domain = provider_config.get_domain() + self._check_name_resolution(domain) + + def _check_name_resolution(self, domain): + """ + Check if we can resolve the given domain name. + + :param domain: the domain to check. + :type domain: str + """ + def do_check(): + """ + Try to resolve the domain name. + """ + socket.gethostbyname(domain.encode('idna')) + + def check_err(failure): + """ + Errback handler for `do_check`. + + :param failure: the failure that triggered the errback. + :type failure: twisted.python.failure.Failure + """ + logger.error(repr(failure)) + logger.error("Can't resolve hostname.") + + msg = self.tr( + "The server at {0} can't be found, because the DNS lookup " + "failed. DNS is the network service that translates a " + "website's name to its Internet address. Either your computer " + "is having trouble connecting to the network, or you are " + "missing some helper files that are needed to securely use " + "DNS while {1} is active. To install these helper files, quit " + "this application and start it again." + ).format(domain, self._eip_name) + + show_err = lambda: QtGui.QMessageBox.critical( + self, self.tr("Connection Error"), msg) + reactor.callLater(0, show_err) + + # python 2.7.4 raises socket.error + # python 2.7.5 raises socket.gaierror + failure.trap(socket.gaierror, socket.error) + + d = threads.deferToThread(do_check) + d.addErrback(check_err) + def _try_autostart_eip(self): """ Tries to autostart EIP -- cgit v1.2.3 From db31692ddf48abed1d02afc56eb33c8d7cd15234 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 13 Mar 2014 17:37:30 -0300 Subject: Support latest psutil version. --- src/leap/bitmask/services/eip/vpnprocess.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 51f0f738..5c100036 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -19,14 +19,20 @@ VPN Manager, spawned in a custom processProtocol. """ import logging import os -import psutil -import psutil.error import shutil import socket import sys from itertools import chain, repeat +import psutil +try: + # psutil < 2.0.0 + from psutil.error import AccessDenied as psutil_AccessDenied +except ImportError: + # psutil >= 2.0.0 + from psutil import AccessDenied as psutil_AccessDenied + from PySide import QtCore from leap.bitmask.config.providerconfig import ProviderConfig @@ -672,7 +678,7 @@ class VPNManager(object): if any(map(lambda s: s.find("LEAPOPENVPN") != -1, p.cmdline)): openvpn_process = p break - except psutil.error.AccessDenied: + except psutil_AccessDenied: pass return openvpn_process -- cgit v1.2.3 From 4d5021c519fd73111f7f88f314491b6a953d9af5 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 17 Mar 2014 10:38:19 -0400 Subject: catch shutdown errors --- src/leap/bitmask/backend.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 34457e34..99e5a04b 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -325,7 +325,8 @@ class Backend(object): Stops the looping call and tries to cancel all the defers. """ log.msg("Stopping worker...") - self._lc.stop() + if self._lc.running: + self._lc.stop() while len(self._ongoing_defers) > 0: d = self._ongoing_defers.pop() d.cancel() @@ -392,7 +393,8 @@ class Backend(object): :param d: defer to remove :type d: twisted.internet.defer.Deferred """ - self._ongoing_defers.remove(d) + if d in self._ongoing_defers: + self._ongoing_defers.remove(d) # XXX: Temporal interface until we migrate to zmq # We simulate the calls to zmq.send_multipart. Once we separate -- cgit v1.2.3 From 21e8d1a4539b0b0837ca0763de4974ac6f216bf7 Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 17 Mar 2014 13:21:51 -0300 Subject: Catch soledad invalid token error (#5191). --- src/leap/bitmask/gui/mail_status.py | 38 ++++++++++++++++++---- src/leap/bitmask/gui/mainwindow.py | 2 ++ src/leap/bitmask/services/mail/conductor.py | 2 ++ src/leap/bitmask/services/mail/connection.py | 2 ++ .../services/soledad/soledadbootstrapper.py | 18 +++++++++- 5 files changed, 54 insertions(+), 8 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 8da26f6d..44a138e2 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -112,6 +112,10 @@ class MailStatusWidget(QtGui.QWidget): callback=self._mail_handle_imap_events, reqcbk=lambda req, resp: None) + register(signal=proto.SOLEDAD_INVALID_AUTH_TOKEN, + callback=self.set_soledad_invalid_auth_token, + reqcbk=lambda req, resp: None) + self._soledad_event.connect( self._mail_handle_soledad_events_slot) self._imap_event.connect( @@ -191,6 +195,17 @@ class MailStatusWidget(QtGui.QWidget): msg = self.tr("There was an unexpected problem with Soledad.") self._set_mail_status(msg, ready=-1) + def set_soledad_invalid_auth_token(self): + """ + SLOT + TRIGGER: + SoledadBootstrapper.soledad_invalid_token + + This method is called when the auth token is invalid + """ + msg = self.tr("Invalid auth token, try logging in again.") + self._set_mail_status(msg, ready=-1) + def _set_mail_status(self, status, ready=0): """ Sets the Mail status in the label and in the tray icon. @@ -384,7 +399,7 @@ class MailStatusWidget(QtGui.QWidget): def about_to_start(self): """ - Displays the correct UI for the point where mail components + Display the correct UI for the point where mail components haven't really started, but they are about to in a second. """ self._set_mail_status(self.tr("About to start, please wait..."), @@ -392,7 +407,7 @@ class MailStatusWidget(QtGui.QWidget): def set_disabled(self): """ - Displays the correct UI for disabled mail. + Display the correct UI for disabled mail. """ self._set_mail_status(self.tr("Disabled"), -1) @@ -403,7 +418,7 @@ class MailStatusWidget(QtGui.QWidget): @QtCore.Slot() def mail_state_disconnected(self): """ - Displays the correct UI for the disconnected state. + Display the correct UI for the disconnected state. """ # XXX this should handle the disabled state better. self._started = False @@ -415,7 +430,7 @@ class MailStatusWidget(QtGui.QWidget): @QtCore.Slot() def mail_state_connecting(self): """ - Displays the correct UI for the connecting state. + Display the correct UI for the connecting state. """ self._disabled = False self._started = True @@ -424,23 +439,32 @@ class MailStatusWidget(QtGui.QWidget): @QtCore.Slot() def mail_state_disconnecting(self): """ - Displays the correct UI for the connecting state. + Display the correct UI for the connecting state. """ self._set_mail_status(self.tr("Disconnecting..."), 1) @QtCore.Slot() def mail_state_connected(self): """ - Displays the correct UI for the connected state. + Display the correct UI for the connected state. """ self._set_mail_status(self.tr("ON"), 2) @QtCore.Slot() def mail_state_disabled(self): """ - Displays the correct UI for the disabled state. + Display the correct UI for the disabled state. """ self._disabled = True status = self.tr("You must be logged in to use {0}.").format( self._service_name) self._set_mail_status(status, -1) + + @QtCore.Slot() + def soledad_invalid_auth_token(self): + """ + Display the correct UI for the invalid token state + """ + self._disabled = True + status = self.tr("Invalid auth token, try logging in again.") + self._set_mail_status(status, -1) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 758bff4e..e4e71dcc 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -262,6 +262,8 @@ class MainWindow(QtGui.QMainWindow): self._soledad_bootstrapped_stage) self._soledad_bootstrapper.soledad_timeout.connect( self._retry_soledad_connection) + self._soledad_bootstrapper.soledad_invalid_auth_token.connect( + self._mail_status.set_soledad_invalid_auth_token) self._soledad_bootstrapper.soledad_failed.connect( self._mail_status.set_soledad_failed) diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index f5892468..79f324dc 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -35,6 +35,7 @@ from leap.common.check import leap_assert from leap.common.events import register as leap_register from leap.common.events import events_pb2 as leap_events + logger = logging.getLogger(__name__) @@ -406,3 +407,4 @@ class MailConductor(IMAPControl, SMTPControl): qtsigs.connecting_signal.connect(widget.mail_state_connecting) qtsigs.disconnecting_signal.connect(widget.mail_state_disconnecting) qtsigs.disconnected_signal.connect(widget.mail_state_disconnected) + qtsigs.soledad_invalid_auth_token.connect(widget.soledad_invalid_auth_token) diff --git a/src/leap/bitmask/services/mail/connection.py b/src/leap/bitmask/services/mail/connection.py index 29378f62..fdc28fe4 100644 --- a/src/leap/bitmask/services/mail/connection.py +++ b/src/leap/bitmask/services/mail/connection.py @@ -93,6 +93,8 @@ class MailConnectionSignals(QtCore.QObject): connection_died_signal = QtCore.Signal() connection_aborted_signal = QtCore.Signal() + soledad_invalid_auth_token = QtCore.Signal() + class MailConnection(AbstractLEAPConnection): diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 7aa86a02..ad5ee4d0 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -44,6 +44,7 @@ from leap.common.check import leap_assert, leap_assert_type, leap_check from leap.common.files import which from leap.keymanager import KeyManager, openpgp from leap.keymanager.errors import KeyNotFound +from leap.soledad.common.errors import InvalidAuthTokenError from leap.soledad.client import Soledad, BootstrapSequenceError logger = logging.getLogger(__name__) @@ -139,6 +140,7 @@ class SoledadBootstrapper(AbstractBootstrapper): gen_key = QtCore.Signal(dict) local_only_ready = QtCore.Signal(dict) soledad_timeout = QtCore.Signal() + soledad_invalid_auth_token = QtCore.Signal() soledad_failed = QtCore.Signal() def __init__(self): @@ -258,6 +260,12 @@ class SoledadBootstrapper(AbstractBootstrapper): return server_url, cert_file + def _soledad_sync_errback(self, failure): + failure.trap(InvalidAuthTokenError) + # in the case of an invalid token we have already turned off mail and + # warned the user in _do_soledad_sync() + + def load_and_sync_soledad(self, uuid=None, offline=False): """ Once everthing is in the right place, we instantiate and sync @@ -298,7 +306,8 @@ class SoledadBootstrapper(AbstractBootstrapper): self._keymanager.get_key( address, openpgp.OpenPGPKey, private=True, fetch_remote=False) - threads.deferToThread(self._do_soledad_sync) + d = threads.deferToThread(self._do_soledad_sync) + d.addErrback(self._soledad_sync_errback) except KeyNotFound: logger.debug("Key not found. Generating key for %s" % (address,)) @@ -350,6 +359,9 @@ class SoledadBootstrapper(AbstractBootstrapper): # ubuntu folks. sync_tries -= 1 continue + except InvalidAuthTokenError: + self.soledad_invalid_auth_token.emit() + raise except Exception as e: logger.exception("Unhandled error while syncing " "soledad: %r" % (e,)) @@ -444,6 +456,10 @@ class SoledadBootstrapper(AbstractBootstrapper): except (sqlite_ProgrammingError, sqlcipher_ProgrammingError) as e: logger.exception("%r" % (e,)) raise + except InvalidAuthTokenError: + # token is invalid, probably expired + logger.error('Invalid auth token while trying to sync Soledad') + raise except Exception as exc: logger.exception("Unhandled error while syncing " "soledad: %r" % (exc,)) -- cgit v1.2.3 From ec04455ddbdf2d1ae1b3fcc3bebc433f89f53601 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 18 Mar 2014 18:04:51 -0300 Subject: Move srpregister interaction to the backend. Also: - remove unused code. - add status attributes for srpauth responses. - split response handling - remove unneeded hack to avoid segfault emitting None --- src/leap/bitmask/backend.py | 84 +++++++++++++++++++++++++- src/leap/bitmask/crypto/srpregister.py | 63 ++++++++++++++++---- src/leap/bitmask/gui/wizard.py | 106 ++++++++++++++++++--------------- 3 files changed, 190 insertions(+), 63 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py index 99e5a04b..45ea451c 100644 --- a/src/leap/bitmask/backend.py +++ b/src/leap/bitmask/backend.py @@ -19,6 +19,7 @@ Backend for everything """ import logging +from functools import partial from Queue import Queue, Empty from twisted.internet import threads, defer @@ -28,6 +29,7 @@ from twisted.python import log import zope.interface from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.crypto.srpregister import SRPRegister from leap.bitmask.provider import get_provider_path from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper @@ -134,6 +136,9 @@ class Provider(object): :param provider: URL for the provider :type provider: unicode + + :returns: the defer for the operation running in a thread. + :rtype: twisted.internet.defer.Deferred """ log.msg("Setting up provider %s..." % (provider.encode("idna"),)) pb = self._provider_bootstrapper @@ -155,8 +160,10 @@ class Provider(object): :param provider: URL for the provider :type provider: unicode - """ + :returns: the defer for the operation running in a thread. + :rtype: twisted.internet.defer.Deferred + """ d = None # If there's no loaded provider or @@ -180,6 +187,57 @@ class Provider(object): return d +class Register(object): + """ + Interfaces with setup and bootstrapping operations for a provider + """ + + zope.interface.implements(ILEAPComponent) + + def __init__(self, signaler=None): + """ + Constructor for the Register component + + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + """ + object.__init__(self) + self.key = "register" + self._signaler = signaler + self._provider_config = ProviderConfig() + + def register_user(self, domain, username, password): + """ + Register a user using the domain and password given as parameters. + + :param domain: the domain we need to register the user. + :type domain: unicode + :param username: the user name + :type username: unicode + :param password: the password for the username + :type password: unicode + + :returns: the defer for the operation running in a thread. + :rtype: twisted.internet.defer.Deferred + """ + # 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() != domain): + self._provider_config.load(get_provider_path(domain)) + + if self._provider_config.loaded(): + srpregister = SRPRegister(signaler=self._signaler, + provider_config=self._provider_config) + return threads.deferToThread( + partial(srpregister.register_user, username, password)) + else: + if self._signaler is not None: + self._signaler.signal(self._signaler.srp_registration_failed) + logger.error("Could not load provider configuration.") + + class Signaler(QtCore.QObject): """ Signaler object, handles converting string commands to Qt signals. @@ -188,8 +246,9 @@ class Signaler(QtCore.QObject): live in the frontend. """ - # Signals for the ProviderBootstrapper + #################### # These will only exist in the frontend + # Signals for the ProviderBootstrapper prov_name_resolution = QtCore.Signal(object) prov_https_connection = QtCore.Signal(object) prov_download_provider_info = QtCore.Signal(object) @@ -205,7 +264,13 @@ class Signaler(QtCore.QObject): prov_cancelled_setup = QtCore.Signal(object) - # These will exist both in the backend and the front end. + # Signals for SRPRegister + srp_registration_finished = QtCore.Signal(object) + srp_registration_failed = QtCore.Signal(object) + srp_registration_taken = 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 @@ -220,6 +285,10 @@ class Signaler(QtCore.QObject): PROV_UNSUPPORTED_API = "prov_unsupported_api" PROV_CANCELLED_SETUP = "prov_cancelled_setup" + SRP_REGISTRATION_FINISHED = "srp_registration_finished" + SRP_REGISTRATION_FAILED = "srp_registration_failed" + SRP_REGISTRATION_TAKEN = "srp_registration_taken" + def __init__(self): """ Constructor for the Signaler @@ -238,6 +307,10 @@ class Signaler(QtCore.QObject): self.PROV_UNSUPPORTED_CLIENT, self.PROV_UNSUPPORTED_API, self.PROV_CANCELLED_SETUP, + + self.SRP_REGISTRATION_FINISHED, + self.SRP_REGISTRATION_FAILED, + self.SRP_REGISTRATION_TAKEN, ] for sig in signals: @@ -296,6 +369,7 @@ class Backend(object): # Component registration self._register(Provider(self._signaler, bypass_checks)) + self._register(Register(self._signaler)) # We have a looping call on a thread executing all the # commands in queue. Right now this queue is an actual Queue @@ -409,3 +483,7 @@ class Backend(object): def provider_bootstrap(self, provider): self._call_queue.put(("provider", "bootstrap", None, provider)) + + def register_user(self, provider, username, password): + self._call_queue.put(("register", "register_user", None, provider, + username, password)) diff --git a/src/leap/bitmask/crypto/srpregister.py b/src/leap/bitmask/crypto/srpregister.py index 02a1ea63..4c52db42 100644 --- a/src/leap/bitmask/crypto/srpregister.py +++ b/src/leap/bitmask/crypto/srpregister.py @@ -16,6 +16,7 @@ # along with this program. If not, see . import binascii +import json import logging import requests @@ -26,6 +27,7 @@ from urlparse import urlparse from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.util.constants import SIGNUP_TIMEOUT +from leap.bitmask.util.request_helpers import get_content from leap.common.check import leap_assert, leap_assert_type logger = logging.getLogger(__name__) @@ -40,16 +42,22 @@ class SRPRegister(QtCore.QObject): USER_VERIFIER_KEY = 'user[password_verifier]' USER_SALT_KEY = 'user[password_salt]' + STATUS_OK = (200, 201) + STATUS_TAKEN = 422 + STATUS_ERROR = -999 # Custom error status + registration_finished = QtCore.Signal(bool, object) - def __init__(self, - provider_config=None, - register_path="users"): + def __init__(self, signaler=None, + provider_config=None, register_path="users"): """ Constructor + :param signaler: Signaler object used to receive notifications + from the backend + :type signaler: Signaler :param provider_config: provider configuration instance, - properly loaded + properly loaded :type privider_config: ProviderConfig :param register_path: webapp path for registering users :type register_path; str @@ -59,6 +67,7 @@ class SRPRegister(QtCore.QObject): leap_assert_type(provider_config, ProviderConfig) self._provider_config = provider_config + self._signaler = signaler # **************************************************** # # Dependency injection helpers, override this for more @@ -104,8 +113,8 @@ class SRPRegister(QtCore.QObject): :param password: password for this username :type password: str - :rtype: tuple - :rparam: (ok, request) + :returns: if the registration went ok or not. + :rtype: bool """ username = username.lower().encode('utf-8') @@ -129,11 +138,7 @@ class SRPRegister(QtCore.QObject): logger.debug("Will try to register user = %s" % (username,)) ok = False - # This should be None, but we don't like when PySide segfaults, - # so it something else. - # To reproduce it, just do: - # self.registration_finished.emit(False, None) - req = [] + req = None try: req = self._session.post(uri, data=user_data, @@ -143,13 +148,45 @@ class SRPRegister(QtCore.QObject): except requests.exceptions.RequestException as exc: logger.error(exc.message) - ok = False else: ok = req.ok - self.registration_finished.emit(ok, req) + status_code = self.STATUS_ERROR + if req is not None: + status_code = req.status_code + self._emit_result(status_code) + + if not ok: + try: + content, _ = get_content(req) + json_content = json.loads(content) + error_msg = json_content.get("errors").get("login")[0] + if not error_msg.istitle(): + error_msg = "%s %s" % (username, error_msg) + logger.error(error_msg) + except Exception as e: + logger.error("Unknown error: %r" % (e, )) + return ok + def _emit_result(self, status_code): + """ + Emit the corresponding signal depending on the status code. + + :param status_code: the status code received. + :type status_code: int or str + """ + logger.debug("Status code is: {0}".format(status_code)) + if self._signaler is None: + return + + if status_code in self.STATUS_OK: + self._signaler.signal(self._signaler.SRP_REGISTRATION_FINISHED) + elif status_code == self.STATUS_TAKEN: + self._signaler.signal(self._signaler.SRP_REGISTRATION_TAKEN) + else: + self._signaler.signal(self._signaler.SRP_REGISTRATION_FAILED) + if __name__ == "__main__": logger = logging.getLogger(name='leap') diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 024b23bc..2f274e33 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -18,22 +18,18 @@ First run wizard """ import logging -import json import random from functools import partial from PySide import QtCore, QtGui -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 import get_provider_path from leap.bitmask.services import get_service_display_name, get_supported from leap.bitmask.util.keyring_helpers import has_keyring from leap.bitmask.util.password import basic_password_checks -from leap.bitmask.util.request_helpers import get_content from ui_wizard import Ui_Wizard @@ -253,16 +249,11 @@ class Wizard(QtGui.QWizard): ok, msg = basic_password_checks(username, password, password2) if ok: - register = SRPRegister(provider_config=self._provider_config) - register.registration_finished.connect( - self._registration_finished) - - threads.deferToThread( - partial(register.register_user, username, password)) + self._set_register_status(self.tr("Starting registration...")) + self._backend.register_user(self._domain, username, password) self._username = username self._password = password - self._set_register_status(self.tr("Starting registration...")) else: self._set_register_status(msg, error=True) self._focus_password() @@ -289,42 +280,59 @@ class Wizard(QtGui.QWizard): # register button self.ui.btnRegister.setVisible(visible) - def _registration_finished(self, ok, req): - if ok: - user_domain = self._username + "@" + self._domain - message = "

" - message += self.tr("User %s successfully registered.") % ( - user_domain, ) - message += "

" - self._set_register_status(message) - - self.ui.lblPassword2.clearFocus() - self._set_registration_fields_visibility(False) - - # Allow the user to remember his password - if has_keyring(): - self.ui.chkRemember.setVisible(True) - self.ui.chkRemember.setEnabled(True) - - self.page(self.REGISTER_USER_PAGE).set_completed() - self.button(QtGui.QWizard.BackButton).setEnabled(False) - else: - old_username = self._username - self._username = None - self._password = None - error_msg = self.tr("Something has gone wrong. " - "Please try again.") - try: - content, _ = get_content(req) - json_content = json.loads(content) - error_msg = json_content.get("errors").get("login")[0] - if not error_msg.istitle(): - error_msg = "%s %s" % (old_username, error_msg) - except Exception as e: - logger.error("Unknown error: %r" % (e,)) - - self._set_register_status(error_msg, error=True) - self.ui.btnRegister.setEnabled(True) + def _registration_finished(self): + """ + SLOT + TRIGGERS: + self._backend.signaler.srp_registration_finished + + The registration has finished successfully, so we do some final steps. + """ + user_domain = self._username + "@" + self._domain + message = "

" + message += self.tr("User %s successfully registered.") % ( + user_domain, ) + message += "

" + self._set_register_status(message) + + self.ui.lblPassword2.clearFocus() + self._set_registration_fields_visibility(False) + + # Allow the user to remember his password + if has_keyring(): + self.ui.chkRemember.setVisible(True) + self.ui.chkRemember.setEnabled(True) + + self.page(self.REGISTER_USER_PAGE).set_completed() + self.button(QtGui.QWizard.BackButton).setEnabled(False) + + def _registration_failed(self): + """ + SLOT + TRIGGERS: + self._backend.signaler.srp_registration_failed + + The registration has failed, so we report the problem. + """ + self._username = self._password = None + + error_msg = self.tr("Something has gone wrong. Please try again.") + self._set_register_status(error_msg, error=True) + self.ui.btnRegister.setEnabled(True) + + def _registration_taken(self): + """ + SLOT + TRIGGERS: + self._backend.signaler.srp_registration_taken + + The requested username is taken, warn the user about that. + """ + self._username = self._password = None + + error_msg = self.tr("The requested username is taken, choose another.") + self._set_register_status(error_msg, error=True) + self.ui.btnRegister.setEnabled(True) def _set_register_status(self, status, error=False): """ @@ -688,6 +696,10 @@ class Wizard(QtGui.QWizard): sig.prov_check_ca_fingerprint.connect(self._check_ca_fingerprint) sig.prov_check_api_certificate.connect(self._check_api_certificate) + sig.srp_registration_finished.connect(self._registration_finished) + sig.srp_registration_failed.connect(self._registration_failed) + sig.srp_registration_taken.connect(self._registration_taken) + def _backend_disconnect(self): """ This method is called when the wizard dialog is closed. -- cgit v1.2.3 From 681ef643a977bc7b7975f10a8aac043dbec53fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 27 Mar 2014 11:28:37 -0300 Subject: Properly set menubar nativeness after setupUi --- src/leap/bitmask/gui/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index e4e71dcc..6d3a05fa 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -138,7 +138,6 @@ class MainWindow(QtGui.QMainWindow): :type bypass_checks: bool """ QtGui.QMainWindow.__init__(self) - self.menuBar().setNativeMenuBar(False) # register leap events ######################################## register(signal=proto.UPDATER_NEW_UPDATES, @@ -158,6 +157,7 @@ class MainWindow(QtGui.QMainWindow): # setup UI self.ui = Ui_MainWindow() self.ui.setupUi(self) + self.menuBar().setNativeMenuBar(True) self._backend = backend.Backend(bypass_checks) self._backend.start() -- cgit v1.2.3 From cebbb1eaee51ad9a7e57016dfc62d53888bc5bd4 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 1 Apr 2014 17:03:08 -0300 Subject: Cleanup strings. --- src/leap/bitmask/util/leap_argparse.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index 56bf26dc..7f81881d 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -27,9 +27,10 @@ def build_parser(): All the options for the leap arg parser Some of these could be switched on only if debug flag is present! """ - epilog = "Copyright 2012-2014 The LEAP Encryption Access Project" - parser = argparse.ArgumentParser(description=""" -Launches the Bitmask client.""", epilog=epilog) + parser = argparse.ArgumentParser( + description="Launches the Bitmask client.", + epilog="Copyright 2012-2014 The LEAP Encryption Access Project") + parser.add_argument('-d', '--debug', action="store_true", help=("Launches Bitmask in debug mode, writing debug " "info to stdout.")) -- cgit v1.2.3 From 5b21dfa2ca18ef4840d908b27228f55b8e65b172 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 1 Apr 2014 17:03:14 -0300 Subject: Add support for self signed certs. Closes #5391. --- src/leap/bitmask/app.py | 2 ++ src/leap/bitmask/config/flags.py | 5 +++++ src/leap/bitmask/provider/providerbootstrapper.py | 7 ++++++- src/leap/bitmask/util/leap_argparse.py | 6 ++++++ 4 files changed, 19 insertions(+), 1 deletion(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 124671b3..02e27123 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -214,6 +214,8 @@ def main(): flags.APP_VERSION_CHECK = opts.app_version_check flags.API_VERSION_CHECK = opts.api_version_check + flags.CA_CERT_FILE = opts.ca_cert_file + BaseConfig.standalone = standalone replace_stdout = True diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py index 82501fb2..5d8bc9b3 100644 --- a/src/leap/bitmask/config/flags.py +++ b/src/leap/bitmask/config/flags.py @@ -45,3 +45,8 @@ API_VERSION_CHECK = True # Offline mode? # Used for skipping soledad bootstrapping/syncs. OFFLINE = False + + +# CA cert path +# used to allow self signed certs in requests that needs SSL +CA_CERT_FILE = None diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index 654d1790..2a519206 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -99,9 +99,14 @@ class ProviderBootstrapper(AbstractBootstrapper): :rtype: bool or str """ if self._bypass_checks: - verify = False + return False + + cert = flags.CA_CERT_FILE + if cert is not None: + verify = cert else: verify = ca_bundle.where() + return verify def _check_name_resolution(self): diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index 7f81881d..88267ff8 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -93,6 +93,12 @@ def build_parser(): "Use at your own risk!") parser.add_argument('--danger', action="store_true", help=help_text) + # optional cert file used to check domains with self signed certs. + parser.add_argument('--ca-cert-file', metavar="/path/to/cacert.pem", + nargs='?', action="store", dest="ca_cert_file", + help='Uses the given cert file to verify ' + 'against domains.') + # Not in use, we might want to reintroduce them. #parser.add_argument('-i', '--no-provider-checks', #action="store_true", default=False, -- cgit v1.2.3 From b3e476088b8b94d9f914199a01b0bee69550396f Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 2 Apr 2014 12:18:07 -0300 Subject: Add hotkey for the Help menu. Closes #5401. --- src/leap/bitmask/gui/ui/mainwindow.ui | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index ce05f8f3..d755115a 100644 --- a/src/leap/bitmask/gui/ui/mainwindow.ui +++ b/src/leap/bitmask/gui/ui/mainwindow.ui @@ -75,7 +75,7 @@ 0 0 524 - 651 + 667 @@ -85,8 +85,6 @@ 0 - - @@ -134,7 +132,6 @@ - @@ -142,8 +139,6 @@ - - @@ -172,7 +167,6 @@ - @@ -180,8 +174,6 @@ - - @@ -204,7 +196,6 @@ - @@ -212,7 +203,6 @@ - @@ -307,7 +297,7 @@ 0 0 524 - 21 + 23 @@ -324,7 +314,7 @@ - Help + &Help -- cgit v1.2.3 From f8f428395b32f5e0d4f4c8dfb5595755dae80e54 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 2 Apr 2014 14:38:19 -0300 Subject: Don't escape logs for pastebin. Closes #5433. --- src/leap/bitmask/gui/loggerwindow.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py index c01642fc..9f396574 100644 --- a/src/leap/bitmask/gui/loggerwindow.py +++ b/src/leap/bitmask/gui/loggerwindow.py @@ -127,7 +127,7 @@ class LoggerWindow(QtGui.QDialog): current_history = [] for line in history: self._add_log_line(line) - message = cgi.escape(line[LeapLogHandler.MESSAGE_KEY]) + message = line[LeapLogHandler.MESSAGE_KEY] current_history.append(message) self._current_history = "\n".join(current_history) @@ -222,7 +222,6 @@ class LoggerWindow(QtGui.QDialog): """ msg = self.tr("Your pastebin link {0}") msg = msg.format(link) - logger.debug(msg) show_info = lambda: QtGui.QMessageBox.information( self, self.tr("Pastebin OK"), msg) self._set_pastebin_sending(False) -- cgit v1.2.3 From 962dbc30fd97176b30eac9df75e8ad1697972bf2 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 2 Apr 2014 16:27:21 -0300 Subject: Refactor check for provided and enabled services. Also use: `item in some_list` instead of `some_list.count(item) > 0`. --- src/leap/bitmask/gui/mainwindow.py | 39 ++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 6d3a05fa..feb1e18b 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1193,14 +1193,35 @@ class MainWindow(QtGui.QMainWindow): self._provider_config.get_domain()) # TODO separate UI from logic. - if self._provider_config.provides_mx() and \ - self._enabled_services.count(MX_SERVICE) > 0: + if self._provides_mx_and_enabled(): self._mail_status.about_to_start() else: self._mail_status.set_disabled() self._maybe_start_eip() + def _provides_mx_and_enabled(self): + """ + Defines if the current provider provides mx and if we have it enabled. + + :returns: True if provides and is enabled, False otherwise + :rtype: bool + """ + provider_config = self._get_best_provider_config() + return (provider_config.provides_mx() and + MX_SERVICE in self._enabled_services) + + def _provides_eip_and_enabled(self): + """ + Defines if the current provider provides eip and if we have it enabled. + + :returns: True if provides and is enabled, False otherwise + :rtype: bool + """ + provider_config = self._get_best_provider_config() + return (provider_config.provides_eip() and + EIP_SERVICE in self._enabled_services) + def _maybe_run_soledad_setup_checks(self): """ Conditionally start Soledad. @@ -1332,8 +1353,7 @@ class MainWindow(QtGui.QMainWindow): # TODO for simmetry, this should be called start_smtp_service # (and delegate all the checks to the conductor) - if self._provider_config.provides_mx() and \ - self._enabled_services.count(MX_SERVICE) > 0: + if self._provides_mx_and_enabled(): self._mail_conductor.smtp_bootstrapper.run_smtp_setup_checks( self._provider_config, self._mail_conductor.smtp_config, @@ -1375,9 +1395,7 @@ class MainWindow(QtGui.QMainWindow): start_fun() return - enabled_services = self._enabled_services - if self._provider_config.provides_mx() and \ - enabled_services.count(MX_SERVICE) > 0: + if self._provides_mx_and_enabled(): start_fun() def _on_mail_client_logged_in(self, req): @@ -1770,10 +1788,7 @@ class MainWindow(QtGui.QMainWindow): provider_config = self._get_best_provider_config() - if provider_config.provides_eip() and \ - self._enabled_services.count(EIP_SERVICE) > 0 and \ - not self._already_started_eip: - + if self._provides_eip_and_enabled() and not self._already_started_eip: # XXX this should be handled by the state machine. self._eip_status.set_eip_status( self.tr("Starting...")) @@ -1788,7 +1803,7 @@ class MainWindow(QtGui.QMainWindow): self._maybe_run_soledad_setup_checks) else: if not self._already_started_eip: - if self._enabled_services.count(EIP_SERVICE) > 0: + if EIP_SERVICE in self._enabled_services: self._eip_status.set_eip_status( self.tr("Not supported"), error=True) -- cgit v1.2.3 From 469a056387fa1d69dd14f0680fa0026e0a3ff80c Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 2 Apr 2014 16:29:29 -0300 Subject: Do not start soledad if its not provided and enabled. [Closes #5411] --- src/leap/bitmask/gui/mainwindow.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index feb1e18b..04a98fac 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1230,6 +1230,9 @@ class MainWindow(QtGui.QMainWindow): if self._already_started_soledad is True: return + if not self._provides_mx_and_enabled(): + return + username = self._login_widget.get_user() password = unicode(self._login_widget.get_password()) provider_domain = self._login_widget.get_selected_provider() -- cgit v1.2.3 From b8e529c1656db1dc059b3cc1cb8d5319614c3986 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 2 Apr 2014 18:23:50 -0300 Subject: Reset checks if the provider is changed in wizard. Closes #5396. --- src/leap/bitmask/gui/wizard.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 2f274e33..e2c1a16e 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -103,6 +103,8 @@ class Wizard(QtGui.QWizard): self.ui.lnProvider.textChanged.connect(self._enable_check) self.ui.rbNewProvider.toggled.connect( lambda x: self._enable_check()) + self.ui.cbProviders.currentIndexChanged[int].connect( + self._reset_provider_check) self.ui.lblUser.returnPressed.connect( self._focus_password) -- cgit v1.2.3 From 379d13b2b56a50251144c52273aa0352df7c0222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 4 Apr 2014 15:33:31 -0300 Subject: Do not do native menu bars in Linux for now --- src/leap/bitmask/gui/mainwindow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/leap/bitmask') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 04a98fac..5abfaa67 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -49,7 +49,7 @@ from leap.bitmask.gui.wizard import Wizard from leap.bitmask.gui.systray import SysTray from leap.bitmask import provider -from leap.bitmask.platform_init import IS_WIN, IS_MAC +from leap.bitmask.platform_init import IS_WIN, IS_MAC, IS_LINUX from leap.bitmask.platform_init.initializers import init_platform from leap.bitmask import backend @@ -157,7 +157,7 @@ class MainWindow(QtGui.QMainWindow): # setup UI self.ui = Ui_MainWindow() self.ui.setupUi(self) - self.menuBar().setNativeMenuBar(True) + self.menuBar().setNativeMenuBar(not IS_LINUX) self._backend = backend.Backend(bypass_checks) self._backend.start() -- cgit v1.2.3