From 68190ead759847c8a3e890579f7fb6b8553cbaf0 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 10 Dec 2013 18:22:53 -0400 Subject: update relnotes --- relnotes.txt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/relnotes.txt b/relnotes.txt index 98af7a30..44b3b488 100644 --- a/relnotes.txt +++ b/relnotes.txt @@ -1,4 +1,4 @@ -ANNOUNCING Bitmask, the internet encryption toolkit, release 0.3.7 +ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.3.8 The LEAP team is pleased to announce the immediate availability of version 0.3.8 of Bitmask, the Internet Encryption Toolkit, codename @@ -65,14 +65,16 @@ the two. INSTALLATION We distribute the current version of Bitmask as standalone bundles for -GNU/Linux and OSX, but it is likely that you are able to run it under -other systems, specially if you are skillful and patience is one of -your virtues. +GNU/Linux, OSX and Windows, but it is likely that you are able to run +it under other systems, specially if you are skillful and patience is +one of your virtues. Have a look at "docs/user/install.rst". -Packages will be soon provided for debian and ubuntu, and the release -of windows bundles will be resumed shortly. +Packages are also provided for debian and ubuntu, add the leap +repository to your apt sources: + +deb http://deb.leap.se/debian wheezy main We will love to hear if you are interested in help making packages available for any other system. -- cgit v1.2.3 From 58e4317945ac857bb57d2a20e38fe9a632b19df0 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 12 Dec 2013 10:14:28 -0300 Subject: Disable and stop EIP on setting save. --- changes/bug-4670_disable-eip-and-disable-actions | 2 + src/leap/bitmask/gui/mainwindow.py | 56 +++++++++++++++++------- src/leap/bitmask/gui/preferenceswindow.py | 3 ++ 3 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 changes/bug-4670_disable-eip-and-disable-actions diff --git a/changes/bug-4670_disable-eip-and-disable-actions b/changes/bug-4670_disable-eip-and-disable-actions new file mode 100644 index 00000000..7f11b3bb --- /dev/null +++ b/changes/bug-4670_disable-eip-and-disable-actions @@ -0,0 +1,2 @@ +- Disable and stop EIP when you set EIP as disabled in the preferences dialog. + Closes #4670. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 929919ac..44fee6b5 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -471,12 +471,49 @@ class MainWindow(QtGui.QMainWindow): Displays the preferences window. """ - preferences_window = PreferencesWindow( + preferences = PreferencesWindow( self, self._srp_auth, self._provider_config, self._soledad, self._login_widget.get_selected_provider()) - self.soledad_ready.connect(preferences_window.set_soledad_ready) - preferences_window.show() + self.soledad_ready.connect(preferences.set_soledad_ready) + preferences.show() + preferences.preferences_saved.connect(self._update_eip_enabled_status) + + def _update_eip_enabled_status(self): + """ + SLOT + TRIGGER: + PreferencesWindow.preferences_saved + + Enable or disable the EIP start/stop actions and stop EIP if the user + disabled that service. + + :returns: if the eip actions were enabled or disabled + :rtype: bool + """ + settings = self._settings + default_provider = settings.get_defaultprovider() + enabled_services = [] + if default_provider is not None: + enabled_services = settings.get_enabled_services(default_provider) + + eip_enabled = False + if EIP_SERVICE in enabled_services: + should_autostart = settings.get_autostart_eip() + if should_autostart and default_provider is not None: + self._eip_status.enable_eip_start() + self._eip_status.set_eip_status("") + eip_enabled = True + else: + # we don't have an usable provider + # so the user needs to log in first + self._eip_status.disable_eip_start() + else: + self._stop_eip() + self._eip_status.disable_eip_start() + self._eip_status.set_eip_status(self.tr("Disabled")) + + return eip_enabled def _show_eip_preferences(self): """ @@ -1177,21 +1214,10 @@ class MainWindow(QtGui.QMainWindow): """ settings = self._settings - should_autostart = settings.get_autostart_eip() - if not should_autostart: - logger.debug('Will not autostart EIP since it is setup ' - 'to not to do it') - self.eip_needs_login.emit() + if not self._update_eip_enabled_status(): return default_provider = settings.get_defaultprovider() - - if default_provider is None: - logger.info("Cannot autostart Encrypted Internet because there is " - "no default provider configured") - self.eip_needs_login.emit() - return - self._enabled_services = settings.get_enabled_services( default_provider) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index b4bddef2..517a90c4 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -42,6 +42,8 @@ class PreferencesWindow(QtGui.QDialog): """ Window that displays the preferences. """ + preferences_saved = QtCore.Signal() + def __init__(self, parent, srp_auth, provider_config, soledad, domain): """ :param parent: parent object of the PreferencesWindow. @@ -369,6 +371,7 @@ class PreferencesWindow(QtGui.QDialog): "Services settings for provider '{0}' saved.".format(provider)) logger.debug(msg) self._set_providers_services_status(msg, success=True) + self.preferences_saved.emit() def _get_provider_config(self, domain): """ -- cgit v1.2.3 From 722cf92e10cf73e79e54f463aa19b2d01bd3cf75 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 11 Dec 2013 22:58:59 -0800 Subject: move login widget to top of main window, move preference buttons to menu. --- ...re-4753-move-login-widget-to-top-of-main-window | 2 + src/leap/bitmask/gui/mainwindow.py | 11 +- src/leap/bitmask/gui/ui/mainwindow.ui | 198 +++++++-------------- 3 files changed, 78 insertions(+), 133 deletions(-) create mode 100644 changes/feature-4753-move-login-widget-to-top-of-main-window diff --git a/changes/feature-4753-move-login-widget-to-top-of-main-window b/changes/feature-4753-move-login-widget-to-top-of-main-window new file mode 100644 index 00000000..0ab23b24 --- /dev/null +++ b/changes/feature-4753-move-login-widget-to-top-of-main-window @@ -0,0 +1,2 @@ +-- Minor UI changes: re-arrange main window so that the login widget is at the top and preferences are available under the menu. + diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 44fee6b5..1c80cce8 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -248,6 +248,8 @@ class MainWindow(QtGui.QMainWindow): self._soledad_bootstrapper.soledad_failed.connect( self._mail_status.set_soledad_failed) + self.ui.action_preferences.triggered.connect(self._show_preferences) + self.ui.action_eip_preferences.triggered.connect(self._show_eip_preferences) self.ui.action_about_leap.triggered.connect(self._about) self.ui.action_quit.triggered.connect(self.quit) self.ui.action_wizard.triggered.connect(self._launch_wizard) @@ -279,8 +281,9 @@ class MainWindow(QtGui.QMainWindow): self._action_visible = QtGui.QAction(self.tr("Hide Main Window"), self) self._action_visible.triggered.connect(self._toggle_visible) - self.ui.btnPreferences.clicked.connect(self._show_preferences) - self.ui.btnEIPPreferences.clicked.connect(self._show_eip_preferences) + # disable buttons for now, may come back later. + # self.ui.btnPreferences.clicked.connect(self._show_preferences) + # self.ui.btnEIPPreferences.clicked.connect(self._show_eip_preferences) self._enabled_services = [] @@ -467,7 +470,8 @@ class MainWindow(QtGui.QMainWindow): """ SLOT TRIGGERS: - self.ui.btnPreferences.clicked + self.ui.btnPreferences.clicked (disabled for now) + self.ui.action_preferences Displays the preferences window. """ @@ -520,6 +524,7 @@ class MainWindow(QtGui.QMainWindow): SLOT TRIGGERS: self.ui.btnEIPPreferences.clicked + self.ui.action_eip_preferences (disabled for now) Displays the EIP preferences window. """ diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index 3b83788e..4dc39d03 100644 --- a/src/leap/bitmask/gui/ui/mainwindow.ui +++ b/src/leap/bitmask/gui/ui/mainwindow.ui @@ -85,108 +85,8 @@ 0 - - - - - 0 - - - 0 - - - - - - 0 - 0 - - - - QFrame{background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(160, 160, 160, 128), stop:1 rgba(255, 255, 255, 0));} - - - - 24 - - - 24 - - - - - - 16 - 75 - true - - - - background-color: rgba(255, 255, 255, 0); - - - Encrypted Internet - - - - - - - - 48 - 20 - - - - - - - - - - - :/images/black/32/gear.png:/images/black/32/gear.png - - - false - - - false - - - false - - - - - - - - - - 12 - - - 0 - - - 12 - - - 0 - - - - - - - - - - Qt::Horizontal - - - + + @@ -199,9 +99,7 @@ false - QFrame{ -background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(160, 160, 160, 128), stop:1 rgba(255, 255, 255, 0)); -} + background-color: rgba(0,0,0,20); border-bottom: 1px solid rgba(0,0,0,30); @@ -214,36 +112,15 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb - 16 75 true - background-color: rgba(255, 255, 255, 0); - - - Login - - - - - - - - 48 - 20 - - - - + background-color: rgba(255, 255, 255, 0); border: none; - - - - - :/images/black/32/gear.png:/images/black/32/gear.png + Please Log In @@ -257,13 +134,54 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb + - + Qt::Horizontal + + + + + + + 0 + + + 0 + + + + + 12 + + + 0 + + + 12 + + + 0 + + + + + + + + + + + Qt::Horizontal + + + + + @@ -286,6 +204,15 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb + + + + + Qt::Horizontal + + + + @@ -390,6 +317,9 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb + + + @@ -406,8 +336,16 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb + + true + + + Account Preferences... + + + - Preferences... + Internet Preferences... -- cgit v1.2.3 From 551e02141d5ffa72b76d2d1541bda2f877c8d379 Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 11 Dec 2013 23:01:43 -0800 Subject: modified eip widget: change font to look better when under login widget, tightened spacing, moved upload and download icons to the buttons themselves, get rid of extra left whitespace padding on bandwidth status. --- src/leap/bitmask/gui/eip_status.py | 4 +-- src/leap/bitmask/gui/ui/eip_status.ui | 66 ++++++----------------------------- 2 files changed, 13 insertions(+), 57 deletions(-) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 4b4d360f..92bb623e 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -41,8 +41,8 @@ class EIPStatusWidget(QtGui.QWidget): EIP Status widget that displays the current state of the EIP service """ DISPLAY_TRAFFIC_RATES = True - RATE_STR = "%14.2f KB/s" - TOTAL_STR = "%14.2f Kb" + RATE_STR = "%1.2f KB/s" + TOTAL_STR = "%1.2f Kb" eip_connection_connected = QtCore.Signal() diff --git a/src/leap/bitmask/gui/ui/eip_status.ui b/src/leap/bitmask/gui/ui/eip_status.ui index d078ca0c..64821ad6 100644 --- a/src/leap/bitmask/gui/ui/eip_status.ui +++ b/src/leap/bitmask/gui/ui/eip_status.ui @@ -25,6 +25,9 @@ + + 0 + @@ -75,13 +78,6 @@ 0 - - - 14 - 75 - true - - Traffic is being routed in the clear @@ -124,12 +120,6 @@ - - - 16777215 - 32 - - 0 @@ -145,16 +135,6 @@ QLayout::SetDefaultConstraint - - - - - - - :/images/light/16/down-arrow.png - - - @@ -175,25 +155,18 @@ 16777215 - - - 11 - 75 - true - - PointingHandCursor - - text-align: left; - 0.0 KB/s true + + :/images/light/16/down-arrow.png + @@ -206,22 +179,12 @@ - 10 + 20 20 - - - - - - - :/images/light/16/up-arrow.png - - - @@ -242,25 +205,18 @@ 16777215 - - - 11 - 75 - true - - PointingHandCursor - - text-align: left; - 0.0 KB/s true + + :/images/light/16/up-arrow.png + @@ -271,7 +227,7 @@ 0 - 20 + 0 -- cgit v1.2.3 From 714c9afedfaee3c0288536cb1c3b138b20bab03c Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 11 Dec 2013 23:02:18 -0800 Subject: login widget: tighten spacing, get rid of non-standard font family. --- src/leap/bitmask/gui/ui/login.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/leap/bitmask/gui/ui/login.ui b/src/leap/bitmask/gui/ui/login.ui index e7ca1652..7e8f9daf 100644 --- a/src/leap/bitmask/gui/ui/login.ui +++ b/src/leap/bitmask/gui/ui/login.ui @@ -215,7 +215,7 @@ 0 - 24 + 0 @@ -255,7 +255,7 @@ color: rgb(132, 132, 132); -font: 75 12pt "Lucida Grande"; +font: 75 12pt; -- cgit v1.2.3 From 2a8632e60a538828e8c5b417c5b02fbdaaccc88d Mon Sep 17 00:00:00 2001 From: elijah Date: Wed, 11 Dec 2013 23:06:03 -0800 Subject: main window ui: remove unused statusbar --- src/leap/bitmask/gui/ui/mainwindow.ui | 1 - 1 file changed, 1 deletion(-) diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index 4dc39d03..ce05f8f3 100644 --- a/src/leap/bitmask/gui/ui/mainwindow.ui +++ b/src/leap/bitmask/gui/ui/mainwindow.ui @@ -334,7 +334,6 @@ - true -- cgit v1.2.3 From 97700c4278fdd67123317584dd66c946ee9340a8 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 12 Dec 2013 16:53:35 -0300 Subject: Add view for stored public keys. Closes #4734. --- changes/bug-4734_akm-stored-public-keys-view | 1 + src/leap/bitmask/gui/advanced_key_management.py | 22 +++++- src/leap/bitmask/gui/ui/advanced_key_management.ui | 79 ++++++++++++++++------ 3 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 changes/bug-4734_akm-stored-public-keys-view diff --git a/changes/bug-4734_akm-stored-public-keys-view b/changes/bug-4734_akm-stored-public-keys-view new file mode 100644 index 00000000..bdba2cb2 --- /dev/null +++ b/changes/bug-4734_akm-stored-public-keys-view @@ -0,0 +1 @@ +- Advanced Key Management: add view for stored public keys. Closes #4734. diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py index 2c0fa034..8f15719d 100644 --- a/src/leap/bitmask/gui/advanced_key_management.py +++ b/src/leap/bitmask/gui/advanced_key_management.py @@ -50,7 +50,8 @@ class AdvancedKeyManagement(QtGui.QWidget): # if Soledad is not started yet if sameProxiedObjects(soledad, None): - self.ui.container.setEnabled(False) + self.ui.gbMyKeyPair.setEnabled(False) + self.ui.gbStoredPublicKeys.setEnabled(False) msg = self.tr("NOTE: " "To use this, you need to enable/start {0}.") msg = msg.format(get_service_display_name(MX_SERVICE)) @@ -79,6 +80,12 @@ class AdvancedKeyManagement(QtGui.QWidget): self.ui.pbImportKeys.clicked.connect(self._import_keys) self.ui.pbExportKeys.clicked.connect(self._export_keys) + # Stretch columns to content + self.ui.twPublicKeys.horizontalHeader().setResizeMode( + 0, QtGui.QHeaderView.Stretch) + + self._list_keys() + def _import_keys(self): """ Imports the user's key pair. @@ -183,3 +190,16 @@ class AdvancedKeyManagement(QtGui.QWidget): return else: logger.debug('Export canceled by the user.') + + def _list_keys(self): + """ + Loads all the public keys stored in the local db to the keys table. + """ + keys = self._keymanager.get_all_keys_in_local_db() + + keys_table = self.ui.twPublicKeys + for key in keys: + row = keys_table.rowCount() + keys_table.insertRow(row) + keys_table.setItem(row, 0, QtGui.QTableWidgetItem(key.address)) + keys_table.setItem(row, 1, QtGui.QTableWidgetItem(key.key_id)) diff --git a/src/leap/bitmask/gui/ui/advanced_key_management.ui b/src/leap/bitmask/gui/ui/advanced_key_management.ui index d61aa87e..1112670f 100644 --- a/src/leap/bitmask/gui/ui/advanced_key_management.ui +++ b/src/leap/bitmask/gui/ui/advanced_key_management.ui @@ -6,8 +6,8 @@ 0 0 - 431 - 188 + 504 + 546 @@ -17,10 +17,13 @@ :/images/mask-icon.png:/images/mask-icon.png - - - - + + + + + My key pair + + @@ -90,20 +93,7 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - - + @@ -135,9 +125,56 @@ + leKeyID + leUser + leFingerprint + label_3 + label_5 + label + + + + + + Stored public keys + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + Qt::ElideRight + + + true + + + + Email + + + + + Key ID + + + + + - + -- cgit v1.2.3 From 8536638d0f57aed211c73c178b41757e28ee8a43 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 17 Dec 2013 15:56:54 -0400 Subject: display mail version too --- src/leap/bitmask/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 3bb9c8c3..719ec8ad 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -55,6 +55,7 @@ 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.common.events import server as event_server +from leap.mail import __version__ as MAIL_VERSION import codecs codecs.register(lambda name: codecs.lookup('utf-8') @@ -170,6 +171,7 @@ def main(): if opts.version: print "Bitmask version: %s" % (VERSION,) + print "leap.mail version: %s" % (MAIL_VERSION,) sys.exit(0) standalone = opts.standalone @@ -217,6 +219,7 @@ def main(): logger.info('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') logger.info('Bitmask version %s', VERSION) + logger.info('leap.mail version %s', MAIL_VERSION) logger.info('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') logger.info('Starting app') -- cgit v1.2.3 From ac55fee7ef10b5e760d72b0a98ef5dd6925fe72e Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 17 Dec 2013 15:57:30 -0400 Subject: take get_db_paths function out of class --- .../services/soledad/soledadbootstrapper.py | 54 +++++++++++----------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index d078ae96..a92c24a0 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -59,6 +59,33 @@ class SoledadInitError(Exception): message = "Error while initializing Soledad" +def get_db_paths(uuid): + """ + Returns the secrets and local db paths needed for soledad + initialization + + :param uuid: uuid for user + :type uuid: str + + :return: a tuple with secrets, local_db paths + :rtype: tuple + """ + prefix = os.path.join(get_path_prefix(), "leap", "soledad") + secrets = "%s/%s.secret" % (prefix, uuid) + local_db = "%s/%s.db" % (prefix, uuid) + + # We remove an empty file if found to avoid complains + # about the db not being properly initialized + if is_file(local_db) and is_empty_file(local_db): + try: + os.remove(local_db) + except OSError: + logger.warning( + "Could not remove empty file %s" + % local_db) + return secrets, local_db + + class SoledadBootstrapper(AbstractBootstrapper): """ Soledad init procedure @@ -127,31 +154,6 @@ class SoledadBootstrapper(AbstractBootstrapper): """ self._soledad_retries += 1 - def _get_db_paths(self, uuid): - """ - Returns the secrets and local db paths needed for soledad - initialization - - :param uuid: uuid for user - :type uuid: str - - :return: a tuple with secrets, local_db paths - :rtype: tuple - """ - prefix = os.path.join(get_path_prefix(), "leap", "soledad") - secrets = "%s/%s.secret" % (prefix, uuid) - local_db = "%s/%s.db" % (prefix, uuid) - - # We remove an empty file if found to avoid complains - # about the db not being properly initialized - if is_file(local_db) and is_empty_file(local_db): - try: - os.remove(local_db) - except OSError: - logger.warning("Could not remove empty file %s" - % local_db) - return secrets, local_db - # initialization def load_and_sync_soledad(self): @@ -163,7 +165,7 @@ class SoledadBootstrapper(AbstractBootstrapper): uuid = self.srpauth.get_uid() token = self.srpauth.get_token() - secrets_path, local_db_path = self._get_db_paths(uuid) + secrets_path, local_db_path = get_db_paths(uuid) # TODO: Select server based on timezone (issue #3308) server_dict = self._soledad_config.get_hosts() -- cgit v1.2.3 From 768a460a0f524e32528063e36a0203a3d83e8de1 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 17 Dec 2013 17:38:18 -0300 Subject: Reset registration error and input widgets. The registration widgets are cleared if the user goes back to the provider selection page. [Closes #4742] --- changes/bug-4742_reset-registration-page-error | 2 ++ src/leap/bitmask/gui/wizard.py | 10 ++++++++++ 2 files changed, 12 insertions(+) create mode 100644 changes/bug-4742_reset-registration-page-error diff --git a/changes/bug-4742_reset-registration-page-error b/changes/bug-4742_reset-registration-page-error new file mode 100644 index 00000000..2b031796 --- /dev/null +++ b/changes/bug-4742_reset-registration-page-error @@ -0,0 +1,2 @@ +- Reset registration error and input widgets if the user goes back to provider + selection in wizard. Closes #4742. diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 5f5224ae..ad0565e4 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -597,6 +597,7 @@ class Wizard(QtGui.QWizard): Prepares the pages when they appear """ if pageId == self.SELECT_PROVIDER_PAGE: + self._clear_register_widgets() skip = self.ui.rbExistingProvider.isChecked() if not self._provider_checks_ok: self._enable_check() @@ -670,3 +671,12 @@ class Wizard(QtGui.QWizard): return self.SERVICES_PAGE return QtGui.QWizard.nextId(self) + + def _clear_register_widgets(self): + """ + Clears the widgets that my be filled and a possible error message. + """ + self._set_register_status("") + self.ui.lblUser.setText("") + self.ui.lblPassword.setText("") + self.ui.lblPassword2.setText("") -- cgit v1.2.3 From 6b7a1fc2d567a0adb05e1976f809ff78f550f98e Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 17 Dec 2013 17:38:40 -0400 Subject: pep8 --- src/leap/bitmask/gui/mainwindow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 1c80cce8..75a16eb9 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -249,7 +249,8 @@ class MainWindow(QtGui.QMainWindow): self._mail_status.set_soledad_failed) self.ui.action_preferences.triggered.connect(self._show_preferences) - self.ui.action_eip_preferences.triggered.connect(self._show_eip_preferences) + self.ui.action_eip_preferences.triggered.connect( + self._show_eip_preferences) self.ui.action_about_leap.triggered.connect(self._about) self.ui.action_quit.triggered.connect(self.quit) self.ui.action_wizard.triggered.connect(self._launch_wizard) -- cgit v1.2.3 From edf47444232c9e3637e2ad71bdd5ffa8cdcc8480 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 17 Dec 2013 15:57:48 -0400 Subject: add repair mailbox utility --- src/leap/bitmask/app.py | 5 + src/leap/bitmask/services/mail/repair.py | 234 +++++++++++++++++++++++++++++++ src/leap/bitmask/util/leap_argparse.py | 6 + 3 files changed, 245 insertions(+) create mode 100644 src/leap/bitmask/services/mail/repair.py diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 719ec8ad..3bf4575e 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -54,6 +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.common.events import server as event_server from leap.mail import __version__ as MAIL_VERSION @@ -174,6 +175,10 @@ def main(): print "leap.mail version: %s" % (MAIL_VERSION,) sys.exit(0) + if opts.acct_to_repair: + repair_account(opts.acct_to_repair) + sys.exit(0) + standalone = opts.standalone bypass_checks = getattr(opts, 'danger', False) debug = opts.debug diff --git a/src/leap/bitmask/services/mail/repair.py b/src/leap/bitmask/services/mail/repair.py new file mode 100644 index 00000000..767df1ef --- /dev/null +++ b/src/leap/bitmask/services/mail/repair.py @@ -0,0 +1,234 @@ +# -*- coding: utf-8 -*- +# repair.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Utils for repairing mailbox indexes. +""" +import logging +import getpass +import os + +from collections import defaultdict + +from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.crypto.srpauth import SRPAuth +from leap.bitmask.util import get_path_prefix +from leap.bitmask.services.soledad.soledadbootstrapper import get_db_paths + +from leap.mail.imap.server import SoledadBackedAccount +from leap.soledad.client import Soledad + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + + +def initialize_soledad(uuid, email, passwd, + secrets, localdb, + gnupg_home, tempdir): + """ + Initializes soledad by hand + + :param email: ID for the user + :param gnupg_home: path to home used by gnupg + :param tempdir: path to temporal dir + :rtype: Soledad instance + """ + # XXX TODO unify with an authoritative source of mocks + # for soledad (or partial initializations). + # This is copied from the imap tests. + + server_url = "http://provider" + cert_file = "" + + class Mock(object): + def __init__(self, return_value=None): + self._return = return_value + + def __call__(self, *args, **kwargs): + return self._return + + class MockSharedDB(object): + + get_doc = Mock() + put_doc = Mock() + lock = Mock(return_value=('atoken', 300)) + unlock = Mock(return_value=True) + + def __call__(self): + return self + + Soledad._shared_db = MockSharedDB() + soledad = Soledad( + uuid, + passwd, + secrets, + localdb, + server_url, + cert_file) + + return soledad + + +class MBOXPlumber(object): + """ + An class that can fix things inside a soledadbacked account. + The idea is to gather in this helper different fixes for mailboxes + that can be invoked when data migration in the client is needed. + """ + + def __init__(self, userid, passwd): + """ + Initializes the plumber with all that's needed to authenticate + against the provider. + + :param userid: user identifier, foo@bar + :type userid: basestring + :param passwd: the soledad passphrase + :type passwd: basestring + """ + self.userid = userid + self.passwd = passwd + user, provider = userid.split('@') + self.user = user + self.sol = None + provider_config_path = os.path.join( + get_path_prefix(), + "leap", "providers", + provider, "provider.json") + provider_config = ProviderConfig() + loaded = provider_config.load(provider_config_path) + if not loaded: + print "could not load provider config!" + return self.exit() + + self.srp = SRPAuth(provider_config) + self.srp.authentication_finished.connect(self.repair_account) + + def start_auth(self): + """ + returns the user identifier for a given provider. + + :param provider: the provider to which we authenticate against. + """ + print "Authenticating with provider..." + self.d = self.srp.authenticate(self.user, self.passwd) + + def repair_account(self, *args): + """ + Gets the user id for this account. + """ + print "Got authenticated." + self.uid = self.srp.get_uid() + if not self.uid: + print "Got BAD UID from provider!" + return self.exit() + print "UID: %s" % (self.uid) + + secrets, localdb = get_db_paths(self.uid) + + self.sol = initialize_soledad( + self.uid, self.userid, self.passwd, + secrets, localdb, "/tmp", "/tmp") + + self.acct = SoledadBackedAccount(self.userid, self.sol) + for mbox_name in self.acct.mailboxes: + self.repair_mbox(mbox_name) + print "done." + self.exit() + + def repair_mbox(self, mbox_name): + """ + Repairs indexes for a given mbox + + :param mbox_name: mailbox to repair + :type mbox_name: basestring + """ + print + print "REPAIRING INDEXES FOR MAILBOX %s" % (mbox_name,) + print "----------------------------------------------" + mbox = self.acct.getMailbox(mbox_name) + len_mbox = mbox.getMessageCount() + print "There are %s messages" % (len_mbox,) + + last_ok = True if mbox.last_uid == len_mbox else False + uids_iter = (doc.content['uid'] for doc in mbox.messages.get_all()) + dupes = self._has_dupes(uids_iter) + if last_ok and not dupes: + print "Mbox does not need repair." + return + + msgs = mbox.messages.get_all() + for zindex, doc in enumerate(msgs): + mindex = zindex + 1 + old_uid = doc.content['uid'] + doc.content['uid'] = mindex + self.sol.put_doc(doc) + print "%s -> %s (%s)" % (mindex, doc.content['uid'], old_uid) + + old_last_uid = mbox.last_uid + mbox.last_uid = len_mbox + print "LAST UID: %s (%s)" % (mbox.last_uid, old_last_uid) + + def _has_dupes(self, sequence): + """ + Returns True if the given sequence of ints has duplicates. + + :param sequence: a sequence of ints + :type sequence: sequence + :rtype: bool + """ + d = defaultdict(lambda: 0) + for uid in sequence: + d[uid] += 1 + if d[uid] != 1: + return True + return False + + def exit(self): + from twisted.internet import reactor + self.d.cancel() + if self.sol: + self.sol.close() + try: + reactor.stop() + except Exception: + pass + return + + +def repair_account(userid): + """ + Starts repair process for a given account. + :param userid: the user id (email-like) + """ + from twisted.internet import reactor + passwd = unicode(getpass.getpass("Passphrase: ")) + + # go mario! + plumber = MBOXPlumber(userid, passwd) + reactor.callLater(1, plumber.start_auth) + reactor.run() + + +if __name__ == "__main__": + import sys + + logging.basicConfig() + + if len(sys.argv) != 2: + print "Usage: repair " + 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 e8a9fda9..00192247 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -51,6 +51,12 @@ Launches Bitmask""", epilog=epilog) 'searching') parser.add_argument('-V', '--version', action="store_true", help='Displays Bitmask version and exits') + parser.add_argument('-r', '--repair-mailboxes', metavar="user@provider", + nargs='?', + action="store", dest="acct_to_repair", + help='Repair mailboxes for a given account. ' + 'Use when upgrading versions after a schema ' + 'change.') # Not in use, we might want to reintroduce them. #parser.add_argument('-i', '--no-provider-checks', -- cgit v1.2.3 From a8fc15ee9fdd92189f53bf7ee700ee4db5beb30e Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 17 Dec 2013 16:22:29 -0400 Subject: add changes file --- changes/feature_4792_repair-mailboxes | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changes/feature_4792_repair-mailboxes diff --git a/changes/feature_4792_repair-mailboxes b/changes/feature_4792_repair-mailboxes new file mode 100644 index 00000000..cb570e8b --- /dev/null +++ b/changes/feature_4792_repair-mailboxes @@ -0,0 +1,3 @@ +- Add --repair-mailboxes command line option. It will be needed to migrate + existing account after a data schema changes, like it will be happening for + 0.5.0. Closes: #4792 -- cgit v1.2.3 From d9d558d44660777795d3e85611b0cfe848a92a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 11 Dec 2013 14:48:49 -0300 Subject: Refactor provider_bootstrapper out of mainwindow --- src/leap/bitmask/backend.py | 381 +++++++++++++++++++++ src/leap/bitmask/gui/mainwindow.py | 139 ++++---- src/leap/bitmask/gui/wizard.py | 68 ++-- src/leap/bitmask/provider/providerbootstrapper.py | 38 +- .../provider/tests/test_providerbootstrapper.py | 3 +- src/leap/bitmask/services/abstractbootstrapper.py | 33 +- 6 files changed, 543 insertions(+), 119 deletions(-) create mode 100644 src/leap/bitmask/backend.py diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py new file mode 100644 index 00000000..8a289a79 --- /dev/null +++ b/src/leap/bitmask/backend.py @@ -0,0 +1,381 @@ +# -*- coding: utf-8 -*- +# backend.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Backend for everything +""" +import logging +import os + +from Queue import Queue, Empty + +from twisted.internet import threads, defer +from twisted.internet.task import LoopingCall +from twisted.python import log + +import zope.interface + +from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper + +# Frontend side +from PySide import QtCore + +logger = logging.getLogger(__name__) + + +class ILEAPComponent(zope.interface.Interface): + """ + Interface that every component for the backend should comply to + """ + + key = zope.interface.Attribute("Key id for this component") + + +class ILEAPService(ILEAPComponent): + """ + Interface that every Service needs to implement + """ + + def start(self): + """ + Starts the service. + """ + pass + + def stop(self): + """ + Stops the service. + """ + pass + + def terminate(self): + """ + Terminates the service, not necessarily in a nice way. + """ + pass + + def status(self): + """ + Returns a json object with the current status for the service. + + :rtype: object (list, str, dict) + """ + # XXX: Use a namedtuple or a specific object instead of a json + # object, since parsing it will be problematic otherwise. + # It has to be something easily serializable though. + pass + + def set_configs(self, keyval): + """ + Sets the config parameters for this Service. + + :param keyval: values to configure + :type keyval: dict, {str: str} + """ + pass + + def get_configs(self, keys): + """ + Returns the configuration values for the list of keys. + + :param keys: keys to retrieve + :type keys: list of str + + :rtype: dict, {str: str} + """ + pass + + +class Provider(object): + """ + Interfaces with setup and bootstrapping operations for a provider + """ + + zope.interface.implements(ILEAPComponent) + + PROBLEM_SIGNAL = "prov_problem_with_provider" + + def __init__(self, signaler=None, bypass_checks=False): + """ + Constructor for the Provider component + + :param signaler: Object in charge of handling communication + back to the frontend + :type signaler: Signaler + :param bypass_checks: Set to true if the app should bypass + first round of checks for CA + certificates at bootstrap + :type bypass_checks: bool + """ + object.__init__(self) + self.key = "provider" + self._provider_bootstrapper = ProviderBootstrapper(signaler, + bypass_checks) + self._download_provider_defer = None + self._provider_config = ProviderConfig() + + def setup_provider(self, provider): + """ + Initiates the setup for a provider + + :param provider: URL for the provider + :type provider: unicode + """ + log.msg("Setting up provider %s..." % (provider.encode("idna"),)) + pb = self._provider_bootstrapper + d = pb.run_provider_select_checks(provider, download_if_needed=True) + self._download_provider_defer = d + return d + + def bootstrap(self, provider): + """ + Second stage of bootstrapping for a provider. + + :param provider: URL for the provider + :type provider: unicode + """ + + d = None + + # If there's no loaded provider or + # we want to connect to other provider... + if (not self._provider_config.loaded() or + self._provider_config.get_domain() != provider): + self._provider_config.load( + os.path.join("leap", "providers", + provider, "provider.json")) + + if self._provider_config.loaded(): + d = self._provider_bootstrapper.run_provider_setup_checks( + self._provider_config, + download_if_needed=True) + else: + if self._signaler is not None: + self._signaler.signal(self.PROBLEM_SIGNAL) + logger.error("Could not load provider configuration.") + self._login_widget.set_enabled(True) + + if d is None: + d = defer.Deferred() + return d + + +class Signaler(QtCore.QObject): + """ + Signaler object, handles converting string commands to Qt signals. + + This is intended for the separation in frontend/backend, this will + live in the frontend. + """ + + # Signals for the ProviderBootstrapper + # These will only exist in the frontend + prov_name_resolution = QtCore.Signal(object) + prov_https_connection = QtCore.Signal(object) + prov_download_provider_info = QtCore.Signal(object) + + prov_download_ca_cert = QtCore.Signal(object) + prov_check_ca_fingerprint = QtCore.Signal(object) + prov_check_api_certificate = QtCore.Signal(object) + + prov_problem_with_provider = QtCore.Signal(object) + + # These will exist both in the backend and the front end. + # The frontend might choose to not "interpret" all the signals + # from the backend, but the backend needs to have all the signals + # it's going to emit defined here + PROV_NAME_RESOLUTION_KEY = "prov_name_resolution" + PROV_HTTPS_CONNECTION_KEY = "prov_https_connection" + PROV_DOWNLOAD_PROVIDER_INFO_KEY = "prov_download_provider_info" + PROV_DOWNLOAD_CA_CERT_KEY = "prov_download_ca_cert" + PROV_CHECK_CA_FINGERPRINT_KEY = "prov_check_ca_fingerprint" + PROV_CHECK_API_CERTIFICATE_KEY = "prov_check_api_certificate" + PROV_PROV_PROBLEM_WITH_PROVIER_KEY = "prov_problem_with_provider" + + def __init__(self): + """ + Constructor for the Signaler + """ + QtCore.QObject.__init__(self) + self._signals = {} + + signals = [ + self.PROV_NAME_RESOLUTION_KEY, + self.PROV_HTTPS_CONNECTION_KEY, + self.PROV_DOWNLOAD_PROVIDER_INFO_KEY, + self.PROV_DOWNLOAD_CA_CERT_KEY, + self.PROV_CHECK_CA_FINGERPRINT_KEY, + self.PROV_CHECK_API_CERTIFICATE_KEY, + self.PROV_PROV_PROBLEM_WITH_PROVIER_KEY + ] + + for sig in signals: + self._signals[sig] = getattr(self, sig) + + def signal(self, key, data=None): + """ + Emits a Qt signal based on the key provided, with the data if provided. + + :param key: string identifying the signal to emit + :type key: str + :param data: object to send with the data + :type data: object + + NOTE: The data object will be a serialized str in the backend, + and an unserialized object in the frontend, but for now we + just care about objects. + """ + # Right now it emits Qt signals. The backend version of this + # will do zmq.send_multipart, and the frontend version will be + # similar to this + log.msg("Signaling %s :: %s" % (key, data)) + try: + self._signals[key].emit(data) + except KeyError: + log.msg("Unknown key for signal %s!" % (key,)) + + +class Backend(object): + """ + Backend for everything, the UI should only use this class. + """ + + PASSED_KEY = "passed" + ERROR_KEY = "error" + + def __init__(self, bypass_checks=False): + """ + Constructor for the backend. + """ + object.__init__(self) + + # Components map for the commands received + self._components = {} + + # Ongoing defers that will be cancelled at stop time + self._ongoing_defers = [] + + # Signaler object to translate commands into Qt signals + self._signaler = Signaler() + + # Component registration + self._register(Provider(self._signaler, bypass_checks)) + + # We have a looping call on a thread executing all the + # commands in queue. Right now this queue is an actual Queue + # object, but it'll become the zmq recv_multipart queue + self._lc = LoopingCall(threads.deferToThread, self._worker) + + # Temporal call_queue for worker, will be replaced with + # recv_multipart os something equivalent in the loopingcall + self._call_queue = Queue() + + @property + def signaler(self): + """ + Public signaler access to let the UI connect to its signals. + """ + return self._signaler + + def start(self): + """ + Starts the looping call + """ + log.msg("Starting worker...") + self._lc.start(0.01) + + def stop(self): + """ + Stops the looping call and tries to cancel all the defers. + """ + log.msg("Stopping worker...") + self._lc.stop() + while len(self._ongoing_defers) > 0: + d = self._ongoing_defers.pop() + d.cancel() + + def _register(self, component): + """ + Registers a component in this backend + + :param component: Component to register + :type component: any object that implements ILEAPComponent + """ + # TODO: assert that the component implements the interfaces + # expected + try: + self._components[component.key] = component + except Exception: + log.msg("There was a problem registering %s" % (component,)) + log.err() + + def _signal_back(self, _, signal): + """ + Helper method to signal back (callback like behavior) to the + UI that an operation finished. + + :param signal: signal name + :type signal: str + """ + self._signaler.signal(signal) + + def _worker(self): + """ + Worker method, called from a different thread and as a part of + a looping call + """ + try: + # this'll become recv_multipart + cmd = self._call_queue.get(block=False) + + # cmd is: component, method, signalback, *args + func = getattr(self._components[cmd[0]], cmd[1]) + d = func(*cmd[3:]) + # A call might not have a callback signal, but if it does, + # we add it to the chain + if cmd[2] is not None: + d.addCallbacks(self._signal_back, log.err, cmd[2]) + d.addCallbacks(self._done_action, log.err, + callbackKeywords={"d": d}) + d.addErrback(log.err) + self._ongoing_defers.append(d) + except Empty: + # If it's just empty we don't have anything to do. + pass + except Exception: + # But we log the rest + log.err() + + def _done_action(self, _, d): + """ + Remover of the defer once it's done + + :param d: defer to remove + :type d: twisted.internet.defer.Deferred + """ + self._ongoing_defers.remove(d) + + # XXX: Temporal interface until we migrate to zmq + # We simulate the calls to zmq.send_multipart. Once we separate + # this in two processes, the methods bellow can be changed to + # send_multipart and this backend class will be really simple. + + def setup_provider(self, provider): + self._call_queue.put(("provider", "setup_provider", None, provider)) + + def provider_bootstrap(self, provider): + self._call_queue.put(("provider", "bootstrap", None, provider)) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 75a16eb9..7dcb9908 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -18,9 +18,9 @@ Main window for Bitmask. """ import logging -import os from PySide import QtCore, QtGui +from functools import partial from twisted.internet import threads from zope.proxy import ProxyBase, setProxiedObject @@ -42,9 +42,10 @@ from leap.bitmask.gui.systray import SysTray from leap.bitmask import provider from leap.bitmask.platform_init import IS_WIN, IS_MAC from leap.bitmask.platform_init.initializers import init_platform -from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper -from leap.bitmask.services import get_service_display_name, EIP_SERVICE +from leap.bitmask import backend + +from leap.bitmask.services import get_service_display_name from leap.bitmask.services.mail import conductor as mail_conductor @@ -138,6 +139,9 @@ class MainWindow(QtGui.QMainWindow): self.ui = Ui_MainWindow() self.ui.setupUi(self) + self._backend = backend.Backend(bypass_checks) + self._backend.start() + self._settings = LeapSettings() self._login_widget = LoginWidget( @@ -180,7 +184,10 @@ class MainWindow(QtGui.QMainWindow): # This is loaded only once, there's a bug when doing that more # than once - self._provider_config = ProviderConfig() + # XXX HACK!! But we need it as long as we are using + # provider_config in here + self._provider_config = ( + self._backend._components["provider"]._provider_config) # Used for automatic start of EIP self._provisional_provider_config = ProviderConfig() self._eip_config = eipconfig.EIPConfig() @@ -191,25 +198,7 @@ class MainWindow(QtGui.QMainWindow): self._srp_auth = None self._logged_user = None - # This thread is always running, although it's quite - # lightweight when it's done setting up provider - # configuration and certificate. - self._provider_bootstrapper = ProviderBootstrapper(bypass_checks) - - # Intermediate stages, only do something if there was an error - self._provider_bootstrapper.name_resolution.connect( - self._intermediate_stage) - self._provider_bootstrapper.https_connection.connect( - self._intermediate_stage) - self._provider_bootstrapper.download_ca_cert.connect( - self._intermediate_stage) - - # Important stages, loads the provider config and checks - # certificates - self._provider_bootstrapper.download_provider_info.connect( - self._load_provider_config) - self._provider_bootstrapper.check_api_certificate.connect( - self._provider_config_loaded) + self._backend_connect() # This thread is similar to the provider bootstrapper self._eip_bootstrapper = EIPBootstrapper() @@ -349,7 +338,9 @@ class MainWindow(QtGui.QMainWindow): if self._first_run(): self._wizard_firstrun = True - self._wizard = Wizard(bypass_checks=bypass_checks) + self._backend_disconnect() + self._wizard = Wizard(backend=self._backend, + bypass_checks=bypass_checks) # Give this window time to finish init and then show the wizard QtCore.QTimer.singleShot(1, self._launch_wizard) self._wizard.accepted.connect(self._finish_init) @@ -359,6 +350,47 @@ class MainWindow(QtGui.QMainWindow): # so this has to be done after eip_machine is started self._finish_init() + def _backend_connect(self): + """ + Helper to connect to backend signals + """ + self._backend.signaler.prov_name_resolution.connect( + self._intermediate_stage) + self._backend.signaler.prov_https_connection.connect( + self._intermediate_stage) + self._backend.signaler.prov_download_ca_cert.connect( + self._intermediate_stage) + + self._backend.signaler.prov_download_provider_info.connect( + self._load_provider_config) + self._backend.signaler.prov_check_api_certificate.connect( + self._provider_config_loaded) + + # Only used at login, no need to disconnect this like we do + # with the other + self._backend.signaler.prov_problem_with_provider.connect( + partial(self._login_widget.set_status, + self.tr("Unable to login: Problem with provider"))) + + def _backend_disconnect(self): + """ + Helper to disconnect from backend signals. + + Some signals are emitted from the wizard, and we want to + ignore those. + """ + self._backend.signaler.prov_name_resolution.disconnect( + self._intermediate_stage) + self._backend.signaler.prov_https_connection.disconnect( + self._intermediate_stage) + self._backend.signaler.prov_download_ca_cert.disconnect( + self._intermediate_stage) + + self._backend.signaler.prov_download_provider_info.disconnect( + self._load_provider_config) + self._backend.signaler.prov_check_api_certificate.disconnect( + self._provider_config_loaded) + def _rejected_wizard(self): """ SLOT @@ -379,6 +411,7 @@ class MainWindow(QtGui.QMainWindow): # This happens if the user finishes the provider # setup but does not register self._wizard = None + self._backend_connect() self._finish_init() def _launch_wizard(self): @@ -394,7 +427,9 @@ class MainWindow(QtGui.QMainWindow): there. """ if self._wizard is None: - self._wizard = Wizard(bypass_checks=self._bypass_checks) + self._backend_disconnect() + self._wizard = Wizard(backend=self._backend, + bypass_checks=self._bypass_checks) self._wizard.accepted.connect(self._finish_init) self._wizard.rejected.connect(self._wizard.close) @@ -628,6 +663,7 @@ class MainWindow(QtGui.QMainWindow): self.eip_needs_login.emit() self._wizard = None + self._backend_connect() else: self._try_autostart_eip() @@ -856,14 +892,12 @@ class MainWindow(QtGui.QMainWindow): # XXX should rename this provider, name clash. provider = self._login_widget.get_selected_provider() - pb = self._provider_bootstrapper - d = pb.run_provider_select_checks(provider, download_if_needed=True) - self._download_provider_defer = d + self._backend.setup_provider(provider) def _load_provider_config(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.download_provider_info + TRIGGER: self._backend.signaler.prov_download_provider_info Once the provider config has been downloaded, this loads the self._provider_config instance with it and starts the second @@ -873,31 +907,13 @@ class MainWindow(QtGui.QMainWindow): run_provider_select_checks :type data: dict """ - if data[self._provider_bootstrapper.PASSED_KEY]: - # XXX should rename this provider, name clash. - provider = self._login_widget.get_selected_provider() - - # If there's no loaded provider or - # we want to connect to other provider... - if (not self._provider_config.loaded() or - self._provider_config.get_domain() != provider): - self._provider_config.load( - os.path.join("leap", "providers", - provider, "provider.json")) - - if self._provider_config.loaded(): - self._provider_bootstrapper.run_provider_setup_checks( - self._provider_config, - download_if_needed=True) - else: - self._login_widget.set_status( - self.tr("Unable to login: Problem with provider")) - logger.error("Could not load provider configuration.") - self._login_widget.set_enabled(True) + if data[self._backend.PASSED_KEY]: + selected_provider = self._login_widget.get_selected_provider() + self._backend.provider_bootstrap(selected_provider) else: self._login_widget.set_status( self.tr("Unable to login: Problem with provider")) - logger.error(data[self._provider_bootstrapper.ERROR_KEY]) + logger.error(data[self._backend.ERROR_KEY]) self._login_widget.set_enabled(True) def _login(self): @@ -939,14 +955,14 @@ class MainWindow(QtGui.QMainWindow): def _provider_config_loaded(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.check_api_certificate + TRIGGER: self._backend.signaler.prov_check_api_certificate Once the provider configuration is loaded, this starts the SRP authentication """ leap_assert(self._provider_config, "We need a provider config!") - if data[self._provider_bootstrapper.PASSED_KEY]: + if data[self._backend.PASSED_KEY]: username = self._login_widget.get_user() password = self._login_widget.get_password() @@ -964,7 +980,7 @@ class MainWindow(QtGui.QMainWindow): else: self._login_widget.set_status( "Unable to login: Problem with provider") - logger.error(data[self._provider_bootstrapper.ERROR_KEY]) + logger.error(data[self._backend.ERROR_KEY]) self._login_widget.set_enabled(True) def _authentication_finished(self, ok, message): @@ -1538,11 +1554,11 @@ class MainWindow(QtGui.QMainWindow): This is used for intermediate bootstrapping stages, in case they fail. """ - passed = data[self._provider_bootstrapper.PASSED_KEY] + passed = data[self._backend.PASSED_KEY] if not passed: self._login_widget.set_status( self.tr("Unable to connect: Problem with provider")) - logger.error(data[self._provider_bootstrapper.ERROR_KEY]) + logger.error(data[self._backend.ERROR_KEY]) self._already_started_eip = False # end of EIP methods --------------------------------------------- @@ -1615,21 +1631,21 @@ class MainWindow(QtGui.QMainWindow): """ SLOT TRIGGERS: - self._provider_bootstrapper.name_resolution - self._provider_bootstrapper.https_connection - self._provider_bootstrapper.download_ca_cert + self._backend.signaler.prov_name_resolution + self._backend.signaler.prov_https_connection + self._backend.signaler.prov_download_ca_cert self._eip_bootstrapper.download_config If there was a problem, displays it, otherwise it does nothing. This is used for intermediate bootstrapping stages, in case they fail. """ - passed = data[self._provider_bootstrapper.PASSED_KEY] + passed = data[self._backend.PASSED_KEY] if not passed: self._login_widget.set_enabled(True) self._login_widget.set_status( self.tr("Unable to connect: Problem with provider")) - logger.error(data[self._provider_bootstrapper.ERROR_KEY]) + logger.error(data[self._backend.ERROR_KEY]) # # window handling methods @@ -1719,6 +1735,7 @@ class MainWindow(QtGui.QMainWindow): # Set this in case that the app is hidden QtGui.QApplication.setQuitOnLastWindowClosed(True) + self._backend.stop() self._cleanup_and_quit() self._really_quit = True diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index ad0565e4..ec007110 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -30,7 +30,6 @@ from twisted.internet import threads from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpregister import SRPRegister -from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper from leap.bitmask.services import get_service_display_name, get_supported from leap.bitmask.util.request_helpers import get_content from leap.bitmask.util.keyring_helpers import has_keyring @@ -55,12 +54,15 @@ class Wizard(QtGui.QWizard): BARE_USERNAME_REGEX = r"^[A-Za-z\d_]+$" - def __init__(self, bypass_checks=False): + def __init__(self, backend, bypass_checks=False): """ Constructor for the main Wizard. + :param backend: Backend being used + :type backend: Backend :param bypass_checks: Set to true if the app should bypass - first round of checks for CA certificates at bootstrap + first round of checks for CA + certificates at bootstrap :type bypass_checks: bool """ QtGui.QWizard.__init__(self) @@ -68,6 +70,8 @@ class Wizard(QtGui.QWizard): self.ui = Ui_Wizard() self.ui.setupUi(self) + self._backend = backend + self.setPixmap(QtGui.QWizard.LogoPixmap, QtGui.QPixmap(":/images/mask-icon.png")) @@ -86,23 +90,25 @@ class Wizard(QtGui.QWizard): self.ui.btnCheck.clicked.connect(self._check_provider) self.ui.lnProvider.returnPressed.connect(self._check_provider) - self._provider_bootstrapper = ProviderBootstrapper(bypass_checks) - self._provider_bootstrapper.name_resolution.connect( + self._backend.signaler.prov_name_resolution.connect( self._name_resolution) - self._provider_bootstrapper.https_connection.connect( + self._backend.signaler.prov_https_connection.connect( self._https_connection) - self._provider_bootstrapper.download_provider_info.connect( + self._backend.signaler.prov_download_provider_info.connect( self._download_provider_info) - self._provider_bootstrapper.download_ca_cert.connect( + self._backend.signaler.prov_download_ca_cert.connect( self._download_ca_cert) - self._provider_bootstrapper.check_ca_fingerprint.connect( + self._backend.signaler.prov_check_ca_fingerprint.connect( self._check_ca_fingerprint) - self._provider_bootstrapper.check_api_certificate.connect( + self._backend.signaler.prov_check_api_certificate.connect( self._check_api_certificate) self._domain = None - self._provider_config = ProviderConfig() + # HACK!! We need provider_config for the time being, it'll be + # removed + self._provider_config = ( + self._backend._components["provider"]._provider_config) # We will store a reference to the defers for eventual use # (eg, to cancel them) but not doing anything with them right now. @@ -385,8 +391,8 @@ class Wizard(QtGui.QWizard): self._domain = self.ui.lnProvider.text() self.ui.lblNameResolution.setPixmap(self.QUESTION_ICON) - self._provider_select_defer = self._provider_bootstrapper.\ - run_provider_select_checks(self._domain) + self._provider_select_defer = self._backend.\ + setup_provider(self._domain) def _skip_provider_checks(self, skip): """ @@ -423,8 +429,8 @@ class Wizard(QtGui.QWizard): :param complete_page: page id to complete :type complete_page: int """ - passed = data[self._provider_bootstrapper.PASSED_KEY] - error = data[self._provider_bootstrapper.ERROR_KEY] + passed = data[self._backend.PASSED_KEY] + error = data[self._backend.ERROR_KEY] if passed: label.setPixmap(self.OK_ICON) if complete: @@ -437,13 +443,13 @@ class Wizard(QtGui.QWizard): def _name_resolution(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.name_resolution + TRIGGER: self._backend.signaler.prov_name_resolution Sets the status for the name resolution check """ self._complete_task(data, self.ui.lblNameResolution) status = "" - passed = data[self._provider_bootstrapper.PASSED_KEY] + passed = data[self._backend.PASSED_KEY] if not passed: status = self.tr("Non-existent " "provider") @@ -456,16 +462,16 @@ class Wizard(QtGui.QWizard): def _https_connection(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.https_connection + TRIGGER: self._backend.signaler.prov_https_connection Sets the status for the https connection check """ self._complete_task(data, self.ui.lblHTTPS) status = "" - passed = data[self._provider_bootstrapper.PASSED_KEY] + passed = data[self._backend.PASSED_KEY] if not passed: status = self.tr("%s") \ - % (data[self._provider_bootstrapper.ERROR_KEY]) + % (data[self._backend.ERROR_KEY]) self.ui.lblProviderSelectStatus.setText(status) else: self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON) @@ -475,7 +481,7 @@ class Wizard(QtGui.QWizard): def _download_provider_info(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.download_provider_info + TRIGGER: self._backend.signaler.prov_download_provider_info Sets the status for the provider information download check. Since this check is the last of this set, it also @@ -490,14 +496,14 @@ class Wizard(QtGui.QWizard): self._provider_checks_ok = True else: new_data = { - self._provider_bootstrapper.PASSED_KEY: False, - self._provider_bootstrapper.ERROR_KEY: + self._backend.PASSED_KEY: False, + self._backend.ERROR_KEY: self.tr("Unable to load provider configuration") } self._complete_task(new_data, self.ui.lblProviderInfo) status = "" - if not data[self._provider_bootstrapper.PASSED_KEY]: + if not data[self._backend.PASSED_KEY]: status = self.tr("Not a valid provider" "") self.ui.lblProviderSelectStatus.setText(status) @@ -507,31 +513,31 @@ class Wizard(QtGui.QWizard): def _download_ca_cert(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.download_ca_cert + TRIGGER: self._backend.signaler.prov_download_ca_cert Sets the status for the download of the CA certificate check """ self._complete_task(data, self.ui.lblDownloadCaCert) - passed = data[self._provider_bootstrapper.PASSED_KEY] + passed = data[self._backend.PASSED_KEY] if passed: self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON) def _check_ca_fingerprint(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.check_ca_fingerprint + TRIGGER: self._backend.signaler.prov_check_ca_fingerprint Sets the status for the CA fingerprint check """ self._complete_task(data, self.ui.lblCheckCaFpr) - passed = data[self._provider_bootstrapper.PASSED_KEY] + passed = data[self._backend.PASSED_KEY] if passed: self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON) def _check_api_certificate(self, data): """ SLOT - TRIGGER: self._provider_bootstrapper.check_api_certificate + TRIGGER: self._backend.signaler.prov_check_api_certificate Sets the status for the API certificate check. Also finishes the provider bootstrapper thread since it's not needed anymore @@ -612,8 +618,8 @@ class Wizard(QtGui.QWizard): sub_title = sub_title.format(self._provider_config.get_name()) self.page(pageId).setSubTitle(sub_title) self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON) - self._provider_setup_defer = self._provider_bootstrapper.\ - run_provider_setup_checks(self._provider_config) + self._provider_setup_defer = self._backend.\ + provider_bootstrap(self._domain) if pageId == self.PRESENT_PROVIDER_PAGE: self.page(pageId).setSubTitle(self.tr("Description of services " diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index f5a2003f..947ba0c9 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -24,8 +24,6 @@ import sys import requests -from PySide import QtCore - from leap.bitmask.config.providerconfig import ProviderConfig, MissingCACert from leap.bitmask.util.request_helpers import get_content from leap.bitmask import util @@ -61,25 +59,19 @@ class ProviderBootstrapper(AbstractBootstrapper): If a check fails, the subsequent checks are not executed """ - # All dicts returned are of the form - # {"passed": bool, "error": str} - name_resolution = QtCore.Signal(dict) - https_connection = QtCore.Signal(dict) - download_provider_info = QtCore.Signal(dict) - - download_ca_cert = QtCore.Signal(dict) - check_ca_fingerprint = QtCore.Signal(dict) - check_api_certificate = QtCore.Signal(dict) - - def __init__(self, bypass_checks=False): + def __init__(self, signaler=None, bypass_checks=False): """ Constructor for provider bootstrapper object + :param signaler: Signaler object used to receive notifications + from the backend + :type signaler: Signaler :param bypass_checks: Set to true if the app should bypass - first round of checks for CA certificates at bootstrap + first round of checks for CA + certificates at bootstrap :type bypass_checks: bool """ - AbstractBootstrapper.__init__(self, bypass_checks) + AbstractBootstrapper.__init__(self, signaler, bypass_checks) self._domain = None self._provider_config = None @@ -238,9 +230,11 @@ class ProviderBootstrapper(AbstractBootstrapper): self._download_if_needed = download_if_needed cb_chain = [ - (self._check_name_resolution, self.name_resolution), - (self._check_https, self.https_connection), - (self._download_provider_info, self.download_provider_info) + (self._check_name_resolution, + self._signaler.PROV_NAME_RESOLUTION_KEY), + (self._check_https, self._signaler.PROV_HTTPS_CONNECTION_KEY), + (self._download_provider_info, + self._signaler.PROV_DOWNLOAD_PROVIDER_INFO_KEY) ] return self.addCallbackChain(cb_chain) @@ -367,9 +361,11 @@ class ProviderBootstrapper(AbstractBootstrapper): self._download_if_needed = download_if_needed cb_chain = [ - (self._download_ca_cert, self.download_ca_cert), - (self._check_ca_fingerprint, self.check_ca_fingerprint), - (self._check_api_certificate, self.check_api_certificate) + (self._download_ca_cert, self._signaler.PROV_DOWNLOAD_CA_CERT_KEY), + (self._check_ca_fingerprint, + self._signaler.PROV_CHECK_CA_FINGERPRINT_KEY), + (self._check_api_certificate, + self._signaler.PROV_CHECK_API_CERTIFICATE_KEY) ] return self.addCallbackChain(cb_chain) diff --git a/src/leap/bitmask/provider/tests/test_providerbootstrapper.py b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py index 88a4ff0b..d8336fec 100644 --- a/src/leap/bitmask/provider/tests/test_providerbootstrapper.py +++ b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py @@ -42,6 +42,7 @@ from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper from leap.bitmask.provider.providerbootstrapper import UnsupportedProviderAPI from leap.bitmask.provider.providerbootstrapper import WrongFingerprint from leap.bitmask.provider.supportedapis import SupportedAPIs +from leap.bitmask.backend import Signaler from leap.bitmask import util from leap.common.files import mkdir_p from leap.common.testing.https_server import where @@ -50,7 +51,7 @@ from leap.common.testing.basetest import BaseLeapTest class ProviderBootstrapperTest(BaseLeapTest): def setUp(self): - self.pb = ProviderBootstrapper() + self.pb = ProviderBootstrapper(Signaler()) def tearDown(self): pass diff --git a/src/leap/bitmask/services/abstractbootstrapper.py b/src/leap/bitmask/services/abstractbootstrapper.py index 6d4d319b..3bee8e01 100644 --- a/src/leap/bitmask/services/abstractbootstrapper.py +++ b/src/leap/bitmask/services/abstractbootstrapper.py @@ -25,6 +25,8 @@ import requests from functools import partial from PySide import QtCore + +from twisted.python import log from twisted.internet import threads from leap.common.check import leap_assert, leap_assert_type @@ -40,10 +42,13 @@ class AbstractBootstrapper(QtCore.QObject): PASSED_KEY = "passed" ERROR_KEY = "error" - def __init__(self, bypass_checks=False): + def __init__(self, signaler=None, bypass_checks=False): """ Constructor for the abstract bootstrapper + :param signaler: Signaler object used to receive notifications + from the backend + :type signaler: Signaler :param bypass_checks: Set to true if the app should bypass first round of checks for CA certificates at bootstrap @@ -71,6 +76,7 @@ class AbstractBootstrapper(QtCore.QObject): self._bypass_checks = bypass_checks self._signal_to_emit = None self._err_msg = None + self._signaler = signaler def _gui_errback(self, failure): """ @@ -89,10 +95,20 @@ class AbstractBootstrapper(QtCore.QObject): err_msg = self._err_msg \ if self._err_msg is not None \ else str(failure.value) - self._signal_to_emit.emit({ + data = { self.PASSED_KEY: False, self.ERROR_KEY: err_msg - }) + } + # TODO: Remove this check when all the bootstrappers are + # in the backend form + if isinstance(self._signal_to_emit, basestring): + if self._signaler is not None: + self._signaler.signal(self._signal_to_emit, data) + else: + logger.warning("Tried to notify but no signaler found") + else: + self._signal_to_emit.emit(data) + log.err(failure) failure.trap(Exception) def _errback(self, failure, signal=None): @@ -127,8 +143,15 @@ class AbstractBootstrapper(QtCore.QObject): :param signal: Signal to emit if it fails here first :type signal: QtCore.SignalInstance """ - if signal: - signal.emit({self.PASSED_KEY: True, self.ERROR_KEY: ""}) + if signal is not None: + data = {self.PASSED_KEY: True, self.ERROR_KEY: ""} + if isinstance(signal, basestring): + if self._signaler is not None: + self._signaler.signal(signal, data) + else: + logger.warning("Tried to notify but no signaler found") + else: + signal.emit(data) def _callback_threader(self, cb, res, *args, **kwargs): return threads.deferToThread(cb, res, *args, **kwargs) -- cgit v1.2.3 From 49d66da0e0bd4f52491e6dbf490010647681f759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 12 Dec 2013 15:50:14 -0300 Subject: Add changes file --- changes/feature_refactor_provider_bootstrapper | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changes/feature_refactor_provider_bootstrapper diff --git a/changes/feature_refactor_provider_bootstrapper b/changes/feature_refactor_provider_bootstrapper new file mode 100644 index 00000000..969b259f --- /dev/null +++ b/changes/feature_refactor_provider_bootstrapper @@ -0,0 +1,2 @@ +- Refactor ProviderBootstrapper out of the UI modules to a Backend + module, obscuring all the details. \ No newline at end of file -- cgit v1.2.3 From 2bb48366937bf0d5191be954ea21c671c3722d04 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 18 Dec 2013 18:08:34 -0300 Subject: Avoids permanent break of resolv.conf. The resolv.conf file gets updated by resolvconf, maintaining it as a symlink allows the system to reset it during boot time. That is useful when the app has a hard crash that gives no chance to roll back our changes. [Closes #4633] --- changes/bug-4633_fix-resolvconf-usage | 1 + pkg/linux/resolv-update | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 changes/bug-4633_fix-resolvconf-usage diff --git a/changes/bug-4633_fix-resolvconf-usage b/changes/bug-4633_fix-resolvconf-usage new file mode 100644 index 00000000..de40d03a --- /dev/null +++ b/changes/bug-4633_fix-resolvconf-usage @@ -0,0 +1 @@ +- Correct resolvconf usage. Avoids permanent break of resolv.conf. Closes #4633. diff --git a/pkg/linux/resolv-update b/pkg/linux/resolv-update index 601d3bd2..c308b788 100755 --- a/pkg/linux/resolv-update +++ b/pkg/linux/resolv-update @@ -70,7 +70,7 @@ SETVAR R="${R}nameserver $NS " done - mv /etc/resolv.conf /etc/resolv.conf.bak + cp /etc/resolv.conf /etc/resolv.conf.bak echo "$comment $custom_head $R @@ -79,8 +79,8 @@ $custom_tail" > /etc/resolv.conf function down() { if [ -f /etc/resolv.conf.bak ] ; then - unlink /etc/resolv.conf - mv /etc/resolv.conf.bak /etc/resolv.conf + cat /etc/resolv.conf.bak > /etc/resolv.conf + rm /etc/resolv.conf.bak fi } -- cgit v1.2.3 From d054554649ccc4c25599046dafe6427a19797891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 19 Dec 2013 16:58:01 -0300 Subject: Use Bitmask icon for admin permission dialog in OSX --- changes/bug_use_bitmask_icon_mac | 2 ++ src/leap/bitmask/services/eip/darwinvpnlauncher.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 changes/bug_use_bitmask_icon_mac diff --git a/changes/bug_use_bitmask_icon_mac b/changes/bug_use_bitmask_icon_mac new file mode 100644 index 00000000..d9015466 --- /dev/null +++ b/changes/bug_use_bitmask_icon_mac @@ -0,0 +1,2 @@ +- Use Bitmask icon instead of LEAP's for the super user dialog in + OSX. Fixes #4273. \ No newline at end of file diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py index fe3fe4c1..a03bfc44 100644 --- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py +++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py @@ -95,7 +95,7 @@ class DarwinVPNLauncher(VPNLauncher): resources_path = os.path.abspath( os.path.join(os.getcwd(), "../../Contents/Resources")) - return os.path.join(resources_path, "leap-client.tiff") + return os.path.join(resources_path, "bitmask.tiff") @classmethod def get_cocoasudo_ovpn_cmd(kls): -- cgit v1.2.3 From 2ef6913425a4cae950c01c9d00c1016afbb3206d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 19 Dec 2013 17:05:38 -0300 Subject: Display domain instead of provider name in the login info --- changes/feature_display_domain_instead_of_provider_name | 2 ++ src/leap/bitmask/gui/mainwindow.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 changes/feature_display_domain_instead_of_provider_name diff --git a/changes/feature_display_domain_instead_of_provider_name b/changes/feature_display_domain_instead_of_provider_name new file mode 100644 index 00000000..0b2c42a6 --- /dev/null +++ b/changes/feature_display_domain_instead_of_provider_name @@ -0,0 +1,2 @@ +- Display domain for provider the user has just logged in. Fixes + #4631. \ No newline at end of file diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 75a16eb9..979becc6 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -999,7 +999,7 @@ class MainWindow(QtGui.QMainWindow): """ self._login_widget.logged_in() - self.ui.lblLoginProvider.setText(self._provider_config.get_name()) + self.ui.lblLoginProvider.setText(self._provider_config.get_domain()) self._enabled_services = self._settings.get_enabled_services( self._provider_config.get_domain()) -- cgit v1.2.3 From 8ba650488ea1f1a50b4c22758f647c9f2ee7839d Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 20 Dec 2013 13:44:53 -0400 Subject: mail logs --- changes/feature_mail-debug | 1 + src/leap/bitmask/app.py | 2 ++ src/leap/bitmask/config/flags.py | 2 ++ src/leap/bitmask/services/mail/imap.py | 12 ++++++------ src/leap/bitmask/services/soledad/soledadbootstrapper.py | 4 ++++ src/leap/bitmask/util/leap_argparse.py | 5 +++++ 6 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 changes/feature_mail-debug diff --git a/changes/feature_mail-debug b/changes/feature_mail-debug new file mode 100644 index 00000000..5535a49a --- /dev/null +++ b/changes/feature_mail-debug @@ -0,0 +1 @@ +- Add ability to write mail logs to a separate file. diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 3bf4575e..b16a51aa 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -183,6 +183,7 @@ def main(): bypass_checks = getattr(opts, 'danger', False) debug = opts.debug logfile = opts.log_file + mail_logfile = opts.mail_log_file openvpn_verb = opts.openvpn_verb try: @@ -198,6 +199,7 @@ def main(): from leap.bitmask.config import flags from leap.common.config.baseconfig import BaseConfig flags.STANDALONE = standalone + flags.MAIL_LOGFILE = mail_logfile BaseConfig.standalone = standalone logger = add_logger_handlers(debug, logfile) diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py index 98395def..ba1b65b9 100644 --- a/src/leap/bitmask/config/flags.py +++ b/src/leap/bitmask/config/flags.py @@ -30,3 +30,5 @@ WARNING: You should NOT use this kind of flags unless you're sure of what # - search for binaries inside the bundled app instead of the system ones. # e.g.: openvpn, gpg STANDALONE = False + +MAIL_LOGFILE = None diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py index 2667f156..5db18cb9 100644 --- a/src/leap/bitmask/services/mail/imap.py +++ b/src/leap/bitmask/services/mail/imap.py @@ -19,10 +19,10 @@ Initialization of imap service """ import logging import os -#import sys +import sys from leap.mail.imap.service import imap -#from twisted.python import log +from twisted.python import log logger = logging.getLogger(__name__) @@ -58,15 +58,15 @@ def start_imap_service(*args, **kwargs): :returns: twisted.internet.task.LoopingCall instance """ + from leap.bitmask.config import flags logger.debug('Launching imap service') override_period = get_mail_check_period() if override_period: kwargs['check_period'] = override_period - # Uncomment the next two lines to get a separate debugging log - # TODO handle this by a separate flag. - #log.startLogging(open('/tmp/leap-imap.log', 'w')) - #log.startLogging(sys.stdout) + if flags.MAIL_LOGFILE: + log.startLogging(open(flags.MAIL_LOGFILE, 'w')) + log.startLogging(sys.stdout) return imap.run_service(*args, **kwargs) diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index a92c24a0..3ab62b2e 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -302,6 +302,10 @@ class SoledadBootstrapper(AbstractBootstrapper): except SSLError as exc: logger.error("%r" % (exc,)) raise SoledadSyncError("Failed to sync soledad") + except u1db_errors.InvalidGeneration as exc: + logger.error("%r" % (exc,)) + raise SoledadSyncError("u1db: InvalidGeneration") + except Exception as exc: logger.exception("Unhandled error while syncing " "soledad: %r" % (exc,)) diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index 00192247..6703b600 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -41,6 +41,11 @@ Launches Bitmask""", epilog=epilog) action="store", dest="log_file", #type=argparse.FileType('w'), help='optional log file') + parser.add_argument('-m', '--mail-logfile', + metavar="MAIL LOG FILE", nargs='?', + action="store", dest="mail_log_file", + #type=argparse.FileType('w'), + help='optional log file for email') parser.add_argument('--openvpn-verbosity', nargs='?', type=int, action="store", dest="openvpn_verb", -- cgit v1.2.3 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. --- changes/feature_moar-version-info | 2 ++ src/leap/bitmask/__init__.py | 1 + src/leap/bitmask/_appname.py | 1 + src/leap/bitmask/gui/mainwindow.py | 5 +++-- 4 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changes/feature_moar-version-info create mode 100644 src/leap/bitmask/_appname.py diff --git a/changes/feature_moar-version-info b/changes/feature_moar-version-info new file mode 100644 index 00000000..fd5452ce --- /dev/null +++ b/changes/feature_moar-version-info @@ -0,0 +1,2 @@ +- Show hash info in About bitmask (for debian versions). +- Add the appname in the reported version string. 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(-) 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(-) 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] --- .../bug-4817_disconnect-signals-on-wizard-close | 1 + src/leap/bitmask/gui/mainwindow.py | 5 +++-- src/leap/bitmask/gui/wizard.py | 23 ++++++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 changes/bug-4817_disconnect-signals-on-wizard-close diff --git a/changes/bug-4817_disconnect-signals-on-wizard-close b/changes/bug-4817_disconnect-signals-on-wizard-close new file mode 100644 index 00000000..78c14756 --- /dev/null +++ b/changes/bug-4817_disconnect-signals-on-wizard-close @@ -0,0 +1 @@ +- Disconnect signals before closing the wizard. Closes #4817. 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). --- ...4885_make-soledad-wait-for-eip-after-logging-in | 1 + src/leap/bitmask/gui/mainwindow.py | 57 +++++++++++++++------- 2 files changed, 41 insertions(+), 17 deletions(-) create mode 100644 changes/feature_4885_make-soledad-wait-for-eip-after-logging-in diff --git a/changes/feature_4885_make-soledad-wait-for-eip-after-logging-in b/changes/feature_4885_make-soledad-wait-for-eip-after-logging-in new file mode 100644 index 00000000..07b198f0 --- /dev/null +++ b/changes/feature_4885_make-soledad-wait-for-eip-after-logging-in @@ -0,0 +1 @@ +- Make first Soledad sync wait for EIP to come up after logging in (#4885). 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). --- changes/bug_4925_close_imap_session | 1 + src/leap/bitmask/services/mail/conductor.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 changes/bug_4925_close_imap_session diff --git a/changes/bug_4925_close_imap_session b/changes/bug_4925_close_imap_session new file mode 100644 index 00000000..f70b3e2b --- /dev/null +++ b/changes/bug_4925_close_imap_session @@ -0,0 +1 @@ +- Properly reset imap session on logout. Closes: #4925 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(-) 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(-) 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 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(-) 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 147233b3bd97b54a5e84944e6496818da34f60d5 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 7 Jan 2014 19:11:14 -0300 Subject: Add changelog for #4636. --- changes/feature-4636_warn-user-if-outdated-app | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changes/feature-4636_warn-user-if-outdated-app diff --git a/changes/feature-4636_warn-user-if-outdated-app b/changes/feature-4636_warn-user-if-outdated-app new file mode 100644 index 00000000..d4bd6eb7 --- /dev/null +++ b/changes/feature-4636_warn-user-if-outdated-app @@ -0,0 +1,2 @@ +- Warn the user on incompatible api error. +- Warn the user if is using an old app version. Closes #4636. -- 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(-) 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(-) 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(-) 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 50438b189c21d984d5697b0532a8960feaecdcb2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 9 Jan 2014 18:02:39 -0400 Subject: bump version compat --- changes/VERSION_COMPAT | 1 + 1 file changed, 1 insertion(+) diff --git a/changes/VERSION_COMPAT b/changes/VERSION_COMPAT index cc00ecf7..1f523ccd 100644 --- a/changes/VERSION_COMPAT +++ b/changes/VERSION_COMPAT @@ -8,3 +8,4 @@ # # BEGIN DEPENDENCY LIST ------------------------- # leap.foo.bar>=x.y.z +leap.mail>=0.3.9 -- 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(-) 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(-) 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. --- changes/feature-4943_offline-mode | 1 + docs/dev/signals.rst | 12 + 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 ++ 12 files changed, 423 insertions(+), 125 deletions(-) create mode 100644 changes/feature-4943_offline-mode create mode 100644 docs/dev/signals.rst diff --git a/changes/feature-4943_offline-mode b/changes/feature-4943_offline-mode new file mode 100644 index 00000000..02fda893 --- /dev/null +++ b/changes/feature-4943_offline-mode @@ -0,0 +1 @@ +- Offline mode for debugging. Closes: #4943 diff --git a/docs/dev/signals.rst b/docs/dev/signals.rst new file mode 100644 index 00000000..536a3746 --- /dev/null +++ b/docs/dev/signals.rst @@ -0,0 +1,12 @@ +Startup process +--------------- + +mainwindow._login -> backend.run_provider_setup_checks +[...provider bootstrap...] +self._provider_config_loaded +[...login...] +authentication_finished +_start_eip_bootstrap +_maybe_start_eip +_maybe_run_soledad_setup_checks +soledadbootstrapper 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(-) 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(-) 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(-) 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 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 40fc60e69841a2f9d0a4ba0f02a54ee06a60b56f Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 13 Jan 2014 12:16:34 -0300 Subject: Install all dependencies unzipped. Otherwise scrypt gets installed as an egg resulting in an error trying to import it. [Closes #4948] --- pkg/scripts/bootstrap_develop.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/scripts/bootstrap_develop.sh b/pkg/scripts/bootstrap_develop.sh index 07063a81..225bc303 100755 --- a/pkg/scripts/bootstrap_develop.sh +++ b/pkg/scripts/bootstrap_develop.sh @@ -115,7 +115,7 @@ setup_develop() { # do a setup develop in every package for package in $PACKAGES; do cd $REPOS_ROOT/$package - python setup.py develop + python setup.py develop --always-unzip done # hack to solve gnupg version problem -- 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 --- changes/feature_import-maildir | 1 + 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 +++-- 5 files changed, 148 insertions(+), 21 deletions(-) create mode 100644 changes/feature_import-maildir diff --git a/changes/feature_import-maildir b/changes/feature_import-maildir new file mode 100644 index 00000000..c5d861a3 --- /dev/null +++ b/changes/feature_import-maildir @@ -0,0 +1 @@ +- Add ability to import a maildir into a local mailbox. 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] --- bug-4942_fix-logout-error-message | 1 + src/leap/bitmask/gui/login.py | 17 --------------- src/leap/bitmask/gui/mainwindow.py | 5 ++--- src/leap/bitmask/gui/ui/login.ui | 43 ++++++++++++++------------------------ 4 files changed, 19 insertions(+), 47 deletions(-) create mode 100644 bug-4942_fix-logout-error-message diff --git a/bug-4942_fix-logout-error-message b/bug-4942_fix-logout-error-message new file mode 100644 index 00000000..f8c8a3b2 --- /dev/null +++ b/bug-4942_fix-logout-error-message @@ -0,0 +1 @@ +- Fix logout error message, display it similarly to other errors in the app. Closes #4942. 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(-) 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] --- changes/bug-4952_fix-unread-email-wording | 1 + src/leap/bitmask/gui/mail_status.py | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 changes/bug-4952_fix-unread-email-wording diff --git a/changes/bug-4952_fix-unread-email-wording b/changes/bug-4952_fix-unread-email-wording new file mode 100644 index 00000000..7bfee8b2 --- /dev/null +++ b/changes/bug-4952_fix-unread-email-wording @@ -0,0 +1 @@ +- Client should say 1 unread email, not emails. Closes #4952. 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] --- changes/feature-4530_add-in-app-help-for-mail | 1 + src/leap/bitmask/gui/mainwindow.py | 30 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 changes/feature-4530_add-in-app-help-for-mail diff --git a/changes/feature-4530_add-in-app-help-for-mail b/changes/feature-4530_add-in-app-help-for-mail new file mode 100644 index 00000000..9d7f3316 --- /dev/null +++ b/changes/feature-4530_add-in-app-help-for-mail @@ -0,0 +1 @@ +- Add in-app indication of how to connect to local imap and smtp. Closes #4530. 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 bcfdeb7aea4cb263a47502abd04f47ab59210b1c Mon Sep 17 00:00:00 2001 From: elijah Date: Tue, 14 Jan 2014 14:14:45 -0800 Subject: bootstrap script -- make run passthrough args work, allow init to be re-run. --- pkg/scripts/bootstrap_develop.sh | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/pkg/scripts/bootstrap_develop.sh b/pkg/scripts/bootstrap_develop.sh index 225bc303..7027a908 100755 --- a/pkg/scripts/bootstrap_develop.sh +++ b/pkg/scripts/bootstrap_develop.sh @@ -17,6 +17,7 @@ # along with this program. If not, see . ###################################################################### set -e # Exit immediately if a command exits with a non-zero status. + REPOSITORIES="bitmask_client leap_pycommon soledad keymanager leap_mail" PACKAGES="leap_pycommon keymanager soledad/common soledad/client soledad/server leap_mail bitmask_client" REPOS_ROOT=`pwd` # Root path for all the needed repositories @@ -40,20 +41,16 @@ clone_repos() { if [[ "$1" == "ro" ]]; then # read-only remotes: - git clone https://leap.se/git/bitmask_client - git clone https://leap.se/git/leap_pycommon - git clone https://leap.se/git/soledad - git clone https://leap.se/git/keymanager - git clone https://leap.se/git/leap_mail + src="https://leap.se/git" else # read-write remotes: - git clone ssh://gitolite@leap.se/bitmask_client - git clone ssh://gitolite@leap.se/leap_pycommon - git clone ssh://gitolite@leap.se/soledad - git clone ssh://gitolite@leap.se/keymanager - git clone ssh://gitolite@leap.se/leap_mail + src="ssh://gitolite@leap.se" fi + for repo in $REPOSITORIES; do + [ ! -d $repo ] && git clone $src/$repo + done + set +x echo "${cc_green}Status: $status done!${cc_normal}" } @@ -163,10 +160,12 @@ update() { } run() { + shift # remove 'run' from arg list + passthrough_args=$@ echo "${cc_green}Status: running client...${cc_normal}" source bitmask.venv/bin/activate set -x - python bitmask_client/src/leap/bitmask/app.py -d $* + python bitmask_client/src/leap/bitmask/app.py -d $passthrough_args set +x } @@ -175,7 +174,7 @@ help() { echo "Bootstraps the environment to start developing the bitmask client" echo "with all the needed repositories and dependencies." echo - echo "Usage: $0 {init | update | help}" + echo "Usage: $0 {init | update | run | help}" echo echo " init : Initialize repositories, create virtualenv and \`python setup.py develop\` all." echo " You can use \`init ro\` in order to use the https remotes if you don't have rw access." @@ -193,7 +192,7 @@ case "$1" in update ;; run) - run + run "$@" ;; *) help -- cgit v1.2.3 From cede9683aa7b5f7659506a6532f92aa90a6b1f4e Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 15 Jan 2014 20:42:06 -0400 Subject: move file to where it belongs... --- bug-4942_fix-logout-error-message | 1 - changes/bug-4942_fix-logout-error-message | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 bug-4942_fix-logout-error-message create mode 100644 changes/bug-4942_fix-logout-error-message diff --git a/bug-4942_fix-logout-error-message b/bug-4942_fix-logout-error-message deleted file mode 100644 index f8c8a3b2..00000000 --- a/bug-4942_fix-logout-error-message +++ /dev/null @@ -1 +0,0 @@ -- Fix logout error message, display it similarly to other errors in the app. Closes #4942. diff --git a/changes/bug-4942_fix-logout-error-message b/changes/bug-4942_fix-logout-error-message new file mode 100644 index 00000000..f8c8a3b2 --- /dev/null +++ b/changes/bug-4942_fix-logout-error-message @@ -0,0 +1 @@ +- Fix logout error message, display it similarly to other errors in the app. Closes #4942. -- 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. --- changes/bug_capture-keyring-errors | 2 ++ src/leap/bitmask/util/keyring_helpers.py | 30 ++++++++++++++++++++---------- 2 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 changes/bug_capture-keyring-errors diff --git a/changes/bug_capture-keyring-errors b/changes/bug_capture-keyring-errors new file mode 100644 index 00000000..a4b7046b --- /dev/null +++ b/changes/bug_capture-keyring-errors @@ -0,0 +1,2 @@ +- Fail gracefully against keyring import errors. + 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. --- changes/feature-4877_disable-akm-import | 1 + src/leap/bitmask/gui/advanced_key_management.py | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 changes/feature-4877_disable-akm-import diff --git a/changes/feature-4877_disable-akm-import b/changes/feature-4877_disable-akm-import new file mode 100644 index 00000000..eeb70e19 --- /dev/null +++ b/changes/feature-4877_disable-akm-import @@ -0,0 +1 @@ +- Disable Advanced Key Manager import feature since it's experimental and may cause data loss. Closes #4877. 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] --- changes/bug-4985_config-help-menu-non-empty-password | 1 + src/leap/bitmask/gui/mainwindow.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 changes/bug-4985_config-help-menu-non-empty-password diff --git a/changes/bug-4985_config-help-menu-non-empty-password b/changes/bug-4985_config-help-menu-non-empty-password new file mode 100644 index 00000000..c6c5266f --- /dev/null +++ b/changes/bug-4985_config-help-menu-non-empty-password @@ -0,0 +1 @@ +- Config help menu: do not use an empty password. Closes #4985. 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] --- .../feature-4488_use-existing-provider-by-default | 1 + src/leap/bitmask/gui/ui/wizard.ui | 73 +++++++++++----------- src/leap/bitmask/gui/wizard.py | 4 ++ 3 files changed, 43 insertions(+), 35 deletions(-) create mode 100644 changes/feature-4488_use-existing-provider-by-default diff --git a/changes/feature-4488_use-existing-provider-by-default b/changes/feature-4488_use-existing-provider-by-default new file mode 100644 index 00000000..feeaa349 --- /dev/null +++ b/changes/feature-4488_use-existing-provider-by-default @@ -0,0 +1 @@ +- Wizard: select by default the use of an existing provider if we have configured at least one. Closes #4488. 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(-) 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(-) 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 4756aee1aa279bbce951b715a84dbb3d2f8d3173 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 24 Jan 2014 11:32:40 -0300 Subject: Add changes file for #4986. --- changes/bug-4986_error-joining-string | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/bug-4986_error-joining-string diff --git a/changes/bug-4986_error-joining-string b/changes/bug-4986_error-joining-string new file mode 100644 index 00000000..935ba6db --- /dev/null +++ b/changes/bug-4986_error-joining-string @@ -0,0 +1 @@ +- Handle wizard close correctly. Closes #4986. -- cgit v1.2.3 From 58badbcf8ce3634061cbde73327be40fe86f672b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 27 Jan 2014 16:14:50 -0400 Subject: ignore line_profiler reports --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 579d3541..1beae562 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.swo *.pyc *.log +*.lprof *.*~ .* *_rc.py -- 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(-) 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 --- changes/bug_5083_sync_soledad_only_on_account_create | 3 +++ src/leap/bitmask/services/soledad/soledadbootstrapper.py | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 changes/bug_5083_sync_soledad_only_on_account_create diff --git a/changes/bug_5083_sync_soledad_only_on_account_create b/changes/bug_5083_sync_soledad_only_on_account_create new file mode 100644 index 00000000..6deaa41f --- /dev/null +++ b/changes/bug_5083_sync_soledad_only_on_account_create @@ -0,0 +1,3 @@ +- Sync Soledad before bootstrapping mail only if the key for the user + is not found locally. Otherwise, defer to thread and + continue. Closes #5083 \ No newline at end of file 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(-) 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(-) 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] --- changes/bug-4984_update-menu-name-in-wizard | 1 + src/leap/bitmask/gui/ui/wizard.ui | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changes/bug-4984_update-menu-name-in-wizard diff --git a/changes/bug-4984_update-menu-name-in-wizard b/changes/bug-4984_update-menu-name-in-wizard new file mode 100644 index 00000000..9048951a --- /dev/null +++ b/changes/bug-4984_update-menu-name-in-wizard @@ -0,0 +1 @@ +- Update menu name in Wizard. Closes #4984. 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(-) 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] --- changes/bug-4995_error-on-every-logout | 1 + src/leap/bitmask/config/leapsettings.py | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 changes/bug-4995_error-on-every-logout diff --git a/changes/bug-4995_error-on-every-logout b/changes/bug-4995_error-on-every-logout new file mode 100644 index 00000000..beb4e4c2 --- /dev/null +++ b/changes/bug-4995_error-on-every-logout @@ -0,0 +1 @@ +- Fix "Something went wrong with the logout" misleading error in every logout. Closes #4995 and #5071. 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. --- .../feature_5095_ensure-imap-flushes-data-before-quitting | 1 + src/leap/bitmask/gui/mainwindow.py | 14 +++++++++++--- src/leap/bitmask/services/mail/conductor.py | 14 ++++++++++++-- 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 changes/feature_5095_ensure-imap-flushes-data-before-quitting diff --git a/changes/feature_5095_ensure-imap-flushes-data-before-quitting b/changes/feature_5095_ensure-imap-flushes-data-before-quitting new file mode 100644 index 00000000..ef1f2888 --- /dev/null +++ b/changes/feature_5095_ensure-imap-flushes-data-before-quitting @@ -0,0 +1 @@ +- Ensure IMAP flushes data to disk before quitting. Closes #5095. 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(-) 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] --- changes/bug_cancel-login-does-not-work | 1 + 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 +- 6 files changed, 90 insertions(+), 38 deletions(-) create mode 100644 changes/bug_cancel-login-does-not-work diff --git a/changes/bug_cancel-login-does-not-work b/changes/bug_cancel-login-does-not-work new file mode 100644 index 00000000..8fbccb5a --- /dev/null +++ b/changes/bug_cancel-login-does-not-work @@ -0,0 +1 @@ +- Cancel login does not work or needs to be pressed twice. Closes #4869, #4973. 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] --- changes/bug-handle-closed-soledad | 1 + src/leap/bitmask/services/soledad/soledadbootstrapper.py | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 changes/bug-handle-closed-soledad diff --git a/changes/bug-handle-closed-soledad b/changes/bug-handle-closed-soledad new file mode 100644 index 00000000..bf0a905e --- /dev/null +++ b/changes/bug-handle-closed-soledad @@ -0,0 +1 @@ +- Handle closed Soledad database on quit, speedup exit. Closes #5130. 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(-) 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 eaf5e77392d98c5de7b5e6bba88eb2121226df36 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 17 Feb 2014 11:13:27 -0400 Subject: profiling target to makefile --- .gitignore | 1 + Makefile | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 1beae562..eb44f637 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.pyc *.log *.lprof +*.pstats *.*~ .* *_rc.py diff --git a/Makefile b/Makefile index 00fec2ed..564fbe15 100644 --- a/Makefile +++ b/Makefile @@ -64,5 +64,8 @@ manpages: apidocs: @sphinx-apidoc -o docs/api src/leap/bitmask +mailprofile: + gprof2dot -f pstats /tmp/leap_mail_profile.pstats -n 0.2 -e 0.2 | dot -Tpdf -o /tmp/leap_mail_profile.pdf + clean : $(RM) $(COMPILED_UI) $(COMPILED_RESOURCES) $(COMPILED_UI:.py=.pyc) $(COMPILED_RESOURCES:.py=.pyc) -- 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(+) 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 f5d174948bf4e2fa6fca8024168a40af922a30a9 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 17 Feb 2014 13:29:28 -0400 Subject: add line_profiler targets to Makefile --- Makefile | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Makefile b/Makefile index 564fbe15..25d2bcf5 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,10 @@ COMPILED_RESOURCES = $(RESOURCES:%.qrc=$(COMPILED_DIR)/%_rc.py) DEBVER = $(shell dpkg-parsechangelog | sed -ne 's,Version: ,,p') +ifndef EDITOR + export EDITOR=vim +endif + # all : resources ui @@ -67,5 +71,11 @@ apidocs: mailprofile: gprof2dot -f pstats /tmp/leap_mail_profile.pstats -n 0.2 -e 0.2 | dot -Tpdf -o /tmp/leap_mail_profile.pdf +do_lineprof: + LEAP_PROFILE_IMAPCMD=1 LEAP_MAIL_MANHOLE=1 kernprof.py -l src/leap/bitmask/app.py --offline --debug + +view_lineprof: + @python -m line_profiler app.py.lprof | $(EDITOR) - + clean : $(RM) $(COMPILED_UI) $(COMPILED_RESOURCES) $(COMPILED_UI:.py=.pyc) $(COMPILED_RESOURCES:.py=.pyc) -- 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). --- changes/feature_5120_update-keymanager-to-webapp-v2 | 1 + .../bitmask/services/soledad/soledadbootstrapper.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 changes/feature_5120_update-keymanager-to-webapp-v2 diff --git a/changes/feature_5120_update-keymanager-to-webapp-v2 b/changes/feature_5120_update-keymanager-to-webapp-v2 new file mode 100644 index 00000000..71aa3d5e --- /dev/null +++ b/changes/feature_5120_update-keymanager-to-webapp-v2 @@ -0,0 +1 @@ +- Update key manager auth to interact with webapp v2 (#5120). 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(-) 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] --- changes/better-logout-handle | 2 ++ src/leap/bitmask/gui/login.py | 11 ++++++++++- src/leap/bitmask/gui/mainwindow.py | 11 ++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 changes/better-logout-handle diff --git a/changes/better-logout-handle b/changes/better-logout-handle new file mode 100644 index 00000000..11797a81 --- /dev/null +++ b/changes/better-logout-handle @@ -0,0 +1,2 @@ +- Fix logging out typo, closes #4815. +- Improve logout action, related to #5131. 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(-) 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(-) 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. --- LICENSE | 10 +- src/leap/bitmask/util/pastebin.py | 814 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 823 insertions(+), 1 deletion(-) create mode 100755 src/leap/bitmask/util/pastebin.py diff --git a/LICENSE b/LICENSE index bca70598..c7d0c108 100644 --- a/LICENSE +++ b/LICENSE @@ -674,8 +674,16 @@ Public License instead of this License. But first, please read . -------------------------------------------------------------------------------- +We are "embedding" PastebinAPI class since currently the installation through +pip does not work. -Leap-client also uses third party icons: +file: src/leap/bitmask/util/pastebin.py +License: GPL +Author: Ian Havelock +website: https://github.com/Morrolan/PastebinAPI +-------------------------------------------------------------------------------- + +Bitmask also uses third party icons: --- data/images/Arrow-Up-32.png 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. --- LICENSE | 6 ++++++ data/images/pastebin.png | Bin 0 -> 2078 bytes data/resources/loggerwindow.qrc | 1 + src/leap/bitmask/gui/ui/loggerwindow.ui | 15 +++++++++++++-- 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 data/images/pastebin.png diff --git a/LICENSE b/LICENSE index c7d0c108..2ccb50e8 100644 --- a/LICENSE +++ b/LICENSE @@ -702,3 +702,9 @@ data/images/mail-unlocked.png License: LGPL - http://www.gnu.org/licenses/lgpl.html Website: http://www.oxygen-icons.org/ +-- +data/images/pastebin.png + +author: Marco Martin +License: LGPL +website: https://www.iconfinder.com/icons/7897/binary_icon diff --git a/data/images/pastebin.png b/data/images/pastebin.png new file mode 100644 index 00000000..62179840 Binary files /dev/null and b/data/images/pastebin.png differ diff --git a/data/resources/loggerwindow.qrc b/data/resources/loggerwindow.qrc index 847ca9a1..a385c50e 100644 --- a/data/resources/loggerwindow.qrc +++ b/data/resources/loggerwindow.qrc @@ -1,5 +1,6 @@ + ../images/pastebin.png ../images/oxygen-icons/edit-bomb.png ../images/oxygen-icons/document-save-as.png ../images/oxygen-icons/dialog-information.png 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(+) 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(+) 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(-) 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 5eb6d978f9eb953d001f3be348df4b8506cbd721 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 26 Feb 2014 15:39:53 -0300 Subject: Add changes file for #5163. --- changes/feature-5163_add-pastebin-button | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/feature-5163_add-pastebin-button diff --git a/changes/feature-5163_add-pastebin-button b/changes/feature-5163_add-pastebin-button new file mode 100644 index 00000000..ec44edaf --- /dev/null +++ b/changes/feature-5163_add-pastebin-button @@ -0,0 +1 @@ +- Add pastebin button to upload logs from the logs window to ease bug report. Closes #5163. -- cgit v1.2.3 From 4440f20105906d654651ca58e022d21f36bc3530 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 27 Feb 2014 18:01:54 -0300 Subject: Update app strings for Transifex. --- data/ts/en_US.ts | 753 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 486 insertions(+), 267 deletions(-) diff --git a/data/ts/en_US.ts b/data/ts/en_US.ts index 64a400ba..562df1a3 100644 --- a/data/ts/en_US.ts +++ b/data/ts/en_US.ts @@ -1,85 +1,234 @@ - EIPPreferences + AdvancedKeyManagement - - EIP Preferences + + Advanced Key Management - - Select gateway for provider + + My key pair - - &Select provider: + + User: - - <Select provider> + + user_name@provider - - Save this provider settings + + Key ID: - - < Providers Gateway Status > + + key ID - - Select gateway: + + Key fingerprint: + + + + + fingerprint + + + + + Export current key pair + + + + + Import custom key pair + + + + + Stored public keys + + + + + Email + + + + + Key ID + + + + + <span style='color:#0000FF;'>NOTE</span>: To use this, you need to enable/start {0}. + + + + + Open keys file + + + + + Input/Output error + + + + + There was an error accessing the file. +Import canceled. + + + + + Data mismatch + + + + + The public and private key should have the same address and fingerprint. +Import canceled. + + + + + Missing key + + + + + You need to provide the public AND private key in the same file. +Import canceled. + + + + + Address mismatch + + + + + The identity for the key needs to be the same as your user address. +Import canceled. + + + + + Are you sure that you want to replace the current key pair whith the imported? + + + + + Import Successful + + + + + The key pair was imported successfully. + + + + + Save keys file + + + + + Export Successful + + + + + The key pair was exported successfully. +Please, store your private key in a safe place. + + + + + There was an error accessing the file. +Export canceled. + + + + + EIPPreferences + + + Select gateway for provider + + + + + <Select provider> - + + < Providers Gateway Status > + + + + Automatic - - Automatic EIP start + + Encrypted Internet Preferences - - <font color='green'><b>Automatic EIP start saved!</b></font> + + Select &provider: - - Save auto start setting + + &Save this provider settings - - Enable Automatic start of EIP + + Select &gateway: EIPPreferencesWindow - + Automatic - + Gateway settings for provider '{0}' saved. - + There was a problem with configuration files. + + + (uninitialized) + + + + + This is an uninitialized provider, please log in first. + + EIPStatus @@ -89,22 +238,22 @@ - + Turn On - + ... - + Traffic is being routed in the clear - + 0.0 KB/s @@ -112,83 +261,78 @@ EIPStatusWidget - - All services are OFF - - - - - Encrypted Internet: {0} - - - - - You must login to use Encrypted Internet - - - - + Turn OFF - + Turn ON - + Traffic is being routed in the clear - + Authenticating... - + Retrieving configuration... - + Waiting to start... - + Assigning IP - + Reconnecting... - + Unable to start VPN, it's already running. - - Encrypted Internet: OFF + + Route traffic through: {0} - - Encrypted Internet: Starting... + + disabled - - Encrypted Internet: ON + + {0}: OFF - - Route traffic through: {0} + + You must login to use {0} + + + + + {0}: Starting... + + + + + {0}: ON @@ -230,7 +374,7 @@ - + Save As @@ -244,6 +388,36 @@ Case Insensitive + + + Send to Pastebin.com + + + + + Sending to pastebin... + + + + + Your pastebin link <a href='{0}'>{0}</a> + + + + + Pastebin OK + + + + + Sending logs to Pastebin failed! + + + + + Pastebin Error + + LoginWidget @@ -273,53 +447,53 @@ - + Log In - + Other... - + Cancel - + ... - + Logout - + Please select a valid provider - + Please provide a valid username - + Please provide a valid password - + Logging in... - - Loggin out... + + Logging out... @@ -341,352 +515,382 @@ - - All services are OFF - - - - + There was an unexpected problem with Soledad. - + OFF - + Mail is OFF - - You must be logged in to use encrypted email. - - - - + Starting.. - + Mail is starting - + ON - + Mail is ON - + Mail is disabled - + Starting... - + Soledad has started... - + Soledad is starting, please wait... - + Looking for key for this user - + Found key! Starting mail... - - Generating new key, please wait... + + Finished generating key! - - Finished generating key! + + Starting mail... - - Starting mail... + + SMTP failed to start, check the logs. - - SMTP has started... + + About to start, please wait... - - SMTP failed to start, check the logs. + + Disabled - - Failed + + {0}: OFF - - IMAP has started... + + You must be logged in to use {0}. - - IMAP failed to start, check the logs. + + Generating new key, this may take a few minutes. - - %s Unread Emails + + {0} Unread Emails in your Inbox - - About to start, please wait... + + 1 Unread Email in your Inbox - - Disabled + + Disconnecting... MainWindow - + There are new updates available, please restart. - + More... - + Help - + &Quit - + &Help - + &Wizard - + Hide Main Window - + The following components will be updated: %s - + Updates available - + Show Main Window - + We could not find any authentication agent in your system.<br/>Make sure you have <b>polkit-gnome-authentication-agent-1</b> running and try again. - + We could not find <b>pkexec</b> in your system. - + We could not find openvpn binary. - + Starting... - + Not supported - + Disabled - - Could not load Encrypted Internet Configuration. + + Bitmask - - Encrypted Internet could not be launched because you did not authenticate properly. + + About &Bitmask - - Encrypted Internet finished in an unexpected manner! + + Mail is OFF - - Bitmask + + The Bitmask app is ready to update, please restart the application. - - About &Bitmask + + About Bitmask - %s - - Mail is OFF + + Unable to login: Problem with provider - - The Bitmask app is ready to update, please restart the application. + + Log in cancelled by the user. - - About Bitmask - %s + + Another openvpn instance is already running, and could not be stopped. - - Version: <b>%s</b><br><br>Bitmask is the Desktop client application for the LEAP platform, supporting encrypted internet proxy, secure email, and secure chat (coming soon).<br><br>LEAP is a non-profit dedicated to giving all internet users access to secure communication. Our focus is on adapting encryption technology to make it easy to use and widely available. <br><br><a href='https://leap.se'>More about LEAP</a> + + Another openvpn instance is already running, and could not be stopped because it was not launched by Bitmask. Please stop it and try again. - - Unable to login: Problem with provider + + There was a problem with the provider - - Log in cancelled by the user. + + Something went wrong with the logout. - - Encrypted Internet cannot be started because the tuntap extension is not installed properly in your system. + + Unable to connect: Problem with provider - - Another openvpn instance is already running, and could not be stopped. + + Login - - Another openvpn instance is already running, and could not be stopped because it was not launched by Bitmask. Please stop it and try again. + + &Bitmask - - There was a problem with the provider + + Show &Log - - Something went wrong with the logout. + + Create a new account... - - Unable to connect: Problem with provider + + File - - Encrypted Internet + + Network is unreachable - - Login + + Please Log In - - &Bitmask + + Account Preferences... + + + + + Internet Preferences... - Preferences... + Advanced Key Management - - Show &Log + + (offline mode) - - Create a new account... + + OFF - - File + + Version: <b>%s</b> (%s)<br><br>%sBitmask is the Desktop client application for the LEAP platform, supporting encrypted internet proxy, secure email, and secure chat (coming soon).<br><br>LEAP is a non-profit dedicated to giving all internet users access to secure communication. Our focus is on adapting encryption technology to make it easy to use and widely available. <br><br><a href='https://leap.se'>More about LEAP</a> - - Encrypted Internet: OFF + + <strong>Instructions to use mail:</strong><br>If you use Thunderbird you can use the Bitmask extension helper. Search for 'Bitmask' in the add-on manager or download it from: {0}.<br><br>You can configure Bitmask manually with these options:<br><em> Incoming -> IMAP, port: {1}<br> Outgoing -> SMTP, port: {2}<br> Username -> your bitmask username.<br> Password -> does not matter, use any text. Just don't leave it empty and don't use your account's password.</em> - - Network is unreachable + + Bitmask Help + + + + + The current client version is not supported by this provider.<br>Please update to latest version.<br><br>You can get the latest version from <a href='{0}'>{1}</a> + + + + + Update Needed + + + + + This provider is not compatible with the client.<br><br>Error: API version incompatible. + + + + + Incompatible Provider - - EIP has stopped + + Could not load {0} configuration. + + + + + {0} cannot be started because the tuntap extension is not installed properly in your system. + + + + + {0} could not be launched because you did not authenticate properly. + + + + + {0} finished in an unexpected manner! @@ -761,45 +965,60 @@ PreferencesWindow - + Automatic - + Changing password... - + Password changed successfully. - + There was a problem changing the password. - + You did not enter a correct current password. - + Services settings for provider '{0}' saved. + + + You need to enable {0} in order to change the password. + + + + + You need to wait until {0} is ready in order to change the password. + + + + + In order to change your password you need to be logged in. + + ProviderBootstrapper - + Provider certificate could not be verified - + Provider does not support HTTPS @@ -807,7 +1026,7 @@ SRPAuth - + Succeeded @@ -824,11 +1043,6 @@ Log In with my credentials - - - <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> - - Sign up for a new account @@ -845,12 +1059,12 @@ - + Check - + https:// @@ -870,202 +1084,192 @@ - + Provider Information - + Description of services offered by this provider - + Name - + Desc - + <b>Services offered:</b> - + services - + <b>Enrollment policy:</b> - + policy - + <b>URL:</b> - + URL - + <b>Description:</b> - + Provider setup - + Gathering configuration options for this provider - + We are downloading some bits that we need to establish a secure connection with the provider for the first time. - + Setting up provider - + Getting info from the Certificate Authority - + Do we trust this Certificate Authority? - + Establishing a trust relationship with this provider - + Register new user - + Register a new user with provider - + <b>Password:</b> - + <b>Re-enter password:</b> - + Register - + Remember my username and password - + Service selection - + Please select the services you would like to have - + &Next > - + Connect - + Starting registration... - + User %s successfully registered. - - Unknown error - - - - + <font color='red'><b>Non-existent provider</b></font> - + <font color='red'><b>%s</b></font> - + Unable to load provider configuration - + <font color='red'><b>Not a valid provider</b></font> - + Services by %s - + Something went wrong while trying to load service %s - - Gathering configuration options for %s - - - - + Description of services offered by %s - + Register a new user with %s @@ -1085,7 +1289,7 @@ - + <b>Username:</b> @@ -1095,70 +1299,85 @@ - + Configure new provider: - + Use existing one: + + + <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> + + + + + Something has gone wrong. Please try again. + + + + + Gathering configuration options for {0} + + __impl - + The server did not send the salt parameter - + The server did not send the B parameter - + The data sent from the server had errors - + Could not connect to the server - + Unknown error (%s) - + Problem getting data from server - + Bad data from server - + Auth verification failed - + Session cookie verification failed - + There was a problem with authentication - + Invalid username or password. -- 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] --- changes/bug-5048_version-checks-in-wizard | 1 + src/leap/bitmask/gui/ui/wizard.ui | 73 +++++++++++++++++++------------ src/leap/bitmask/gui/wizard.py | 11 +++-- 3 files changed, 51 insertions(+), 34 deletions(-) create mode 100644 changes/bug-5048_version-checks-in-wizard diff --git a/changes/bug-5048_version-checks-in-wizard b/changes/bug-5048_version-checks-in-wizard new file mode 100644 index 00000000..a5593e6c --- /dev/null +++ b/changes/bug-5048_version-checks-in-wizard @@ -0,0 +1 @@ +- Use version checks in the wizard when the user choose to use an existing provider. Closes #5048. 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(-) 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 06c8d6f98768f173854585e3c9b36c4fa6bfe753 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 5 Mar 2014 15:28:08 -0400 Subject: script to graph cpu/mem usage. Related: #5259 Use as: RESOURCE_TIME=5 make resource_graph where RESOURCE_TIME are the minutes to run for. --- .gitignore | 2 ++ Makefile | 8 ++++++++ pkg/scripts/monitor_resource.zsh | 26 ++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100755 pkg/scripts/monitor_resource.zsh diff --git a/.gitignore b/.gitignore index eb44f637..e4b03d7a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.log *.lprof *.pstats +*.data *.*~ .* *_rc.py @@ -33,3 +34,4 @@ config/* CHANGELOG~ data/bitmask.pro +bitmask-resources.png diff --git a/Makefile b/Makefile index 25d2bcf5..2bd39eb0 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,10 @@ ifndef EDITOR export EDITOR=vim endif +ifndef RESOURCE_TIME + export RESOURCE_TIME=10 +endif + # all : resources ui @@ -77,5 +81,9 @@ do_lineprof: view_lineprof: @python -m line_profiler app.py.lprof | $(EDITOR) - +resource_graph: + ./pkg/scripts/monitor_resource.zsh `pgrep bitmask` $(RESOURCE_TIME) + display bitmask-resources.png + clean : $(RM) $(COMPILED_UI) $(COMPILED_RESOURCES) $(COMPILED_UI:.py=.pyc) $(COMPILED_RESOURCES:.py=.pyc) diff --git a/pkg/scripts/monitor_resource.zsh b/pkg/scripts/monitor_resource.zsh new file mode 100755 index 00000000..ac468e34 --- /dev/null +++ b/pkg/scripts/monitor_resource.zsh @@ -0,0 +1,26 @@ +#!/bin/zsh +if (( ! $# )); then + echo "Usage: $0:t " >&2 + return 1; +fi + +OUT="$1-usage.data" +GRAPH="bitmask-resources.png" +MAX=150 +let "ticks=$2*60/3" +echo "cpu mem" >> $OUT +for i in {1..$ticks}; do; + cpu=$(ps -p $1 -o pcpu | grep -v %) + mem=$(ps wuh -p $1 | awk '{print $4}') + echo "$cpu $mem" >> $OUT; + sleep 3; + echo $i / $ticks; +done; + +gnuplot -e "set term dumb; \ +set key outside; set yrange [0:$MAX]; \ +plot for [col=1:2] '$OUT' using 0:col title columnheader s c" + +gnuplot -e "set term png; set output '$GRAPH'; \ +set key outside; set yrange [0:$MAX]; \ +plot for [col=1:2] '$OUT' using 0:col with lines title columnheader" -- 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] --- changes/bug_5153-select-domain-in-eip-preferences | 1 + src/leap/bitmask/gui/eip_preferenceswindow.py | 21 ++++++++++++++++----- src/leap/bitmask/gui/mainwindow.py | 3 ++- 3 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 changes/bug_5153-select-domain-in-eip-preferences diff --git a/changes/bug_5153-select-domain-in-eip-preferences b/changes/bug_5153-select-domain-in-eip-preferences new file mode 100644 index 00000000..2c00c745 --- /dev/null +++ b/changes/bug_5153-select-domain-in-eip-preferences @@ -0,0 +1 @@ +Set as selected default for the eip preferences window the item selented in the bitmask main window. Closes #5153. 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 --- Makefile | 1 + src/leap/bitmask/app.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/Makefile b/Makefile index 2bd39eb0..b637fefc 100644 --- a/Makefile +++ b/Makefile @@ -82,6 +82,7 @@ view_lineprof: @python -m line_profiler app.py.lprof | $(EDITOR) - resource_graph: + #./pkg/scripts/monitor_resource.zsh `pgrep twistd` $(RESOURCE_TIME) ./pkg/scripts/monitor_resource.zsh `pgrep bitmask` $(RESOURCE_TIME) display bitmask-resources.png 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. --- changes/better-soledad-retry | 1 + src/leap/bitmask/gui/mainwindow.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 changes/better-soledad-retry diff --git a/changes/better-soledad-retry b/changes/better-soledad-retry new file mode 100644 index 00000000..ac21f76c --- /dev/null +++ b/changes/better-soledad-retry @@ -0,0 +1 @@ +In case of soledad bootstrap error (e.g.: network failure), re run all the setup process. 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(-) 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 --- changes/feature_remove_qtreactor | 1 + pkg/requirements.pip | 1 - src/leap/bitmask/app.py | 17 ++++++----------- src/leap/bitmask/gui/mainwindow.py | 1 + src/leap/bitmask/gui/twisted_main.py | 18 ------------------ 5 files changed, 8 insertions(+), 30 deletions(-) create mode 100644 changes/feature_remove_qtreactor diff --git a/changes/feature_remove_qtreactor b/changes/feature_remove_qtreactor new file mode 100644 index 00000000..5cf3a854 --- /dev/null +++ b/changes/feature_remove_qtreactor @@ -0,0 +1 @@ +- Remove qt4reactor as a dependency. \ No newline at end of file diff --git a/pkg/requirements.pip b/pkg/requirements.pip index 04cd33de..c8f6b101 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -13,7 +13,6 @@ python-dateutil psutil ipaddr twisted -qt4reactor python-daemon # this should not be needed for Windows. keyring zope.proxy 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 61b12ad3a7c941f5d4c4b7f674d7ae397efbb30b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 10 Mar 2014 03:29:53 -0400 Subject: changes file --- changes/feature_nice-process | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/feature_nice-process diff --git a/changes/feature_nice-process b/changes/feature_nice-process new file mode 100644 index 00000000..3de26e75 --- /dev/null +++ b/changes/feature_nice-process @@ -0,0 +1 @@ +- Add ability to nice application via environment variable. -- cgit v1.2.3 From 806fe6f8f6664da99f5405af5a831f0e48e3fe64 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 10 Mar 2014 03:31:32 -0400 Subject: comment workaround for systems w/o pgrep --- Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b637fefc..cb1f8218 100644 --- a/Makefile +++ b/Makefile @@ -76,13 +76,16 @@ mailprofile: gprof2dot -f pstats /tmp/leap_mail_profile.pstats -n 0.2 -e 0.2 | dot -Tpdf -o /tmp/leap_mail_profile.pdf do_lineprof: - LEAP_PROFILE_IMAPCMD=1 LEAP_MAIL_MANHOLE=1 kernprof.py -l src/leap/bitmask/app.py --offline --debug + LEAP_PROFILE_IMAPCMD=1 LEAP_MAIL_MANHOLE=1 kernprof.py -l src/leap/bitmask/app.py --debug + +do_lineprof_offline: + LEAP_PROFILE_IMAPCMD=1 LEAP_MAIL_MANHOLE=1 kernprof.py -l src/leap/bitmask/app.py --offline --debug -N view_lineprof: @python -m line_profiler app.py.lprof | $(EDITOR) - resource_graph: - #./pkg/scripts/monitor_resource.zsh `pgrep twistd` $(RESOURCE_TIME) + #./pkg/scripts/monitor_resource.zsh `ps aux | grep app.py | head -1 | awk '{print $$2}'` $(RESOURCE_TIME) ./pkg/scripts/monitor_resource.zsh `pgrep bitmask` $(RESOURCE_TIME) display bitmask-resources.png -- 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(-) 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(-) 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 ed4fcb096c3166b76075921a484ee045e96f60b2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 11 Mar 2014 12:10:15 -0400 Subject: add cProfile+viewer target to Makefile --- .gitignore | 1 + Makefile | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index e4b03d7a..0e5bb658 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.lprof *.pstats *.data +*.cprofile *.*~ .* *_rc.py diff --git a/Makefile b/Makefile index cb1f8218..358af126 100644 --- a/Makefile +++ b/Makefile @@ -72,6 +72,12 @@ manpages: apidocs: @sphinx-apidoc -o docs/api src/leap/bitmask +do_cprofile: + python -m cProfile -o bitmask.cprofile src/leap/bitmask/app.py --debug -N + +view_cprofile: + cprofilev bitmask.cprofile + mailprofile: gprof2dot -f pstats /tmp/leap_mail_profile.pstats -n 0.2 -e 0.2 | dot -Tpdf -o /tmp/leap_mail_profile.pdf -- cgit v1.2.3 From 64edb4177ef27cc011b950aa30b03cf8dbdc1ba5 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 11 Mar 2014 13:12:05 -0300 Subject: Add changes file for #5219. --- changes/bug-5219_srpauth-error-messages | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/bug-5219_srpauth-error-messages diff --git a/changes/bug-5219_srpauth-error-messages b/changes/bug-5219_srpauth-error-messages new file mode 100644 index 00000000..d46b1504 --- /dev/null +++ b/changes/bug-5219_srpauth-error-messages @@ -0,0 +1 @@ +- Move error messages from srpauth to the GUI and refactor signals. Closes #5219. -- cgit v1.2.3 From 9c1c3da9a39120d615288c56eee6b54b05ebaf7f Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 12 Mar 2014 18:00:58 -0300 Subject: Add dependency needed by the latest PyOpenSSL. --- README.rst | 2 +- docs/dev/quickstart.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index fc0f9772..3ec2685e 100644 --- a/README.rst +++ b/README.rst @@ -46,7 +46,7 @@ With a Debian based system, to be able to run Bitmask you need to run the follow $ sudo apt-get install git python-dev python-setuptools python-virtualenv python-pip python-openssl libsqlite3-dev g++ openvpn - pyside-tools python-pyside + pyside-tools python-pyside libffi-dev Installing ----------- diff --git a/docs/dev/quickstart.rst b/docs/dev/quickstart.rst index 96dcaeb9..a8be955e 100644 --- a/docs/dev/quickstart.rst +++ b/docs/dev/quickstart.rst @@ -25,8 +25,8 @@ Prerequisites $ sudo apt-get install git python-dev python-setuptools python-virtualenv python-pip python-openssl libsqlite3-dev g++ openvpn - pyside-tools python-pyside - + pyside-tools python-pyside libffi-dev + .. python-qt4 ??? (for translations) .. TODO I'm pretty sure python-qt4 shoudln't be there... Nor libsqlite-dev, that's a bug in python-sqlcipher/soledad. @@ -75,7 +75,7 @@ Running Run ``bitmask_client`` in debug mode:: - (bitmask)$ bitmask --debug + (bitmask)$ bitmask --debug You should see the ``bitmask_client`` window prompting to connect to an existing node or add a new one. If not, something went wrong, maybe ask -- 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] --- changes/feature_check-for-dns-resolution | 1 + src/leap/bitmask/gui/mainwindow.py | 52 +++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 changes/feature_check-for-dns-resolution diff --git a/changes/feature_check-for-dns-resolution b/changes/feature_check-for-dns-resolution new file mode 100644 index 00000000..f0e2bf3e --- /dev/null +++ b/changes/feature_check-for-dns-resolution @@ -0,0 +1 @@ +- After connecting EIP check for DNS resolution and warn the user on error. Closes #5301. 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. --- changes/bug_update-psutil-version | 1 + src/leap/bitmask/services/eip/vpnprocess.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 changes/bug_update-psutil-version diff --git a/changes/bug_update-psutil-version b/changes/bug_update-psutil-version new file mode 100644 index 00000000..be9fedde --- /dev/null +++ b/changes/bug_update-psutil-version @@ -0,0 +1 @@ +Update requirements and code for the new psutil version. 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 8bd80cfa21de0edd5bb0b0d1d28a8fce167a0921 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 14 Mar 2014 17:43:33 -0300 Subject: Remove docs from README in favor of leap_doc ones. --- README.rst | 101 ++----------------------------------------------------------- 1 file changed, 3 insertions(+), 98 deletions(-) diff --git a/README.rst b/README.rst index 3ec2685e..ff84d56b 100644 --- a/README.rst +++ b/README.rst @@ -17,111 +17,16 @@ Currently we distribute pre-compiled `bundles`_ for Linux, OSX and Windows. .. _`PySide`: http://qt-project.org/wiki/PySide .. _`the LEAP Platform`: https://github.com/leapcode/leap_platform -.. _`bundles`: https://downloads.leap.se/client/ +.. _`bundles`: https://dl.bitmask.net Read the Docs! ------------------ -The latest documentation is available at `Read The Docs`_. +The latest documentation is available at `LEAP`_. -.. _`Read The Docs`: http://bitmask.rtfd.org +.. _`LEAP`: https://leap.se/en/docs/client -Dependencies ------------------- - -Bitmask depends on these libraries: - -* ``python 2.6`` or ``2.7`` -* ``qt4 libraries`` -* ``libopenssl`` -* ``openvpn`` - -Python packages are listed in ``pkg/requirements.pip`` and ``pkg/test-requirements.pip`` - -Getting dependencies under debian -++++++++++++++++++++++++++++++++++ - -With a Debian based system, to be able to run Bitmask you need to run the following command:: - - $ sudo apt-get install git python-dev python-setuptools - python-virtualenv python-pip python-openssl libsqlite3-dev g++ openvpn - pyside-tools python-pyside libffi-dev - -Installing ------------ - -Quick install, from the cheese shop:: - - $ sudo pip install leap.bitmask - -If you prefer to install from sources:: - - $ make - $ sudo python2 setup.py install - - -Running -------- - -After a successful installation, there should be a launcher called ``bitmask`` somewhere in your path:: - - $ bitmask - -If you are testing a new provider and do not have a CA certificate chain tied to your SSL certificate, you should execute Bitmask in the following way:: - - $ bitmask --danger - -But **DO NOT use it on a regular basis**. - -**WARNING**: If you use the --danger flag you may be victim to a MITM_ attack without noticing. Use at your own risk. - -.. _MITM: http://en.wikipedia.org/wiki/Man-in-the-middle_attack - -Hacking -======= - -Get the source from the main Bitmask repo:: - - git clone https://leap.se/git/bitmask_client - -The code is also browsable online at:: - - https://leap.se/git/?p=bitmask_client.git - -Some steps need to be run when setting a development environment for the first time. - -Enable a **virtualenv** to isolate your libraries. (Current *.gitignore* knows about a virtualenv in the root tree. If you do not like that place, just change ``.`` for **):: - - $ virtualenv . - $ source bin/activate - -Make sure you are in the development branch:: - - (bitmask)$ git checkout develop - -Symlink your global pyside libraries:: - - (bitmask)$ pkg/postmkvenv.sh - -And make your working tree available to your pythonpath:: - - (bitmask)$ python2 setup.py develop - -Run Bitmask:: - - (bitmask)$ bitmask --debug - -Testing -======= - -Have a look at ``pkg/test-requirements.pip`` for the tests dependencies. - -To run the test suite:: - - $ ./run_tests.sh - -which the first time should automagically install all the needed dependencies in your virtualenv for you. License ======= -- 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(-) 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 a2e2356ff0d740441b3431be0a2e095940412b2a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 17 Mar 2014 10:41:08 -0400 Subject: Catch erros during shutdown - Do not remove defers not in list - Do not try to stop a non-running loop. --- changes/bug-shutdown-errors | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/bug-shutdown-errors diff --git a/changes/bug-shutdown-errors b/changes/bug-shutdown-errors new file mode 100644 index 00000000..a2de1249 --- /dev/null +++ b/changes/bug-shutdown-errors @@ -0,0 +1 @@ +- Catch shutdown errors. Closes: #5313 -- cgit v1.2.3 From 23d77edd4c748e9758b48b5297f8b3c899c5c156 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 17 Mar 2014 14:27:29 -0300 Subject: Pin psutil version to avoid conflicts with gnupg. gnupg 1.2.5 requires psutil==1.2.1 --- changes/bug-5309_psutil-version-conflict | 1 + pkg/requirements.pip | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changes/bug-5309_psutil-version-conflict diff --git a/changes/bug-5309_psutil-version-conflict b/changes/bug-5309_psutil-version-conflict new file mode 100644 index 00000000..e7ccb2fc --- /dev/null +++ b/changes/bug-5309_psutil-version-conflict @@ -0,0 +1 @@ +- Fix psutil version to avoid conflicts with gnupg required version. Closes #5309. diff --git a/pkg/requirements.pip b/pkg/requirements.pip index c8f6b101..850ccf44 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -10,7 +10,7 @@ requests>=1.1.0 srp>=1.0.2 pyopenssl python-dateutil -psutil +psutil==1.2.1 ipaddr twisted python-daemon # this should not be needed for Windows. -- 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). --- .../feature_5191_handle-invalid-auth-token-error | 2 ++ 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 +++++++++- 6 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 changes/feature_5191_handle-invalid-auth-token-error diff --git a/changes/feature_5191_handle-invalid-auth-token-error b/changes/feature_5191_handle-invalid-auth-token-error new file mode 100644 index 00000000..6acdaad0 --- /dev/null +++ b/changes/feature_5191_handle-invalid-auth-token-error @@ -0,0 +1,2 @@ +- Handle invalid auth tokens when syncing Soledad, and show an error on the + GUI (#5191). 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 --- changes/feature_move-srpregister-to-backend | 1 + src/leap/bitmask/backend.py | 84 +++++++++++++++++++++- src/leap/bitmask/crypto/srpregister.py | 63 +++++++++++++---- src/leap/bitmask/gui/wizard.py | 106 ++++++++++++++++------------ 4 files changed, 191 insertions(+), 63 deletions(-) create mode 100644 changes/feature_move-srpregister-to-backend diff --git a/changes/feature_move-srpregister-to-backend b/changes/feature_move-srpregister-to-backend new file mode 100644 index 00000000..0625432f --- /dev/null +++ b/changes/feature_move-srpregister-to-backend @@ -0,0 +1 @@ +- Move/refactor srpregister to backend. 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 114857f4d31697ee58e0b1417013412fd6132efa Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 21 Mar 2014 17:23:45 -0300 Subject: Add script to quickly verify some code rules. --- pkg/scripts/check_code.sh | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100755 pkg/scripts/check_code.sh diff --git a/pkg/scripts/check_code.sh b/pkg/scripts/check_code.sh new file mode 100755 index 00000000..7bbd91f6 --- /dev/null +++ b/pkg/scripts/check_code.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Escape code +esc=`echo -en "\033"` + +# Set colors +cc_green="${esc}[0;32m" +cc_red="${esc}[0;31m" +cc_normal=`echo -en "${esc}[m\017"` + + +[[ -z "$1" ]] && WHERE='src/leap/bitmask' || WHERE=$1 + +PEP8="pep8 --ignore=E202,W602 --exclude=*_rc.py,ui_*,_version.py $WHERE" +echo "${cc_green}$PEP8${cc_normal}" +$PEP8 + +echo +FLAKE8="flake8 --ignore=E202,W602 --exclude=*_rc.py,ui_*,_version.py $WHERE" +echo "${cc_green}$FLAKE8${cc_normal}" +$FLAKE8 + +echo +echo "${cc_green}Looking for 'print's, no prints in code, use logging/twisted.log.${cc_normal}" +echo `git grep -n "print " | wc -l` 'coincidences.' + +echo +echo "${cc_green}Grepping for 'pdb' code left behind.${cc_normal}" +git grep -n "pdb" + +echo +echo "${cc_green}Grepping for 'XXX|TODO|FIXME|NOTE|HACK'.${cc_normal}" +echo `git grep -E "XXX|TODO|FIXME|NOTE|HACK" | wc -l` 'coincidences.' -- 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(-) 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(-) 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. --- changes/feature-5391_support-selfsigned-certs | 1 + 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 ++++++ 5 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 changes/feature-5391_support-selfsigned-certs diff --git a/changes/feature-5391_support-selfsigned-certs b/changes/feature-5391_support-selfsigned-certs new file mode 100644 index 00000000..58c68f23 --- /dev/null +++ b/changes/feature-5391_support-selfsigned-certs @@ -0,0 +1 @@ +- Add support for self signed certs. Closes #5391. 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. --- changes/feature-5401_add-shortcut-for-help-menu | 1 + src/leap/bitmask/gui/ui/mainwindow.ui | 16 +++------------- 2 files changed, 4 insertions(+), 13 deletions(-) create mode 100644 changes/feature-5401_add-shortcut-for-help-menu diff --git a/changes/feature-5401_add-shortcut-for-help-menu b/changes/feature-5401_add-shortcut-for-help-menu new file mode 100644 index 00000000..9e33388b --- /dev/null +++ b/changes/feature-5401_add-shortcut-for-help-menu @@ -0,0 +1 @@ +- Add hotkey for the Help menu. Closes #5401. 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 e2abbc4dd9ca497e97778190114c5877a1c77501 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 2 Apr 2014 13:02:56 -0300 Subject: Update bitmask url in PKG-INFO. Closes #5395. --- changes/bug-5395_update-bitmask-website-in-pkginfo | 1 + setup.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changes/bug-5395_update-bitmask-website-in-pkginfo diff --git a/changes/bug-5395_update-bitmask-website-in-pkginfo b/changes/bug-5395_update-bitmask-website-in-pkginfo new file mode 100644 index 00000000..a762a8bb --- /dev/null +++ b/changes/bug-5395_update-bitmask-website-in-pkginfo @@ -0,0 +1 @@ +- Update bitmask url in PKG-INFO. Closes #5395. diff --git a/setup.py b/setup.py index dca8f44a..de31be4b 100755 --- a/setup.py +++ b/setup.py @@ -229,7 +229,7 @@ setup( author_email='info@leap.se', maintainer='Kali Kaneko', maintainer_email='kali@leap.se', - url='https://bitmask.rtfd.org', + url='https://bitmask.net', download_url=DOWNLOAD_URL, license='GPL-3+', packages=find_packages( -- 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. --- changes/bug-5433_unescape-logs-for-pastebin | 1 + src/leap/bitmask/gui/loggerwindow.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 changes/bug-5433_unescape-logs-for-pastebin diff --git a/changes/bug-5433_unescape-logs-for-pastebin b/changes/bug-5433_unescape-logs-for-pastebin new file mode 100644 index 00000000..289dd64a --- /dev/null +++ b/changes/bug-5433_unescape-logs-for-pastebin @@ -0,0 +1 @@ +- Don't escape logs for pastebin. Closes #5433. 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(-) 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] --- changes/bug-5411_mail-is-not-disabled-at-first-start | 1 + src/leap/bitmask/gui/mainwindow.py | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 changes/bug-5411_mail-is-not-disabled-at-first-start diff --git a/changes/bug-5411_mail-is-not-disabled-at-first-start b/changes/bug-5411_mail-is-not-disabled-at-first-start new file mode 100644 index 00000000..3cba7a01 --- /dev/null +++ b/changes/bug-5411_mail-is-not-disabled-at-first-start @@ -0,0 +1 @@ +- Do not start soledad and mail if the mail service is disabled. Closes #5411. 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. --- changes/bug-5396_disable-next-if-provider-is-changed | 1 + src/leap/bitmask/gui/wizard.py | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 changes/bug-5396_disable-next-if-provider-is-changed diff --git a/changes/bug-5396_disable-next-if-provider-is-changed b/changes/bug-5396_disable-next-if-provider-is-changed new file mode 100644 index 00000000..5a7a3449 --- /dev/null +++ b/changes/bug-5396_disable-next-if-provider-is-changed @@ -0,0 +1 @@ +- Disable 'next' button if the checks passed but the provider is changed. Closes #5396. 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 63d2c8e247fadcb9c7f8f0e8a189c6dc490e7442 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 4 Apr 2014 10:12:05 -0300 Subject: Add script to sum changes status. --- pkg/scripts/stats.sh | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100755 pkg/scripts/stats.sh diff --git a/pkg/scripts/stats.sh b/pkg/scripts/stats.sh new file mode 100755 index 00000000..2b7a8b18 --- /dev/null +++ b/pkg/scripts/stats.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +REPOSITORIES="bitmask_client leap_pycommon soledad keymanager leap_mail" + +CHANGED=0 +INSERTIONS=0 +DELETIONS=0 +MERGES_TOTAL=0 + +echo "Changes summary between closest annotated tag to HEAD" +echo "=====================================================" +echo + +for repo in $REPOSITORIES; do + cd $repo + echo "Stats for: $repo" + # the 'describe' command gives the closest annotated tag + STATS=$(git diff --shortstat `git describe --abbrev=0`..HEAD) + MERGES=$(git log --merges `git describe --abbrev=0`..HEAD | wc -l) + echo "Stats:$STATS" + echo "Merges: $MERGES" + VALUES=(`echo $STATS | awk '{ print $1, $4, $6 }'`) # use array to store values + CHANGED=$(echo $CHANGED + ${VALUES[0]} | bc) + INSERTIONS=$(echo $INSERTIONS + ${VALUES[1]} | bc) + DELETIONS=$(echo $DELETIONS + ${VALUES[2]} | bc) + MERGES_TOTAL=$(echo $MERGES_TOTAL + $MERGES | bc) + echo "----------------------------------------------------------------------" + cd .. +done + +echo +echo "TOTAL" +echo "Stats: $CHANGED files changed, $INSERTIONS insertions(+), $DELETIONS deletions(-)" +echo "Merges: $MERGES_TOTAL" -- 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 --- changes/do_not_do_native_menu_in_linux | 1 + src/leap/bitmask/gui/mainwindow.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changes/do_not_do_native_menu_in_linux diff --git a/changes/do_not_do_native_menu_in_linux b/changes/do_not_do_native_menu_in_linux new file mode 100644 index 00000000..2a117a7e --- /dev/null +++ b/changes/do_not_do_native_menu_in_linux @@ -0,0 +1 @@ +- Workaround a bug in Ubuntu where the menu is not displayed in the global menu bar. Fixes #5420. \ No newline at end of file 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 From a984b0fce963d92e508c5db9995b5e3450874f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 4 Apr 2014 16:50:16 -0300 Subject: Update release notes --- relnotes.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/relnotes.txt b/relnotes.txt index 44b3b488..071b671e 100644 --- a/relnotes.txt +++ b/relnotes.txt @@ -1,8 +1,8 @@ -ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.3.8 +ANNOUNCING Bitmask, the Internet Encryption Toolkit, release 0.5.0 The LEAP team is pleased to announce the immediate availability of -version 0.3.8 of Bitmask, the Internet Encryption Toolkit, codename -"Three week child". +version 0.5.0 of Bitmask, the Internet Encryption Toolkit, codename +"Long time no see". https://downloads.leap.se/client/ @@ -30,11 +30,11 @@ http://bitmask.rtfd.org/ WARNING: This is still part of a beta release of our software, a lot of testing and auditing is still needed, so indeed use it, and feed us back, fork it and contribute to its development, but by any means DO -NOT trust your life to it (yet!). +NOT trust your life to it. WHAT CAN THIS VERSION OF BITMASK DO FOR ME? -Bitmask 0.3.8 improves greatly its mail support and stability in +Bitmask 0.5.0 improves greatly its mail support and stability in general, among other various bug fixes. You can refer to the CHANGELOG for the meat. @@ -66,7 +66,7 @@ INSTALLATION We distribute the current version of Bitmask as standalone bundles for GNU/Linux, OSX and Windows, but it is likely that you are able to run -it under other systems, specially if you are skillful and patience is +it under other systems, specially if you are skillful and patience is one of your virtues. Have a look at "docs/user/install.rst". @@ -95,6 +95,6 @@ beyond any border. The LEAP team, -Dec 6, 2013 +Apr 4, 2014 Somewhere in the middle of the intertubes. EOF -- cgit v1.2.3 From b40cc1c561f1f9375295ac31f857446e056cba07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 4 Apr 2014 16:50:49 -0300 Subject: Update requirements --- changes/VERSION_COMPAT | 1 - pkg/requirements.pip | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/changes/VERSION_COMPAT b/changes/VERSION_COMPAT index 1f523ccd..cc00ecf7 100644 --- a/changes/VERSION_COMPAT +++ b/changes/VERSION_COMPAT @@ -8,4 +8,3 @@ # # BEGIN DEPENDENCY LIST ------------------------- # leap.foo.bar>=x.y.z -leap.mail>=0.3.9 diff --git a/pkg/requirements.pip b/pkg/requirements.pip index 850ccf44..be4ea858 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -20,7 +20,7 @@ zope.proxy leap.common>=0.3.4 leap.soledad.client>=0.4.2 leap.keymanager>=0.3.6 -leap.mail>=0.3.7 +leap.mail>=0.3.9 # Remove this when u1db fixes its dependency on oauth oauth -- cgit v1.2.3 From eb4cdab9c6b8ff66bb4667cc6195d2c366122540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 4 Apr 2014 16:57:19 -0300 Subject: Fold in changes --- CHANGELOG.rst | 95 ++++++++++++++++++++++ changes/better-logout-handle | 2 - changes/better-soledad-retry | 1 - changes/bug-4633_fix-resolvconf-usage | 1 - changes/bug-4670_disable-eip-and-disable-actions | 2 - changes/bug-4734_akm-stored-public-keys-view | 1 - changes/bug-4742_reset-registration-page-error | 2 - .../bug-4817_disconnect-signals-on-wizard-close | 1 - changes/bug-4942_fix-logout-error-message | 1 - changes/bug-4952_fix-unread-email-wording | 1 - changes/bug-4984_update-menu-name-in-wizard | 1 - .../bug-4985_config-help-menu-non-empty-password | 1 - changes/bug-4986_error-joining-string | 1 - changes/bug-4995_error-on-every-logout | 1 - changes/bug-5048_version-checks-in-wizard | 1 - changes/bug-5219_srpauth-error-messages | 1 - changes/bug-5309_psutil-version-conflict | 1 - changes/bug-5395_update-bitmask-website-in-pkginfo | 1 - .../bug-5396_disable-next-if-provider-is-changed | 1 - .../bug-5411_mail-is-not-disabled-at-first-start | 1 - changes/bug-5433_unescape-logs-for-pastebin | 1 - changes/bug-handle-closed-soledad | 1 - changes/bug-shutdown-errors | 1 - changes/bug_4925_close_imap_session | 1 - .../bug_5083_sync_soledad_only_on_account_create | 3 - changes/bug_5153-select-domain-in-eip-preferences | 1 - changes/bug_cancel-login-does-not-work | 1 - changes/bug_capture-keyring-errors | 2 - changes/bug_update-psutil-version | 1 - changes/bug_use_bitmask_icon_mac | 2 - changes/do_not_do_native_menu_in_linux | 1 - .../feature-4488_use-existing-provider-by-default | 1 - changes/feature-4530_add-in-app-help-for-mail | 1 - changes/feature-4636_warn-user-if-outdated-app | 2 - ...re-4753-move-login-widget-to-top-of-main-window | 2 - changes/feature-4877_disable-akm-import | 1 - changes/feature-4943_offline-mode | 1 - changes/feature-5163_add-pastebin-button | 1 - changes/feature-5391_support-selfsigned-certs | 1 - changes/feature-5401_add-shortcut-for-help-menu | 1 - changes/feature_4792_repair-mailboxes | 3 - ...4885_make-soledad-wait-for-eip-after-logging-in | 1 - ...e_5095_ensure-imap-flushes-data-before-quitting | 1 - .../feature_5120_update-keymanager-to-webapp-v2 | 1 - .../feature_5191_handle-invalid-auth-token-error | 2 - changes/feature_check-for-dns-resolution | 1 - ...feature_display_domain_instead_of_provider_name | 2 - changes/feature_import-maildir | 1 - changes/feature_mail-debug | 1 - changes/feature_moar-version-info | 2 - changes/feature_move-srpregister-to-backend | 1 - changes/feature_nice-process | 1 - changes/feature_refactor_provider_bootstrapper | 2 - changes/feature_remove_qtreactor | 1 - 54 files changed, 95 insertions(+), 68 deletions(-) delete mode 100644 changes/better-logout-handle delete mode 100644 changes/better-soledad-retry delete mode 100644 changes/bug-4633_fix-resolvconf-usage delete mode 100644 changes/bug-4670_disable-eip-and-disable-actions delete mode 100644 changes/bug-4734_akm-stored-public-keys-view delete mode 100644 changes/bug-4742_reset-registration-page-error delete mode 100644 changes/bug-4817_disconnect-signals-on-wizard-close delete mode 100644 changes/bug-4942_fix-logout-error-message delete mode 100644 changes/bug-4952_fix-unread-email-wording delete mode 100644 changes/bug-4984_update-menu-name-in-wizard delete mode 100644 changes/bug-4985_config-help-menu-non-empty-password delete mode 100644 changes/bug-4986_error-joining-string delete mode 100644 changes/bug-4995_error-on-every-logout delete mode 100644 changes/bug-5048_version-checks-in-wizard delete mode 100644 changes/bug-5219_srpauth-error-messages delete mode 100644 changes/bug-5309_psutil-version-conflict delete mode 100644 changes/bug-5395_update-bitmask-website-in-pkginfo delete mode 100644 changes/bug-5396_disable-next-if-provider-is-changed delete mode 100644 changes/bug-5411_mail-is-not-disabled-at-first-start delete mode 100644 changes/bug-5433_unescape-logs-for-pastebin delete mode 100644 changes/bug-handle-closed-soledad delete mode 100644 changes/bug-shutdown-errors delete mode 100644 changes/bug_4925_close_imap_session delete mode 100644 changes/bug_5083_sync_soledad_only_on_account_create delete mode 100644 changes/bug_5153-select-domain-in-eip-preferences delete mode 100644 changes/bug_cancel-login-does-not-work delete mode 100644 changes/bug_capture-keyring-errors delete mode 100644 changes/bug_update-psutil-version delete mode 100644 changes/bug_use_bitmask_icon_mac delete mode 100644 changes/do_not_do_native_menu_in_linux delete mode 100644 changes/feature-4488_use-existing-provider-by-default delete mode 100644 changes/feature-4530_add-in-app-help-for-mail delete mode 100644 changes/feature-4636_warn-user-if-outdated-app delete mode 100644 changes/feature-4753-move-login-widget-to-top-of-main-window delete mode 100644 changes/feature-4877_disable-akm-import delete mode 100644 changes/feature-4943_offline-mode delete mode 100644 changes/feature-5163_add-pastebin-button delete mode 100644 changes/feature-5391_support-selfsigned-certs delete mode 100644 changes/feature-5401_add-shortcut-for-help-menu delete mode 100644 changes/feature_4792_repair-mailboxes delete mode 100644 changes/feature_4885_make-soledad-wait-for-eip-after-logging-in delete mode 100644 changes/feature_5095_ensure-imap-flushes-data-before-quitting delete mode 100644 changes/feature_5120_update-keymanager-to-webapp-v2 delete mode 100644 changes/feature_5191_handle-invalid-auth-token-error delete mode 100644 changes/feature_check-for-dns-resolution delete mode 100644 changes/feature_display_domain_instead_of_provider_name delete mode 100644 changes/feature_import-maildir delete mode 100644 changes/feature_mail-debug delete mode 100644 changes/feature_moar-version-info delete mode 100644 changes/feature_move-srpregister-to-backend delete mode 100644 changes/feature_nice-process delete mode 100644 changes/feature_refactor_provider_bootstrapper delete mode 100644 changes/feature_remove_qtreactor diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 866d6f28..1728f358 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,101 @@ History ------- +2014 +==== + +0.5.0 Apr 4 -- the "Long time no see" release: +++++++++++++++++++++++++++++++++++++++++++++++ +- Fix logging out typo, closes #4815. +- Improve logout action, related to #5131. +- In case of soledad bootstrap error (e.g.: network failure), re run + all the setup process. +- Correct resolvconf usage. Avoids permanent break of + resolv.conf. Closes #4633. +- Disable and stop EIP when you set EIP as disabled in the preferences + dialog. Closes #4670. +- Advanced Key Management: add view for stored public keys. Closes + #4734. +- Reset registration error and input widgets if the user goes back to + provider selection in wizard. Closes #4742. +- Disconnect signals before closing the wizard. Closes #4817. +- Fix logout error message, display it similarly to other errors in + the app. Closes #4942. +- Client should say 1 unread email, not emails. Closes #4952. +- Update menu name in Wizard. Closes #4984. +- Config help menu: do not use an empty password. Closes #4985. +- Handle wizard close correctly. Closes #4986. +- Fix "Something went wrong with the logout" misleading error in every + logout. Closes #4995 and #5071. +- Use version checks in the wizard when the user choose to use an + existing provider. Closes #5048. +- Move error messages from srpauth to the GUI and refactor + signals. Closes #5219. +- Fix psutil version to avoid conflicts with gnupg required + version. Closes #5309. +- Update bitmask url in PKG-INFO. Closes #5395. +- Disable 'next' button if the checks passed but the provider is + changed. Closes #5396. +- Do not start soledad and mail if the mail service is + disabled. Closes #5411. +- Don't escape logs for pastebin. Closes #5433. +- Handle closed Soledad database on quit, speedup exit. Closes #5130. +- Catch shutdown errors. Closes: #5313 +- Properly reset imap session on logout. Closes: #4925 +- Sync Soledad before bootstrapping mail only if the key for the user + is not found locally. Otherwise, defer to thread and + continue. Closes #5083. +- Set as selected default for the eip preferences window the item + selented in the bitmask main window. Closes #5153. +- Cancel login does not work or needs to be pressed twice. Closes + #4869, #4973. +- Fail gracefully against keyring import errors. +- Update requirements and code for the new psutil version. +- Use Bitmask icon instead of LEAP's for the super user dialog in + OSX. Fixes #4273. +- Workaround a bug in Ubuntu where the menu is not displayed in the + global menu bar. Fixes #5420. +- Wizard: select by default the use of an existing provider if we have + configured at least one. Closes #4488. +- Add in-app indication of how to connect to local imap and + smtp. Closes #4530. +- Warn the user on incompatible api error. +- Warn the user if is using an old app version. Closes #4636. +- Minor UI changes: re-arrange main window so that the login widget is + at the top and preferences are available under the menu. +- Disable Advanced Key Manager import feature since it's experimental + and may cause data loss. Closes #4877. +- Offline mode for debugging. Closes: #4943 +- Add pastebin button to upload logs from the logs window to ease bug + report. Closes #5163. +- Add support for self signed certs. Closes #5391. +- Add hotkey for the Help menu. Closes #5401. +- Add --repair-mailboxes command line option. It will be needed to + migrate existing account after a data schema changes, like it will + be happening for 0.5.0. Closes #4792. +- Make first Soledad sync wait for EIP to come up after logging in. + Fixes #4885. +- Ensure IMAP flushes data to disk before quitting. Closes #5095. +- Update key manager auth to interact with webapp v2. Fixes #5120. +- Handle invalid auth tokens when syncing Soledad, and show an error + on the GUI. Fixes #5191. +- After connecting EIP check for DNS resolution and warn the user on + error. Closes #5301. +- Display domain for provider the user has just logged in. Fixes + #4631. +- Add ability to import a maildir into a local mailbox. +- Add ability to write mail logs to a separate file. +- Show hash info in About bitmask (for debian versions). +- Add the appname in the reported version string. +- Move/refactor SRPRegister to the backend. +- Add ability to nice application via environment variable. +- Refactor ProviderBootstrapper out of the UI modules to a Backend + module, obscuring all the details. +- Remove qt4reactor as a dependency. + +2013 +==== + 0.3.8 Dec 6 -- the "Three week child" release: +++++++++++++++++++++++++++++++++++++++++++++++ - Make the preferences window selects the current selected provider in diff --git a/changes/better-logout-handle b/changes/better-logout-handle deleted file mode 100644 index 11797a81..00000000 --- a/changes/better-logout-handle +++ /dev/null @@ -1,2 +0,0 @@ -- Fix logging out typo, closes #4815. -- Improve logout action, related to #5131. diff --git a/changes/better-soledad-retry b/changes/better-soledad-retry deleted file mode 100644 index ac21f76c..00000000 --- a/changes/better-soledad-retry +++ /dev/null @@ -1 +0,0 @@ -In case of soledad bootstrap error (e.g.: network failure), re run all the setup process. diff --git a/changes/bug-4633_fix-resolvconf-usage b/changes/bug-4633_fix-resolvconf-usage deleted file mode 100644 index de40d03a..00000000 --- a/changes/bug-4633_fix-resolvconf-usage +++ /dev/null @@ -1 +0,0 @@ -- Correct resolvconf usage. Avoids permanent break of resolv.conf. Closes #4633. diff --git a/changes/bug-4670_disable-eip-and-disable-actions b/changes/bug-4670_disable-eip-and-disable-actions deleted file mode 100644 index 7f11b3bb..00000000 --- a/changes/bug-4670_disable-eip-and-disable-actions +++ /dev/null @@ -1,2 +0,0 @@ -- Disable and stop EIP when you set EIP as disabled in the preferences dialog. - Closes #4670. diff --git a/changes/bug-4734_akm-stored-public-keys-view b/changes/bug-4734_akm-stored-public-keys-view deleted file mode 100644 index bdba2cb2..00000000 --- a/changes/bug-4734_akm-stored-public-keys-view +++ /dev/null @@ -1 +0,0 @@ -- Advanced Key Management: add view for stored public keys. Closes #4734. diff --git a/changes/bug-4742_reset-registration-page-error b/changes/bug-4742_reset-registration-page-error deleted file mode 100644 index 2b031796..00000000 --- a/changes/bug-4742_reset-registration-page-error +++ /dev/null @@ -1,2 +0,0 @@ -- Reset registration error and input widgets if the user goes back to provider - selection in wizard. Closes #4742. diff --git a/changes/bug-4817_disconnect-signals-on-wizard-close b/changes/bug-4817_disconnect-signals-on-wizard-close deleted file mode 100644 index 78c14756..00000000 --- a/changes/bug-4817_disconnect-signals-on-wizard-close +++ /dev/null @@ -1 +0,0 @@ -- Disconnect signals before closing the wizard. Closes #4817. diff --git a/changes/bug-4942_fix-logout-error-message b/changes/bug-4942_fix-logout-error-message deleted file mode 100644 index f8c8a3b2..00000000 --- a/changes/bug-4942_fix-logout-error-message +++ /dev/null @@ -1 +0,0 @@ -- Fix logout error message, display it similarly to other errors in the app. Closes #4942. diff --git a/changes/bug-4952_fix-unread-email-wording b/changes/bug-4952_fix-unread-email-wording deleted file mode 100644 index 7bfee8b2..00000000 --- a/changes/bug-4952_fix-unread-email-wording +++ /dev/null @@ -1 +0,0 @@ -- Client should say 1 unread email, not emails. Closes #4952. diff --git a/changes/bug-4984_update-menu-name-in-wizard b/changes/bug-4984_update-menu-name-in-wizard deleted file mode 100644 index 9048951a..00000000 --- a/changes/bug-4984_update-menu-name-in-wizard +++ /dev/null @@ -1 +0,0 @@ -- Update menu name in Wizard. Closes #4984. diff --git a/changes/bug-4985_config-help-menu-non-empty-password b/changes/bug-4985_config-help-menu-non-empty-password deleted file mode 100644 index c6c5266f..00000000 --- a/changes/bug-4985_config-help-menu-non-empty-password +++ /dev/null @@ -1 +0,0 @@ -- Config help menu: do not use an empty password. Closes #4985. diff --git a/changes/bug-4986_error-joining-string b/changes/bug-4986_error-joining-string deleted file mode 100644 index 935ba6db..00000000 --- a/changes/bug-4986_error-joining-string +++ /dev/null @@ -1 +0,0 @@ -- Handle wizard close correctly. Closes #4986. diff --git a/changes/bug-4995_error-on-every-logout b/changes/bug-4995_error-on-every-logout deleted file mode 100644 index beb4e4c2..00000000 --- a/changes/bug-4995_error-on-every-logout +++ /dev/null @@ -1 +0,0 @@ -- Fix "Something went wrong with the logout" misleading error in every logout. Closes #4995 and #5071. diff --git a/changes/bug-5048_version-checks-in-wizard b/changes/bug-5048_version-checks-in-wizard deleted file mode 100644 index a5593e6c..00000000 --- a/changes/bug-5048_version-checks-in-wizard +++ /dev/null @@ -1 +0,0 @@ -- Use version checks in the wizard when the user choose to use an existing provider. Closes #5048. diff --git a/changes/bug-5219_srpauth-error-messages b/changes/bug-5219_srpauth-error-messages deleted file mode 100644 index d46b1504..00000000 --- a/changes/bug-5219_srpauth-error-messages +++ /dev/null @@ -1 +0,0 @@ -- Move error messages from srpauth to the GUI and refactor signals. Closes #5219. diff --git a/changes/bug-5309_psutil-version-conflict b/changes/bug-5309_psutil-version-conflict deleted file mode 100644 index e7ccb2fc..00000000 --- a/changes/bug-5309_psutil-version-conflict +++ /dev/null @@ -1 +0,0 @@ -- Fix psutil version to avoid conflicts with gnupg required version. Closes #5309. diff --git a/changes/bug-5395_update-bitmask-website-in-pkginfo b/changes/bug-5395_update-bitmask-website-in-pkginfo deleted file mode 100644 index a762a8bb..00000000 --- a/changes/bug-5395_update-bitmask-website-in-pkginfo +++ /dev/null @@ -1 +0,0 @@ -- Update bitmask url in PKG-INFO. Closes #5395. diff --git a/changes/bug-5396_disable-next-if-provider-is-changed b/changes/bug-5396_disable-next-if-provider-is-changed deleted file mode 100644 index 5a7a3449..00000000 --- a/changes/bug-5396_disable-next-if-provider-is-changed +++ /dev/null @@ -1 +0,0 @@ -- Disable 'next' button if the checks passed but the provider is changed. Closes #5396. diff --git a/changes/bug-5411_mail-is-not-disabled-at-first-start b/changes/bug-5411_mail-is-not-disabled-at-first-start deleted file mode 100644 index 3cba7a01..00000000 --- a/changes/bug-5411_mail-is-not-disabled-at-first-start +++ /dev/null @@ -1 +0,0 @@ -- Do not start soledad and mail if the mail service is disabled. Closes #5411. diff --git a/changes/bug-5433_unescape-logs-for-pastebin b/changes/bug-5433_unescape-logs-for-pastebin deleted file mode 100644 index 289dd64a..00000000 --- a/changes/bug-5433_unescape-logs-for-pastebin +++ /dev/null @@ -1 +0,0 @@ -- Don't escape logs for pastebin. Closes #5433. diff --git a/changes/bug-handle-closed-soledad b/changes/bug-handle-closed-soledad deleted file mode 100644 index bf0a905e..00000000 --- a/changes/bug-handle-closed-soledad +++ /dev/null @@ -1 +0,0 @@ -- Handle closed Soledad database on quit, speedup exit. Closes #5130. diff --git a/changes/bug-shutdown-errors b/changes/bug-shutdown-errors deleted file mode 100644 index a2de1249..00000000 --- a/changes/bug-shutdown-errors +++ /dev/null @@ -1 +0,0 @@ -- Catch shutdown errors. Closes: #5313 diff --git a/changes/bug_4925_close_imap_session b/changes/bug_4925_close_imap_session deleted file mode 100644 index f70b3e2b..00000000 --- a/changes/bug_4925_close_imap_session +++ /dev/null @@ -1 +0,0 @@ -- Properly reset imap session on logout. Closes: #4925 diff --git a/changes/bug_5083_sync_soledad_only_on_account_create b/changes/bug_5083_sync_soledad_only_on_account_create deleted file mode 100644 index 6deaa41f..00000000 --- a/changes/bug_5083_sync_soledad_only_on_account_create +++ /dev/null @@ -1,3 +0,0 @@ -- Sync Soledad before bootstrapping mail only if the key for the user - is not found locally. Otherwise, defer to thread and - continue. Closes #5083 \ No newline at end of file diff --git a/changes/bug_5153-select-domain-in-eip-preferences b/changes/bug_5153-select-domain-in-eip-preferences deleted file mode 100644 index 2c00c745..00000000 --- a/changes/bug_5153-select-domain-in-eip-preferences +++ /dev/null @@ -1 +0,0 @@ -Set as selected default for the eip preferences window the item selented in the bitmask main window. Closes #5153. diff --git a/changes/bug_cancel-login-does-not-work b/changes/bug_cancel-login-does-not-work deleted file mode 100644 index 8fbccb5a..00000000 --- a/changes/bug_cancel-login-does-not-work +++ /dev/null @@ -1 +0,0 @@ -- Cancel login does not work or needs to be pressed twice. Closes #4869, #4973. diff --git a/changes/bug_capture-keyring-errors b/changes/bug_capture-keyring-errors deleted file mode 100644 index a4b7046b..00000000 --- a/changes/bug_capture-keyring-errors +++ /dev/null @@ -1,2 +0,0 @@ -- Fail gracefully against keyring import errors. - diff --git a/changes/bug_update-psutil-version b/changes/bug_update-psutil-version deleted file mode 100644 index be9fedde..00000000 --- a/changes/bug_update-psutil-version +++ /dev/null @@ -1 +0,0 @@ -Update requirements and code for the new psutil version. diff --git a/changes/bug_use_bitmask_icon_mac b/changes/bug_use_bitmask_icon_mac deleted file mode 100644 index d9015466..00000000 --- a/changes/bug_use_bitmask_icon_mac +++ /dev/null @@ -1,2 +0,0 @@ -- Use Bitmask icon instead of LEAP's for the super user dialog in - OSX. Fixes #4273. \ No newline at end of file diff --git a/changes/do_not_do_native_menu_in_linux b/changes/do_not_do_native_menu_in_linux deleted file mode 100644 index 2a117a7e..00000000 --- a/changes/do_not_do_native_menu_in_linux +++ /dev/null @@ -1 +0,0 @@ -- Workaround a bug in Ubuntu where the menu is not displayed in the global menu bar. Fixes #5420. \ No newline at end of file diff --git a/changes/feature-4488_use-existing-provider-by-default b/changes/feature-4488_use-existing-provider-by-default deleted file mode 100644 index feeaa349..00000000 --- a/changes/feature-4488_use-existing-provider-by-default +++ /dev/null @@ -1 +0,0 @@ -- Wizard: select by default the use of an existing provider if we have configured at least one. Closes #4488. diff --git a/changes/feature-4530_add-in-app-help-for-mail b/changes/feature-4530_add-in-app-help-for-mail deleted file mode 100644 index 9d7f3316..00000000 --- a/changes/feature-4530_add-in-app-help-for-mail +++ /dev/null @@ -1 +0,0 @@ -- Add in-app indication of how to connect to local imap and smtp. Closes #4530. diff --git a/changes/feature-4636_warn-user-if-outdated-app b/changes/feature-4636_warn-user-if-outdated-app deleted file mode 100644 index d4bd6eb7..00000000 --- a/changes/feature-4636_warn-user-if-outdated-app +++ /dev/null @@ -1,2 +0,0 @@ -- Warn the user on incompatible api error. -- Warn the user if is using an old app version. Closes #4636. diff --git a/changes/feature-4753-move-login-widget-to-top-of-main-window b/changes/feature-4753-move-login-widget-to-top-of-main-window deleted file mode 100644 index 0ab23b24..00000000 --- a/changes/feature-4753-move-login-widget-to-top-of-main-window +++ /dev/null @@ -1,2 +0,0 @@ --- Minor UI changes: re-arrange main window so that the login widget is at the top and preferences are available under the menu. - diff --git a/changes/feature-4877_disable-akm-import b/changes/feature-4877_disable-akm-import deleted file mode 100644 index eeb70e19..00000000 --- a/changes/feature-4877_disable-akm-import +++ /dev/null @@ -1 +0,0 @@ -- Disable Advanced Key Manager import feature since it's experimental and may cause data loss. Closes #4877. diff --git a/changes/feature-4943_offline-mode b/changes/feature-4943_offline-mode deleted file mode 100644 index 02fda893..00000000 --- a/changes/feature-4943_offline-mode +++ /dev/null @@ -1 +0,0 @@ -- Offline mode for debugging. Closes: #4943 diff --git a/changes/feature-5163_add-pastebin-button b/changes/feature-5163_add-pastebin-button deleted file mode 100644 index ec44edaf..00000000 --- a/changes/feature-5163_add-pastebin-button +++ /dev/null @@ -1 +0,0 @@ -- Add pastebin button to upload logs from the logs window to ease bug report. Closes #5163. diff --git a/changes/feature-5391_support-selfsigned-certs b/changes/feature-5391_support-selfsigned-certs deleted file mode 100644 index 58c68f23..00000000 --- a/changes/feature-5391_support-selfsigned-certs +++ /dev/null @@ -1 +0,0 @@ -- Add support for self signed certs. Closes #5391. diff --git a/changes/feature-5401_add-shortcut-for-help-menu b/changes/feature-5401_add-shortcut-for-help-menu deleted file mode 100644 index 9e33388b..00000000 --- a/changes/feature-5401_add-shortcut-for-help-menu +++ /dev/null @@ -1 +0,0 @@ -- Add hotkey for the Help menu. Closes #5401. diff --git a/changes/feature_4792_repair-mailboxes b/changes/feature_4792_repair-mailboxes deleted file mode 100644 index cb570e8b..00000000 --- a/changes/feature_4792_repair-mailboxes +++ /dev/null @@ -1,3 +0,0 @@ -- Add --repair-mailboxes command line option. It will be needed to migrate - existing account after a data schema changes, like it will be happening for - 0.5.0. Closes: #4792 diff --git a/changes/feature_4885_make-soledad-wait-for-eip-after-logging-in b/changes/feature_4885_make-soledad-wait-for-eip-after-logging-in deleted file mode 100644 index 07b198f0..00000000 --- a/changes/feature_4885_make-soledad-wait-for-eip-after-logging-in +++ /dev/null @@ -1 +0,0 @@ -- Make first Soledad sync wait for EIP to come up after logging in (#4885). diff --git a/changes/feature_5095_ensure-imap-flushes-data-before-quitting b/changes/feature_5095_ensure-imap-flushes-data-before-quitting deleted file mode 100644 index ef1f2888..00000000 --- a/changes/feature_5095_ensure-imap-flushes-data-before-quitting +++ /dev/null @@ -1 +0,0 @@ -- Ensure IMAP flushes data to disk before quitting. Closes #5095. diff --git a/changes/feature_5120_update-keymanager-to-webapp-v2 b/changes/feature_5120_update-keymanager-to-webapp-v2 deleted file mode 100644 index 71aa3d5e..00000000 --- a/changes/feature_5120_update-keymanager-to-webapp-v2 +++ /dev/null @@ -1 +0,0 @@ -- Update key manager auth to interact with webapp v2 (#5120). diff --git a/changes/feature_5191_handle-invalid-auth-token-error b/changes/feature_5191_handle-invalid-auth-token-error deleted file mode 100644 index 6acdaad0..00000000 --- a/changes/feature_5191_handle-invalid-auth-token-error +++ /dev/null @@ -1,2 +0,0 @@ -- Handle invalid auth tokens when syncing Soledad, and show an error on the - GUI (#5191). diff --git a/changes/feature_check-for-dns-resolution b/changes/feature_check-for-dns-resolution deleted file mode 100644 index f0e2bf3e..00000000 --- a/changes/feature_check-for-dns-resolution +++ /dev/null @@ -1 +0,0 @@ -- After connecting EIP check for DNS resolution and warn the user on error. Closes #5301. diff --git a/changes/feature_display_domain_instead_of_provider_name b/changes/feature_display_domain_instead_of_provider_name deleted file mode 100644 index 0b2c42a6..00000000 --- a/changes/feature_display_domain_instead_of_provider_name +++ /dev/null @@ -1,2 +0,0 @@ -- Display domain for provider the user has just logged in. Fixes - #4631. \ No newline at end of file diff --git a/changes/feature_import-maildir b/changes/feature_import-maildir deleted file mode 100644 index c5d861a3..00000000 --- a/changes/feature_import-maildir +++ /dev/null @@ -1 +0,0 @@ -- Add ability to import a maildir into a local mailbox. diff --git a/changes/feature_mail-debug b/changes/feature_mail-debug deleted file mode 100644 index 5535a49a..00000000 --- a/changes/feature_mail-debug +++ /dev/null @@ -1 +0,0 @@ -- Add ability to write mail logs to a separate file. diff --git a/changes/feature_moar-version-info b/changes/feature_moar-version-info deleted file mode 100644 index fd5452ce..00000000 --- a/changes/feature_moar-version-info +++ /dev/null @@ -1,2 +0,0 @@ -- Show hash info in About bitmask (for debian versions). -- Add the appname in the reported version string. diff --git a/changes/feature_move-srpregister-to-backend b/changes/feature_move-srpregister-to-backend deleted file mode 100644 index 0625432f..00000000 --- a/changes/feature_move-srpregister-to-backend +++ /dev/null @@ -1 +0,0 @@ -- Move/refactor srpregister to backend. diff --git a/changes/feature_nice-process b/changes/feature_nice-process deleted file mode 100644 index 3de26e75..00000000 --- a/changes/feature_nice-process +++ /dev/null @@ -1 +0,0 @@ -- Add ability to nice application via environment variable. diff --git a/changes/feature_refactor_provider_bootstrapper b/changes/feature_refactor_provider_bootstrapper deleted file mode 100644 index 969b259f..00000000 --- a/changes/feature_refactor_provider_bootstrapper +++ /dev/null @@ -1,2 +0,0 @@ -- Refactor ProviderBootstrapper out of the UI modules to a Backend - module, obscuring all the details. \ No newline at end of file diff --git a/changes/feature_remove_qtreactor b/changes/feature_remove_qtreactor deleted file mode 100644 index 5cf3a854..00000000 --- a/changes/feature_remove_qtreactor +++ /dev/null @@ -1 +0,0 @@ -- Remove qt4reactor as a dependency. \ No newline at end of file -- cgit v1.2.3