From 9c39068d292d17fb3c29ec3c53de5250ddfb40d7 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 2 Sep 2015 14:04:01 -0300 Subject: [feat] load credentials from environment variable Look for file defined in the `BITMASK_CREDENTIALS` env variable and load `provider`, `username` and `password` data. Trigger login if they were loaded correctly. The credentials file should look like this: [Credentials] username = my-account@my-provider.com password = secret - Resolves: #7419 --- src/leap/bitmask/gui/mainwindow.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'src') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 312048ba..b0f14aef 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -17,8 +17,11 @@ """ Main window for Bitmask. """ +import os import time +import ConfigParser + from datetime import datetime import psutil @@ -781,6 +784,10 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): if self._wizard: self._load_from_wizard() else: + if self._load_credentials_from_env(): + self._login() + return + domain = self._settings.get_provider() if domain is not None: self._providers.select_provider_by_name(domain) @@ -796,6 +803,35 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): if self._login_widget.load_user_from_keyring(saved_user): self._login() + def _load_credentials_from_env(self): + """ + Load username and password into the login widget from a file specified + in an environment variable. This is useful for test purposes. + + :return: True if credentials were loaded, False otherwise + :rtype: bool + """ + credentials = os.environ.get("BITMASK_CREDENTIALS") + + if credentials is None: + return False + + try: + config = ConfigParser.ConfigParser() + config.read(credentials) + username, domain = config.get('Credentials', 'username').split('@') + password = config.get('Credentials', 'password') + except Exception, e: + print "Error reading credentials file: {0}".format(e) + return False + + self._providers.select_provider_by_name(domain) + self._login_widget.set_provider(domain) + self._login_widget.set_user(username) + self._login_widget.set_password(password) + + return True + def _show_hide_unsupported_services(self): """ Given a set of configured providers, it creates a set of -- cgit v1.2.3 From f55ad5698d989bc8185cb7b5eb552e1adebb0b39 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 7 Sep 2015 14:18:37 -0300 Subject: [bug] fix argument number on window raise event - Resolves: #7415 --- src/leap/bitmask/gui/mainwindow.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index b0f14aef..c5939a6d 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1640,14 +1640,12 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): # window handling methods # - def _on_raise_window_event(self, event, content): + def _on_raise_window_event(self, event): """ Callback for the raise window event :param event: The event that triggered the callback. :type event: str - :param content: The content of the event. - :type content: list """ if IS_WIN: locks.raise_window_ack() -- cgit v1.2.3 From 580ad326c319113b85c85ce417e461acf15d36fe Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 10 Sep 2015 16:22:03 -0400 Subject: [bug] add compatibility for logbook versions < 0.7.0 for the version in ubuntu trusty, the call for the zeromq handler initialization gets one less argument --- src/leap/bitmask/logs/safezmqhandler.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/leap/bitmask/logs/safezmqhandler.py b/src/leap/bitmask/logs/safezmqhandler.py index 4f7aca9b..4b3d26c7 100644 --- a/src/leap/bitmask/logs/safezmqhandler.py +++ b/src/leap/bitmask/logs/safezmqhandler.py @@ -19,9 +19,11 @@ A thread-safe zmq handler for LogBook. """ import json import threading +from distutils import LooseVersion from logbook.queues import ZeroMQHandler from logbook import NOTSET +from logbook import __version__ as LOGBOOK_VERSION import zmq @@ -53,8 +55,13 @@ class SafeZMQHandler(ZeroMQHandler): # instead of calling the ZeroMQHandler's one. # The best approach may be to inherit directly from `logbook.Handler`. - ZeroMQHandler.__init__(self, uri, level, filter, bubble, context, - multi) + args = (self, uri, level, filter, bubble, context) + + # Workaround for an API change: version 0.6.0 in trusty doesn't accepts + # the multi parameter. + if LooseVersion(LOGBOOK_VERSION) >= LooseVersion('0.7.0'): + args += (multi,) + ZeroMQHandler.__init__(*args) current_id = self._get_caller_id() # we store the socket created on the parent -- cgit v1.2.3 From cd9a84070591e18780bd244925f443351e71d1cb Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 11 Sep 2015 08:40:37 -0400 Subject: [bug] fix import there's an error in the import --- src/leap/bitmask/logs/safezmqhandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/leap/bitmask/logs/safezmqhandler.py b/src/leap/bitmask/logs/safezmqhandler.py index 4b3d26c7..6d2c4f50 100644 --- a/src/leap/bitmask/logs/safezmqhandler.py +++ b/src/leap/bitmask/logs/safezmqhandler.py @@ -19,7 +19,7 @@ A thread-safe zmq handler for LogBook. """ import json import threading -from distutils import LooseVersion +from distutils.version import LooseVersion from logbook.queues import ZeroMQHandler from logbook import NOTSET -- cgit v1.2.3 From ad0f20ccb2bf9d4e2fda346a061dcfd8342f7561 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 14 Sep 2015 17:50:32 -0400 Subject: [pkg] revert attempt to be compatible with logbook 0.6.0 we need the 'multi' feature all the same, so we'll be depending on logbook >= 0.7.0. --- src/leap/bitmask/logs/safezmqhandler.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'src') diff --git a/src/leap/bitmask/logs/safezmqhandler.py b/src/leap/bitmask/logs/safezmqhandler.py index 6d2c4f50..4e5f59e4 100644 --- a/src/leap/bitmask/logs/safezmqhandler.py +++ b/src/leap/bitmask/logs/safezmqhandler.py @@ -19,11 +19,9 @@ A thread-safe zmq handler for LogBook. """ import json import threading -from distutils.version import LooseVersion from logbook.queues import ZeroMQHandler from logbook import NOTSET -from logbook import __version__ as LOGBOOK_VERSION import zmq @@ -55,12 +53,7 @@ class SafeZMQHandler(ZeroMQHandler): # instead of calling the ZeroMQHandler's one. # The best approach may be to inherit directly from `logbook.Handler`. - args = (self, uri, level, filter, bubble, context) - - # Workaround for an API change: version 0.6.0 in trusty doesn't accepts - # the multi parameter. - if LooseVersion(LOGBOOK_VERSION) >= LooseVersion('0.7.0'): - args += (multi,) + args = (self, uri, level, filter, bubble, context, multi) ZeroMQHandler.__init__(*args) current_id = self._get_caller_id() -- cgit v1.2.3 From 961d9e6a1c7a5041d5b019581dbf08f16f29ea53 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 16 Sep 2015 16:07:57 -0400 Subject: [bug] authenticate logout calls to api logout calls were not being authenticated, so we were receiving 401 return code. --- src/leap/bitmask/crypto/srpauth.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 452bfa66..97a4e958 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -552,12 +552,19 @@ class SRPAuthImpl(object): self._provider_config. get_api_version(), "logout") + cookies = {self.SESSION_ID_KEY: self.get_session_id()} + headers = { + self.AUTHORIZATION_KEY: + "Token token={0}".format(self.get_token()) + } try: - self._session.delete(logout_url, - data=self.get_session_id(), - verify=self._provider_config. - get_ca_cert_path(), - timeout=REQUEST_TIMEOUT) + res = self._session.delete( + logout_url, + cookies=cookies, + headers=headers, + verify=self._provider_config. + get_ca_cert_path(), + timeout=REQUEST_TIMEOUT) except Exception as e: logger.warning("Something went wrong with the logout: %r" % (e,)) @@ -568,7 +575,10 @@ class SRPAuthImpl(object): self.set_token(None) # Also reset the session self._session = self._fetcher.session() - logger.debug("Successfully logged out.") + if res.status_code == 204: + logger.debug("Successfully logged out.") + else: + logger.debug("Logout status code: %s" % res.status_code) def set_session_id(self, session_id): with self._session_id_lock: -- cgit v1.2.3 From 26dc74bcc9afbe45d6082e95a3a72cda16d2941b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 17 Sep 2015 10:44:44 -0400 Subject: [bug] fix assignment of the incoming service the second callback was actually broken, not being called when it was supposed to be. the reason is that IncomingMail.startService returns a deferred which callback is called with the loopingCall instance only when the loopingCall is stopped. --- src/leap/bitmask/services/mail/imapcontroller.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/leap/bitmask/services/mail/imapcontroller.py b/src/leap/bitmask/services/mail/imapcontroller.py index e5313477..5053d897 100644 --- a/src/leap/bitmask/services/mail/imapcontroller.py +++ b/src/leap/bitmask/services/mail/imapcontroller.py @@ -27,6 +27,7 @@ class IMAPController(object): """ IMAP Controller. """ + def __init__(self, soledad, keymanager): """ Initialize IMAP variables. @@ -63,12 +64,11 @@ class IMAPController(object): self._soledad, userid=userid) - def start_incoming_service(incoming_mail): - d = incoming_mail.startService() - d.addCallback(lambda started: incoming_mail) - return d - - def assign_incoming_service(incoming_mail): + def start_and_assign_incoming_service(incoming_mail): + # this returns a deferred that will be called when the looping call + # is stopped, we could add any shutdown/cleanup callback to that + # deferred, but unused by the moment. + incoming_mail.startService() self.incoming_mail_service = incoming_mail return incoming_mail @@ -78,8 +78,7 @@ class IMAPController(object): self._soledad, self.imap_factory, userid) - d.addCallback(start_incoming_service) - d.addCallback(assign_incoming_service) + d.addCallback(start_and_assign_incoming_service) d.addErrback(lambda f: logger.error(f.printTraceback())) def stop_imap_service(self): -- cgit v1.2.3 From 10f6f5b17e0ac1d5d4b433a428efc1beffd999a7 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 16 Sep 2015 16:23:50 +0200 Subject: [feat] remove taskthread dependency Refactor ivan's code (aa4b684d0682ff9faf1577653fa5ceabbc6e0f20) to remove the time.sleep. - Resolves: #7414 --- src/leap/bitmask/backend/backend_proxy.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/leap/bitmask/backend/backend_proxy.py b/src/leap/bitmask/backend/backend_proxy.py index 30b7c5d1..ae300b32 100644 --- a/src/leap/bitmask/backend/backend_proxy.py +++ b/src/leap/bitmask/backend/backend_proxy.py @@ -27,8 +27,6 @@ import zmq from zmq.eventloop import ioloop from zmq.eventloop import zmqstream -from taskthread import TimerTask - from leap.bitmask.backend.api import API, STOP_REQUEST, PING_REQUEST from leap.bitmask.backend.settings import Settings from leap.bitmask.backend.utils import generate_zmq_certificates_if_needed @@ -141,7 +139,8 @@ class BackendProxy(object): self._do_work = threading.Event() self._work_lock = threading.Lock() self._connection = ZmqREQConnection(self.SERVER, self._set_online) - self._heartbeat = TimerTask(self._ping, delay=self.PING_INTERVAL) + self._heartbeat = threading.Timer(self.PING_INTERVAL, + self._heartbeat_loop) self._ping_event = threading.Event() self.online = False self.settings = Settings() @@ -197,17 +196,25 @@ class BackendProxy(object): """ with self._work_lock: # avoid sending after connection was closed self._do_work.clear() - self._heartbeat.stop() + self._heartbeat.cancel() self._connection.stop() logger.debug("BackendProxy worker stopped.") - def _ping(self): + def _heartbeat_loop(self): """ - Heartbeat helper. - Sends a PING request just to know that the server is alive. + Sends a PING request every PING_INTERVAL just to know that the server + is alive. """ self._send_request(PING_REQUEST) + # lets acquire the lock to prevent heartbeat timer to get cancel while + # we set a new one + with self._work_lock: + if self._do_work.is_set(): + self._heartbeat = threading.Timer(self.PING_INTERVAL, + self._heartbeat_loop) + self._heartbeat.start() + def _api_call(self, *args, **kwargs): """ Call the `api_method` method in backend (through zmq). -- cgit v1.2.3 From 905059dcc203680f86316c83d538f14896adb928 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 17 Sep 2015 22:53:38 -0400 Subject: [style] pep8 fixes --- src/leap/bitmask/app.py | 1 + src/leap/bitmask/gui/eip_status.py | 3 ++- src/leap/bitmask/gui/login.py | 6 ++++-- src/leap/bitmask/logs/utils.py | 3 ++- src/leap/bitmask/updater.py | 4 +++- src/leap/bitmask/util/__init__.py | 3 ++- src/leap/bitmask/util/pastebin.py | 8 +++++--- 7 files changed, 19 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index a2e2aa1a..c9c02b59 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -133,6 +133,7 @@ def log_lsb_release_info(logger): for line in distro_info: logger.info(line) + def fix_qtplugins_path(): # This is a small workaround for a bug in macholib, there is a slight typo # in the path for the qt plugins that is added to the dynamic loader path diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 8334c2ee..64a408c4 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -96,7 +96,8 @@ class EIPStatusWidget(QtGui.QWidget): # Action for the systray self._eip_disabled_action = QtGui.QAction( - u"{0} is {1}".format(self._service_name, self.tr("disabled")), self) + u"{0} is {1}".format( + self._service_name, self.tr("disabled")), self) def connect_backend_signals(self): """ diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index 756dd63c..4c2bd9c5 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -19,10 +19,12 @@ Login widget implementation The login sequence is the following: - _do_login - - backend.provider_setup (check_name_resolution, check_https, download_provider_info) + - backend.provider_setup ( + check_name_resolution, check_https, download_provider_info) - on error: _provider_setup_intermediate - on success: _load_provider_config - - backend.provider_bootstrap (download_ca_cert, check_ca_fingerprint, check_api_certificate) + - backend.provider_bootstrap ( + download_ca_cert, check_ca_fingerprint, check_api_certificate) - on error: _provider_setup_intermediate - on success: _provider_config_loaded - backend.user_login diff --git a/src/leap/bitmask/logs/utils.py b/src/leap/bitmask/logs/utils.py index 683fb542..f709da8b 100644 --- a/src/leap/bitmask/logs/utils.py +++ b/src/leap/bitmask/logs/utils.py @@ -112,7 +112,7 @@ def replace_stdout_stderr_with_logging(logger=None): log.startLogging(sys.stdout) -class QtLogHandler(logbook.Handler, logbook.StringFormatterHandlerMixin): +class QtLogHandler(logbook.Handler, logbook.StringFormatterHandlerMixin): """ Custom log handler which emits a log record with the message properly formatted using a Qt Signal. @@ -186,6 +186,7 @@ class QtLogHandler(logbook.Handler, logbook.StringFormatterHandlerMixin): class _LogController(object): + def __init__(self): self._qt_handler = QtLogHandler(format_string=LOG_FORMAT) self._logbook_controller = None diff --git a/src/leap/bitmask/updater.py b/src/leap/bitmask/updater.py index c35eff5f..7cb23a0a 100644 --- a/src/leap/bitmask/updater.py +++ b/src/leap/bitmask/updater.py @@ -50,6 +50,7 @@ DELAY_KEY = "updater_delay" class Updater(threading.Thread): + def __init__(self): """ Initialize the list of mirrors, paths and other TUF dependencies from @@ -162,7 +163,8 @@ class Updater(threading.Thread): """ Find the remote repo path deneding on the platform. - :return: the path to add to the remote repo url for the specific platform. + :return: the path to add to the remote repo url for the specific + platform. :rtype: str :raises NotImplemented: When the system where bitmask is running is not diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py index 9853803a..b788abd0 100644 --- a/src/leap/bitmask/util/__init__.py +++ b/src/leap/bitmask/util/__init__.py @@ -156,6 +156,7 @@ def flags_to_dict(): return values + def here(module=None): if getattr(sys, 'frozen', False): # we are running in a |PyInstaller| bundle @@ -163,6 +164,6 @@ def here(module=None): else: dirname = os.path.dirname if module: - return dirname(module.__file__) + return dirname(module.__file__) else: return dirname(__file__) diff --git a/src/leap/bitmask/util/pastebin.py b/src/leap/bitmask/util/pastebin.py index a3bdba02..6d50ac9e 100644 --- a/src/leap/bitmask/util/pastebin.py +++ b/src/leap/bitmask/util/pastebin.py @@ -366,7 +366,9 @@ class PastebinAPI(object): Usage Example:: from pastebin import PastebinAPI x = PastebinAPI() - details = x.user_details('453a994e0e2f1efae07f8759e59e075b', 'c57a18e6c0ae228cd4bd16fe36da381a') + details = x.user_details( + '453a994e0e2f1efae07f8759e59e075b', + 'c57a18e6c0ae228cd4bd16fe36da381a') print details MonkeyPuzzle @@ -486,7 +488,8 @@ class PastebinAPI(object): DLiSspYT 1332714730 - Pastebin.py - Python 3.2 Pastebin.com API + Pastebin.py - + Python 3.2 Pastebin.com API 25300 0 0 @@ -609,7 +612,6 @@ class PastebinAPI(object): 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. -- cgit v1.2.3 From c110760ac5b710dd7220e639f3c3e25acd43a560 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 18 Sep 2015 12:53:57 -0300 Subject: [bug] show unread emails after sync complete Right after a complete sync we show the unread emails. - Resolves: #7453 --- src/leap/bitmask/gui/mail_status.py | 44 ++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 1a38c8cf..eebae49b 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -53,6 +53,8 @@ class MailStatusWidget(QtGui.QWidget): self._disabled = True self._started = False + self._unread_mails = 0 + self.ui = Ui_MailStatusWidget() self.ui.setupUi(self) @@ -92,6 +94,8 @@ class MailStatusWidget(QtGui.QWidget): callback=self._mail_handle_soledad_events) register(event=catalog.SOLEDAD_INVALID_AUTH_TOKEN, callback=self.set_soledad_invalid_auth_token) + register(event=catalog.SOLEDAD_DONE_DATA_SYNC, + callback=self._mail_handle_soledad_events) register(event=catalog.MAIL_UNREAD_MESSAGES, callback=self._mail_handle_imap_events) @@ -276,6 +280,14 @@ class MailStatusWidget(QtGui.QWidget): else: ext_status = self.tr("Sync: upload complete.") + ready = 2 + elif event == catalog.SOLEDAD_DONE_DATA_SYNC: + if self._unread_mails > 0: + self._show_unread_mails() + return + else: + ext_status = self.tr("Sync: completed.") + ready = 2 else: leap_assert(False, @@ -395,21 +407,33 @@ class MailStatusWidget(QtGui.QWidget): # We could make this configurable to include all unread mail # or all unread mail in subscribed folders. if self._started: - count = content - if count != "0": - status = self.tr("{0} Unread Emails " - "in your Inbox").format(count) - if count == "1": - status = self.tr("1 Unread Email in your Inbox") - - self._set_mail_status(status, ready=2) - else: - self._set_mail_status("", ready=2) + try: + self._unread_mails = int(content) + except: + self._unread_mails = 0 + + self._show_unread_mails() elif event == catalog.IMAP_SERVICE_STARTED: self._imap_started = True if ext_status is not None: self._set_mail_status(ext_status, ready=1) + def _show_unread_mails(self): + """ + Show the user the amount of unread emails. + """ + count = self._unread_mails + + if count > 0: + status = self.tr("{0} Unread Emails " + "in your Inbox").format(count) + if count == 1: + status = self.tr("1 Unread Email in your Inbox") + + self._set_mail_status(status, ready=2) + else: + self._set_mail_status("", ready=2) + def about_to_start(self): """ Display the correct UI for the point where mail components -- cgit v1.2.3 From 978cbcc4c124fe3dcf05283d0790b828e072be25 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 18 Sep 2015 13:27:37 -0400 Subject: [bug] correctly return expected value for methods that check services these methods were returning None, and therefore breaking soledad password change (since it checks whether mail is enabled before changing soledad pass after srp pass change). - Resolves: #7470 --- src/leap/bitmask/gui/account.py | 4 ++-- src/leap/bitmask/gui/passwordwindow.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/leap/bitmask/gui/account.py b/src/leap/bitmask/gui/account.py index c941c3fa..81f96389 100644 --- a/src/leap/bitmask/gui/account.py +++ b/src/leap/bitmask/gui/account.py @@ -43,7 +43,7 @@ class Account(): return self._settings.get_enabled_services(self.domain) def is_email_enabled(self): - MX_SERVICE in self.services() + return MX_SERVICE in self.services() def is_eip_enabled(self): - EIP_SERVICE in self.services() + return EIP_SERVICE in self.services() diff --git a/src/leap/bitmask/gui/passwordwindow.py b/src/leap/bitmask/gui/passwordwindow.py index 94cf25da..219a5634 100644 --- a/src/leap/bitmask/gui/passwordwindow.py +++ b/src/leap/bitmask/gui/passwordwindow.py @@ -203,6 +203,12 @@ class PasswordWindow(QtGui.QDialog, Flashable): new_password = self.ui.new_password_lineedit.text() logger.debug("SRP password changed successfully.") + # FIXME ---- both changes need to be made atomically! + # if there is some problem changing password in soledad (for instance, + # it checks for length), any exception raised will be lost and we will + # have an inconsistent state between soledad and srp passwords. + # We need to implement rollaback. + if self.is_soledad_needed(): self._backend.soledad_change_password(new_password=new_password) else: -- cgit v1.2.3 From 68b9e59d695ae896e405fd5f98eb2953dd5c9a2e Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 18 Sep 2015 16:29:48 -0300 Subject: [bug] track soledad ready state on a shared place Connecting to the `soledad_bootstrap_finished` signal is not enough since the password change window is created after the signal is emitted, that way we were not able to tell when soledad is ready to be used. - Resolves: #7474 --- src/leap/bitmask/gui/app.py | 2 ++ src/leap/bitmask/gui/mainwindow.py | 8 +++----- src/leap/bitmask/gui/passwordwindow.py | 5 ++--- src/leap/bitmask/gui/preferenceswindow.py | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/leap/bitmask/gui/app.py b/src/leap/bitmask/gui/app.py index 02357b2b..97fd0549 100644 --- a/src/leap/bitmask/gui/app.py +++ b/src/leap/bitmask/gui/app.py @@ -43,6 +43,8 @@ class App(QtGui.QWidget): self.signaler = LeapSignaler() self.signaler.start() + self.soledad_started = False + # periodically check if the backend is alive self._backend_checker = QtCore.QTimer(self) self._backend_checker.timeout.connect(self._check_backend_status) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index c5939a6d..387c6283 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -201,8 +201,6 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._login_widget.login_offline_finished.connect( self._maybe_run_soledad_setup_checks) - self._soledad_started = False - # This is created once we have a valid provider config self._logged_in_offline = False @@ -544,7 +542,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): # XXX: handle differently not logged in user? akm = AdvancedKeyManagement(self, mx_provided, logged_user, - self._backend, self._soledad_started) + self._backend, self.app.soledad_started) akm.show() def _show_preferences(self): @@ -1238,7 +1236,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._backend.soledad_cancel_bootstrap() self._backend.soledad_close() - self._soledad_started = False + self.app.soledad_started = True def _on_user_logged_in(self): """ @@ -1414,7 +1412,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): """ logger.debug("Done bootstrapping Soledad") - self._soledad_started = True + self.app.soledad_started = True self.soledad_ready.emit() ################################################################### diff --git a/src/leap/bitmask/gui/passwordwindow.py b/src/leap/bitmask/gui/passwordwindow.py index 219a5634..d9309b2f 100644 --- a/src/leap/bitmask/gui/passwordwindow.py +++ b/src/leap/bitmask/gui/passwordwindow.py @@ -71,9 +71,10 @@ class PasswordWindow(QtGui.QDialog, Flashable): self.ui.cancel_button.setEnabled(True) self.flash_error(self.tr("Please log in to change your password.")) - if self.is_soledad_needed() and not self._soledad_ready: + if self.is_soledad_needed() and not self.app.soledad_started: self._enable_password_widgets(False) self.ui.cancel_button.setEnabled(True) + self.flash_message( self.tr("Please wait for data storage to be ready.")) @@ -146,7 +147,6 @@ class PasswordWindow(QtGui.QDialog, Flashable): sig.soledad_password_change_error.connect( self._soledad_change_password_problem) - self._soledad_ready = False sig.soledad_bootstrap_finished.connect(self._on_soledad_ready) def _change_password(self): @@ -269,4 +269,3 @@ class PasswordWindow(QtGui.QDialog, Flashable): Signaler.soledad_bootstrap_finished """ self._enable_password_widgets(True) - self._soledad_ready = True diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index baa71252..44c4641c 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -70,7 +70,7 @@ class PreferencesWindow(QtGui.QDialog): # only allow a single preferences window at a time. if PreferencesWindow._current_window is not None: - PreferencesWindow._current_window.close_window() + PreferencesWindow._current_window.close() PreferencesWindow._current_window = self def _add_icons(self): -- cgit v1.2.3 From 262a9a750a573ae6003ebf19d5a1867bc19d28c0 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 21 Sep 2015 16:48:13 -0300 Subject: [bug] fix typo on backend name --- src/leap/bitmask/gui/passwordwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/leap/bitmask/gui/passwordwindow.py b/src/leap/bitmask/gui/passwordwindow.py index d9309b2f..dedfcb10 100644 --- a/src/leap/bitmask/gui/passwordwindow.py +++ b/src/leap/bitmask/gui/passwordwindow.py @@ -210,7 +210,7 @@ class PasswordWindow(QtGui.QDialog, Flashable): # We need to implement rollaback. if self.is_soledad_needed(): - self._backend.soledad_change_password(new_password=new_password) + self.app.backend.soledad_change_password(new_password=new_password) else: self._change_password_success() -- cgit v1.2.3 From ce63b5a0277b3ff062d04a17af9e9a927b7d9b87 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 2 Sep 2015 16:04:06 -0400 Subject: [feature] retrieve specific smtp certificate. Closes: #4284 --- src/leap/bitmask/crypto/certs.py | 38 ++++++++++++++-------- src/leap/bitmask/services/mail/smtpbootstrapper.py | 6 ++-- src/leap/bitmask/services/mail/smtpconfig.py | 7 +++- 3 files changed, 34 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/leap/bitmask/crypto/certs.py b/src/leap/bitmask/crypto/certs.py index 4b669376..017af144 100644 --- a/src/leap/bitmask/crypto/certs.py +++ b/src/leap/bitmask/crypto/certs.py @@ -30,7 +30,7 @@ from leap.common import certs as leap_certs logger = get_logger() -def download_client_cert(provider_config, path, session): +def download_client_cert(provider_config, path, session, kind="vpn"): """ Downloads the client certificate for each service. @@ -41,32 +41,45 @@ def download_client_cert(provider_config, path, session): :param session: a fetcher.session instance. For the moment we only support requests.sessions :type session: requests.sessions.Session + :param kind: the kind of certificate being requested. Valid values are + "vpn" or "smtp". + :type kind: string """ - # TODO we should implement the @with_srp_auth decorator - # again. srp_auth = SRPAuth(provider_config) session_id = srp_auth.get_session_id() token = srp_auth.get_token() cookies = None if session_id is not None: cookies = {"_session_id": session_id} - cert_uri = "%s/%s/cert" % ( + + if kind == "vpn": + cert_uri_template = "%s/%s/cert" + method = 'get' + params = {} + elif kind == 'smtp': + cert_uri_template = "%s/%s/smtp_cert" + method = 'post' + params = {'address': srp_auth.get_username()} + else: + raise ValueError("Incorrect value passed to kind parameter") + + cert_uri = cert_uri_template % ( provider_config.get_api_uri(), provider_config.get_api_version()) - logger.debug('getting cert from uri: %s' % cert_uri) + + logger.debug('getting %s cert from uri: %s' % (kind, cert_uri)) headers = {} # API v2 will only support token auth, but in v1 we can send both if token is not None: - headers["Authorization"] = 'Token token="{0}"'.format(token) + headers["Authorization"] = 'Token token={0}'.format(token) - res = session.get(cert_uri, - verify=provider_config - .get_ca_cert_path(), - cookies=cookies, - timeout=REQUEST_TIMEOUT, - headers=headers) + call = getattr(session, method) + res = call(cert_uri, verify=provider_config.get_ca_cert_path(), + cookies=cookies, params=params, + timeout=REQUEST_TIMEOUT, + headers=headers, data=params) res.raise_for_status() client_cert = res.content @@ -74,7 +87,6 @@ def download_client_cert(provider_config, path, session): # XXX raise more specific exception. raise Exception("The downloaded certificate is not a " "valid PEM file") - mkdir_p(os.path.dirname(path)) try: diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py index cd871803..a1b520ef 100644 --- a/src/leap/bitmask/services/mail/smtpbootstrapper.py +++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py @@ -87,7 +87,7 @@ class SMTPBootstrapper(AbstractBootstrapper): logger.debug("Using hostname %s for SMTP" % (hostname,)) client_cert_path = self._smtp_config.get_client_cert_path( - self._provider_config, about_to_download=True) + self._userid, self._provider_config, about_to_download=True) if not is_file(client_cert_path): # For re-download if something is wrong with the cert @@ -101,7 +101,7 @@ class SMTPBootstrapper(AbstractBootstrapper): download_client_cert(self._provider_config, client_cert_path, - self._session) + self._session, kind="smtp") def _start_smtp_service(self): """ @@ -117,7 +117,7 @@ class SMTPBootstrapper(AbstractBootstrapper): host = hosts[hostname][self.IP_KEY].encode("utf-8") port = hosts[hostname][self.PORT_KEY] client_cert_path = self._smtp_config.get_client_cert_path( - self._provider_config, about_to_download=True) + self._userid, self._provider_config, about_to_download=True) from leap.mail.smtp import setup_smtp_gateway diff --git a/src/leap/bitmask/services/mail/smtpconfig.py b/src/leap/bitmask/services/mail/smtpconfig.py index 2d8de411..f78b3449 100644 --- a/src/leap/bitmask/services/mail/smtpconfig.py +++ b/src/leap/bitmask/services/mail/smtpconfig.py @@ -53,19 +53,24 @@ class SMTPConfig(ServiceConfig): return self._safe_get_value("locations") def get_client_cert_path(self, + userid, providerconfig=None, about_to_download=False): """ Returns the path to the certificate used by smtp + :param userid: the user id, in user@provider form """ + leap_assert(userid, "Need an userid") leap_assert(providerconfig, "We need a provider") leap_assert_type(providerconfig, ProviderConfig) + username = userid.split("@")[0] + cert_path = os.path.join(get_path_prefix(), "leap", "providers", providerconfig.get_domain(), - "keys", "client", "smtp.pem") + "keys", "client", "smtp_%s.pem" % username) if not about_to_download: leap_assert(os.path.exists(cert_path), -- cgit v1.2.3 From f292d910ae8bf98448dd02cd26e9e4f2ed8f6339 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 21 Sep 2015 15:51:34 -0400 Subject: [bug] add compatibility mode for platform < 0.8 there was a bug in plaform that made webapp not serve correctly the smtp certificates. with this fallback, we try to support platforms < 0.8, although we should deprecate this behavior in bitmask 0.10 --- src/leap/bitmask/services/mail/smtpbootstrapper.py | 25 +++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py index a1b520ef..a577509e 100644 --- a/src/leap/bitmask/services/mail/smtpbootstrapper.py +++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py @@ -18,6 +18,9 @@ SMTP bootstrapping """ import os +import warnings + +from requests.exceptions import HTTPError from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.certs import download_client_cert @@ -99,9 +102,25 @@ class SMTPBootstrapper(AbstractBootstrapper): check_and_fix_urw_only(client_cert_path) return - download_client_cert(self._provider_config, - client_cert_path, - self._session, kind="smtp") + try: + download_client_cert(self._provider_config, + client_cert_path, + self._session, kind="smtp") + except HTTPError as exc: + if exc.message.startswith('403 Client Error'): + logger.debug( + 'Auth problem downloading smtp certificate... ' + 'It might be a provider problem, will try ' + 'fetching from vpn pool') + warnings.warn( + 'Compatibility hack for platform 0.7 not fully ' + 'supporting smtp certificates. Will be deprecated in ' + 'bitmask 0.10') + download_client_cert(self._provider_config, + client_cert_path, + self._session, kind="vpn") + else: + raise def _start_smtp_service(self): """ -- cgit v1.2.3