From 2aa7cb8710a1e0d3a99698566a11db3d54818a41 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 4 Dec 2014 11:17:36 -0600 Subject: Use the new keymanager async API --- src/leap/bitmask/backend/components.py | 85 +++++---------- .../services/soledad/soledadbootstrapper.py | 117 ++++++++++++--------- 2 files changed, 95 insertions(+), 107 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 4b63af84..2523faea 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -906,52 +906,6 @@ class Keymanager(object): # NOTE: This feature is disabled right now since is dangerous return - new_key = '' - signal = None - try: - with open(filename, 'r') as keys_file: - new_key = keys_file.read() - except IOError as e: - logger.error("IOError importing key. {0!r}".format(e)) - signal = self._signaler.keymanager_import_ioerror - self._signaler.signal(signal) - return - - keymanager = self._keymanager_proxy - try: - # NOTE: parse_openpgp_ascii_key is not in keymanager anymore - # the API for that will need some thinking - public_key, private_key = keymanager.parse_openpgp_ascii_key( - new_key) - except (KeyAddressMismatch, KeyFingerprintMismatch) as e: - logger.error(repr(e)) - signal = self._signaler.keymanager_import_datamismatch - self._signaler.signal(signal) - return - - if public_key is None or private_key is None: - signal = self._signaler.keymanager_import_missingkey - self._signaler.signal(signal) - return - - current_public_key = keymanager.get_key(username, openpgp.OpenPGPKey) - if public_key.address != current_public_key.address: - logger.error("The key does not match the ID") - signal = self._signaler.keymanager_import_addressmismatch - self._signaler.signal(signal) - return - - keymanager.delete_key(self._key) - keymanager.delete_key(self._key_priv) - keymanager.put_key(public_key) - keymanager.put_key(private_key) - keymanager.send_key(openpgp.OpenPGPKey) - - logger.debug('Import ok') - signal = self._signaler.keymanager_import_ok - - self._signaler.signal(signal) - def export_keys(self, username, filename): """ Export the given username's keys to a file. @@ -963,35 +917,50 @@ class Keymanager(object): """ keymanager = self._keymanager_proxy - public_key = keymanager.get_key(username, openpgp.OpenPGPKey) - private_key = keymanager.get_key(username, openpgp.OpenPGPKey, - private=True) - try: + def export(keys): + public_key, private_key = keys + # XXX: This is blocking. We could use writeToFD, but is POSIX only + # https://twistedmatrix.com/documents/current/api/twisted.internet.fdesc.html#writeToFD with open(filename, 'w') as keys_file: keys_file.write(public_key.key_data) keys_file.write(private_key.key_data) logger.debug('Export ok') self._signaler.signal(self._signaler.keymanager_export_ok) - except IOError as e: - logger.error("IOError exporting key. {0!r}".format(e)) + + def log_error(failure): + logger.error( + "Error exporting key. {0!r}".format(failure.value)) self._signaler.signal(self._signaler.keymanager_export_error) + dpub = keymanager.get_key(username, openpgp.OpenPGPKey) + dpriv = keymanager.get_key(username, openpgp.OpenPGPKey, + private=True) + d = defer.gatherResults([dpub, dpriv]) + d.addCallback(export) + d.addErrback(log_error) + def list_keys(self): """ List all the keys stored in the local DB. """ - keys = self._keymanager_proxy.get_all_keys() - self._signaler.signal(self._signaler.keymanager_keys_list, keys) + d = self._keymanager_proxy.get_all_keys() + d.addCallback( + lambda keys: + self._signaler.signal(self._signaler.keymanager_keys_list, keys)) def get_key_details(self, username): """ List all the keys stored in the local DB. """ - public_key = self._keymanager_proxy.get_key(username, - openpgp.OpenPGPKey) - details = (public_key.key_id, public_key.fingerprint) - self._signaler.signal(self._signaler.keymanager_key_details, details) + def signal_details(public_key): + details = (public_key.key_id, public_key.fingerprint) + self._signaler.signal(self._signaler.keymanager_key_details, + details) + + d = self._keymanager_proxy.get_key(username, + openpgp.OpenPGPKey) + d.addCallback(signal_details) class Mail(object): diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 2044a27c..ac7ca836 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -27,7 +27,7 @@ from ssl import SSLError from sqlite3 import ProgrammingError as sqlite_ProgrammingError from u1db import errors as u1db_errors -from twisted.internet import threads +from twisted.internet import threads, defer from zope.proxy import sameProxiedObjects from pysqlcipher.dbapi2 import ProgrammingError as sqlcipher_ProgrammingError @@ -317,19 +317,26 @@ class SoledadBootstrapper(AbstractBootstrapper): if flags.OFFLINE: self._init_keymanager(self._address, token) else: - try: - address = make_address( - self._user, self._provider_config.get_domain()) - self._init_keymanager(address, token) - self._keymanager.get_key( + address = make_address( + self._user, self._provider_config.get_domain()) + + def key_not_found(failure): + if failure.check(KeyNotFound): + logger.debug("Key not found. Generating key for %s" % + (address,)) + self._do_soledad_sync() + else: + return failure + + d = self._init_keymanager(address, token) + d.addCallback( + lambda _: self._keymanager.get_key( address, openpgp.OpenPGPKey, - private=True, fetch_remote=False) - 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,)) - self._do_soledad_sync() + private=True, fetch_remote=False)) + d.addCallbacks( + lambda _: threads.deferToThread(self._do_soledad_sync), + key_not_found) + d.addErrback(self._soledad_sync_errback) def _pick_server(self, uuid): """ @@ -537,6 +544,7 @@ class SoledadBootstrapper(AbstractBootstrapper): :type address: str :param token: the auth token for accessing webapp. :type token: str + :rtype: Deferred """ srp_auth = self.srpauth logger.debug('initializing keymanager...') @@ -576,20 +584,27 @@ class SoledadBootstrapper(AbstractBootstrapper): if flags.OFFLINE is False: # make sure key is in 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) - # but we do not raise + + def send_errback(failure): + if failure.check(KeyNotFound): + logger.debug('No key found for %s, will generate soon.' + % address) + else: + logger.error("Error sending key to server.") + logger.exception(failure.value) + # but we do not raise + + d = self._keymanager.send_key(openpgp.OpenPGPKey) + d.addErrback(send_errback) + return d + else: + return defer.succeed(None) def _gen_key(self): """ Generates the key pair if needed, uploads it to the webapp and nickserver + :rtype: Deferred """ leap_assert(self._provider_config is not None, "We need a provider configuration!") @@ -600,30 +615,33 @@ class SoledadBootstrapper(AbstractBootstrapper): self._user, self._provider_config.get_domain()) logger.debug("Retrieving key for %s" % (address,)) - try: - self._keymanager.get_key( - address, openpgp.OpenPGPKey, private=True, fetch_remote=False) - return - except KeyNotFound: - logger.debug("Key not found. Generating key for %s" % (address,)) - - # generate key - try: - self._keymanager.gen_key(openpgp.OpenPGPKey) - except Exception as exc: - logger.error("Error while generating key!") - logger.exception(exc) - raise - - # send key - try: - self._keymanager.send_key(openpgp.OpenPGPKey) - except Exception as exc: - logger.error("Error while sending key!") - logger.exception(exc) - raise - - logger.debug("Key generated successfully.") + def if_not_found_generate(failure): + if failure.check(KeyNotFound): + logger.debug("Key not found. Generating key for %s" + % (address,)) + d = self._keymanager.gen_key(openpgp.OpenPGPKey) + d.addCallbacks(send_key, log_key_error("generating")) + return d + else: + return failure + + def send_key(_): + d = self._keymanager.send_key(openpgp.OpenPGPKey) + d.addCallbacks( + lambda _: logger.debug("Key generated successfully."), + log_key_error("sending")) + + def log_key_error(step): + def log_err(failure): + logger.error("Error while %s key!", (step,)) + logger.exception(failure.value) + return failure + return log_err + + d = self._keymanager.get_key( + address, openpgp.OpenPGPKey, private=True, fetch_remote=False) + d.addErrback(if_not_found_generate) + return d def run_soledad_setup_checks(self, provider_config, user, password, download_if_needed=False): @@ -664,9 +682,10 @@ class SoledadBootstrapper(AbstractBootstrapper): self.load_and_sync_soledad(uuid) if not flags.OFFLINE: - self._gen_key() - - self._signaler.signal(signal_finished) + d = self._gen_key() + d.addCallback(lambda _: self._signaler.signal(signal_finished)) + else: + self._signaler.signal(signal_finished) except Exception as e: # TODO: we should handle more specific exceptions in here self._soledad = None -- cgit v1.2.3 From 99a955c64266dc9dc596170beb024f84cd322254 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 15 Jan 2015 09:40:55 -0600 Subject: Use the incoming mail IService From kali: add some notes about the improper handling of the mailbox required to initialize the account, and draft some notes about how to improve this in next iterations. --- src/leap/bitmask/services/mail/conductor.py | 2 +- src/leap/bitmask/services/mail/imap.py | 40 ++++++++++++++++++--- src/leap/bitmask/services/mail/imapcontroller.py | 44 ++++++++++++++++-------- 3 files changed, 67 insertions(+), 19 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 0fb9f4fa..42bdd032 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -262,7 +262,7 @@ class MailConductor(IMAPControl, SMTPControl): if self._firewall is not None: self._firewall.start() if not offline: - logger.debug("not starting smtp in offline mode") + logger.debug("Starting smtp service...") self.start_smtp_service(download_if_needed=download_if_needed) self.start_imap_service() diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py index 5db18cb9..d044a600 100644 --- a/src/leap/bitmask/services/mail/imap.py +++ b/src/leap/bitmask/services/mail/imap.py @@ -21,7 +21,9 @@ import logging import os import sys +from leap.mail.constants import INBOX_NAME from leap.mail.imap.service import imap +from leap.mail.incoming.service import IncomingMail, INCOMING_CHECK_PERIOD from twisted.python import log logger = logging.getLogger(__name__) @@ -49,6 +51,9 @@ def get_mail_check_period(): logger.warning("Unhandled error while getting %s: %r" % ( INCOMING_CHECK_PERIOD_ENV, exc)) + + if period is None: + period = INCOMING_CHECK_PERIOD return period @@ -61,12 +66,39 @@ def start_imap_service(*args, **kwargs): 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 - if flags.MAIL_LOGFILE: log.startLogging(open(flags.MAIL_LOGFILE, 'w')) log.startLogging(sys.stdout) return imap.run_service(*args, **kwargs) + + +def start_incoming_mail_service(keymanager, soledad, imap_factory, userid): + """ + Initalizes and starts the incomming mail service. + + :returns: a Deferred that will be fired with the IncomingMail instance + """ + def setUpIncomingMail(inbox): + incoming_mail = IncomingMail( + keymanager, + soledad, + inbox, + userid, + check_period=get_mail_check_period()) + return incoming_mail + + # XXX: do I really need to know here how to get a mailbox?? + # XXX: ideally, the parent service in mail would take care of initializing + # the account, and passing the mailbox to the incoming service. + # In an even better world, we just would subscribe to a channel that would + # pass us the serialized object to be inserted. + # XXX: I think we might be at risk here because the account top object + # currently just returns as many mailbox objects as it's asked for, so + # we should be careful about concurrent operations (ie, maybe just use + # this one to do inserts, or let account have an in-memory map of + # mailboxes, and just return references to them). + acc = imap_factory.theAccount + d = acc.callWhenReady(lambda _: acc.getMailbox(INBOX_NAME)) + d.addCallback(setUpIncomingMail) + return d diff --git a/src/leap/bitmask/services/mail/imapcontroller.py b/src/leap/bitmask/services/mail/imapcontroller.py index d0bf4c34..bb9eb9dc 100644 --- a/src/leap/bitmask/services/mail/imapcontroller.py +++ b/src/leap/bitmask/services/mail/imapcontroller.py @@ -43,9 +43,12 @@ class IMAPController(object): self._soledad = soledad self._keymanager = keymanager - self.imap_service = None + # XXX: this should live in its own controller + # or, better, just be managed by a composite Mail Service in + # leap.mail. self.imap_port = None self.imap_factory = None + self.incoming_mail_service = None def start_imap_service(self, userid, offline=False): """ @@ -58,16 +61,28 @@ class IMAPController(object): """ logger.debug('Starting imap service') - self.imap_service, self.imap_port, \ - self.imap_factory = imap.start_imap_service( - self._soledad, - self._keymanager, - userid=userid, - offline=offline) + self.imap_port, self.imap_factory = imap.start_imap_service( + 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): + self.incoming_mail_service = incoming_mail + return incoming_mail if offline is False: - logger.debug("Starting loop") - self.imap_service.start_loop() + d = imap.start_incoming_mail_service( + self._keymanager, + self._soledad, + self.imap_factory, + userid) + d.addCallback(start_incoming_service) + d.addCallback(assign_incoming_service) + d.addErrback(lambda f: logger.error(f.printTraceback())) def stop_imap_service(self, cv): """ @@ -77,11 +92,12 @@ class IMAPController(object): indeed stops. :type cv: threading.Condition """ - if self.imap_service is not None: + if self.incoming_mail_service is not None: # Stop the loop call in the fetcher - self.imap_service.stop() - self.imap_service = None + self.incoming_mail_service.stopService() + self.incoming_mail_service = None + if self.imap_port is not None: # Stop listening on the IMAP port self.imap_port.stopListening() @@ -98,6 +114,6 @@ class IMAPController(object): """ Fetch incoming mail. """ - if self.imap_service: + if self.incoming_mail_service is not None: logger.debug('Client connected, fetching mail...') - self.imap_service.fetch() + self.incoming_mail_service.fetch() -- cgit v1.2.3 From 39c0419cfa7e74e365c02091fc3cfe455da15404 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 16 Jan 2015 11:53:29 -0400 Subject: fix mail imports for new mail api (0.4.0) --- src/leap/bitmask/backend/components.py | 3 +-- src/leap/bitmask/services/mail/plumber.py | 12 ++++-------- src/leap/bitmask/services/soledad/soledadbootstrapper.py | 3 ++- 3 files changed, 7 insertions(+), 11 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 2523faea..8ed43364 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -59,9 +59,8 @@ from leap.bitmask.util.privilege_policies import LinuxPolicyChecker from leap.common import certs as leap_certs from leap.keymanager import openpgp -from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch -from leap.soledad.client import NoStorageSecret, PassphraseTooShort +from leap.soledad.client.secrets import NoStorageSecret, PassphraseTooShort logger = logging.getLogger(__name__) diff --git a/src/leap/bitmask/services/mail/plumber.py b/src/leap/bitmask/services/mail/plumber.py index 1af65c5d..58625db5 100644 --- a/src/leap/bitmask/services/mail/plumber.py +++ b/src/leap/bitmask/services/mail/plumber.py @@ -17,6 +17,8 @@ """ Utils for manipulating local mailboxes. """ +# TODO --- this module has not yet catched up with 0.9.0 + import getpass import logging import os @@ -32,9 +34,7 @@ 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.mail.imap.memorystore import MemoryStore -from leap.mail.imap.soledadstore import SoledadStore +from leap.mail.imap.account import IMAPAccount from leap.soledad.client import Soledad logger = logging.getLogger(__name__) @@ -140,11 +140,7 @@ class MBOXPlumber(object): self.sol = initialize_soledad( self.uuid, self.userid, self.passwd, secrets, localdb, "/tmp", "/tmp") - memstore = MemoryStore( - permanent_store=SoledadStore(self.sol), - write_period=5) - self.acct = SoledadBackedAccount(self.userid, self.sol, - memstore=memstore) + self.acct = IMAPAccount(self.userid, self.sol) return True # diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index ac7ca836..045b2e19 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -45,7 +45,8 @@ 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 +from leap.soledad.client import Soledad +from leap.soledad.client.secrets import BootstrapSequenceError logger = logging.getLogger(__name__) -- cgit v1.2.3 From 46fabd42ad4cac7ce1b7f1818064f1c2e5c002c1 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 16 Jan 2015 20:01:40 -0400 Subject: pass userid correctly, and cast it to string --- src/leap/bitmask/gui/mainwindow.py | 10 +++++++--- src/leap/bitmask/services/mail/smtpbootstrapper.py | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index f5983abb..bf61a6ba 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1191,9 +1191,13 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._disconnect_login_wait() user = self._login_widget.get_logged_user() - domain = self._providers.get_selected_provider() - full_user_id = make_address(user, domain) - self._mail_conductor.userid = full_user_id + # XXX the widget now gives us the full user id. + # this is confusing. + #domain = self._providers.get_selected_provider() + #full_user_id = make_address(user, domain) + # XXX the casting to str (needed by smtp gateway) should be done + # in a better place. + self._mail_conductor.userid = str(user) self._start_eip_bootstrap() self.ui.action_create_new_account.setEnabled(True) diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py index 9dd61488..6f45469b 100644 --- a/src/leap/bitmask/services/mail/smtpbootstrapper.py +++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py @@ -120,6 +120,7 @@ class SMTPBootstrapper(AbstractBootstrapper): self._provider_config, about_to_download=True) from leap.mail.smtp import setup_smtp_gateway + self._smtp_service, self._smtp_port = setup_smtp_gateway( port=2013, userid=self._userid, @@ -152,7 +153,7 @@ class SMTPBootstrapper(AbstractBootstrapper): self._provider_config = ProviderConfig.get_provider_config(domain) self._keymanager = keymanager self._smtp_config = SMTPConfig() - self._userid = userid + self._userid = str(userid) self._download_if_needed = download_if_needed try: -- cgit v1.2.3 From 782fb8d66aabb29d68712e2fc220d967ef8dfcf5 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 16 Jan 2015 20:03:11 -0400 Subject: remove use of threading.Condition we should deal with this with pure deferreds --- src/leap/bitmask/backend/components.py | 13 +++++++------ src/leap/bitmask/services/mail/imapcontroller.py | 17 ++++++----------- 2 files changed, 13 insertions(+), 17 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 8ed43364..2ae796f6 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -17,13 +17,16 @@ """ Backend components """ + +# TODO [ ] Get rid of all this deferToThread mess, or at least contain +# all of it into its own threadpool. + import logging import os import socket import time from functools import partial -from threading import Condition from twisted.internet import threads, defer from twisted.python import log @@ -1038,12 +1041,10 @@ class Mail(object): """ Stop imap and wait until the service is stopped to signal that is done. """ - cv = Condition() - cv.acquire() - threads.deferToThread(self._imap_controller.stop_imap_service, cv) + # FIXME just get a fucking deferred and signal as a callback, with + # timeout and cancellability + threads.deferToThread(self._imap_controller.stop_imap_service) logger.debug('Waiting for imap service to stop.') - cv.wait(self.SERVICE_STOP_TIMEOUT) - logger.debug('IMAP stopped') self._signaler.signal(self._signaler.imap_stopped) def stop_imap_service(self): diff --git a/src/leap/bitmask/services/mail/imapcontroller.py b/src/leap/bitmask/services/mail/imapcontroller.py index bb9eb9dc..1b39ef91 100644 --- a/src/leap/bitmask/services/mail/imapcontroller.py +++ b/src/leap/bitmask/services/mail/imapcontroller.py @@ -84,16 +84,16 @@ class IMAPController(object): d.addCallback(assign_incoming_service) d.addErrback(lambda f: logger.error(f.printTraceback())) - def stop_imap_service(self, cv): + def stop_imap_service(self): """ Stop IMAP service (fetcher, factory and port). - - :param cv: A condition variable to which we can signal when imap - indeed stops. - :type cv: threading.Condition """ if self.incoming_mail_service is not None: # Stop the loop call in the fetcher + + # XXX BUG -- the deletion of the reference should be made + # after stopService() triggers its deferred (ie, cleanup has been + # made) self.incoming_mail_service.stopService() self.incoming_mail_service = None @@ -103,12 +103,7 @@ class IMAPController(object): # Stop the protocol self.imap_factory.theAccount.closed = True - self.imap_factory.doStop(cv) - else: - # Release the condition variable so the caller doesn't have to wait - cv.acquire() - cv.notify() - cv.release() + self.imap_factory.doStop() def fetch_incoming_mail(self): """ -- cgit v1.2.3 From 965d8a6bd4646fe6cc285e18355bbe2ce514b73b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 21 Jan 2015 11:03:28 -0400 Subject: do not terminate the session on the backend, moved to mail factory.do_Stop will handle this now. --- src/leap/bitmask/services/mail/imapcontroller.py | 1 - 1 file changed, 1 deletion(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/services/mail/imapcontroller.py b/src/leap/bitmask/services/mail/imapcontroller.py index 1b39ef91..d374ac29 100644 --- a/src/leap/bitmask/services/mail/imapcontroller.py +++ b/src/leap/bitmask/services/mail/imapcontroller.py @@ -102,7 +102,6 @@ class IMAPController(object): 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 c8b5865364ffabc5400642684050286c5c836352 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 9 Feb 2015 11:51:36 -0400 Subject: Re-add mail instructions for 0.9.0 release (beta mail) --- src/leap/bitmask/gui/mainwindow.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index bf61a6ba..808b0410 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -210,10 +210,8 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self.ui.action_wizard.triggered.connect(self._show_wizard) self.ui.action_show_logs.triggered.connect(self._show_logger_window) - # XXX hide the help menu since it only shows email information and - # right now we don't have stable mail and just confuses users. - self.ui.action_help.setVisible(False) - # self.ui.action_help.triggered.connect(self._help) + self.ui.action_help.setVisible(True) + self.ui.action_help.triggered.connect(self._help) self.ui.action_create_new_account.triggered.connect( self._on_provider_changed) -- cgit v1.2.3 From e52f9a239a146c06a0683e47eaf9d53f4deb332b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 11 Feb 2015 23:46:42 -0400 Subject: enable --offline mode for email again --- src/leap/bitmask/app.py | 17 +-- src/leap/bitmask/backend/backend_proxy.py | 3 + src/leap/bitmask/backend/components.py | 5 +- src/leap/bitmask/backend/settings.py | 27 +++-- src/leap/bitmask/config/leapsettings.py | 32 ------ src/leap/bitmask/gui/mainwindow.py | 10 +- .../services/soledad/soledadbootstrapper.py | 116 +++++++++++---------- src/leap/bitmask/util/leap_argparse.py | 41 ++++---- 8 files changed, 117 insertions(+), 134 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 9056d2a6..72c03cb9 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -127,9 +127,7 @@ def start_app(): } flags.STANDALONE = opts.standalone - # XXX Disabled right now since it's not tested after login refactor - # flags.OFFLINE = opts.offline - flags.OFFLINE = False + flags.OFFLINE = opts.offline flags.MAIL_LOGFILE = opts.mail_log_file flags.APP_VERSION_CHECK = opts.app_version_check flags.API_VERSION_CHECK = opts.api_version_check @@ -139,16 +137,19 @@ def start_app(): flags.CA_CERT_FILE = opts.ca_cert_file 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 + + # XXX mail repair commands disabled for now + # 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 = create_logger(opts.debug, opts.log_file, 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) + # XXX mail repair commands disabled for now + # do_mail_plumbing(opts) try: event_server.ensure_server(event_server.SERVER_PORT) diff --git a/src/leap/bitmask/backend/backend_proxy.py b/src/leap/bitmask/backend/backend_proxy.py index 3e79289f..e9ad9b4d 100644 --- a/src/leap/bitmask/backend/backend_proxy.py +++ b/src/leap/bitmask/backend/backend_proxy.py @@ -28,6 +28,7 @@ import time import zmq 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 from leap.bitmask.backend.utils import get_backend_certificates @@ -54,6 +55,8 @@ class BackendProxy(object): self._socket = None + self.settings = Settings() + # initialize ZMQ stuff: context = zmq.Context() logger.debug("Connecting to server...") diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 2ae796f6..a843147e 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -779,6 +779,8 @@ class Soledad(object): """ provider_config = ProviderConfig.get_provider_config(domain) if provider_config is not None: + # XXX FIXME --- remove defer-to-thread or at least put it in a + # separate threadpool. self._soledad_defer = threads.deferToThread( self._soledad_bootstrapper.run_soledad_setup_checks, provider_config, username, password, @@ -816,8 +818,9 @@ class Soledad(object): Signaler.soledad_offline_finished Signaler.soledad_offline_failed """ - self._soledad_bootstrapper.load_offline_soledad( + d = self._soledad_bootstrapper.load_offline_soledad( username, password, uuid) + d.addCallback(self._set_proxies_cb) def cancel_bootstrap(self): """ diff --git a/src/leap/bitmask/backend/settings.py b/src/leap/bitmask/backend/settings.py index 5cb4c616..ed70ca6b 100644 --- a/src/leap/bitmask/backend/settings.py +++ b/src/leap/bitmask/backend/settings.py @@ -122,37 +122,36 @@ class Settings(object): self._settings.set(provider, self.GATEWAY_KEY, gateway) self._save() - def get_uuid(self, username): + def get_uuid(self, full_user_id): """ Gets the uuid for a given username. - :param username: the full user identifier in the form user@provider - :type username: basestring + :param full_user_id: the full user identifier in the form user@provider + :type full_user_id: basestring """ - leap_assert("@" in username, + leap_assert("@" in full_user_id, "Expected username in the form user@provider") - user, provider = username.split('@') + username, provider = full_user_id.split('@') + return self._get_value(provider, full_user_id, "") - return self._get_value(provider, username, "") - - def set_uuid(self, username, value): + def set_uuid(self, full_user_id, value): """ Sets the uuid for a given username. - :param username: the full user identifier in the form user@provider - :type username: str or unicode + :param full_user_id: the full user identifier in the form user@provider + :type full_user_id: 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, + leap_assert("@" in full_user_id, "Expected username in the form user@provider") - user, provider = username.split('@') + user, provider = full_user_id.split('@') if value is None: - self._settings.remove_option(provider, username) + self._settings.remove_option(provider, full_user_id) else: leap_assert(len(value) > 0, "We cannot save an empty uuid") self._add_section(provider) - self._settings.set(provider, username, value) + self._settings.set(provider, full_user_id, value) self._save() diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index 13a1e99e..fd3e1592 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -353,35 +353,3 @@ 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: 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('@') - 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) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 808b0410..35253ebe 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -126,6 +126,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._backend = self.app.backend self._leap_signaler = self.app.signaler self._settings = self.app.settings + self._backend_settings = self._backend.settings # Login Widget self._login_widget = LoginWidget(self._backend, @@ -1191,8 +1192,9 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): user = self._login_widget.get_logged_user() # XXX the widget now gives us the full user id. # this is confusing. - #domain = self._providers.get_selected_provider() - #full_user_id = make_address(user, domain) + # domain = self._providers.get_selected_provider() + # full_user_id = make_address(user, domain) + # XXX the casting to str (needed by smtp gateway) should be done # in a better place. self._mail_conductor.userid = str(user) @@ -1313,10 +1315,10 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): if flags.OFFLINE: full_user_id = make_address(username, provider_domain) - uuid = self._settings.get_uuid(full_user_id) + uuid = self._backend_settings.get_uuid(full_user_id) self._mail_conductor.userid = full_user_id - if uuid is None: + if not uuid: # 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.") diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 045b2e19..e8946fa4 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -28,6 +28,7 @@ from sqlite3 import ProgrammingError as sqlite_ProgrammingError from u1db import errors as u1db_errors from twisted.internet import threads, defer +import zope.proxy from zope.proxy import sameProxiedObjects from pysqlcipher.dbapi2 import ProgrammingError as sqlcipher_ProgrammingError @@ -59,31 +60,6 @@ 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. @@ -146,7 +122,6 @@ class SoledadBootstrapper(AbstractBootstrapper): self._provider_config = None self._soledad_config = None - self._keymanager = None self._download_if_needed = False self._user = "" @@ -155,7 +130,9 @@ class SoledadBootstrapper(AbstractBootstrapper): self._uuid = "" self._srpauth = None - self._soledad = None + + self._soledad = zope.proxy.ProxyBase(None) + self._keymanager = zope.proxy.ProxyBase(None) @property def keymanager(self): @@ -190,9 +167,13 @@ class SoledadBootstrapper(AbstractBootstrapper): self._password = password self._uuid = uuid try: - self.load_and_sync_soledad(uuid, offline=True) - self._signaler.signal(self._signaler.soledad_offline_finished) + d = self.load_and_sync_soledad(uuid, offline=True) + d.addCallback( + lambda _: self._signaler.signal( + self._signaler.soledad_offline_finished)) + return d except Exception as e: + # XXX convert exception into failure.trap # TODO: we should handle more specific exceptions in here logger.exception(e) self._signaler.signal(self._signaler.soledad_offline_failed) @@ -248,7 +229,7 @@ class SoledadBootstrapper(AbstractBootstrapper): # warned the user in _do_soledad_sync() def _do_soledad_init(self, uuid, secrets_path, local_db_path, - server_url, cert_file, token): + server_url, cert_file, token, syncable): """ Initialize soledad, retry if necessary and raise an exception if we can't succeed. @@ -274,7 +255,7 @@ class SoledadBootstrapper(AbstractBootstrapper): logger.debug("Trying to init soledad....") self._try_soledad_init( uuid, secrets_path, local_db_path, - server_url, cert_file, token) + server_url, cert_file, token, syncable) logger.debug("Soledad has been initialized.") return except Exception as exc: @@ -296,6 +277,8 @@ class SoledadBootstrapper(AbstractBootstrapper): :type uuid: unicode, or None. :param offline: whether to instantiate soledad for offline use. :type offline: bool + + :rtype: defer.Deferred """ local_param = self._get_soledad_local_params(uuid, offline) remote_param = self._get_soledad_server_params(uuid, offline) @@ -303,21 +286,26 @@ class SoledadBootstrapper(AbstractBootstrapper): secrets_path, local_db_path, token = local_param server_url, cert_file = remote_param - try: - self._do_soledad_init(uuid, secrets_path, local_db_path, - server_url, cert_file, token) - except SoledadInitError: - # re-raise the exceptions from try_init, - # we're currently handling the retries from the - # soledad-launcher in the gui. - raise - - leap_assert(not sameProxiedObjects(self._soledad, None), - "Null soledad, error while initializing") + if offline: + return self._load_soledad_nosync( + uuid, secrets_path, local_db_path, cert_file, token) - if flags.OFFLINE: - self._init_keymanager(self._address, token) else: + # We assume online operation from here on. + # XXX split in subfunction + syncable = True + try: + self._do_soledad_init(uuid, secrets_path, local_db_path, + server_url, cert_file, token, syncable) + except SoledadInitError: + # re-raise the exceptions from try_init, + # we're currently handling the retries from the + # soledad-launcher in the gui. + raise + + leap_assert(not sameProxiedObjects(self._soledad, None), + "Null soledad, error while initializing") + address = make_address( self._user, self._provider_config.get_domain()) @@ -335,9 +323,21 @@ class SoledadBootstrapper(AbstractBootstrapper): address, openpgp.OpenPGPKey, private=True, fetch_remote=False)) d.addCallbacks( + # XXX WTF --- FIXME remove this call to thread, soledad already + # does the sync in its own threadpool. -- kali lambda _: threads.deferToThread(self._do_soledad_sync), key_not_found) d.addErrback(self._soledad_sync_errback) + return d + + def _load_soledad_nosync(self, uuid, secrets_path, local_db_path, + cert_file, token): + + syncable = False + self._do_soledad_init(uuid, secrets_path, local_db_path, + "", cert_file, token, syncable) + d = self._init_keymanager(self._address, token) + return d def _pick_server(self, uuid): """ @@ -410,7 +410,7 @@ class SoledadBootstrapper(AbstractBootstrapper): raise SoledadSyncError() def _try_soledad_init(self, uuid, secrets_path, local_db_path, - server_url, cert_file, auth_token): + server_url, cert_file, auth_token, syncable): """ Try to initialize soledad. @@ -433,9 +433,6 @@ class SoledadBootstrapper(AbstractBootstrapper): # (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, @@ -445,7 +442,8 @@ class SoledadBootstrapper(AbstractBootstrapper): server_url=server_url, cert_file=cert_file.encode(encoding), auth_token=auth_token, - defer_encryption=True) + defer_encryption=True, + syncable=syncable) # XXX All these errors should be handled by soledad itself, # and return a subclass of SoledadInitializationFailed @@ -668,7 +666,9 @@ class SoledadBootstrapper(AbstractBootstrapper): self._user = user self._password = password - if flags.OFFLINE: + is_offline = flags.OFFLINE + + if is_offline: signal_finished = self._signaler.soledad_offline_finished signal_failed = self._signaler.soledad_offline_failed else: @@ -676,17 +676,23 @@ class SoledadBootstrapper(AbstractBootstrapper): signal_failed = self._signaler.soledad_bootstrap_failed try: - self._download_config() + if not is_offline: + # XXX FIXME make this async too! (use txrequests) + # Also, why the fuck would we want to download it *every time*? + # We should be fine by using last-time config, or at least + # trying it. + self._download_config() - # soledad config is ok, let's proceed to load and sync soledad - uuid = self.srpauth.get_uuid() - self.load_and_sync_soledad(uuid) + # soledad config is ok, let's proceed to load and sync soledad + uuid = self.srpauth.get_uuid() + self.load_and_sync_soledad(uuid) - if not flags.OFFLINE: d = self._gen_key() d.addCallback(lambda _: self._signaler.signal(signal_finished)) + return d else: self._signaler.signal(signal_finished) + return defer.succeed(True) except Exception as e: # TODO: we should handle more specific exceptions in here self._soledad = None diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index 346caed5..12fd9736 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -74,26 +74,27 @@ def build_parser(): help='Verbosity level for openvpn logs [1-6]') # mail stuff - # XXX Disabled right now since it's not tested after login refactor - # 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('--acct', metavar="user@provider", - nargs='?', - 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. Use with --acct') - parser.add_argument('--import-maildir', metavar="/path/to/Maildir", - nargs='?', - 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') + parser.add_argument('-o', '--offline', action="store_true", + help='Starts Bitmask in offline mode: will not ' + 'try to sync with remote replicas for email.') + + # XXX not yet updated to new mail api for mail 0.4.0 + + # parser.add_argument('--acct', metavar="user@provider", + #nargs='?', + #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. Use with --acct') + # parser.add_argument('--import-maildir', metavar="/path/to/Maildir", + #nargs='?', + #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 82652e174b4f38a29ce488421eb6c0067d0c657f Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sat, 14 Feb 2015 17:52:27 -0600 Subject: Do the soledad sync the twisted way This closes: #6658 and #6691 --- src/leap/bitmask/backend/components.py | 6 +- .../services/soledad/soledadbootstrapper.py | 376 ++++++++++----------- 2 files changed, 174 insertions(+), 208 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index a843147e..1efcda6c 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -779,10 +779,8 @@ class Soledad(object): """ provider_config = ProviderConfig.get_provider_config(domain) if provider_config is not None: - # XXX FIXME --- remove defer-to-thread or at least put it in a - # separate threadpool. - self._soledad_defer = threads.deferToThread( - self._soledad_bootstrapper.run_soledad_setup_checks, + sb = self._soledad_bootstrapper + self._soledad_defer = sb.run_soledad_setup_checks( provider_config, username, password, download_if_needed=True) self._soledad_defer.addCallback(self._set_proxies_cb) diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index e8946fa4..f079b0cd 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -21,14 +21,12 @@ import logging import os import socket import sys -import time from ssl import SSLError from sqlite3 import ProgrammingError as sqlite_ProgrammingError from u1db import errors as u1db_errors -from twisted.internet import threads, defer -import zope.proxy +from twisted.internet import defer, reactor from zope.proxy import sameProxiedObjects from pysqlcipher.dbapi2 import ProgrammingError as sqlcipher_ProgrammingError @@ -60,9 +58,6 @@ 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. """ -# TODO these exceptions could be moved to soledad itself -# after settling this down. - class SoledadSyncError(Exception): message = "Error while syncing Soledad" @@ -109,10 +104,6 @@ class SoledadBootstrapper(AbstractBootstrapper): PUBKEY_KEY = "user[public_key]" MAX_INIT_RETRIES = 10 - MAX_SYNC_RETRIES = 10 - WAIT_MAX_SECONDS = 600 - # WAIT_STEP_SECONDS = 1 - WAIT_STEP_SECONDS = 5 def __init__(self, signaler=None): AbstractBootstrapper.__init__(self, signaler) @@ -125,30 +116,24 @@ class SoledadBootstrapper(AbstractBootstrapper): self._download_if_needed = False self._user = "" - self._password = "" + self._password = u"" self._address = "" self._uuid = "" self._srpauth = None - self._soledad = zope.proxy.ProxyBase(None) - self._keymanager = zope.proxy.ProxyBase(None) - - @property - def keymanager(self): - return self._keymanager - - @property - def soledad(self): - return self._soledad + self._soledad = None + self._keymanager = None @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) + if self._srpauth is None: + leap_assert(self._provider_config is not None, + "We need a provider config") + self._srpauth = SRPAuth(self._provider_config) + return self._srpauth # initialization @@ -166,18 +151,19 @@ class SoledadBootstrapper(AbstractBootstrapper): self._address = username self._password = password self._uuid = uuid - try: - d = self.load_and_sync_soledad(uuid, offline=True) - d.addCallback( - lambda _: self._signaler.signal( - self._signaler.soledad_offline_finished)) - return d - except Exception as e: - # XXX convert exception into failure.trap + + def error(failure): # TODO: we should handle more specific exceptions in here - logger.exception(e) + logger.exception(failure.value) self._signaler.signal(self._signaler.soledad_offline_failed) + d = self.load_and_sync_soledad(uuid, offline=True) + d.addCallback( + lambda _: self._signaler.signal( + self._signaler.soledad_offline_finished)) + d.addErrback(error) + return d + def _get_soledad_local_params(self, uuid, offline=False): """ Return the locals parameters needed for the soledad initialization. @@ -211,23 +197,17 @@ class SoledadBootstrapper(AbstractBootstrapper): :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: + if uuid is None: + uuid = self.srpauth.get_uuid() server_url = self._pick_server(uuid) cert_file = self._provider_config.get_ca_cert_path() 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 _do_soledad_init(self, uuid, secrets_path, local_db_path, server_url, cert_file, token, syncable): """ @@ -268,16 +248,18 @@ class SoledadBootstrapper(AbstractBootstrapper): logger.exception(exc) raise SoledadInitError() - def load_and_sync_soledad(self, uuid=None, offline=False): + def load_and_sync_soledad(self, uuid=u"", 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. + :type uuid: unicode. :param offline: whether to instantiate soledad for offline use. :type offline: bool + :return: A Deferred which fires when soledad is sync, or which fails + with SoledadInitError or SoledadSyncError :rtype: defer.Deferred """ local_param = self._get_soledad_local_params(uuid, offline) @@ -291,54 +273,46 @@ class SoledadBootstrapper(AbstractBootstrapper): uuid, secrets_path, local_db_path, cert_file, token) else: - # We assume online operation from here on. - # XXX split in subfunction - syncable = True - try: - self._do_soledad_init(uuid, secrets_path, local_db_path, - server_url, cert_file, token, syncable) - except SoledadInitError: - # re-raise the exceptions from try_init, - # we're currently handling the retries from the - # soledad-launcher in the gui. - raise + return self._load_soledad_online(uuid, secrets_path, local_db_path, + server_url, cert_file, token) - leap_assert(not sameProxiedObjects(self._soledad, None), - "Null soledad, error while initializing") + def _load_soledad_online(self, uuid, secrets_path, local_db_path, + server_url, cert_file, token): + syncable = True + try: + self._do_soledad_init(uuid, secrets_path, local_db_path, + server_url, cert_file, token, syncable) + except SoledadInitError as e: + # re-raise the exceptions from try_init, + # we're currently handling the retries from the + # soledad-launcher in the gui. + return defer.fail(e) - address = make_address( - self._user, self._provider_config.get_domain()) + leap_assert(not sameProxiedObjects(self._soledad, None), + "Null soledad, error while initializing") - def key_not_found(failure): - if failure.check(KeyNotFound): - logger.debug("Key not found. Generating key for %s" % - (address,)) - self._do_soledad_sync() - else: - return failure + address = make_address( + self._user, self._provider_config.get_domain()) + syncer = Syncer(self._soledad, self._signaler) - d = self._init_keymanager(address, token) - d.addCallback( - lambda _: self._keymanager.get_key( - address, openpgp.OpenPGPKey, - private=True, fetch_remote=False)) - d.addCallbacks( - # XXX WTF --- FIXME remove this call to thread, soledad already - # does the sync in its own threadpool. -- kali - lambda _: threads.deferToThread(self._do_soledad_sync), - key_not_found) - d.addErrback(self._soledad_sync_errback) - return d + d = self._init_keymanager(address, token) + d.addCallback(lambda _: syncer.sync()) + d.addErrback(self._soledad_sync_errback) + return d def _load_soledad_nosync(self, uuid, secrets_path, local_db_path, cert_file, token): - syncable = False self._do_soledad_init(uuid, secrets_path, local_db_path, "", cert_file, token, syncable) d = self._init_keymanager(self._address, token) return d + 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 + def _pick_server(self, uuid): """ Choose a soledad server to sync against. @@ -363,52 +337,6 @@ class SoledadBootstrapper(AbstractBootstrapper): logger.debug("Using soledad server url: %s" % (server_url,)) return server_url - def _do_soledad_sync(self): - """ - Do several retries to get an initial soledad sync. - """ - # and now, let's sync - sync_tries = self.MAX_SYNC_RETRIES - step = self.WAIT_STEP_SECONDS - max_wait = self.WAIT_MAX_SECONDS - while sync_tries > 0: - wait = 0 - try: - logger.debug("Trying to sync soledad....") - self._try_soledad_sync() - while self.soledad.syncing: - time.sleep(step) - wait += step - if wait >= max_wait: - raise SoledadSyncError("timeout!") - logger.debug("Soledad has been synced!") - # so long, and thanks for all the fish - return - except SoledadSyncError: - # maybe it's my connection, but I'm getting - # ssl handshake timeouts and read errors quite often. - # A particularly big sync is a disaster. - # This deserves further investigation, maybe the - # retry strategy can be pushed to u1db, or at least - # it's something worthy to talk about with the - # ubuntu folks. - sync_tries += 1 - msg = "Sync failed, retrying... (retry {0} of {1})".format( - sync_tries, self.MAX_SYNC_RETRIES) - logger.warning(msg) - continue - except InvalidAuthTokenError: - self._signaler.signal( - self._signaler.soledad_invalid_auth_token) - raise - except Exception as e: - # XXX release syncing lock - logger.exception("Unhandled error while syncing " - "soledad: %r" % (e,)) - break - - raise SoledadSyncError() - def _try_soledad_init(self, uuid, secrets_path, local_db_path, server_url, cert_file, auth_token, syncable): """ @@ -462,34 +390,6 @@ class SoledadBootstrapper(AbstractBootstrapper): "Soledad: %r" % (exc,)) raise - def _try_soledad_sync(self): - """ - Try to sync soledad. - Raises SoledadSyncError if not successful. - """ - try: - logger.debug("BOOTSTRAPPER: trying to sync Soledad....") - # pass defer_decryption=False to get inline decryption - # for debugging. - self._soledad.sync(defer_decryption=True) - 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 (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,)) - raise SoledadSyncError("Failed to sync soledad") - def _download_config(self): """ Download the Soledad config for the given provider @@ -545,11 +445,10 @@ class SoledadBootstrapper(AbstractBootstrapper): :type token: str :rtype: Deferred """ - srp_auth = self.srpauth logger.debug('initializing keymanager...') - if flags.OFFLINE is True: - args = (address, "https://localhost", self._soledad) + if flags.OFFLINE: + nickserver_uri = "https://localhost" kwargs = { "ca_cert_path": "", "api_uri": "", @@ -558,27 +457,18 @@ class SoledadBootstrapper(AbstractBootstrapper): "gpgbinary": self._get_gpg_bin_path() } else: - args = ( - address, - "https://nicknym.%s:6425" % ( - self._provider_config.get_domain(),), - self._soledad - ) + nickserver_uri = "https://nicknym.%s:6425" % ( + self._provider_config.get_domain(),) kwargs = { "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(), - "uid": srp_auth.get_uuid(), + "uid": self.srpauth.get_uuid(), "gpgbinary": self._get_gpg_bin_path() } - try: - self._keymanager = KeyManager(*args, **kwargs) - except KeyNotFound: - logger.debug('key for %s not found.' % address) - except Exception as exc: - logger.exception(exc) - raise + self._keymanager = KeyManager(address, nickserver_uri, self._soledad, + **kwargs) if flags.OFFLINE is False: # make sure key is in server @@ -586,8 +476,9 @@ class SoledadBootstrapper(AbstractBootstrapper): def send_errback(failure): if failure.check(KeyNotFound): - logger.debug('No key found for %s, will generate soon.' - % address) + logger.debug( + 'No key found for %s, it might be because soledad not ' + 'synced yet or it will generate it soon.' % address) else: logger.error("Error sending key to server.") logger.exception(failure.value) @@ -615,14 +506,12 @@ class SoledadBootstrapper(AbstractBootstrapper): logger.debug("Retrieving key for %s" % (address,)) def if_not_found_generate(failure): - if failure.check(KeyNotFound): - logger.debug("Key not found. Generating key for %s" - % (address,)) - d = self._keymanager.gen_key(openpgp.OpenPGPKey) - d.addCallbacks(send_key, log_key_error("generating")) - return d - else: - return failure + failure.trap(KeyNotFound) + logger.debug("Key not found. Generating key for %s" + % (address,)) + d = self._keymanager.gen_key(openpgp.OpenPGPKey) + d.addCallbacks(send_key, log_key_error("generating")) + return d def send_key(_): d = self._keymanager.send_key(openpgp.OpenPGPKey) @@ -657,6 +546,8 @@ class SoledadBootstrapper(AbstractBootstrapper): files if the have changed since the time it was previously downloaded. :type download_if_needed: bool + + :return: Deferred """ leap_assert_type(provider_config, ProviderConfig) @@ -666,36 +557,113 @@ class SoledadBootstrapper(AbstractBootstrapper): self._user = user self._password = password - is_offline = flags.OFFLINE - - if is_offline: + if flags.OFFLINE: signal_finished = self._signaler.soledad_offline_finished - signal_failed = self._signaler.soledad_offline_failed - else: - signal_finished = self._signaler.soledad_bootstrap_finished - signal_failed = self._signaler.soledad_bootstrap_failed + self._signaler.signal(signal_finished) + return defer.succeed(True) + + signal_finished = self._signaler.soledad_bootstrap_finished + signal_failed = self._signaler.soledad_bootstrap_failed try: - if not is_offline: - # XXX FIXME make this async too! (use txrequests) - # Also, why the fuck would we want to download it *every time*? - # We should be fine by using last-time config, or at least - # trying it. - self._download_config() - - # soledad config is ok, let's proceed to load and sync soledad - uuid = self.srpauth.get_uuid() - self.load_and_sync_soledad(uuid) - - d = self._gen_key() - d.addCallback(lambda _: self._signaler.signal(signal_finished)) - return d - else: - self._signaler.signal(signal_finished) - return defer.succeed(True) + # XXX FIXME make this async too! (use txrequests) + # Also, why the fuck would we want to download it *every time*? + # We should be fine by using last-time config, or at least + # trying it. + self._download_config() + uuid = self.srpauth.get_uuid() except Exception as e: # TODO: we should handle more specific exceptions in here self._soledad = None self._keymanager = None - logger.exception("Error while bootstrapping Soledad: %r" % (e, )) + logger.exception("Error while bootstrapping Soledad: %r" % (e,)) self._signaler.signal(signal_failed) + return defer.succeed(None) + + # soledad config is ok, let's proceed to load and sync soledad + d = self.load_and_sync_soledad(uuid) + d.addCallback(lambda _: self._gen_key()) + d.addCallback(lambda _: self._signaler.signal(signal_finished)) + return d + + +class Syncer(object): + """ + Takes care of retries, timeouts and other issues while syncing + """ + # XXX: the timeout and proably all the stuff here should be moved to + # soledad + + MAX_SYNC_RETRIES = 10 + WAIT_MAX_SECONDS = 600 + + def __init__(self, soledad, signaler): + self._tries = 0 + self._soledad = soledad + self._signaler = signaler + + def sync(self): + self._callback_deferred = defer.Deferred() + self._try_sync() + return self._callback_deferred + + def _try_sync(self): + logger.debug("BOOTSTRAPPER: trying to self Soledad....") + # pass defer_decryption=False to get inline decryption + # for debugging. + self._sync_deferred = self._soledad.sync(defer_decryption=True) + self._sync_deferred.addCallbacks(self._success, self._error) + reactor.callLater(self.WAIT_MAX_SECONDS, self._timeout) + + def _success(self, result): + logger.debug("Soledad has been synced!") + self._callback_deferred.callback(result) + # so long, and thanks for all the fish + + def _error(self, failure): + if failure.check(InvalidAuthTokenError): + logger.error('Invalid auth token while trying to self Soledad') + self._signaler.signal( + self._signaler.soledad_invalid_auth_token) + self._callback_deferred.fail(failure) + elif failure.check(sqlite_ProgrammingError, + sqlcipher_ProgrammingError): + logger.exception("%r" % (failure.value,)) + self._callback_deferred.fail(failure) + elif failure.check(SSLError): + logger.error("%r" % (failure.value,)) + self._retry() + elif failure.check(u1db_errors.InvalidGeneration): + logger.error("%r" % (failure.value,)) + self._retry() + else: + logger.exception("Unhandled error while syncing " + "soledad: %r" % (failure.value,)) + self._retry() + + def _timeout(self): + if not self._soledad.syncing: + # timeout only if is still syncing + return + + # maybe it's my connection, but I'm getting + # ssl handshake timeouts and read errors quite often. + # A particularly big sync is a disaster. + # This deserves further investigation, maybe the + # retry strategy can be pushed to u1db, or at least + # it's something worthy to talk about with the + # ubuntu folks. + self._sync_deferred.cancel() + self._retry() + + def _retry(self): + self._tries += 1 + if self._tries > self.MAX_SYNC_RETRIES: + msg = "Sync failed, retrying... (retry {0} of {1})".format( + self._tries, self.MAX_SYNC_RETRIES) + logger.warning(msg) + self._try_sync() + else: + logger.error("Sync failed {0} times".format(self._tries)) + self._callback_deferred.fail( + SoledadSyncError("Too many retries")) -- cgit v1.2.3 From f605abccb3a41d32982ebbcb50ccc7ef0bc50abe Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 18 Feb 2015 14:50:29 -0400 Subject: Re-add public properties removed in previous commit the set_proxies_cb function in backend/components expects to have access to these public properties. --- src/leap/bitmask/services/soledad/soledadbootstrapper.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/leap') diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index f079b0cd..879ed1d0 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -135,6 +135,14 @@ class SoledadBootstrapper(AbstractBootstrapper): self._srpauth = SRPAuth(self._provider_config) return self._srpauth + @property + def soledad(self): + return self._soledad + + @property + def keymanager(self): + return self._keymanager + # initialization def load_offline_soledad(self, username, password, uuid): -- cgit v1.2.3 From 2a3d13c9734651ed8011029665c82ecdd63cad98 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 18 Feb 2015 11:34:13 -0600 Subject: Use the collection instead of the mailbox to initalize IncomingMail --- src/leap/bitmask/services/mail/imap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py index d044a600..da753a0b 100644 --- a/src/leap/bitmask/services/mail/imap.py +++ b/src/leap/bitmask/services/mail/imap.py @@ -83,7 +83,7 @@ def start_incoming_mail_service(keymanager, soledad, imap_factory, userid): incoming_mail = IncomingMail( keymanager, soledad, - inbox, + inbox.collection, userid, check_period=get_mail_check_period()) return incoming_mail -- cgit v1.2.3 From b5af597c9ee51a3334416b775ef1397a3bef4430 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 19 Feb 2015 18:34:44 -0300 Subject: [Cherry Pick] Regression fix: Use the right provider to log in. FIX: Login attempt is made against previously selected provider. Closes #6654. Cherry-picked from release/0.8.x since it's a very annoying bug for the testing cycles for the next release (0.9.0) -- kali. --- src/leap/bitmask/gui/mainwindow.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src/leap') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 35253ebe..55628847 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -775,6 +775,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): domain = self._settings.get_provider() if domain is not None: self._providers.select_provider_by_name(domain) + self._login_widget.set_provider(domain) if not self._settings.get_remember(): # nothing to do here -- cgit v1.2.3 From c24ddb25c4266c2328590a0499846858ff4b9ea1 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 9 Mar 2015 12:28:55 -0300 Subject: [feat] add support for xfce-polkit agent Resolves: #6713 --- src/leap/bitmask/util/privilege_policies.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src/leap') diff --git a/src/leap/bitmask/util/privilege_policies.py b/src/leap/bitmask/util/privilege_policies.py index 65132133..2acc63cf 100644 --- a/src/leap/bitmask/util/privilege_policies.py +++ b/src/leap/bitmask/util/privilege_policies.py @@ -187,6 +187,7 @@ class LinuxPolicyChecker(PolicyChecker): 'ps aux | grep "[l]xsession"', 'ps aux | grep "[g]nome-shell"', 'ps aux | grep "[f]ingerprint-polkit-agent"', + 'ps aux | grep "[x]fce-polkit"', ] is_running = [commands.getoutput(cmd) for cmd in polkit_options] -- cgit v1.2.3 From e59e47675e92115f6c98b13cae518ede22e935eb Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Mon, 9 Mar 2015 14:39:46 +0100 Subject: [bug] Use Deferred.errback The code was using 'fail' which is not a Deferred method. - Resolves: #6772 --- src/leap/bitmask/services/soledad/soledadbootstrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 879ed1d0..6cb4c116 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -673,5 +673,5 @@ class Syncer(object): self._try_sync() else: logger.error("Sync failed {0} times".format(self._tries)) - self._callback_deferred.fail( + self._callback_deferred.errback( SoledadSyncError("Too many retries")) -- cgit v1.2.3 From fb68d7f9047e88e9c5bfb5dd1c74a62e7c3c77b4 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Thu, 5 Mar 2015 12:26:51 -0400 Subject: [doc] Remove unneded comment about collection refs This concern is addressed by the following mail pullreq, that will be into 0.4.0 release: https://github.com/leapcode/leap_mail/pull/175 By the way, it is really funny that I cannot tell if this comment was written by meskio, as the blame says, or if I was the original author of the remark. I should stop drinking so much coffee. --- src/leap/bitmask/services/mail/imap.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py index da753a0b..30646a63 100644 --- a/src/leap/bitmask/services/mail/imap.py +++ b/src/leap/bitmask/services/mail/imap.py @@ -93,11 +93,6 @@ def start_incoming_mail_service(keymanager, soledad, imap_factory, userid): # the account, and passing the mailbox to the incoming service. # In an even better world, we just would subscribe to a channel that would # pass us the serialized object to be inserted. - # XXX: I think we might be at risk here because the account top object - # currently just returns as many mailbox objects as it's asked for, so - # we should be careful about concurrent operations (ie, maybe just use - # this one to do inserts, or let account have an in-memory map of - # mailboxes, and just return references to them). acc = imap_factory.theAccount d = acc.callWhenReady(lambda _: acc.getMailbox(INBOX_NAME)) d.addCallback(setUpIncomingMail) -- cgit v1.2.3 From acd206ea48610743d6078e1c9b510c8b486fb979 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 13 Mar 2015 17:07:57 -0300 Subject: [feat] make 'check' button selected by default Also set text to 'bold' so it's easier to find at a first sight. - Resolves: #5526 --- src/leap/bitmask/gui/ui/wizard.ui | 56 ++++++++++++++++++++++----------------- src/leap/bitmask/gui/wizard.py | 1 + 2 files changed, 32 insertions(+), 25 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui index 0e28ecbf..b125577e 100644 --- a/src/leap/bitmask/gui/ui/wizard.ui +++ b/src/leap/bitmask/gui/ui/wizard.ui @@ -43,7 +43,7 @@ Welcome to Bitmask - + 0 @@ -59,7 +59,7 @@ - + Qt::RichText @@ -112,7 +112,7 @@ Choose a provider - + 1 @@ -187,15 +187,15 @@ 22 - - true - :/images/black/22/question.png + + true + @@ -218,15 +218,15 @@ 22 - - true - :/images/black/22/question.png + + true + @@ -249,15 +249,15 @@ 22 - - true - :/images/black/22/question.png + + true + @@ -363,6 +363,12 @@ + + + 75 + true + + Check @@ -380,7 +386,7 @@ About this provider - + 2 @@ -522,7 +528,7 @@ Provider setup - + 3 @@ -590,15 +596,15 @@ 22 - - true - :/images/black/22/question.png + + true + @@ -621,15 +627,15 @@ 22 - - true - :/images/black/22/question.png + + true + @@ -673,15 +679,15 @@ 22 - - true - :/images/black/22/question.png + + true + @@ -720,7 +726,7 @@ Register new user - + 4 @@ -845,7 +851,7 @@ Service selection - + 5 diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 5da021d1..527df9b7 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -675,6 +675,7 @@ class Wizard(QtGui.QWizard, SignalTracker): skip = self.ui.rbExistingProvider.isChecked() if not self._provider_checks_ok: self._enable_check() + self.ui.btnCheck.setFocus() self._skip_provider_checks(skip) else: self._enable_check(reset=False) -- cgit v1.2.3 From 6e8639a480d63acd5c8bc044a8b1ca907c325233 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 13 Mar 2015 15:59:35 -0300 Subject: [bug] enable providers combo box on check fail After a provider fails to pass the checks, the providers combo box is not enabled when it should, we just took care of the provider line edit. - Resolves: #6418 --- src/leap/bitmask/gui/wizard.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 527df9b7..c66c6269 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -264,6 +264,20 @@ class Wizard(QtGui.QWizard, SignalTracker): if reset: self._reset_provider_check() + def _provider_widget_set_enabled(self, enabled): + """ + Enable/Disable the provider widget. + The widget to use depends on whether the used decided to use an + existing provider or a new one. + + :param enabled: the new state for the widget + :type enabled: bool + """ + if self.ui.rbNewProvider.isChecked(): + self.ui.lnProvider.setEnabled(enabled) + else: + self.ui.cbProviders.setEnabled(enabled) + def _focus_username(self): """ Focus at the username lineedit for the registration page @@ -439,11 +453,7 @@ class Wizard(QtGui.QWizard, SignalTracker): self.ui.grpCheckProvider.setVisible(True) self.ui.btnCheck.setEnabled(False) - # Disable provider widget - if self.ui.rbNewProvider.isChecked(): - self.ui.lnProvider.setEnabled(False) - else: - self.ui.cbProviders.setEnabled(False) + self._provider_widget_set_enabled(False) self.button(QtGui.QWizard.BackButton).clearFocus() @@ -510,7 +520,7 @@ class Wizard(QtGui.QWizard, SignalTracker): self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON) self.ui.lblProviderSelectStatus.setText(status) self.ui.btnCheck.setEnabled(not passed) - self.ui.lnProvider.setEnabled(not passed) + self._provider_widget_set_enabled(not passed) def _https_connection(self, data): """ @@ -529,7 +539,8 @@ class Wizard(QtGui.QWizard, SignalTracker): else: self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON) self.ui.btnCheck.setEnabled(not passed) - self.ui.lnProvider.setEnabled(not passed) + + self._provider_widget_set_enabled(not passed) def _download_provider_info(self, data): """ @@ -558,13 +569,9 @@ class Wizard(QtGui.QWizard, SignalTracker): status = self.tr("Not a valid provider" "") self.ui.lblProviderSelectStatus.setText(status) - self.ui.btnCheck.setEnabled(True) - # Enable provider widget - if self.ui.rbNewProvider.isChecked(): - self.ui.lnProvider.setEnabled(True) - else: - self.ui.cbProviders.setEnabled(True) + self.ui.btnCheck.setEnabled(True) + self._provider_widget_set_enabled(True) def _provider_get_details(self, details): """ -- cgit v1.2.3 From 3cb7948dafe3a9c9a65dcdbd1da1d5405e1ef459 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 4 Mar 2015 18:03:47 -0300 Subject: [bug] use ports specified in eip-service.json Replace the hardcoded port '1194' for the port specified in eip-service.json. Choose the best port to use according which one is enabled in the eip-service.json file Resolves: #6541 --- src/leap/bitmask/services/eip/eipconfig.py | 18 ++++++++++++++ src/leap/bitmask/services/eip/vpnlauncher.py | 35 +++++++++++++++++++++------- src/leap/bitmask/services/eip/vpnprocess.py | 6 +++-- 3 files changed, 49 insertions(+), 10 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/services/eip/eipconfig.py b/src/leap/bitmask/services/eip/eipconfig.py index f4d6b216..d5947eb1 100644 --- a/src/leap/bitmask/services/eip/eipconfig.py +++ b/src/leap/bitmask/services/eip/eipconfig.py @@ -302,6 +302,24 @@ class EIPConfig(ServiceConfig): logger.error("Invalid ip address in config: %s" % (ip_addr_str,)) return None + def get_gateway_ports(self, index=0): + """ + Return the ports of the gateway. + + :param index: the gateway number to get the ports from + :type index: int + + :rtype: list of int + """ + gateways = self.get_gateways() + leap_assert(len(gateways) > 0, "We don't have any gateway!") + if index > len(gateways): + index = 0 + logger.warning("Provided an unknown gateway index %s, " + + "defaulting to 0") + + return gateways[index]["capabilities"]["ports"] + def get_client_cert_path(self, providerconfig=None, about_to_download=False): diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index 72e19413..7793d624 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -106,12 +106,15 @@ class VPNLauncher(object): UP_SCRIPT = None DOWN_SCRIPT = None + PREFERRED_PORTS = ("443", "80", "53", "1194") + @classmethod @abstractmethod def get_gateways(kls, eipconfig, providerconfig): """ - Return the selected gateways for a given provider, looking at the EIP - config file. + Return a list with the selected gateways for a given provider, looking + at the EIP config file. + Each item of the list is a tuple containing (gateway, port). :param eipconfig: eip configuration object :type eipconfig: EIPConfig @@ -122,21 +125,37 @@ class VPNLauncher(object): :rtype: list """ gateways = [] + settings = Settings() domain = providerconfig.get_domain() gateway_conf = settings.get_selected_gateway(domain) gateway_selector = VPNGatewaySelector(eipconfig) if gateway_conf == GATEWAY_AUTOMATIC: - gateways = gateway_selector.get_gateways() + gws = gateway_selector.get_gateways() else: - gateways = [gateway_conf] + gws = [gateway_conf] - if not gateways: + if not gws: logger.error('No gateway was found!') raise VPNLauncherException('No gateway was found!') - logger.debug("Using gateways ips: {0}".format(', '.join(gateways))) + for idx, gw in enumerate(gws): + ports = eipconfig.get_gateway_ports(idx) + + the_port = "1194" # default port + + # pick the port preferring this order: + for port in kls.PREFERRED_PORTS: + if port in ports: + the_port = port + break + else: + continue + + gateways.append((gw, the_port)) + + logger.debug("Using gateways (ip, port): {0!r}".format(gateways)) return gateways @classmethod @@ -194,8 +213,8 @@ class VPNLauncher(object): gateways = kls.get_gateways(eipconfig, providerconfig) - for gw in gateways: - args += ['--remote', gw, '1194', 'udp'] + for ip, port in gateways: + args += ['--remote', ip, port, 'udp'] args += [ '--client', diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 8dc6021f..3e46418c 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -961,9 +961,11 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): :rtype: list """ - gateways = self._launcher.get_gateways( + gateways_ports = self._launcher.get_gateways( self._eipconfig, self._providerconfig) - return gateways + + # filter out ports since we don't need that info + return [gateway for gateway, port in gateways_ports] # shutdown -- cgit v1.2.3 From ae3d99c4ddf94c2d2d055ce8f96afc1f2b2e6e8f Mon Sep 17 00:00:00 2001 From: Duda Dornelles Date: Mon, 12 Jan 2015 15:32:16 -0200 Subject: [bug] Use less threads. On SRP#authenticate, no need to use so many threads. --- src/leap/bitmask/crypto/srpauth.py | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index c2a5f158..f679430b 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -562,7 +562,7 @@ class SRPAuth(object): self._reset_session() # FIXME --------------------------------------------------------- - # 1. it makes no sense to defer each callback to a thread + # 1. it makes no sense to defer each callback to a thread - DONE # 2. the decision to use threads should be at another level. # (although it's not really needed, that was a hack around # the gui blocks) @@ -573,20 +573,10 @@ class SRPAuth(object): username=username, password=password) - d.addCallback( - partial(self._threader, - self._start_authentication), - username=username) - d.addCallback( - partial(self._threader, - self._process_challenge), - username=username) - d.addCallback( - partial(self._threader, - self._extract_data)) - d.addCallback(partial(self._threader, - self._verify_session)) - + d.addCallback(partial(self._start_authentication, username=username)) + d.addCallback(partial(self._process_challenge, username=username)) + d.addCallback(self._extract_data) + d.addCallback(self._verify_session) d.addCallback(self._authenticate_ok) d.addErrback(self._authenticate_error) return d -- cgit v1.2.3 From 7c1c639ec70930210df053eced606be4a4f5838a Mon Sep 17 00:00:00 2001 From: Duda Dornelles Date: Mon, 12 Jan 2015 17:34:12 -0200 Subject: [refactor] move SRPAuth to SRPAuthImpl Decouple SRPAuth from QT signaler and move it to SRPAuthImpl --- src/leap/bitmask/crypto/srpauth.py | 946 +++++++++++++++++++------------------ 1 file changed, 484 insertions(+), 462 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index f679430b..0a6513c8 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -118,465 +118,516 @@ class SRPAuthNoSessionId(SRPAuthenticationError): pass -class SRPAuth(object): +class SRPAuthImpl(object): """ - SRPAuth singleton + Implementation of the SRPAuth interface """ - class __impl(object): + LOGIN_KEY = "login" + A_KEY = "A" + CLIENT_AUTH_KEY = "client_auth" + SESSION_ID_KEY = "_session_id" + USER_VERIFIER_KEY = 'user[password_verifier]' + USER_SALT_KEY = 'user[password_salt]' + AUTHORIZATION_KEY = "Authorization" + + def __init__(self, provider_config): + """ + Constructor for SRPAuth implementation + + :param provider_config: ProviderConfig needed to authenticate. + :type provider_config: ProviderConfig """ - Implementation of the SRPAuth interface + leap_assert(provider_config, + "We need a provider config to authenticate") + + self._provider_config = provider_config + self._settings = Settings() + + # **************************************************** # + # Dependency injection helpers, override this for more + # granular testing + self._fetcher = requests + self._srp = srp + self._hashfun = self._srp.SHA256 + self._ng = self._srp.NG_1024 + # **************************************************** # + + self._reset_session() + + self._session_id = None + self._session_id_lock = threading.Lock() + self._uuid = None + self._uuid_lock = threading.Lock() + self._token = None + self._token_lock = threading.Lock() + + self._srp_user = None + self._srp_a = None + + # User credentials stored for password changing checks + self._username = None + self._password = None + + def _reset_session(self): """ + Resets the current session and sets max retries to 30. + """ + self._session = self._fetcher.session() + # We need to bump the default retries, otherwise logout + # fails most of the times + # NOTE: This is a workaround for the moment, the server + # side seems to return correctly every time, but it fails + # on the client end. + if requests_has_max_retries: + adapter = HTTPAdapter(max_retries=30) + else: + adapter = HTTPAdapter() + self._session.mount('https://', adapter) + + def _safe_unhexlify(self, val): + """ + Rounds the val to a multiple of 2 and returns the + unhexlified value - LOGIN_KEY = "login" - A_KEY = "A" - CLIENT_AUTH_KEY = "client_auth" - SESSION_ID_KEY = "_session_id" - USER_VERIFIER_KEY = 'user[password_verifier]' - USER_SALT_KEY = 'user[password_salt]' - AUTHORIZATION_KEY = "Authorization" + :param val: hexlified value + :type val: str - def __init__(self, provider_config, signaler=None): - """ - Constructor for SRPAuth implementation + :rtype: binary hex data + :return: unhexlified val + """ + return binascii.unhexlify(val) \ + if (len(val) % 2 == 0) else binascii.unhexlify('0' + val) - :param provider_config: ProviderConfig needed to authenticate. - :type provider_config: ProviderConfig - :param signaler: Signaler object used to receive notifications - from the backend - :type signaler: Signaler - """ - leap_assert(provider_config, - "We need a provider config to authenticate") + def _authentication_preprocessing(self, username, password): + """ + Generates the SRP.User to get the A SRP parameter - self._provider_config = provider_config - self._signaler = signaler - self._settings = Settings() - - # **************************************************** # - # Dependency injection helpers, override this for more - # granular testing - self._fetcher = requests - self._srp = srp - self._hashfun = self._srp.SHA256 - self._ng = self._srp.NG_1024 - # **************************************************** # - - self._reset_session() - - self._session_id = None - self._session_id_lock = threading.Lock() - self._uuid = None - self._uuid_lock = threading.Lock() - self._token = None - self._token_lock = threading.Lock() - - self._srp_user = None - self._srp_a = None + :param username: username to login + :type username: str + :param password: password for the username + :type password: str + """ + logger.debug("Authentication preprocessing...") - # User credentials stored for password changing checks - self._username = None - self._password = None + self._srp_user = self._srp.User(username.encode('utf-8'), + password.encode('utf-8'), + self._hashfun, self._ng) + _, A = self._srp_user.start_authentication() - def _reset_session(self): - """ - Resets the current session and sets max retries to 30. - """ - self._session = self._fetcher.session() - # We need to bump the default retries, otherwise logout - # fails most of the times - # NOTE: This is a workaround for the moment, the server - # side seems to return correctly every time, but it fails - # on the client end. - if requests_has_max_retries: - adapter = HTTPAdapter(max_retries=30) - else: - adapter = HTTPAdapter() - self._session.mount('https://', adapter) + self._srp_a = A - def _safe_unhexlify(self, val): - """ - Rounds the val to a multiple of 2 and returns the - unhexlified value + def _start_authentication(self, _, username): + """ + Sends the first request for authentication to retrieve the + salt and B parameter + + Might raise all SRPAuthenticationError based: + SRPAuthenticationError + SRPAuthConnectionError + SRPAuthBadStatusCode + SRPAuthNoSalt + SRPAuthNoB + + :param _: IGNORED, output from the previous callback (None) + :type _: IGNORED + :param username: username to login + :type username: str - :param val: hexlified value - :type val: str + :return: salt and B parameters + :rtype: tuple + """ + logger.debug("Starting authentication process...") + try: + auth_data = { + self.LOGIN_KEY: username, + self.A_KEY: binascii.hexlify(self._srp_a) + } + sessions_url = "%s/%s/%s/" % \ + (self._provider_config.get_api_uri(), + self._provider_config.get_api_version(), + "sessions") + + ca_cert_path = self._provider_config.get_ca_cert_path() + ca_cert_path = ca_cert_path.encode(sys.getfilesystemencoding()) + + init_session = self._session.post(sessions_url, + data=auth_data, + verify=ca_cert_path, + timeout=REQUEST_TIMEOUT) + # 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): {0!r}".format(e)) + raise SRPAuthConnectionError() + except Exception as e: + logger.error("Unknown error: %r" % (e,)) + raise SRPAuthenticationError() - :rtype: binary hex data - :return: unhexlified val - """ - return binascii.unhexlify(val) \ - if (len(val) % 2 == 0) else binascii.unhexlify('0' + val) + content, mtime = reqhelper.get_content(init_session) - def _authentication_preprocessing(self, username, password): - """ - Generates the SRP.User to get the A SRP parameter + if init_session.status_code not in (200,): + logger.error("No valid response (salt): " + "Status code = %r. Content: %r" % + (init_session.status_code, content)) + if init_session.status_code == 422: + logger.error("Invalid username or password.") + raise SRPAuthBadUserOrPassword() - :param username: username to login - :type username: str - :param password: password for the username - :type password: str - """ - logger.debug("Authentication preprocessing...") + logger.error("There was a problem with authentication.") + raise SRPAuthBadStatusCode() - self._srp_user = self._srp.User(username.encode('utf-8'), - password.encode('utf-8'), - self._hashfun, self._ng) - _, A = self._srp_user.start_authentication() + json_content = json.loads(content) + salt = json_content.get("salt", None) + B = json_content.get("B", None) - self._srp_a = A + if salt is None: + logger.error("The server didn't send the salt parameter.") + raise SRPAuthNoSalt() + if B is None: + logger.error("The server didn't send the B parameter.") + raise SRPAuthNoB() - def _start_authentication(self, _, username): - """ - Sends the first request for authentication to retrieve the - salt and B parameter + return salt, B - Might raise all SRPAuthenticationError based: - SRPAuthenticationError - SRPAuthConnectionError - SRPAuthBadStatusCode - SRPAuthNoSalt - SRPAuthNoB + def _process_challenge(self, salt_B, username): + """ + Given the salt and B processes the auth challenge and + generates the M2 parameter + + Might raise SRPAuthenticationError based: + SRPAuthenticationError + SRPAuthBadDataFromServer + SRPAuthConnectionError + SRPAuthJSONDecodeError + SRPAuthBadUserOrPassword + + :param salt_B: salt and B parameters for the username + :type salt_B: tuple + :param username: username for this session + :type username: str - :param _: IGNORED, output from the previous callback (None) - :type _: IGNORED - :param username: username to login - :type username: str + :return: the M2 SRP parameter + :rtype: str + """ + logger.debug("Processing challenge...") + try: + salt, B = salt_B + unhex_salt = self._safe_unhexlify(salt) + unhex_B = self._safe_unhexlify(B) + except (TypeError, ValueError) as e: + logger.error("Bad data from server: %r" % (e,)) + raise SRPAuthBadDataFromServer() + M = self._srp_user.process_challenge(unhex_salt, unhex_B) + + auth_url = "%s/%s/%s/%s" % (self._provider_config.get_api_uri(), + self._provider_config. + get_api_version(), + "sessions", + username) + + auth_data = { + self.CLIENT_AUTH_KEY: binascii.hexlify(M) + } - :return: salt and B parameters - :rtype: tuple - """ - logger.debug("Starting authentication process...") - try: - auth_data = { - self.LOGIN_KEY: username, - self.A_KEY: binascii.hexlify(self._srp_a) - } - sessions_url = "%s/%s/%s/" % \ - (self._provider_config.get_api_uri(), - self._provider_config.get_api_version(), - "sessions") - - ca_cert_path = self._provider_config.get_ca_cert_path() - ca_cert_path = ca_cert_path.encode(sys.getfilesystemencoding()) - - init_session = self._session.post(sessions_url, - data=auth_data, - verify=ca_cert_path, - timeout=REQUEST_TIMEOUT) - # 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): {0!r}".format(e)) - raise SRPAuthConnectionError() - except Exception as e: - logger.error("Unknown error: %r" % (e,)) - raise SRPAuthenticationError() - - content, mtime = reqhelper.get_content(init_session) - - if init_session.status_code not in (200,): - logger.error("No valid response (salt): " - "Status code = %r. Content: %r" % - (init_session.status_code, content)) - if init_session.status_code == 422: - logger.error("Invalid username or password.") - raise SRPAuthBadUserOrPassword() - - 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("The server didn't send the salt parameter.") - raise SRPAuthNoSalt() - if B is None: - logger.error("The server didn't send the B parameter.") - raise SRPAuthNoB() - - return salt, B - - def _process_challenge(self, salt_B, username): - """ - Given the salt and B processes the auth challenge and - generates the M2 parameter - - Might raise SRPAuthenticationError based: - SRPAuthenticationError - SRPAuthBadDataFromServer - SRPAuthConnectionError - SRPAuthJSONDecodeError - SRPAuthBadUserOrPassword - - :param salt_B: salt and B parameters for the username - :type salt_B: tuple - :param username: username for this session - :type username: str - - :return: the M2 SRP parameter - :rtype: str - """ - logger.debug("Processing challenge...") - try: - salt, B = salt_B - unhex_salt = self._safe_unhexlify(salt) - unhex_B = self._safe_unhexlify(B) - except (TypeError, ValueError) as e: - logger.error("Bad data from server: %r" % (e,)) - raise SRPAuthBadDataFromServer() - M = self._srp_user.process_challenge(unhex_salt, unhex_B) - - auth_url = "%s/%s/%s/%s" % (self._provider_config.get_api_uri(), - self._provider_config. - get_api_version(), - "sessions", - username) + try: + auth_result = self._session.put(auth_url, + data=auth_data, + verify=self._provider_config. + get_ca_cert_path(), + timeout=REQUEST_TIMEOUT) + except requests.exceptions.ConnectionError as e: + logger.error("No connection made (HAMK): %r" % (e,)) + raise SRPAuthConnectionError() - auth_data = { - self.CLIENT_AUTH_KEY: binascii.hexlify(M) - } + try: + content, mtime = reqhelper.get_content(auth_result) + except JSONDecodeError: + logger.error("Bad JSON content in auth result.") + raise SRPAuthJSONDecodeError() + if auth_result.status_code == 422: + error = "" try: - auth_result = self._session.put(auth_url, - data=auth_data, - verify=self._provider_config. - get_ca_cert_path(), - timeout=REQUEST_TIMEOUT) - except requests.exceptions.ConnectionError as e: - logger.error("No connection made (HAMK): %r" % (e,)) - raise SRPAuthConnectionError() + error = json.loads(content).get("errors", "") + except ValueError: + logger.error("Problem parsing the received response: %s" + % (content,)) + except AttributeError: + logger.error("Expecting a dict but something else was " + "received: %s", (content,)) + logger.error("[%s] Wrong password (HAMK): [%s]" % + (auth_result.status_code, error)) + 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() + + return json.loads(content) + + def _extract_data(self, json_content): + """ + Extracts the necessary parameters from json_content (M2, + id, token) - try: - content, mtime = reqhelper.get_content(auth_result) - except JSONDecodeError: - logger.error("Bad JSON content in auth result.") - raise SRPAuthJSONDecodeError() - - if auth_result.status_code == 422: - error = "" - try: - error = json.loads(content).get("errors", "") - except ValueError: - logger.error("Problem parsing the received response: %s" - % (content,)) - except AttributeError: - logger.error("Expecting a dict but something else was " - "received: %s", (content,)) - logger.error("[%s] Wrong password (HAMK): [%s]" % - (auth_result.status_code, error)) - raise SRPAuthBadUserOrPassword() + Might raise SRPAuthenticationError based: + SRPBadDataFromServer - 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() + :param json_content: Data received from the server + :type json_content: dict + """ + try: + M2 = json_content.get("M2", None) + uuid = json_content.get("id", None) + token = json_content.get("token", None) + except Exception as e: + logger.error(e) + raise SRPAuthBadDataFromServer() - return json.loads(content) + self.set_uuid(uuid) + self.set_token(token) - def _extract_data(self, json_content): - """ - Extracts the necessary parameters from json_content (M2, - id, token) + if M2 is None or self.get_uuid() is None: + logger.error("Something went wrong. Content = %r" % + (json_content,)) + raise SRPAuthBadDataFromServer() - Might raise SRPAuthenticationError based: - SRPBadDataFromServer + events_signal( + proto.CLIENT_UID, content=uuid, + reqcbk=lambda req, res: None) # make the rpc call async - :param json_content: Data received from the server - :type json_content: dict - """ - try: - M2 = json_content.get("M2", None) - uuid = json_content.get("id", None) - token = json_content.get("token", None) - except Exception as e: - logger.error(e) - raise SRPAuthBadDataFromServer() + return M2 - self.set_uuid(uuid) - self.set_token(token) + def _verify_session(self, M2): + """ + Verifies the session based on the M2 parameter. If the + verification succeeds, it sets the session_id for this + session - if M2 is None or self.get_uuid() is None: - logger.error("Something went wrong. Content = %r" % - (json_content,)) - raise SRPAuthBadDataFromServer() + Might raise SRPAuthenticationError based: + SRPAuthBadDataFromServer + SRPAuthVerificationFailed - events_signal( - proto.CLIENT_UID, content=uuid, - reqcbk=lambda req, res: None) # make the rpc call async + :param M2: M2 SRP parameter + :type M2: str + """ + logger.debug("Verifying session...") + try: + unhex_M2 = self._safe_unhexlify(M2) + except TypeError: + logger.error("Bad data from server (HAWK)") + raise SRPAuthBadDataFromServer() - return M2 + self._srp_user.verify_session(unhex_M2) - def _verify_session(self, M2): - """ - Verifies the session based on the M2 parameter. If the - verification succeeds, it sets the session_id for this - session + if not self._srp_user.authenticated(): + logger.error("Auth verification failed.") + raise SRPAuthVerificationFailed() + logger.debug("Session verified.") - Might raise SRPAuthenticationError based: - SRPAuthBadDataFromServer - SRPAuthVerificationFailed + 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() - :param M2: M2 SRP parameter - :type M2: str - """ - logger.debug("Verifying session...") - try: - unhex_M2 = self._safe_unhexlify(M2) - except TypeError: - logger.error("Bad data from server (HAWK)") - raise SRPAuthBadDataFromServer() + events_signal( + proto.CLIENT_SESSION_ID, content=session_id, + reqcbk=lambda req, res: None) # make the rpc call asynch - self._srp_user.verify_session(unhex_M2) + self.set_session_id(session_id) + logger.debug("SUCCESS LOGIN") + return True - if not self._srp_user.authenticated(): - logger.error("Auth verification failed.") - raise SRPAuthVerificationFailed() - logger.debug("Session verified.") + def _threader(self, cb, res, *args, **kwargs): + return threads.deferToThread(cb, res, *args, **kwargs) - 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() + def _change_password(self, current_password, new_password): + """ + Changes the password for the currently logged user if the current + password match. + It requires to be authenticated. - events_signal( - proto.CLIENT_SESSION_ID, content=session_id, - reqcbk=lambda req, res: None) # make the rpc call async + Might raise: + SRPAuthBadUserOrPassword + requests.exceptions.HTTPError - self.set_session_id(session_id) + :param current_password: the current password for the logged user. + :type current_password: str + :param new_password: the new password for the user + :type new_password: str + """ + leap_assert(self.get_uuid() is not None) + + if current_password != self._password: + raise SRPAuthBadUserOrPassword + + url = "%s/%s/users/%s.json" % ( + self._provider_config.get_api_uri(), + self._provider_config.get_api_version(), + self.get_uuid()) + + salt, verifier = self._srp.create_salted_verification_key( + self._username.encode('utf-8'), new_password.encode('utf-8'), + self._hashfun, self._ng) + + cookies = {self.SESSION_ID_KEY: self.get_session_id()} + headers = { + self.AUTHORIZATION_KEY: + "Token token={0}".format(self.get_token()) + } + user_data = { + self.USER_VERIFIER_KEY: binascii.hexlify(verifier), + self.USER_SALT_KEY: binascii.hexlify(salt) + } + + change_password = self._session.put( + url, data=user_data, + verify=self._provider_config.get_ca_cert_path(), + cookies=cookies, + timeout=REQUEST_TIMEOUT, + headers=headers) + + # In case of non 2xx it raises HTTPError + change_password.raise_for_status() + + self._password = new_password - def _threader(self, cb, res, *args, **kwargs): - return threads.deferToThread(cb, res, *args, **kwargs) + def change_password(self, current_password, new_password): + """ + Changes the password for the currently logged user if the current + password match. + It requires to be authenticated. - def _change_password(self, current_password, new_password): - """ - Changes the password for the currently logged user if the current - password match. - It requires to be authenticated. + :param current_password: the current password for the logged user. + :type current_password: str + :param new_password: the new password for the user + :type new_password: str + """ + d = threads.deferToThread( + self._change_password, current_password, new_password) + return d - Might raise: - SRPAuthBadUserOrPassword - requests.exceptions.HTTPError + def authenticate(self, username, password): + """ + Executes the whole authentication process for a user - :param current_password: the current password for the logged user. - :type current_password: str - :param new_password: the new password for the user - :type new_password: str - """ - leap_assert(self.get_uuid() is not None) + Might raise SRPAuthenticationError - if current_password != self._password: - raise SRPAuthBadUserOrPassword + :param username: username for this session + :type username: unicode + :param password: password for this user + :type password: unicode - url = "%s/%s/users/%s.json" % ( - self._provider_config.get_api_uri(), - self._provider_config.get_api_version(), - self.get_uuid()) + :returns: A defer on a different thread + :rtype: twisted.internet.defer.Deferred + """ + leap_assert(self.get_session_id() is None, "Already logged in") + + # User credentials stored for password changing checks + self._username = username + self._password = password + + self._reset_session() + + d = threads.deferToThread(self._authentication_preprocessing, + username=username, + password=password) + + d.addCallback(partial(self._start_authentication, username=username)) + d.addCallback(partial(self._process_challenge, username=username)) + d.addCallback(self._extract_data) + d.addCallback(self._verify_session) + return d - salt, verifier = self._srp.create_salted_verification_key( - self._username.encode('utf-8'), new_password.encode('utf-8'), - self._hashfun, self._ng) + def logout(self): + """ + Logs out the current session. + Expects a session_id to exists, might raise AssertionError + """ + logger.debug("Starting logout...") - cookies = {self.SESSION_ID_KEY: self.get_session_id()} - headers = { - self.AUTHORIZATION_KEY: - "Token token={0}".format(self.get_token()) - } - user_data = { - self.USER_VERIFIER_KEY: binascii.hexlify(verifier), - self.USER_SALT_KEY: binascii.hexlify(salt) - } + if self.get_session_id() is None: + logger.debug("Already logged out") + return - change_password = self._session.put( - url, data=user_data, - verify=self._provider_config.get_ca_cert_path(), - cookies=cookies, - timeout=REQUEST_TIMEOUT, - headers=headers) + logout_url = "%s/%s/%s/" % (self._provider_config.get_api_uri(), + self._provider_config. + get_api_version(), + "logout") + try: + self._session.delete(logout_url, + data=self.get_session_id(), + 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,)) + raise + else: + self.set_session_id(None) + self.set_uuid(None) + self.set_token(None) + # Also reset the session + self._session = self._fetcher.session() + logger.debug("Successfully logged out.") - # In case of non 2xx it raises HTTPError - change_password.raise_for_status() + def set_session_id(self, session_id): + with self._session_id_lock: + self._session_id = session_id - self._password = new_password + def get_session_id(self): + with self._session_id_lock: + return self._session_id - def change_password(self, current_password, new_password): - """ - Changes the password for the currently logged user if the current - password match. - It requires to be authenticated. + def set_uuid(self, uuid): + with self._uuid_lock: + full_uid = "%s@%s" % ( + self._username, self._provider_config.get_domain()) + if uuid is not None: # avoid removing the uuid from settings + self._settings.set_uuid(full_uid, uuid) + self._uuid = uuid - :param current_password: the current password for the logged user. - :type current_password: str - :param new_password: the new password for the user - :type new_password: str - """ - d = threads.deferToThread( - self._change_password, current_password, new_password) - d.addCallback(self._change_password_ok) - d.addErrback(self._change_password_error) + def get_uuid(self): + with self._uuid_lock: + return self._uuid - def _change_password_ok(self, _): - """ - Password change callback. - """ - if self._signaler is not None: - self._signaler.signal(self._signaler.srp_password_change_ok) + def set_token(self, token): + with self._token_lock: + self._token = token - def _change_password_error(self, failure): - """ - Password change errback. - """ - logger.debug( - "Error changing password. Failure: {0}".format(failure)) - if self._signaler is None: - return + def get_token(self): + with self._token_lock: + return self._token - if failure.check(SRPAuthBadUserOrPassword): - self._signaler.signal(self._signaler.srp_password_change_badpw) - else: - self._signaler.signal(self._signaler.srp_password_change_error) + def is_authenticated(self): + """ + Return whether the user is authenticated or not. - def authenticate(self, username, password): - """ - Executes the whole authentication process for a user + :rtype: bool + """ + user = self._srp_user + if user is not None: + return user.authenticated() - Might raise SRPAuthenticationError + return False - :param username: username for this session - :type username: unicode - :param password: password for this user - :type password: unicode - :returns: A defer on a different thread - :rtype: twisted.internet.defer.Deferred - """ - leap_assert(self.get_session_id() is None, "Already logged in") - - # User credentials stored for password changing checks - self._username = username - self._password = password - - self._reset_session() - - # FIXME --------------------------------------------------------- - # 1. it makes no sense to defer each callback to a thread - DONE - # 2. the decision to use threads should be at another level. - # (although it's not really needed, that was a hack around - # the gui blocks) - # it makes very hard to test this. The __impl could be - # separated and decoupled from the provider_config abstraction. - - d = threads.deferToThread(self._authentication_preprocessing, - username=username, - password=password) - - d.addCallback(partial(self._start_authentication, username=username)) - d.addCallback(partial(self._process_challenge, username=username)) - d.addCallback(self._extract_data) - d.addCallback(self._verify_session) +class SRPAuth(object): + """ + SRPAuth singleton + """ + class __impl(SRPAuthImpl): + + def __init__(self, provider_config, signaler=None): + SRPAuthImpl.__init__(self, provider_config) + self._signaler = signaler + + def authenticate(self, username, password): + d = SRPAuthImpl.authenticate(self, username, password) d.addCallback(self._authenticate_ok) d.addErrback(self._authenticate_error) return d @@ -620,82 +671,53 @@ class SRPAuth(object): self._signaler.signal(signal) - def logout(self): + def change_password(self, current_password, new_password): + """ + Changes the password for the currently logged user if the current + password match. + It requires to be authenticated. + + :param current_password: the current password for the logged user. + :type current_password: str + :param new_password: the new password for the user + :type new_password: str """ - Logs out the current session. - Expects a session_id to exists, might raise AssertionError + d = SRPAuthImpl.change_password(self, current_password, new_password) + d.addCallback(self._change_password_ok) + d.addErrback(self._change_password_error) + return d + + def _change_password_ok(self, _): """ - logger.debug("Starting logout...") + Password change callback. + """ + if self._signaler is not None: + self._signaler.signal(self._signaler.srp_password_change_ok) - if self.get_session_id() is None: - logger.debug("Already logged out") + def _change_password_error(self, failure): + """ + Password change errback. + """ + logger.debug( + "Error changing password. Failure: {0}".format(failure)) + if self._signaler is None: return - logout_url = "%s/%s/%s/" % (self._provider_config.get_api_uri(), - self._provider_config. - get_api_version(), - "logout") + if failure.check(SRPAuthBadUserOrPassword): + self._signaler.signal(self._signaler.srp_password_change_badpw) + else: + self._signaler.signal(self._signaler.srp_password_change_error) + + def logout(self): try: - self._session.delete(logout_url, - data=self.get_session_id(), - 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,)) + SRPAuthImpl.logout(self) + if self._signaler is not None: + self._signaler.signal(self._signaler.srp_logout_ok) + except Exception: if self._signaler is not None: self._signaler.signal(self._signaler.srp_logout_error) raise - else: - self.set_session_id(None) - self.set_uuid(None) - self.set_token(None) - # Also reset the session - self._session = self._fetcher.session() - logger.debug("Successfully logged out.") - if self._signaler is not None: - self._signaler.signal(self._signaler.srp_logout_ok) - - def set_session_id(self, session_id): - with self._session_id_lock: - self._session_id = session_id - - def get_session_id(self): - with self._session_id_lock: - return self._session_id - - def set_uuid(self, uuid): - with self._uuid_lock: - full_uid = "%s@%s" % ( - self._username, self._provider_config.get_domain()) - 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): - with self._uuid_lock: - return self._uuid - - def set_token(self, token): - with self._token_lock: - self._token = token - - def get_token(self): - with self._token_lock: - return self._token - - def is_authenticated(self): - """ - Return whether the user is authenticated or not. - - :rtype: bool - """ - user = self._srp_user - if user is not None: - return user.authenticated() - return False __instance = None -- cgit v1.2.3 From b095e605e6ee7875e221f03278507825c2a3ad63 Mon Sep 17 00:00:00 2001 From: Neissi Lima Date: Mon, 12 Jan 2015 18:00:37 -0200 Subject: [refactor] move SRPRegister to SRPRegisterImpl Decouple SRPRegister from QT signaler and create SRPRegisterImpl --- src/leap/bitmask/crypto/srpregister.py | 105 ++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 42 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/crypto/srpregister.py b/src/leap/bitmask/crypto/srpregister.py index 86510de1..2964b4b7 100644 --- a/src/leap/bitmask/crypto/srpregister.py +++ b/src/leap/bitmask/crypto/srpregister.py @@ -33,40 +33,18 @@ from leap.common.check import leap_assert, leap_assert_type logger = logging.getLogger(__name__) -class SRPRegister(QtCore.QObject): - """ - Registers a user to a specific provider using SRP - """ +class SRPRegisterImpl: USER_LOGIN_KEY = 'user[login]' 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 - 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 - :type privider_config: ProviderConfig - :param register_path: webapp path for registering users - :type register_path; str - """ - QtCore.QObject.__init__(self) + def __init__(self, provider_config, register_path): leap_assert(provider_config, "Please provide a provider") leap_assert_type(provider_config, ProviderConfig) self._provider_config = provider_config - self._signaler = signaler - # **************************************************** # # Dependency injection helpers, override this for more # granular testing @@ -83,25 +61,8 @@ class SRPRegister(QtCore.QObject): self._port = "443" self._register_path = register_path - self._session = self._fetcher.session() - def _get_registration_uri(self): - """ - Returns the URI where the register request should be made for - the provider - - :rtype: str - """ - - uri = "https://%s:%s/%s/%s" % ( - self._provider, - self._port, - self._provider_config.get_api_version(), - self._register_path) - - return uri - def register_user(self, username, password): """ Registers a user with the validator based on the password provider @@ -152,7 +113,6 @@ class SRPRegister(QtCore.QObject): status_code = self.STATUS_ERROR if req is not None: status_code = req.status_code - self._emit_result(status_code) if not ok: try: @@ -165,6 +125,67 @@ class SRPRegister(QtCore.QObject): except Exception as e: logger.error("Unknown error: %r" % (e, )) + return ok, status_code + + def _get_registration_uri(self): + """ + Returns the URI where the register request should be made for + the provider + + :rtype: str + """ + + uri = "https://%s:%s/%s/%s" % ( + self._provider, + self._port, + self._provider_config.get_api_version(), + self._register_path) + + return uri + + +class SRPRegister(QtCore.QObject): + """ + Registers a user to a specific provider using SRP + """ + + STATUS_OK = (200, 201) + STATUS_TAKEN = 422 + + + 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 + :type privider_config: ProviderConfig + :param register_path: webapp path for registering users + :type register_path; str + """ + self._srp_register = SRPRegisterImpl(provider_config, register_path) + QtCore.QObject.__init__(self) + + self._signaler = signaler + + def register_user(self, username, password): + """ + Registers a user with the validator based on the password provider + + :param username: username to register + :type username: str + :param password: password for this username + :type password: str + + :returns: if the registration went ok or not. + :rtype: bool + """ + ok, status_code = self._srp_register.register_user(username, password) + self._emit_result(status_code) return ok def _emit_result(self, status_code): -- cgit v1.2.3 From ee69ce76ec382c35b19823d5fc838264bd054ca8 Mon Sep 17 00:00:00 2001 From: Duda Dornelles Date: Tue, 13 Jan 2015 12:36:42 -0200 Subject: [style] fix pep8 violations --- src/leap/bitmask/crypto/srpauth.py | 6 +++--- src/leap/bitmask/crypto/srpregister.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 0a6513c8..fe177e5a 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -535,8 +535,8 @@ class SRPAuthImpl(object): d = threads.deferToThread(self._authentication_preprocessing, username=username, password=password) - d.addCallback(partial(self._start_authentication, username=username)) + d.addCallback(partial(self._process_challenge, username=username)) d.addCallback(self._extract_data) d.addCallback(self._verify_session) @@ -682,7 +682,8 @@ class SRPAuth(object): :param new_password: the new password for the user :type new_password: str """ - d = SRPAuthImpl.change_password(self, current_password, new_password) + d = SRPAuthImpl.change_password(self, current_password, + new_password) d.addCallback(self._change_password_ok) d.addErrback(self._change_password_error) return d @@ -718,7 +719,6 @@ class SRPAuth(object): self._signaler.signal(self._signaler.srp_logout_error) raise - __instance = None def __init__(self, provider_config, signaler=None): diff --git a/src/leap/bitmask/crypto/srpregister.py b/src/leap/bitmask/crypto/srpregister.py index 2964b4b7..e3007b6c 100644 --- a/src/leap/bitmask/crypto/srpregister.py +++ b/src/leap/bitmask/crypto/srpregister.py @@ -152,7 +152,6 @@ class SRPRegister(QtCore.QObject): STATUS_OK = (200, 201) STATUS_TAKEN = 422 - def __init__(self, signaler=None, provider_config=None, register_path="users"): """ -- cgit v1.2.3 From f745288f4ddf49e72604709f3c1eb75e47b3b369 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 8 Apr 2015 16:03:22 -0300 Subject: [bug] handle user cancel keyring open operation Catch the keyring.errors.InitError exception. The automatic login sequence now stops correctly instead of freezing if the user cancel the keyring open operation. - Resolves: #6682 --- src/leap/bitmask/gui/login.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/leap') diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index 90df0b73..716a8609 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -32,6 +32,7 @@ The login sequence is the following: """ import logging +from keyring.errors import InitError as KeyringInitError from PySide import QtCore, QtGui from ui_login import Ui_LoginWidget @@ -365,6 +366,9 @@ class LoginWidget(QtGui.QWidget, SignalTracker): # Only save the username if it was saved correctly in # the keyring self._settings.set_user(full_user_id) + except KeyringInitError as e: + logger.error("Failed to unlock keyring, maybe the user " + "cancelled the operation {0!r}".format(e)) except Exception as e: logger.exception("Problem saving data to keyring. %r" % (e,)) @@ -653,6 +657,9 @@ class LoginWidget(QtGui.QWidget, SignalTracker): saved_password = keyring.get_password(self.KEYRING_KEY, u_user) except ValueError as e: logger.debug("Incorrect Password. %r." % (e,)) + except KeyringInitError as e: + logger.error("Failed to unlock keyring, maybe the user " + "cancelled the operation {0!r}".format(e)) if saved_password is not None: self.set_password(saved_password) -- cgit v1.2.3 From 1644e9effe528aa2f95de4c4726704028b0fabc8 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 6 Apr 2015 15:48:06 -0300 Subject: [bug] enable atexit subprocess termination - Resolves: #6426 - Resolves: #6681 --- src/leap/bitmask/app.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 72c03cb9..0517a071 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -39,6 +39,7 @@ # M:::::::::::~NMMM7???7MMMM:::::::::::::::::::::::NMMMI??I7MMMM:::::::::::::M # M::::::::::::::7MMMMMMM+:::::::::::::::::::::::::::?MMMMMMMZ:::::::::::::::M # (thanks to: http://www.glassgiant.com/ascii/) +import atexit import multiprocessing import os import sys @@ -73,6 +74,7 @@ def kill_the_children(): me = os.getpid() parent = psutil.Process(me) print "Killing all the children processes..." + for child in parent.get_children(recursive=True): try: child.terminate() @@ -81,7 +83,7 @@ def kill_the_children(): # XXX This is currently broken, but we need to fix it to avoid # orphaned processes in case of a crash. -# atexit.register(kill_the_children) +atexit.register(kill_the_children) def do_display_version(opts): -- cgit v1.2.3 From b2623678d53c27707ea38d222e3dfee0f171ed35 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 15 Apr 2015 16:34:56 -0300 Subject: [bug] do not disable autostart on system quit() If the quit() call is triggered by the system logout we should not disable the autostart. Otherwise bitmask won't autostart on the next session start. - Resolves: #6424 --- src/leap/bitmask/frontend_app.py | 8 +++++++- src/leap/bitmask/gui/mainwindow.py | 12 +++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/frontend_app.py b/src/leap/bitmask/frontend_app.py index b0a149f9..55e188f7 100644 --- a/src/leap/bitmask/frontend_app.py +++ b/src/leap/bitmask/frontend_app.py @@ -51,7 +51,13 @@ def signal_handler(window, pid, signum, frame): if pid == my_pid: pname = multiprocessing.current_process().name logger.debug("{0}: SIGNAL #{1} catched.".format(pname, signum)) - window.quit() + disable_autostart = True + if signum == 15: # SIGTERM + # Do not disable autostart on SIGTERM since this is the signal that + # the system sends to bitmask when the user asks to do a system + # logout. + disable_autostart = False + window.quit(disable_autostart=disable_autostart) def run_frontend(options, flags_dict, backend_pid=None): diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 55628847..4b665337 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1054,7 +1054,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): if not e.spontaneous(): # if the system requested the `close` then we should quit. self._system_quit = True - self.quit() + self.quit(disable_autostart=False) return if QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \ @@ -1622,18 +1622,24 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): logger.debug('Terminating vpn') self._backend.eip_stop(shutdown=True) - def quit(self): + def quit(self, disable_autostart=True): """ Start the quit sequence and wait for services to finish. Cleanup and close the main window before quitting. + + :param disable_autostart: whether we should disable the autostart + feature or not + :type disable_autostart: bool """ if self._quitting: return + if disable_autostart: + autostart.set_autostart(False) + self._quitting = True self._close_to_tray = False logger.debug('Quitting...') - autostart.set_autostart(False) # first thing to do quitting, hide the mainwindow and show tooltip. self.hide() -- cgit v1.2.3 From 81f6c71ef80b7070538f87d209f60c01dc0a27db Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 29 Apr 2015 12:10:50 -0300 Subject: [feat] update api port for pinned riseup - Related: #6876 --- src/leap/bitmask/provider/pinned_riseup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/provider/pinned_riseup.py b/src/leap/bitmask/provider/pinned_riseup.py index 8cc51506..8cfca6ce 100644 --- a/src/leap/bitmask/provider/pinned_riseup.py +++ b/src/leap/bitmask/provider/pinned_riseup.py @@ -22,7 +22,7 @@ DOMAIN = "riseup.net" PROVIDER_JSON = """ { - "api_uri": "https://api.black.riseup.net:4430", + "api_uri": "https://api.black.riseup.net:443", "api_version": "1", "ca_cert_fingerprint": "SHA256: a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494", "ca_cert_uri": "https://black.riseup.net/ca.crt", -- cgit v1.2.3 From 01b005b0642454e3d670089ed7e530eda8e9ef91 Mon Sep 17 00:00:00 2001 From: drebs Date: Wed, 28 Jan 2015 16:17:51 -0200 Subject: [feat] use txzmq in backend Before this commit, the backend used plain pyzmq bindings for communicating with the frontend. This implements the txzmq twisted-powered bindings instead. Closes: #6360 --- src/leap/bitmask/backend/backend.py | 128 ++++++++-------- src/leap/bitmask/backend/backend_proxy.py | 239 +++++++++++++++++------------- src/leap/bitmask/backend/components.py | 3 +- src/leap/bitmask/gui/app.py | 1 - 4 files changed, 202 insertions(+), 169 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/backend/backend.py b/src/leap/bitmask/backend/backend.py index cff731ba..fcbb19dc 100644 --- a/src/leap/bitmask/backend/backend.py +++ b/src/leap/bitmask/backend/backend.py @@ -17,17 +17,16 @@ # FIXME this is missing module documentation. It would be fine to say a couple # of lines about the whole backend architecture. -# TODO use txzmq bindings instead. import json import os -import threading import time import psutil -from twisted.internet import defer, reactor, threads +from twisted.internet import defer, reactor, threads, task +import txzmq import zmq try: from zmq.auth.thread import ThreadAuthenticator @@ -43,43 +42,42 @@ import logging logger = logging.getLogger(__name__) -class Backend(object): +class TxZmqREPConnection(object): """ - Backend server. - Receives signals from backend_proxy and emit signals if needed. + A twisted based zmq rep connection. """ - # XXX we might want to make this configurable per-platform, - # and use the most performant socket type on each one. - if flags.ZMQ_HAS_CURVE: - # XXX this should not be hardcoded. Make it configurable. - PORT = '5556' - BIND_ADDR = "tcp://127.0.0.1:%s" % PORT - else: - SOCKET_FILE = "/tmp/bitmask.socket.0" - BIND_ADDR = "ipc://%s" % SOCKET_FILE - PING_INTERVAL = 2 # secs - - def __init__(self, frontend_pid=None): + def __init__(self, server_address, process_request): """ - Backend constructor, create needed instances. + Initialize the connection. + + :param server_address: The address of the backend zmq server. + :type server: str + :param process_request: A callable used to process incoming requests. + :type process_request: callable(messageParts) """ - self._signaler = Signaler() + self._server_address = server_address + self._process_request = process_request + self._zmq_factory = None + self._zmq_connection = None + self._init_txzmq() - self._frontend_pid = frontend_pid + def _init_txzmq(self): + """ + Configure the txzmq components and connection. + """ + self._zmq_factory = txzmq.ZmqFactory() + self._zmq_factory.registerForShutdown() + self._zmq_connection = txzmq.ZmqREPConnection(self._zmq_factory) - self._do_work = threading.Event() # used to stop the worker thread. - self._zmq_socket = None + context = self._zmq_factory.context + socket = self._zmq_connection.socket - self._ongoing_defers = [] - self._init_zmq() + def _gotMessage(messageId, messageParts): + self._zmq_connection.reply(messageId, "OK") + self._process_request(messageParts) - def _init_zmq(self): - """ - Configure the zmq components and connection. - """ - context = zmq.Context() - socket = context.socket(zmq.REP) + self._zmq_connection.gotMessage = _gotMessage if flags.ZMQ_HAS_CURVE: # Start an authenticator for this context. @@ -95,37 +93,39 @@ class Backend(object): socket.curve_secretkey = secret socket.curve_server = True # must come before bind - socket.bind(self.BIND_ADDR) - if not flags.ZMQ_HAS_CURVE: - os.chmod(self.SOCKET_FILE, 0600) + proto, addr = self._server_address.split('://') # tcp/ipc, ip/socket + socket.bind(self._server_address) + if proto == 'ipc': + os.chmod(addr, 0600) - self._zmq_socket = socket - def _worker(self): - """ - Receive requests and send it to process. +class Backend(object): + """ + Backend server. + Receives signals from backend_proxy and emit signals if needed. + """ + # XXX we might want to make this configurable per-platform, + # and use the most performant socket type on each one. + if flags.ZMQ_HAS_CURVE: + # XXX this should not be hardcoded. Make it configurable. + PORT = '5556' + BIND_ADDR = "tcp://127.0.0.1:%s" % PORT + else: + SOCKET_FILE = "/tmp/bitmask.socket.0" + BIND_ADDR = "ipc://%s" % SOCKET_FILE - Note: we use a simple while since is less resource consuming than a - Twisted's LoopingCall. + PING_INTERVAL = 2 # secs + + def __init__(self, frontend_pid=None): """ - pid = self._frontend_pid - check_wait = 0 - while self._do_work.is_set(): - # Wait for next request from client - try: - request = self._zmq_socket.recv(zmq.NOBLOCK) - self._zmq_socket.send("OK") - # logger.debug("Received request: '{0}'".format(request)) - self._process_request(request) - except zmq.ZMQError as e: - if e.errno != zmq.EAGAIN: - raise - time.sleep(0.01) - - check_wait += 0.01 - if pid is not None and check_wait > self.PING_INTERVAL: - check_wait = 0 - self._check_frontend_alive() + Backend constructor, create needed instances. + """ + self._signaler = Signaler() + self._frontend_pid = frontend_pid + self._frontend_checker = None + self._ongoing_defers = [] + self._zmq_connection = TxZmqREPConnection( + self.BIND_ADDR, self._process_request) def _check_frontend_alive(self): """ @@ -160,25 +160,27 @@ class Backend(object): for d in self._ongoing_defers: d.cancel() + logger.debug("Stopping the Twisted reactor...") reactor.stop() - logger.debug("Twisted reactor stopped.") def run(self): """ Start the ZMQ server and run the loop to handle requests. """ self._signaler.start() - self._do_work.set() - threads.deferToThread(self._worker) + self._frontend_checker = task.LoopingCall(self._check_frontend_alive) + self._frontend_checker.start(self.PING_INTERVAL) + logger.debug("Starting Twisted reactor.") reactor.run() + logger.debug("Finished Twisted reactor.") def stop(self): """ Stop the server and the zmq request parse loop. """ - logger.debug("STOP received.") + logger.debug("Stopping the backend...") self._signaler.stop() - self._do_work.clear() + self._frontend_checker.stop() threads.deferToThread(self._stop_reactor) def _process_request(self, request_json): diff --git a/src/leap/bitmask/backend/backend_proxy.py b/src/leap/bitmask/backend/backend_proxy.py index 04046d3d..8d6930d6 100644 --- a/src/leap/bitmask/backend/backend_proxy.py +++ b/src/leap/bitmask/backend/backend_proxy.py @@ -21,11 +21,13 @@ to the backend. # XXX should document the relationship to the API here. import functools -import Queue import threading -import time 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 @@ -37,35 +39,39 @@ import logging logger = logging.getLogger(__name__) -class BackendProxy(object): +class ZmqREQConnection(threading.Thread): """ - The BackendProxy handles calls from the GUI and forwards (through ZMQ) - to the backend. + A threaded zmq req connection. """ - if flags.ZMQ_HAS_CURVE: - PORT = '5556' - SERVER = "tcp://localhost:%s" % PORT - else: - SERVER = "ipc:///tmp/bitmask.socket.0" - - POLL_TIMEOUT = 4000 # ms - POLL_TRIES = 3 - - PING_INTERVAL = 2 # secs - - def __init__(self): - generate_zmq_certificates_if_needed() - - self._socket = None + def __init__(self, server_address, on_recv): + """ + Initialize the connection. - self.settings = Settings() + :param server_address: The address of the backend zmq server. + :type server: str + :param on_recv: The callback to be executed when a message is + received. + :type on_recv: callable(msg) + """ + threading.Thread.__init__(self) + self._server_address = server_address + self._on_recv = on_recv + self._stream = None + self._init_zmq() - # initialize ZMQ stuff: + def _init_zmq(self): + """ + Configure the zmq components and connection. + """ + logger.debug("Setting up ZMQ connection to server...") context = zmq.Context() - logger.debug("Connecting to server...") socket = context.socket(zmq.REQ) + # we use zmq's eventloop in order to asynchronously send requests + loop = ioloop.ZMQIOLoop.current() + self._stream = zmqstream.ZMQStream(socket, loop) + if flags.ZMQ_HAS_CURVE: # public, secret = zmq.curve_keypair() client_keys = zmq.curve_keypair() @@ -79,66 +85,128 @@ class BackendProxy(object): socket.setsockopt(zmq.RCVTIMEO, 1000) socket.setsockopt(zmq.LINGER, 0) # Terminate early - socket.connect(self.SERVER) - self._socket = socket - self._ping_at = 0 + self._stream.on_recv(self._on_recv) + + def run(self): + """ + Run the threaded stream connection loop. + """ + self._stream.socket.connect(self._server_address) + logger.debug("Starting ZMQ loop.") + self._stream.io_loop.start() + logger.debug("Finished ZMQ loop.") + + def stop(self): + """ + Stop the threaded connection loop. + """ + self._stream.io_loop.stop() + + def send(self, *args, **kwargs): + """ + Send a message through this connection. + """ + # Important note: calling send on the zmqstream from another + # thread doesn’t properly tell the IOLoop thread that there’s an + # event to process. This could cuase small delays if the IOLoop is + # already processing lots of events, but it can cause the message + # to never send if the zmq socket is the only one it’s handling. + # + # Because of that, we want ZmqREQConnection.send to hand off the + # stream.send to the IOLoop’s thread via IOLoop.add_callback: + self._stream.io_loop.add_callback( + lambda: self._stream.send(*args, **kwargs)) + + +class BackendProxy(object): + """ + The BackendProxy handles calls from the GUI and forwards (through ZMQ) + to the backend. + """ + + if flags.ZMQ_HAS_CURVE: + PORT = '5556' + SERVER = "tcp://localhost:%s" % PORT + else: + SERVER = "ipc:///tmp/bitmask.socket.0" + + PING_INTERVAL = 2 # secs + + def __init__(self): + """ + Initialize the backend proxy. + """ + generate_zmq_certificates_if_needed() + 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._ping_event = threading.Event() self.online = False + self.settings = Settings() - self._call_queue = Queue.Queue() - self._worker_caller = threading.Thread(target=self._worker) + def _set_online(self, _): + """ + Mark the backend as being online. - def start(self): - self._worker_caller.start() + This is used as the zmq connection's on_recv callback, and so it is + passed the received message as a parameter. Because we currently don't + use that message, we just ignore it for now. + """ + self.online = True + # the following event is used when checking whether the backend is + # online + self._ping_event.set() + + def _set_offline(self): + """ + Mark the backend as being offline. + """ + self.online = False def check_online(self): """ Return whether the backend is accessible or not. You don't need to do `run` in order to use this. - :rtype: bool """ - # we use a small timeout in order to response quickly if the backend is - # offline - self._send_request(PING_REQUEST, retry=False, timeout=500) - self._socket.close() + logger.debug("Checking whether backend is online...") + self._send_request(PING_REQUEST) + # self._ping_event will eventually be set by the zmq connection's + # on_recv callback, so we use a small timeout in order to response + # quickly if the backend is offline + if not self._ping_event.wait(0.5): + logger.warning("Backend is offline!") + self._set_offline() return self.online - def _worker(self): + def start(self): """ - Worker loop that processes the Queue of pending requests to do. + Start the backend proxy. """ - while True: - try: - request = self._call_queue.get(block=False) - # break the loop after sending the 'stop' action to the - # backend. - if request == STOP_REQUEST: - break - - self._send_request(request) - except Queue.Empty: - pass - time.sleep(0.01) - self._ping() + logger.debug("Starting backend proxy...") + self._do_work.set() + self._connection.start() + self.check_online() + self._heartbeat.start() - logger.debug("BackendProxy worker stopped.") - - def _reset_ping(self): + def _stop(self): """ - Reset the ping timeout counter. - This is called for every ping and request. + Stop the backend proxy. """ - self._ping_at = time.time() + self.PING_INTERVAL + with self._work_lock: # avoid sending after connection was closed + self._do_work.clear() + self._heartbeat.stop() + self._connection.stop() + logger.debug("BackendProxy worker stopped.") def _ping(self): """ Heartbeat helper. Sends a PING request just to know that the server is alive. """ - if time.time() > self._ping_at: - self._send_request(PING_REQUEST) - self._reset_ping() + self._send_request(PING_REQUEST) def _api_call(self, *args, **kwargs): """ @@ -162,6 +230,8 @@ class BackendProxy(object): 'arguments': kwargs, } + request_json = None + try: request_json = zmq.utils.jsonapi.dumps(request) except Exception as e: @@ -172,12 +242,12 @@ class BackendProxy(object): raise # queue the call in order to handle the request in a thread safe way. - self._call_queue.put(request_json) + self._send_request(request_json) if api_method == STOP_REQUEST: - self._call_queue.put(STOP_REQUEST) + self._stop() - def _send_request(self, request, retry=True, timeout=None): + def _send_request(self, request): """ Send the given request to the server. This is used from a thread safe loop in order to avoid sending a @@ -185,49 +255,10 @@ class BackendProxy(object): :param request: the request to send. :type request: str - :param retry: whether we should retry or not in case of timeout. - :type retry: bool - :param timeout: a custom timeout (milliseconds) to wait for a response. - :type timeout: int """ - # logger.debug("Sending request to backend: {0}".format(request)) - self._socket.send(request) - - poll = zmq.Poller() - poll.register(self._socket, zmq.POLLIN) - - reply = None - - tries = 0 - if not retry: - tries = self.POLL_TRIES + 1 # this means: no retries left - - if timeout is None: - timeout = self.POLL_TIMEOUT - - while True: - socks = dict(poll.poll(timeout)) - if socks.get(self._socket) == zmq.POLLIN: - reply = self._socket.recv() - break - - tries += 1 - if tries < self.POLL_TRIES: - logger.warning('Retrying receive... {0}/{1}'.format( - tries, self.POLL_TRIES)) - else: - break - - if reply is None: - msg = "Timeout error contacting backend." - logger.critical(msg) - self.online = False - else: - # msg = "Received reply for '{0}' -> '{1}'".format(request, reply) - # logger.debug(msg) - self.online = True - # request received, no ping needed for other interval. - self._reset_ping() + with self._work_lock: # avoid sending after connection was closed + if self._do_work.is_set(): + self._connection.send(request) def __getattribute__(self, name): """ diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 1efcda6c..0a2f1029 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -63,7 +63,8 @@ from leap.common import certs as leap_certs from leap.keymanager import openpgp -from leap.soledad.client.secrets import NoStorageSecret, PassphraseTooShort +from leap.soledad.client.secrets import PassphraseTooShort +from leap.soledad.client.secrets import NoStorageSecret logger = logging.getLogger(__name__) diff --git a/src/leap/bitmask/gui/app.py b/src/leap/bitmask/gui/app.py index 5fe031b1..46f276e1 100644 --- a/src/leap/bitmask/gui/app.py +++ b/src/leap/bitmask/gui/app.py @@ -20,7 +20,6 @@ and the signaler get signals from the backend. """ import logging -from functools import partial from PySide import QtCore, QtGui from leap.bitmask.config.leapsettings import LeapSettings -- cgit v1.2.3 From 9515c944279e08221de1852ab373d24232dccbf1 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 29 Apr 2015 00:55:53 +0200 Subject: [bug] fix soledad bootstrap sync issues * Instead of checking if soledad is still syncing for the timeuot cancel the delayed call. * Count retries properly. * Now soledad sync only returns SoledadError (#6981). --- .../bitmask/services/soledad/soledadbootstrapper.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 6cb4c116..9864e4e1 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -22,7 +22,6 @@ import os import socket import sys -from ssl import SSLError from sqlite3 import ProgrammingError as sqlite_ProgrammingError from u1db import errors as u1db_errors @@ -621,14 +620,17 @@ class Syncer(object): # for debugging. self._sync_deferred = self._soledad.sync(defer_decryption=True) self._sync_deferred.addCallbacks(self._success, self._error) - reactor.callLater(self.WAIT_MAX_SECONDS, self._timeout) + self._timeout_delayed_call = reactor.callLater(self.WAIT_MAX_SECONDS, + self._timeout) def _success(self, result): logger.debug("Soledad has been synced!") + self._timeout_delayed_call.cancel() self._callback_deferred.callback(result) # so long, and thanks for all the fish def _error(self, failure): + self._timeout_delayed_call.cancel() if failure.check(InvalidAuthTokenError): logger.error('Invalid auth token while trying to self Soledad') self._signaler.signal( @@ -638,22 +640,11 @@ class Syncer(object): sqlcipher_ProgrammingError): logger.exception("%r" % (failure.value,)) self._callback_deferred.fail(failure) - elif failure.check(SSLError): - logger.error("%r" % (failure.value,)) - self._retry() - elif failure.check(u1db_errors.InvalidGeneration): - logger.error("%r" % (failure.value,)) - self._retry() else: - logger.exception("Unhandled error while syncing " - "soledad: %r" % (failure.value,)) + logger.error("%r" % (failure.value,)) self._retry() def _timeout(self): - if not self._soledad.syncing: - # timeout only if is still syncing - return - # maybe it's my connection, but I'm getting # ssl handshake timeouts and read errors quite often. # A particularly big sync is a disaster. @@ -666,7 +657,7 @@ class Syncer(object): def _retry(self): self._tries += 1 - if self._tries > self.MAX_SYNC_RETRIES: + if self._tries < self.MAX_SYNC_RETRIES: msg = "Sync failed, retrying... (retry {0} of {1})".format( self._tries, self.MAX_SYNC_RETRIES) logger.warning(msg) -- cgit v1.2.3 From 5a0de9fd25d783039345674680c67a2bd91815a5 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 7 May 2015 11:21:02 -0300 Subject: [feat] adapt to new events api on common - Related: #6359 --- src/leap/bitmask/app.py | 2 +- src/leap/bitmask/crypto/srpauth.py | 13 +-- src/leap/bitmask/gui/mail_status.py | 170 ++++++++++++++-------------- src/leap/bitmask/gui/mainwindow.py | 37 +++--- src/leap/bitmask/platform_init/locks.py | 7 +- src/leap/bitmask/services/mail/conductor.py | 53 +++++---- 6 files changed, 140 insertions(+), 142 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 0517a071..db390a63 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -154,7 +154,7 @@ def start_app(): # do_mail_plumbing(opts) try: - event_server.ensure_server(event_server.SERVER_PORT) + event_server.ensure_server() except Exception as e: # We don't even have logger configured in here print "Could not ensure server: %r" % (e,) diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index fe177e5a..1e96030e 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -37,8 +37,8 @@ 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 from leap.common.check import leap_assert -from leap.common.events import signal as events_signal -from leap.common.events import events_pb2 as proto +from leap.common.events import emit, catalog + logger = logging.getLogger(__name__) @@ -395,9 +395,7 @@ class SRPAuthImpl(object): (json_content,)) raise SRPAuthBadDataFromServer() - events_signal( - proto.CLIENT_UID, content=uuid, - reqcbk=lambda req, res: None) # make the rpc call async + emit(catalog.CLIENT_UID, uuid) # make the rpc call async return M2 @@ -433,9 +431,8 @@ class SRPAuthImpl(object): logger.error("Bad cookie from server (missing _session_id)") raise SRPAuthNoSessionId() - events_signal( - proto.CLIENT_SESSION_ID, content=session_id, - reqcbk=lambda req, res: None) # make the rpc call asynch + # make the rpc call async + emit(catalog.CLIENT_SESSION_ID, session_id) self.set_session_id(session_id) logger.debug("SUCCESS LOGIN") diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index bbfbafb5..a3a1be37 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -25,7 +25,7 @@ from leap.bitmask.platform_init import IS_LINUX from leap.bitmask.services import get_service_display_name, MX_SERVICE from leap.common.check import leap_assert, leap_assert_type from leap.common.events import register -from leap.common.events import events_pb2 as proto +from leap.common.events import catalog from ui_mail_status import Ui_MailStatusWidget @@ -38,7 +38,7 @@ class MailStatusWidget(QtGui.QWidget): """ _soledad_event = QtCore.Signal(object) _smtp_event = QtCore.Signal(object) - _imap_event = QtCore.Signal(object) + _imap_event = QtCore.Signal(object, object) _keymanager_event = QtCore.Signal(object) def __init__(self, parent=None): @@ -70,51 +70,39 @@ class MailStatusWidget(QtGui.QWidget): self.ERROR_ICON_TRAY = None self._set_mail_icons() - register(signal=proto.KEYMANAGER_LOOKING_FOR_KEY, - callback=self._mail_handle_keymanager_events, - reqcbk=lambda req, resp: None) + register(event=catalog.KEYMANAGER_LOOKING_FOR_KEY, + callback=self._mail_handle_keymanager_events) - register(signal=proto.KEYMANAGER_KEY_FOUND, - callback=self._mail_handle_keymanager_events, - reqcbk=lambda req, resp: None) + register(event=catalog.KEYMANAGER_KEY_FOUND, + callback=self._mail_handle_keymanager_events) - # register(signal=proto.KEYMANAGER_KEY_NOT_FOUND, - # callback=self._mail_handle_keymanager_events, - # reqcbk=lambda req, resp: None) + # register(event=catalog.KEYMANAGER_KEY_NOT_FOUND, + # callback=self._mail_handle_keymanager_events) - register(signal=proto.KEYMANAGER_STARTED_KEY_GENERATION, - callback=self._mail_handle_keymanager_events, - reqcbk=lambda req, resp: None) + register(event=catalog.KEYMANAGER_STARTED_KEY_GENERATION, + callback=self._mail_handle_keymanager_events) - register(signal=proto.KEYMANAGER_FINISHED_KEY_GENERATION, - callback=self._mail_handle_keymanager_events, - reqcbk=lambda req, resp: None) + register(event=catalog.KEYMANAGER_FINISHED_KEY_GENERATION, + callback=self._mail_handle_keymanager_events) - register(signal=proto.KEYMANAGER_DONE_UPLOADING_KEYS, - callback=self._mail_handle_keymanager_events, - reqcbk=lambda req, resp: None) + register(event=catalog.KEYMANAGER_DONE_UPLOADING_KEYS, + callback=self._mail_handle_keymanager_events) - register(signal=proto.SOLEDAD_DONE_DOWNLOADING_KEYS, - callback=self._mail_handle_soledad_events, - reqcbk=lambda req, resp: None) + register(event=catalog.SOLEDAD_DONE_DOWNLOADING_KEYS, + callback=self._mail_handle_soledad_events) - register(signal=proto.SOLEDAD_DONE_UPLOADING_KEYS, - callback=self._mail_handle_soledad_events, - reqcbk=lambda req, resp: None) + register(event=catalog.SOLEDAD_DONE_UPLOADING_KEYS, + callback=self._mail_handle_soledad_events) - register(signal=proto.IMAP_UNREAD_MAIL, - callback=self._mail_handle_imap_events, - reqcbk=lambda req, resp: None) - register(signal=proto.IMAP_SERVICE_STARTED, - callback=self._mail_handle_imap_events, - reqcbk=lambda req, resp: None) - register(signal=proto.SMTP_SERVICE_STARTED, - callback=self._mail_handle_imap_events, - reqcbk=lambda req, resp: None) + register(event=catalog.MAIL_UNREAD_MESSAGES, + callback=self._mail_handle_imap_events) + register(event=catalog.IMAP_SERVICE_STARTED, + callback=self._mail_handle_imap_events) + register(event=catalog.SMTP_SERVICE_STARTED, + callback=self._mail_handle_imap_events) - register(signal=proto.SOLEDAD_INVALID_AUTH_TOKEN, - callback=self.set_soledad_invalid_auth_token, - reqcbk=lambda req, resp: None) + register(event=catalog.SOLEDAD_INVALID_AUTH_TOKEN, + callback=self.set_soledad_invalid_auth_token) self._soledad_event.connect( self._mail_handle_soledad_events_slot) @@ -194,12 +182,14 @@ 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): + def set_soledad_invalid_auth_token(self, event, content): """ - TRIGGERS: - SoledadBootstrapper.soledad_invalid_token - This method is called when the auth token is invalid + + :param event: The event that triggered the callback. + :type event: str + :param content: The content of the event. + :type content: list """ msg = self.tr("Invalid auth token, try logging in again.") self._set_mail_status(msg, ready=-1) @@ -239,58 +229,62 @@ class MailStatusWidget(QtGui.QWidget): self._action_mail_status.setText(tray_status) self._update_systray_tooltip() - def _mail_handle_soledad_events(self, req): + def _mail_handle_soledad_events(self, event, content): """ Callback for handling events that are emitted from Soledad - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest + :param event: The event that triggered the callback. + :type event: str + :param content: The content of the event. + :type content: list """ - self._soledad_event.emit(req) + self._soledad_event.emit(event) - def _mail_handle_soledad_events_slot(self, req): + def _mail_handle_soledad_events_slot(self, event): """ TRIGGERS: _mail_handle_soledad_events Reacts to an Soledad event - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest + :param event: The event that triggered the callback. + :type event: str """ self._set_mail_status(self.tr("Starting..."), ready=1) ext_status = "" - if req.event == proto.SOLEDAD_DONE_UPLOADING_KEYS: + if event == catalog.SOLEDAD_DONE_UPLOADING_KEYS: ext_status = self.tr("Soledad has started...") - elif req.event == proto.SOLEDAD_DONE_DOWNLOADING_KEYS: + elif event == catalog.SOLEDAD_DONE_DOWNLOADING_KEYS: ext_status = self.tr("Soledad is starting, please wait...") else: leap_assert(False, "Don't know how to handle this state: %s" - % (req.event)) + % (event)) self._set_mail_status(ext_status, ready=1) - def _mail_handle_keymanager_events(self, req): + def _mail_handle_keymanager_events(self, event, content): """ Callback for the KeyManager events - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest + :param event: The event that triggered the callback. + :type event: str + :param content: The content of the event. + :type content: list """ - self._keymanager_event.emit(req) + self._keymanager_event.emit(event) - def _mail_handle_keymanager_events_slot(self, req): + def _mail_handle_keymanager_events_slot(self, event): """ TRIGGERS: _mail_handle_keymanager_events Reacts to an KeyManager event - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest + :param event: The event that triggered the callback. + :type event: str """ # We want to ignore this kind of events once everything has # started @@ -299,88 +293,92 @@ class MailStatusWidget(QtGui.QWidget): ext_status = "" - if req.event == proto.KEYMANAGER_LOOKING_FOR_KEY: + if event == catalog.KEYMANAGER_LOOKING_FOR_KEY: ext_status = self.tr("Initial sync in progress, please wait...") - elif req.event == proto.KEYMANAGER_KEY_FOUND: + elif event == catalog.KEYMANAGER_KEY_FOUND: ext_status = self.tr("Found key! Starting mail...") - # elif req.event == proto.KEYMANAGER_KEY_NOT_FOUND: + # elif event == catalog.KEYMANAGER_KEY_NOT_FOUND: # ext_status = self.tr("Key not found!") - elif req.event == proto.KEYMANAGER_STARTED_KEY_GENERATION: + elif event == catalog.KEYMANAGER_STARTED_KEY_GENERATION: ext_status = self.tr( "Generating new key, this may take a few minutes.") - elif req.event == proto.KEYMANAGER_FINISHED_KEY_GENERATION: + elif event == catalog.KEYMANAGER_FINISHED_KEY_GENERATION: ext_status = self.tr("Finished generating key!") - elif req.event == proto.KEYMANAGER_DONE_UPLOADING_KEYS: + elif event == catalog.KEYMANAGER_DONE_UPLOADING_KEYS: ext_status = self.tr("Starting mail...") else: leap_assert(False, "Don't know how to handle this state: %s" - % (req.event)) + % (event)) self._set_mail_status(ext_status, ready=1) - def _mail_handle_smtp_events(self, req): + def _mail_handle_smtp_events(self, event): """ Callback for the SMTP events - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest + :param event: The event that triggered the callback. + :type event: str """ - self._smtp_event.emit(req) + self._smtp_event.emit(event) - def _mail_handle_smtp_events_slot(self, req): + def _mail_handle_smtp_events_slot(self, event): """ TRIGGERS: _mail_handle_smtp_events Reacts to an SMTP event - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest + :param event: The event that triggered the callback. + :type event: str """ ext_status = "" - if req.event == proto.SMTP_SERVICE_STARTED: + if event == catalog.SMTP_SERVICE_STARTED: self._smtp_started = True - elif req.event == proto.SMTP_SERVICE_FAILED_TO_START: + elif event == catalog.SMTP_SERVICE_FAILED_TO_START: ext_status = self.tr("SMTP failed to start, check the logs.") else: leap_assert(False, "Don't know how to handle this state: %s" - % (req.event)) + % (event)) self._set_mail_status(ext_status, ready=2) # ----- XXX deprecate (move to mail conductor) - def _mail_handle_imap_events(self, req): + def _mail_handle_imap_events(self, event, content): """ Callback for the IMAP events - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest + :param event: The event that triggered the callback. + :type event: str + :param content: The content of the event. + :type content: list """ - self._imap_event.emit(req) + self._imap_event.emit(event, content) - def _mail_handle_imap_events_slot(self, req): + def _mail_handle_imap_events_slot(self, event, content): """ TRIGGERS: _mail_handle_imap_events Reacts to an IMAP event - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest + :param event: The event that triggered the callback. + :type event: str + :param content: The content of the event. + :type content: list """ ext_status = None - if req.event == proto.IMAP_UNREAD_MAIL: + if event == catalog.MAIL_UNREAD_MESSAGES: # 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 + count = content if count != "0": status = self.tr("{0} Unread Emails " "in your Inbox").format(count) @@ -390,7 +388,7 @@ class MailStatusWidget(QtGui.QWidget): self._set_mail_status(status, ready=2) else: self._set_mail_status("", ready=2) - elif req.event == proto.IMAP_SERVICE_STARTED: + elif event == catalog.IMAP_SERVICE_STARTED: self._imap_started = True if ext_status is not None: self._set_mail_status(ext_status, ready=1) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 4b665337..0df6ef84 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -63,7 +63,7 @@ from leap.bitmask.util.keyring_helpers import has_keyring from leap.bitmask.logs.leap_log_handler import LeapLogHandler from leap.common.events import register -from leap.common.events import events_pb2 as proto +from leap.common.events import catalog from leap.mail.imap.service.imap import IMAP_PORT @@ -107,12 +107,10 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): autostart.set_autostart(True) # register leap events ######################################## - register(signal=proto.UPDATER_NEW_UPDATES, - callback=self._new_updates_available, - reqcbk=lambda req, resp: None) # make rpc call async - register(signal=proto.RAISE_WINDOW, - callback=self._on_raise_window_event, - reqcbk=lambda req, resp: None) # make rpc call async + register(event=catalog.UPDATER_NEW_UPDATES, + callback=self._new_updates_available) # make rpc call async + register(event=catalog.RAISE_WINDOW, + callback=self._on_raise_window_event) # make rpc call async # end register leap events #################################### self._updates_content = "" @@ -682,29 +680,31 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): # updates # - def _new_updates_available(self, req): + def _new_updates_available(self, event, content): """ Callback for the new updates event - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest + :param event: The event that triggered the callback. + :type event: str + :param content: The content of the event. + :type content: list """ - self.new_updates.emit(req) + self.new_updates.emit(content) - def _react_to_new_updates(self, req): + def _react_to_new_updates(self, content): """ TRIGGERS: self.new_updates Display the new updates label and sets the updates_content - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest + :param content: The content of the event. + :type content: list """ self.moveToThread(QtCore.QCoreApplication.instance().thread()) self.ui.lblNewUpdates.setVisible(True) self.ui.btnMore.setVisible(True) - self._updates_content = req.content + self._updates_content = content def _updates_details(self): """ @@ -1570,9 +1570,14 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): # window handling methods # - def _on_raise_window_event(self, req): + def _on_raise_window_event(self, event, content): """ 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() diff --git a/src/leap/bitmask/platform_init/locks.py b/src/leap/bitmask/platform_init/locks.py index ac45a5ce..eff900fa 100644 --- a/src/leap/bitmask/platform_init/locks.py +++ b/src/leap/bitmask/platform_init/locks.py @@ -23,8 +23,7 @@ import os import platform from leap.bitmask.platform_init import IS_WIN, IS_UNIX -from leap.common.events import signal as signal_event -from leap.common.events import events_pb2 as proto +from leap.common.events import emit, catalog if IS_UNIX: from fcntl import flock, LOCK_EX, LOCK_NB @@ -364,7 +363,7 @@ def we_are_the_one_and_only(): locker.get_lock() we_are_the_one = locker.locked_by_us if not we_are_the_one: - signal_event(proto.RAISE_WINDOW) + emit(catalog.RAISE_WINDOW) return we_are_the_one elif IS_WIN: @@ -385,7 +384,7 @@ def we_are_the_one_and_only(): # let's assume it's a stalled lock we_are_the_one = True - signal_event(proto.RAISE_WINDOW) + emit(catalog.RAISE_WINDOW) while check_interval(): if get_modification_ts(lock_path) > ts: diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 42bdd032..b76ce436 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -24,7 +24,7 @@ from leap.bitmask.gui import statemachines from leap.bitmask.services.mail import connection as mail_connection from leap.bitmask.services.mail.emailfirewall import get_email_firewall -from leap.common.events import events_pb2 as leap_events +from leap.common.events import catalog from leap.common.events import register as leap_register @@ -42,15 +42,12 @@ class IMAPControl(object): self.imap_machine = None self.imap_connection = None - leap_register(signal=leap_events.IMAP_SERVICE_STARTED, - callback=self._handle_imap_events, - reqcbk=lambda req, resp: None) - leap_register(signal=leap_events.IMAP_SERVICE_FAILED_TO_START, - callback=self._handle_imap_events, - reqcbk=lambda req, resp: None) - leap_register(signal=leap_events.IMAP_CLIENT_LOGIN, - callback=self._handle_imap_events, - reqcbk=lambda req, resp: None) + leap_register(event=catalog.IMAP_SERVICE_STARTED, + callback=self._handle_imap_events) + leap_register(event=catalog.IMAP_SERVICE_FAILED_TO_START, + callback=self._handle_imap_events) + leap_register(event=catalog.IMAP_CLIENT_LOGIN, + callback=self._handle_imap_events) def set_imap_connection(self, imap_connection): """ @@ -77,18 +74,20 @@ class IMAPControl(object): self._backend.imap_stop_service() - def _handle_imap_events(self, req): + def _handle_imap_events(self, event, content): """ Callback handler for the IMAP events - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest + :param event: The event that triggered the callback. + :type event: str + :param content: The content of the event. + :type content: list """ - if req.event == leap_events.IMAP_SERVICE_STARTED: + if event == catalog.IMAP_SERVICE_STARTED: self._on_imap_connected() - elif req.event == leap_events.IMAP_SERVICE_FAILED_TO_START: + elif event == catalog.IMAP_SERVICE_FAILED_TO_START: self._on_imap_failed() - elif req.event == leap_events.IMAP_CLIENT_LOGIN: + elif event == catalog.IMAP_CLIENT_LOGIN: self._on_mail_client_logged_in() def _on_mail_client_logged_in(self): @@ -124,12 +123,10 @@ class SMTPControl(object): self.smtp_connection = None self.smtp_machine = None - leap_register(signal=leap_events.SMTP_SERVICE_STARTED, - callback=self._handle_smtp_events, - reqcbk=lambda req, resp: None) - leap_register(signal=leap_events.SMTP_SERVICE_FAILED_TO_START, - callback=self._handle_smtp_events, - reqcbk=lambda req, resp: None) + leap_register(event=catalog.SMTP_SERVICE_STARTED, + callback=self._handle_smtp_events) + leap_register(event=catalog.SMTP_SERVICE_FAILED_TO_START, + callback=self._handle_smtp_events) def set_smtp_connection(self, smtp_connection): """ @@ -158,16 +155,18 @@ class SMTPControl(object): self.smtp_connection.qtsigs.disconnecting_signal.emit() self._backend.smtp_stop_service() - def _handle_smtp_events(self, req): + def _handle_smtp_events(self, event, content): """ Callback handler for the SMTP events. - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest + :param event: The event that triggered the callback. + :type event: str + :param content: The content of the event. + :type content: list """ - if req.event == leap_events.SMTP_SERVICE_STARTED: + if event == catalog.SMTP_SERVICE_STARTED: self.on_smtp_connected() - elif req.event == leap_events.SMTP_SERVICE_FAILED_TO_START: + elif event == catalog.SMTP_SERVICE_FAILED_TO_START: self.on_smtp_failed() def on_smtp_connecting(self): -- cgit v1.2.3 From 907a864c774cc43fcf0ebff3d6b5082901661e98 Mon Sep 17 00:00:00 2001 From: drebs Date: Fri, 29 May 2015 19:02:44 -0300 Subject: [bug] move events server init to backend If the events server is initialized in a different process than the backend, the txzmq socket raises an "zmq.error.ZMQError: Interrupted system call" exception during the events server initialization. Despite that, communication seems to work flawlessly after the initialization. Moving the events server initialization to the same process as the backend causes the exception to not be raised during events server intialization. --- src/leap/bitmask/app.py | 7 ------- src/leap/bitmask/backend_app.py | 5 +++++ 2 files changed, 5 insertions(+), 7 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index db390a63..731c168c 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -57,7 +57,6 @@ from leap.bitmask.services.mail import plumber from leap.bitmask.util import leap_argparse, flags_to_dict from leap.bitmask.util.requirement_checker import check_requirements -from leap.common.events import server as event_server from leap.mail import __version__ as MAIL_VERSION import codecs @@ -153,12 +152,6 @@ def start_app(): # XXX mail repair commands disabled for now # do_mail_plumbing(opts) - try: - event_server.ensure_server() - except Exception as e: - # 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)) diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py index 3e88a95a..5fc8d1e2 100644 --- a/src/leap/bitmask/backend_app.py +++ b/src/leap/bitmask/backend_app.py @@ -21,6 +21,8 @@ import logging import multiprocessing import signal +from leap.common.events import server as event_server + from leap.bitmask.backend.leapbackend import LeapBackend from leap.bitmask.backend.utils import generate_zmq_certificates from leap.bitmask.config import flags @@ -68,6 +70,9 @@ def run_backend(bypass_checks=False, flags_dict=None, frontend_pid=None): if flags_dict is not None: dict_to_flags(flags_dict) + # start the events server + event_server.ensure_server() + backend = LeapBackend(bypass_checks=bypass_checks, frontend_pid=frontend_pid) backend.run() -- cgit v1.2.3 From 10dd29e09e48ae8c1aab8e38cfcede1d49e190ec Mon Sep 17 00:00:00 2001 From: drebs Date: Mon, 1 Jun 2015 12:32:33 -0300 Subject: [bug] do not unset soledad proxy object too early The backend was unsetting the soledad proxied object before actually calling the soledad close() method and this was causing the application to hang when exitting. As the local soledad component's cancel_bootstrap() method is called before the close() method, we'd better not unset the proxied object there, and let only the close method do it. This commit fixes this by just removing the line that unsets the proxied object in the wrong place. --- src/leap/bitmask/backend/components.py | 1 - 1 file changed, 1 deletion(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index 0a2f1029..d602156d 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -829,7 +829,6 @@ class Soledad(object): logger.debug("Cancelling soledad defer.") self._soledad_defer.cancel() self._soledad_defer = None - zope.proxy.setProxiedObject(self._soledad_proxy, None) def close(self): """ -- cgit v1.2.3 From d8afaba3f1ec9f12d9206c511bccd61da308638c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Parm=C3=A9nides=20GV?= Date: Fri, 5 Jun 2015 11:08:16 +0200 Subject: bug regression: polkit agent is not automatically launched #6652 Polkit was being launched and detected correctly, but Bitmask didn't propagate this result to upper layers, so mainwindow thought the platform wasn't initialized and then quitted without explanation. Tested on debian testing, on June 5th 2015, using i3 window manager. - Resolves: #6652 --- src/leap/bitmask/platform_init/initializers.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src/leap') diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index 086927dd..be8a7ab9 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -202,6 +202,7 @@ def check_polkit(): try: LinuxPolicyChecker.maybe_pkexec() + return True except Exception: logger.error("No polkit agent running.") -- cgit v1.2.3 From 4d33b0637ae1280ba818b40eb16003974666721b Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 8 Jun 2015 12:09:53 -0400 Subject: [bug] fix attribute error on mail conductor due to remaining bits that had not been changed after a refactor. - Resolves: #7093 --- src/leap/bitmask/services/mail/conductor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index b76ce436..2f1d20e6 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -94,7 +94,9 @@ class IMAPControl(object): """ On mail client logged in, fetch incoming mail. """ - self._controller.imap_service_fetch() + # XXX needs to be adapted to the new-ish incoming mail service. + # Doing nothing for now, this could be moved to mail package itself. + logger.debug("A MUA has logged in, should react by forcing a fetch.") def _on_imap_connecting(self): """ -- cgit v1.2.3 From 8752f7f03a04ca7fa1169885adc9dbfce8bebbd4 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 10 Jun 2015 13:35:05 -0300 Subject: [bug] don't run event server on the standalone We don't need to run the event server on the backend if we are running from the standalone bundle since the launcher takes care of that. - Related: #7126 --- src/leap/bitmask/backend_app.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py index 5fc8d1e2..af71ba12 100644 --- a/src/leap/bitmask/backend_app.py +++ b/src/leap/bitmask/backend_app.py @@ -70,8 +70,13 @@ def run_backend(bypass_checks=False, flags_dict=None, frontend_pid=None): if flags_dict is not None: dict_to_flags(flags_dict) - # start the events server - event_server.ensure_server() + # HACK we should be able to run the ensure_server anyway but right now it + # breaks if we run it twice. + if not flags.STANDALONE: + # start the events server + # This is not needed for the standalone bundle since the launcher takes + # care of it. + event_server.ensure_server() backend = LeapBackend(bypass_checks=bypass_checks, frontend_pid=frontend_pid) -- cgit v1.2.3 From 584a6d93ad1fe1ba46929108f002a16a8b70e95d Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 5 Jun 2015 13:36:51 -0300 Subject: [feat] add a zmq based logger, change log window - Add a new handler for a zmq/thread-safe log send between components. - Update the log window to use this new handler. - Remove old custom handler We have implemented a new handler that uses logbook, so this one is no longer needed. - Adapt log silencer to new handler - Use log file always as default - Related: #6733 --- src/leap/bitmask/app.py | 11 +- src/leap/bitmask/config/flags.py | 4 + src/leap/bitmask/gui/loggerwindow.py | 263 --------------- src/leap/bitmask/gui/logwindow.py | 360 +++++++++++++++++++++ src/leap/bitmask/logs/__init__.py | 6 +- src/leap/bitmask/logs/leap_log_handler.py | 137 -------- src/leap/bitmask/logs/log_silencer.py | 10 +- src/leap/bitmask/logs/safezmqhandler.py | 118 +++++++ .../bitmask/logs/tests/test_leap_log_handler.py | 120 ------- src/leap/bitmask/logs/utils.py | 87 ++--- src/leap/bitmask/util/leap_argparse.py | 3 - 11 files changed, 530 insertions(+), 589 deletions(-) delete mode 100644 src/leap/bitmask/gui/loggerwindow.py create mode 100644 src/leap/bitmask/gui/logwindow.py delete mode 100644 src/leap/bitmask/logs/leap_log_handler.py create mode 100644 src/leap/bitmask/logs/safezmqhandler.py delete mode 100644 src/leap/bitmask/logs/tests/test_leap_log_handler.py (limited to 'src/leap') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 731c168c..adab8652 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -124,7 +124,6 @@ def start_app(): options = { 'start_hidden': opts.start_hidden, 'debug': opts.debug, - 'log_file': opts.log_file, } flags.STANDALONE = opts.standalone @@ -137,7 +136,13 @@ def start_app(): flags.CA_CERT_FILE = opts.ca_cert_file - replace_stdout = True + flags.DEBUG = opts.debug + + logger = get_logger() + + # NOTE: since we are not using this right now, the code that replaces the + # stdout needs to be reviewed when we enable this again + # replace_stdout = True # XXX mail repair commands disabled for now # if opts.repair or opts.import_maildir: @@ -145,8 +150,6 @@ def start_app(): # this could be more generic with a Command class. # replace_stdout = False - logger = create_logger(opts.debug, opts.log_file, 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. # XXX mail repair commands disabled for now diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py index cdde1971..1cf1d15a 100644 --- a/src/leap/bitmask/config/flags.py +++ b/src/leap/bitmask/config/flags.py @@ -58,3 +58,7 @@ SKIP_WIZARD_CHECKS = False # This flag tells us whether the current pyzmq supports using CurveZMQ or not. ZMQ_HAS_CURVE = None + +# Store the needed loglevel globally since the logger handlers goes through +# threads and processes +DEBUG = False diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py deleted file mode 100644 index 463d2412..00000000 --- a/src/leap/bitmask/gui/loggerwindow.py +++ /dev/null @@ -1,263 +0,0 @@ -# -*- coding: utf-8 -*- -# loggerwindow.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 . - -""" -History log window -""" -import cgi -import logging - -from PySide import QtCore, QtGui - -from ui_loggerwindow import Ui_LoggerWindow - -from leap.bitmask.util.constants import PASTEBIN_API_DEV_KEY -from leap.bitmask.logs.leap_log_handler import LeapLogHandler -from leap.bitmask.util import pastebin -from leap.common.check import leap_assert, leap_assert_type - -logger = logging.getLogger(__name__) - - -class LoggerWindow(QtGui.QDialog): - """ - Window that displays a history of the logged messages in the app. - """ - _paste_ok = QtCore.Signal(object) - _paste_error = QtCore.Signal(object) - - def __init__(self, parent, handler): - """ - Initialize the widget with the custom handler. - - :param handler: Custom handler that supports history and signal. - :type handler: LeapLogHandler. - """ - QtGui.QDialog.__init__(self, parent) - leap_assert(handler, "We need a handler for the logger window") - leap_assert_type(handler, LeapLogHandler) - - # Load UI - self.ui = Ui_LoggerWindow() - self.ui.setupUi(self) - - # Make connections - self.ui.btnSave.clicked.connect(self._save_log_to_file) - self.ui.btnDebug.toggled.connect(self._load_history), - self.ui.btnInfo.toggled.connect(self._load_history), - self.ui.btnWarning.toggled.connect(self._load_history), - self.ui.btnError.toggled.connect(self._load_history), - 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._paste_ok.connect(self._pastebin_ok) - self._paste_error.connect(self._pastebin_err) - - self._current_filter = "" - self._current_history = "" - - # Load logging history and connect logger with the widget - self._logging_handler = handler - self._connect_to_handler() - self._load_history() - - def _connect_to_handler(self): - """ - This method connects the loggerwindow with the handler through a - signal communicate the logger events. - """ - self._logging_handler.new_log.connect(self._add_log_line) - - def _add_log_line(self, log): - """ - Adds a line to the history, only if it's in the desired levels to show. - - :param log: a log record to be inserted in the widget - :type log: a dict with RECORD_KEY and MESSAGE_KEY. - the record contains the LogRecord of the logging module, - the message contains the formatted message for the log. - """ - html_style = { - logging.DEBUG: "background: #CDFFFF;", - logging.INFO: "background: white;", - logging.WARNING: "background: #FFFF66;", - logging.ERROR: "background: red; color: white;", - logging.CRITICAL: "background: red; color: white; font: bold;" - } - level = log[LeapLogHandler.RECORD_KEY].levelno - message = cgi.escape(log[LeapLogHandler.MESSAGE_KEY]) - - if self._logs_to_display[level]: - open_tag = "" - open_tag += "" - close_tag = "" - message = open_tag + message + close_tag - - filter_by = self._current_filter - msg = message - if self.ui.cbCaseInsensitive.isChecked(): - msg = msg.upper() - filter_by = filter_by.upper() - - if msg.find(filter_by) != -1: - self.ui.txtLogHistory.append(message) - - def _load_history(self): - """ - Load the previous logged messages in the widget. - They are stored in the custom handler. - """ - 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 = line[LeapLogHandler.MESSAGE_KEY] - current_history.append(message) - - self._current_history = "\n".join(current_history) - - def _set_logs_to_display(self): - """ - Sets the logs_to_display dict getting the toggled options from the ui - """ - self._logs_to_display = { - logging.DEBUG: self.ui.btnDebug.isChecked(), - logging.INFO: self.ui.btnInfo.isChecked(), - logging.WARNING: self.ui.btnWarning.isChecked(), - logging.ERROR: self.ui.btnError.isChecked(), - logging.CRITICAL: self.ui.btnCritical.isChecked() - } - - def _filter_by(self, text): - """ - Sets the text to use for filtering logs in the log window. - - :param text: the text to compare with the logs when filtering. - :type text: str - """ - self._current_filter = text - self._load_history() - - def _save_log_to_file(self): - """ - Lets the user save the current log to a file - """ - fileName, filtr = QtGui.QFileDialog.getSaveFileName( - self, self.tr("Save As"), - options=QtGui.QFileDialog.DontUseNativeDialog) - - if fileName: - try: - with open(fileName, 'w') as output: - history = self.ui.txtLogHistory.toPlainText() - # Chop some \n. - # html->plain adds several \n because the html is made - # using table cells. - history = history.replace('\n\n\n', '\n') - - output.write(history) - logger.debug('Log saved in %s' % (fileName, )) - except IOError, e: - logger.error("Error saving log file: %r" % (e, )) - 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_ok(self, link): - """ - Handle a successful paste. - - :param link: the recently created pastebin link. - :type link: str - """ - self._set_pastebin_sending(False) - msg = self.tr("Your pastebin link {0}") - msg = msg.format(link) - - # We save the dialog in an instance member to avoid dialog being - # deleted right after we exit this method - self._msgBox = msgBox = QtGui.QMessageBox( - QtGui.QMessageBox.Information, self.tr("Pastebin OK"), msg) - msgBox.setWindowModality(QtCore.Qt.NonModal) - msgBox.show() - - def _pastebin_err(self, failure): - """ - Handle a failure in paste. - - :param failure: the exception that made the paste fail. - :type failure: Exception - """ - self._set_pastebin_sending(False) - logger.error(repr(failure)) - - msg = self.tr("Sending logs to Pastebin failed!") - if isinstance(failure, pastebin.PostLimitError): - msg = self.tr('Maximum amount of submissions reached for today.') - - # We save the dialog in an instance member to avoid dialog being - # deleted right after we exit this method - self._msgBox = msgBox = QtGui.QMessageBox( - QtGui.QMessageBox.Critical, self.tr("Pastebin Error"), msg) - msgBox.setWindowModality(QtCore.Qt.NonModal) - msgBox.show() - - 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 = pastebin.PastebinAPI() - try: - link = pb.paste(PASTEBIN_API_DEV_KEY, content, - paste_name="Bitmask log", - paste_expire_date='1M') - # convert to 'raw' link - link = "http://pastebin.com/raw.php?i=" + link.split('/')[-1] - - self._paste_ok.emit(link) - except Exception as e: - self._paste_error.emit(e) - - self._set_pastebin_sending(True) - - self._paste_thread = QtCore.QThread() - self._paste_thread.run = lambda: do_pastebin() - self._paste_thread.start() diff --git a/src/leap/bitmask/gui/logwindow.py b/src/leap/bitmask/gui/logwindow.py new file mode 100644 index 00000000..123f14cc --- /dev/null +++ b/src/leap/bitmask/gui/logwindow.py @@ -0,0 +1,360 @@ +# -*- coding: utf-8 -*- +# logwindow.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 . + +""" +History log window +""" +import cgi + +from PySide import QtCore, QtGui + +import logbook +from logbook.queues import ZeroMQSubscriber + +from ui_loggerwindow import Ui_LoggerWindow + +from leap.bitmask.logs import LOG_FORMAT +from leap.bitmask.logs.utils import get_logger +from leap.bitmask.util.constants import PASTEBIN_API_DEV_KEY +from leap.bitmask.util import pastebin + +logger = get_logger() + +# log history global variable used to store received logs through different +# opened instances of this window +_LOGS_HISTORY = [] + + +class QtLogHandler(logbook.Handler, logbook.StringFormatterHandlerMixin): + """ + Custom log handler which emits a log record with the message properly + formatted using a Qt Signal. + """ + + class _QtSignaler(QtCore.QObject): + """ + inline class used to hold the `new_log` Signal, if this is used + directly in the outside class it fails due how PySide works. + + This is the message we get if not use this method: + TypeError: Error when calling the metaclass bases + metaclass conflict: the metaclass of a derived class must be a + (non-strict) subclass of the metaclasses of all its bases + + """ + new_log = QtCore.Signal(object) + + def emit(self, data): + """ + emit the `new_log` Signal with the given `data` parameter. + + :param data: the data to emit along with the signal. + :type data: object + """ + # WARNING: the new-style connection does NOT work because PySide + # translates the emit method to self.emit, and that collides with + # the emit method for logging.Handler + # self.new_log.emit(log_item) + QtCore.QObject.emit(self, QtCore.SIGNAL('new_log(PyObject)'), data) + + def __init__(self, level=logbook.NOTSET, format_string=None, + encoding=None, filter=None, bubble=False): + + logbook.Handler.__init__(self, level, filter, bubble) + logbook.StringFormatterHandlerMixin.__init__(self, format_string) + + self.qt = self._QtSignaler() + + def __enter__(self): + return logbook.Handler.__enter__(self) + + def __exit__(self, exc_type, exc_value, tb): + return logbook.Handler.__exit__(self, exc_type, exc_value, tb) + + def emit(self, record): + """ + Emit the specified logging record using a Qt Signal. + Also add it to the history in order to be able to access it later. + + :param record: the record to emit + :type record: logbook.LogRecord + """ + global _LOGS_HISTORY + record.msg = self.format(record) + # NOTE: not optimal approach, we may want to look at + # bisect.insort with a custom approach to use key or + # http://code.activestate.com/recipes/577197-sortedcollection/ + # Sort logs on arrival, logs transmitted over zmq may arrive unsorted. + _LOGS_HISTORY.append(record) + _LOGS_HISTORY = sorted(_LOGS_HISTORY, key=lambda r: r.time) + + # XXX: emitting the record on arrival does not allow us to sort here so + # in the GUI the logs may arrive with with some time sort problem. + # We should implement a sort-on-arrive for the log window. + # Maybe we should switch to a tablewidget item that sort automatically + # by timestamp. + # As a user workaround you can close/open the log window + self.qt.emit(record) + + +class LoggerWindow(QtGui.QDialog): + """ + Window that displays a history of the logged messages in the app. + """ + _paste_ok = QtCore.Signal(object) + _paste_error = QtCore.Signal(object) + + def __init__(self, parent): + """ + Initialize the widget. + """ + QtGui.QDialog.__init__(self, parent) + + # Load UI + self.ui = Ui_LoggerWindow() + self.ui.setupUi(self) + + # Make connections + self.ui.btnSave.clicked.connect(self._save_log_to_file) + self.ui.btnDebug.toggled.connect(self._load_history), + self.ui.btnInfo.toggled.connect(self._load_history), + self.ui.btnWarning.toggled.connect(self._load_history), + self.ui.btnError.toggled.connect(self._load_history), + 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._paste_ok.connect(self._pastebin_ok) + self._paste_error.connect(self._pastebin_err) + + self._current_filter = "" + self._current_history = "" + + self._set_logs_to_display() + + self._my_handler = QtLogHandler(format_string=LOG_FORMAT) + self._my_handler.qt.new_log.connect(self._add_log_line) + + self._load_history() + self._connect_to_logbook() + + def _connect_to_logbook(self): + """ + Run in the background the log receiver. + """ + subscriber = ZeroMQSubscriber('tcp://127.0.0.1:5000', multi=True) + self._logbook_controller = subscriber.dispatch_in_background( + self._my_handler) + + def _add_log_line(self, log): + """ + Adds a line to the history, only if it's in the desired levels to show. + + :param log: a log record to be inserted in the widget + :type log: Logbook.LogRecord. + """ + html_style = { + logbook.DEBUG: "background: #CDFFFF;", + logbook.INFO: "background: white;", + logbook.WARNING: "background: #FFFF66;", + logbook.ERROR: "background: red; color: white;", + logbook.CRITICAL: "background: red; color: white; font: bold;" + } + level = log.level + message = cgi.escape(log.msg) + + if self._logs_to_display[level]: + open_tag = "" + open_tag += "" + close_tag = "" + message = open_tag + message + close_tag + + filter_by = self._current_filter + msg = message + if self.ui.cbCaseInsensitive.isChecked(): + msg = msg.upper() + filter_by = filter_by.upper() + + if msg.find(filter_by) != -1: + self.ui.txtLogHistory.append(message) + + def _load_history(self): + """ + Load the previous logged messages in the widget. + They are stored in the custom handler. + """ + self._set_logs_to_display() + self.ui.txtLogHistory.clear() + current_history = [] + for record in _LOGS_HISTORY: + self._add_log_line(record) + current_history.append(record.msg) + + self._current_history = "\n".join(current_history) + + def _set_logs_to_display(self): + """ + Sets the logs_to_display dict getting the toggled options from the ui + """ + self._logs_to_display = { + logbook.DEBUG: self.ui.btnDebug.isChecked(), + logbook.INFO: self.ui.btnInfo.isChecked(), + logbook.WARNING: self.ui.btnWarning.isChecked(), + logbook.ERROR: self.ui.btnError.isChecked(), + logbook.CRITICAL: self.ui.btnCritical.isChecked() + } + + def _filter_by(self, text): + """ + Sets the text to use for filtering logs in the log window. + + :param text: the text to compare with the logs when filtering. + :type text: str + """ + self._current_filter = text + self._load_history() + + def _save_log_to_file(self): + """ + Lets the user save the current log to a file + """ + fileName, filtr = QtGui.QFileDialog.getSaveFileName( + self, self.tr("Save As"), + options=QtGui.QFileDialog.DontUseNativeDialog) + + if fileName: + try: + with open(fileName, 'w') as output: + history = self.ui.txtLogHistory.toPlainText() + # Chop some \n. + # html->plain adds several \n because the html is made + # using table cells. + history = history.replace('\n\n\n', '\n') + + output.write(history) + logger.debug('Log saved in %s' % (fileName, )) + except IOError, e: + logger.error("Error saving log file: %r" % (e, )) + 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_ok(self, link): + """ + Handle a successful paste. + + :param link: the recently created pastebin link. + :type link: str + """ + self._set_pastebin_sending(False) + msg = self.tr("Your pastebin link {0}") + msg = msg.format(link) + + # We save the dialog in an instance member to avoid dialog being + # deleted right after we exit this method + self._msgBox = msgBox = QtGui.QMessageBox( + QtGui.QMessageBox.Information, self.tr("Pastebin OK"), msg) + msgBox.setWindowModality(QtCore.Qt.NonModal) + msgBox.show() + + def _pastebin_err(self, failure): + """ + Handle a failure in paste. + + :param failure: the exception that made the paste fail. + :type failure: Exception + """ + self._set_pastebin_sending(False) + logger.error(repr(failure)) + + msg = self.tr("Sending logs to Pastebin failed!") + if isinstance(failure, pastebin.PostLimitError): + msg = self.tr('Maximum amount of submissions reached for today.') + + # We save the dialog in an instance member to avoid dialog being + # deleted right after we exit this method + self._msgBox = msgBox = QtGui.QMessageBox( + QtGui.QMessageBox.Critical, self.tr("Pastebin Error"), msg) + msgBox.setWindowModality(QtCore.Qt.NonModal) + msgBox.show() + + 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 = pastebin.PastebinAPI() + try: + link = pb.paste(PASTEBIN_API_DEV_KEY, content, + paste_name="Bitmask log", + paste_expire_date='1M') + # convert to 'raw' link + link = "http://pastebin.com/raw.php?i=" + link.split('/')[-1] + + self._paste_ok.emit(link) + except Exception as e: + self._paste_error.emit(e) + + self._set_pastebin_sending(True) + + self._paste_thread = QtCore.QThread() + self._paste_thread.run = lambda: do_pastebin() + self._paste_thread.start() + + def closeEvent(self, e): + """ + Disconnect logger on close. + """ + self._disconnect_logger() + e.accept() + + def reject(self): + """ + Disconnect logger on reject. + """ + self._disconnect_logger() + QtGui.QDialog.reject(self) + + def _disconnect_logger(self): + """ + Stop the background thread that receives messages through zmq, also + close the subscriber socket. + This allows us to re-create the subscriber when we reopen this window + without getting an error at trying to connect twice to the zmq port. + """ + self._logbook_controller.stop() + self._logbook_controller.subscriber.close() diff --git a/src/leap/bitmask/logs/__init__.py b/src/leap/bitmask/logs/__init__.py index 0516b304..837a5ed9 100644 --- a/src/leap/bitmask/logs/__init__.py +++ b/src/leap/bitmask/logs/__init__.py @@ -1,3 +1,3 @@ -# levelname length == 8, since 'CRITICAL' is the longest -LOG_FORMAT = ('%(asctime)s - %(levelname)-8s - ' - 'L#%(lineno)-4s : %(name)s:%(funcName)s() - %(message)s') +LOG_FORMAT = (u'[{record.time:%Y-%m-%d %H:%M:%S}] ' + u'{record.level_name: <8} - L#{record.lineno: <4} : ' + u'{record.module}:{record.func_name} - {record.message}') diff --git a/src/leap/bitmask/logs/leap_log_handler.py b/src/leap/bitmask/logs/leap_log_handler.py deleted file mode 100644 index 24141638..00000000 --- a/src/leap/bitmask/logs/leap_log_handler.py +++ /dev/null @@ -1,137 +0,0 @@ -# -*- coding: utf-8 -*- -# leap_log_handler.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 . -""" -Custom handler for the logger window. -""" -import logging - -from PySide import QtCore - -from leap.bitmask.logs import LOG_FORMAT - - -class LogHandler(logging.Handler): - """ - This is the custom handler that implements our desired formatting - and also keeps a history of all the logged events. - """ - - MESSAGE_KEY = 'message' - RECORD_KEY = 'record' - - def __init__(self, qtsignal): - """ - LogHander initialization. - Calls parent method and keeps a reference to the qtsignal - that will be used to fire the gui update. - """ - # TODO This is going to eat lots of memory after some time. - # Should be pruned at some moment. - self._log_history = [] - - logging.Handler.__init__(self) - self._qtsignal = qtsignal - - def _get_format(self, logging_level): - """ - Sets the log format depending on the parameter. - It uses html and css to set the colors for the logs. - - :param logging_level: the debug level to define the color. - :type logging_level: str. - """ - formatter = logging.Formatter(LOG_FORMAT) - return formatter - - def emit(self, logRecord): - """ - This method is fired every time that a record is logged by the - logging module. - This method reimplements logging.Handler.emit that is fired - in every logged message. - - :param logRecord: the record emitted by the logging module. - :type logRecord: logging.LogRecord. - """ - self.setFormatter(self._get_format(logRecord.levelname)) - log = self.format(logRecord) - log_item = {self.RECORD_KEY: logRecord, self.MESSAGE_KEY: log} - self._log_history.append(log_item) - self._qtsignal(log_item) - - -class HandlerAdapter(object): - """ - New style class that accesses all attributes from the LogHandler. - - Used as a workaround for a problem with multiple inheritance with Pyside - that surfaced under OSX with pyside 1.1.0. - """ - MESSAGE_KEY = 'message' - RECORD_KEY = 'record' - - def __init__(self, qtsignal): - self._handler = LogHandler(qtsignal=qtsignal) - - def setLevel(self, *args, **kwargs): - return self._handler.setLevel(*args, **kwargs) - - def addFilter(self, *args, **kwargs): - return self._handler.addFilter(*args, **kwargs) - - def handle(self, *args, **kwargs): - return self._handler.handle(*args, **kwargs) - - @property - def level(self): - return self._handler.level - - -class LeapLogHandler(QtCore.QObject, HandlerAdapter): - """ - Custom logging handler. It emits Qt signals so it can be plugged to a gui. - - Its inner handler also stores an history of logs that can be fetched after - having been connected to a gui. - """ - # All dicts returned are of the form - # {'record': LogRecord, 'message': str} - new_log = QtCore.Signal(dict) - - def __init__(self): - """ - LeapLogHandler initialization. - Initializes parent classes. - """ - QtCore.QObject.__init__(self) - HandlerAdapter.__init__(self, qtsignal=self.qtsignal) - - def qtsignal(self, log_item): - # WARNING: the new-style connection does NOT work because PySide - # translates the emit method to self.emit, and that collides with - # the emit method for logging.Handler - # self.new_log.emit(log_item) - QtCore.QObject.emit( - self, - QtCore.SIGNAL('new_log(PyObject)'), log_item) - - @property - def log_history(self): - """ - Returns the history of the logged messages. - """ - return self._handler._log_history diff --git a/src/leap/bitmask/logs/log_silencer.py b/src/leap/bitmask/logs/log_silencer.py index 56b290e4..540532cb 100644 --- a/src/leap/bitmask/logs/log_silencer.py +++ b/src/leap/bitmask/logs/log_silencer.py @@ -17,14 +17,13 @@ """ Filter for leap logs. """ -import logging import os import re from leap.bitmask.util import get_path_prefix -class SelectiveSilencerFilter(logging.Filter): +class SelectiveSilencerFilter(object): """ Configurable filter for root leap logger. @@ -75,7 +74,7 @@ class SelectiveSilencerFilter(logging.Filter): return map(lambda line: re.sub('\s', '', line), lines) - def filter(self, record): + def filter(self, record, handler): """ Implements the filter functionality for this Filter @@ -86,7 +85,10 @@ class SelectiveSilencerFilter(logging.Filter): """ if not self.rules: return True - logger_path = record.name + logger_path = record.module + if logger_path is None: + return True + for path in self.rules: if logger_path.startswith(path): return False diff --git a/src/leap/bitmask/logs/safezmqhandler.py b/src/leap/bitmask/logs/safezmqhandler.py new file mode 100644 index 00000000..7aac6a6a --- /dev/null +++ b/src/leap/bitmask/logs/safezmqhandler.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +# safezmqhandler.py +# Copyright (C) 2013, 2014, 2015 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 . +""" +A thread-safe zmq handler for LogBook. +""" +import json +import threading + +from logbook.queues import ZeroMQHandler +from logbook import NOTSET + +import zmq + + +class SafeZMQHandler(ZeroMQHandler): + """ + A ZMQ log handler for LogBook that is thread-safe. + + This log handler makes use of the existing zmq handler and if the user + tries to log something from a different thread than the one used to + create the handler a new socket is created for that thread. + + Note: In ZMQ, Contexts are threadsafe objects, but Sockets are not. + """ + + def __init__(self, uri=None, level=NOTSET, filter=None, bubble=False, + context=None, multi=False): + + ZeroMQHandler.__init__(self, uri, level, filter, bubble, context, + multi) + + current_id = self._get_caller_id() + # we store the socket created on the parent + self._sockets = {current_id: self.socket} + + # store the settings for new socket creation + self._multi = multi + self._uri = uri + + def _get_caller_id(self): + """ + Return an id for the caller that depends on the current thread. + Thanks to this we can detect if we are running in a thread different + than the one who created the socket and create a new one for it. + + :rtype: int + """ + # NOTE it makes no sense to use multiprocessing id since the sockets + # list can't/shouldn't be shared between processes. We only use + # thread id. The user needs to make sure that the handler is created + # inside each process. + return threading.current_thread().ident + + def _get_new_socket(self): + """ + Return a new socket using the `uri` and `multi` parameters given in the + constructor. + + :rtype: zmq.Socket + """ + socket = None + + if self._multi: + socket = self.context.socket(zmq.PUSH) + if self._uri is not None: + socket.connect(self._uri) + else: + socket = self.context.socket(zmq.PUB) + if self._uri is not None: + socket.bind(self._uri) + + return socket + + def emit(self, record): + """ + Emit the given `record` through the socket. + + :param record: the record to emit + :type record: Logbook.LogRecord + """ + current_id = self._get_caller_id() + socket = None + + if current_id in self._sockets: + socket = self._sockets[current_id] + else: + # TODO: create new socket + socket = self._get_new_socket() + self._sockets[current_id] = socket + + socket.send(json.dumps(self.export_record(record)).encode("utf-8")) + + def close(self, linger=-1): + """ + Close all the sockets and linger `linger` time. + + This reimplements the ZeroMQHandler.close method that is used by + context methods. + + :param linger: time to linger, -1 to not to. + :type linger: int + """ + for socket in self._sockets.values(): + socket.close(linger) diff --git a/src/leap/bitmask/logs/tests/test_leap_log_handler.py b/src/leap/bitmask/logs/tests/test_leap_log_handler.py deleted file mode 100644 index 20b09aef..00000000 --- a/src/leap/bitmask/logs/tests/test_leap_log_handler.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -# test_leap_log_handler.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 . -""" -tests for leap_log_handler -""" -try: - import unittest2 as unittest -except ImportError: - import unittest - -import logging - -from leap.bitmask.logs.leap_log_handler import LeapLogHandler -from leap.bitmask.util.pyside_tests_helper import BasicPySlotCase -from leap.common.testing.basetest import BaseLeapTest - -from mock import Mock - - -class LeapLogHandlerTest(BaseLeapTest, BasicPySlotCase): - """ - LeapLogHandlerTest's tests. - """ - def _callback(self, *args): - """ - Simple callback to track if a signal was emitted. - """ - self.called = True - self.emitted_msg = args[0][LeapLogHandler.MESSAGE_KEY] - - def setUp(self): - BasicPySlotCase.setUp(self) - - # Create the logger - level = logging.DEBUG - self.logger = logging.getLogger(name='test') - self.logger.setLevel(level) - - # Create the handler - self.leap_handler = LeapLogHandler() - self.leap_handler.setLevel(level) - self.logger.addHandler(self.leap_handler) - - def tearDown(self): - BasicPySlotCase.tearDown(self) - try: - self.leap_handler.new_log.disconnect() - except Exception: - pass - - def test_history_starts_empty(self): - self.assertEqual(self.leap_handler.log_history, []) - - def test_one_log_captured(self): - self.logger.debug('test') - self.assertEqual(len(self.leap_handler.log_history), 1) - - def test_history_records_order(self): - self.logger.debug('test 01') - self.logger.debug('test 02') - self.logger.debug('test 03') - - logs = [] - for message in self.leap_handler.log_history: - logs.append(message[LeapLogHandler.RECORD_KEY].msg) - - self.assertIn('test 01', logs) - self.assertIn('test 02', logs) - self.assertIn('test 03', logs) - - def test_history_messages_order(self): - self.logger.debug('test 01') - self.logger.debug('test 02') - self.logger.debug('test 03') - - logs = [] - for message in self.leap_handler.log_history: - logs.append(message[LeapLogHandler.MESSAGE_KEY]) - - self.assertIn('test 01', logs[0]) - self.assertIn('test 02', logs[1]) - self.assertIn('test 03', logs[2]) - - def test_emits_signal(self): - log_format = '%(name)s - %(levelname)s - %(message)s' - formatter = logging.Formatter(log_format) - get_format = Mock(return_value=formatter) - self.leap_handler._handler._get_format = get_format - - self.leap_handler.new_log.connect(self._callback) - self.logger.debug('test') - - expected_log_msg = "test - DEBUG - test" - - # signal emitted - self.assertTrue(self.called) - - # emitted message - self.assertEqual(self.emitted_msg, expected_log_msg) - - # Mock called - self.assertTrue(get_format.called) - - -if __name__ == "__main__": - unittest.main() diff --git a/src/leap/bitmask/logs/utils.py b/src/leap/bitmask/logs/utils.py index 8367937a..72efae97 100644 --- a/src/leap/bitmask/logs/utils.py +++ b/src/leap/bitmask/logs/utils.py @@ -1,80 +1,57 @@ import logging import sys +from leap.bitmask.config import flags from leap.bitmask.logs import LOG_FORMAT from leap.bitmask.logs.log_silencer import SelectiveSilencerFilter -from leap.bitmask.logs.leap_log_handler import LeapLogHandler +from leap.bitmask.logs.safezmqhandler import SafeZMQHandler from leap.bitmask.logs.streamtologger import StreamToLogger from leap.bitmask.platform_init import IS_WIN +import logbook +from logbook.more import ColorizedStderrHandler -def create_logger(debug=False, logfile=None, replace_stdout=True): - """ - Create the logger and attach the handlers. - - :param debug: the level of the messages that we should log - :type debug: bool - :param logfile: the file name of where we should to save the logs - :type logfile: str - :return: the new logger with the attached handlers. - :rtype: logging.Logger - """ - # TODO: get severity from command line args - if debug: - level = logging.DEBUG - else: - level = logging.WARNING - # Create logger and formatter - logger = logging.getLogger(name='leap') - logger.setLevel(level) - formatter = logging.Formatter(LOG_FORMAT) +def get_logger(debug=True, logfile=None, replace_stdout=True): + level = logbook.WARNING + if flags.DEBUG: + level = logbook.NOTSET - # Console handler - try: - import coloredlogs - console = coloredlogs.ColoredStreamHandler(level=level) - except ImportError: - console = logging.StreamHandler() - console.setLevel(level) - console.setFormatter(formatter) - using_coloredlog = False - else: - using_coloredlog = True - - if using_coloredlog: - replace_stdout = False + # This handler consumes logs not handled by the others + null_handler = logbook.NullHandler(bubble=False) + null_handler.push_application() silencer = SelectiveSilencerFilter() - console.addFilter(silencer) - logger.addHandler(console) - logger.debug('Console handler plugged!') - # LEAP custom handler - leap_handler = LeapLogHandler() - leap_handler.setLevel(level) - leap_handler.addFilter(silencer) - logger.addHandler(leap_handler) - logger.debug('Leap handler plugged!') + zmq_handler = SafeZMQHandler('tcp://127.0.0.1:5000', multi=True, + level=level, filter=silencer.filter) + zmq_handler.push_application() + + file_handler = logbook.FileHandler('bitmask.log', format_string=LOG_FORMAT, + bubble=True, filter=silencer.filter) + file_handler.push_application() - # File handler - if logfile is not None: - logger.debug('Setting logfile to %s ', logfile) - fileh = logging.FileHandler(logfile) - fileh.setLevel(logging.DEBUG) - fileh.setFormatter(formatter) - fileh.addFilter(silencer) - logger.addHandler(fileh) - logger.debug('File handler plugged!') + # don't use simple stream, go for colored log handler instead + # stream_handler = logbook.StreamHandler(sys.stdout, + # format_string=LOG_FORMAT, + # bubble=True) + # stream_handler.push_application() + stream_handler = ColorizedStderrHandler( + level=level, format_string=LOG_FORMAT, bubble=True, + filter=silencer.filter) + stream_handler.push_application() - if replace_stdout: - replace_stdout_stderr_with_logging(logger) + logger = logbook.Logger('leap') return logger def replace_stdout_stderr_with_logging(logger): """ + NOTE: + we are not using this right now (see commented lines on app.py), + this needs to be reviewed since the log handler has changed. + Replace: - the standard output - the standard error diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index 12fd9736..383e5f8a 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -38,9 +38,6 @@ def build_parser(): help='Displays Bitmask version and exits.') # files - parser.add_argument('-l', '--logfile', metavar="LOG FILE", nargs='?', - action="store", dest="log_file", - help='Optional log file.') parser.add_argument('-m', '--mail-logfile', metavar="MAIL LOG FILE", nargs='?', action="store", dest="mail_log_file", -- cgit v1.2.3 From 32658ae3108bc67a102cf6e0153d468b3a8ae1b0 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 12 Jun 2015 14:34:18 -0300 Subject: [feat] replace old log handler with new one Replace logging.getLogger with custom get_logger. Remove some unneeded dependencies and reorder other. --- src/leap/bitmask/app.py | 13 ++++----- src/leap/bitmask/backend/backend.py | 7 ++--- src/leap/bitmask/backend/backend_proxy.py | 4 +-- src/leap/bitmask/backend/components.py | 7 ++--- src/leap/bitmask/backend/leapbackend.py | 5 ++-- src/leap/bitmask/backend/settings.py | 4 +-- src/leap/bitmask/backend/signaler.py | 4 +-- src/leap/bitmask/backend/signaler_qt.py | 4 +-- src/leap/bitmask/backend/utils.py | 4 +-- src/leap/bitmask/backend_app.py | 13 +++++---- src/leap/bitmask/config/leapsettings.py | 4 +-- src/leap/bitmask/config/providerconfig.py | 4 +-- src/leap/bitmask/crypto/certs.py | 4 +-- src/leap/bitmask/crypto/srpauth.py | 6 ++-- src/leap/bitmask/crypto/srpregister.py | 3 +- src/leap/bitmask/frontend_app.py | 4 +-- src/leap/bitmask/gui/advanced_key_management.py | 7 ++--- src/leap/bitmask/gui/app.py | 5 ++-- src/leap/bitmask/gui/eip_preferenceswindow.py | 5 ++-- src/leap/bitmask/gui/eip_status.py | 5 ++-- src/leap/bitmask/gui/login.py | 5 ++-- src/leap/bitmask/gui/mail_status.py | 5 ++-- src/leap/bitmask/gui/mainwindow.py | 32 ++++------------------ src/leap/bitmask/gui/passwordwindow.py | 8 +++--- src/leap/bitmask/gui/preferences_account_page.py | 6 ++-- src/leap/bitmask/gui/preferences_email_page.py | 6 ++-- src/leap/bitmask/gui/preferenceswindow.py | 5 ++-- src/leap/bitmask/gui/signaltracker.py | 6 ++-- src/leap/bitmask/gui/statemachines.py | 5 ++-- src/leap/bitmask/gui/wizard.py | 4 +-- src/leap/bitmask/logs/utils.py | 2 +- src/leap/bitmask/platform_init/initializers.py | 5 ++-- src/leap/bitmask/platform_init/locks.py | 4 +-- src/leap/bitmask/provider/pinned.py | 5 ++-- src/leap/bitmask/provider/providerbootstrapper.py | 4 +-- src/leap/bitmask/services/__init__.py | 4 +-- src/leap/bitmask/services/abstractbootstrapper.py | 5 ++-- src/leap/bitmask/services/eip/conductor.py | 5 ++-- src/leap/bitmask/services/eip/darwinvpnlauncher.py | 4 +-- src/leap/bitmask/services/eip/eipbootstrapper.py | 4 +-- src/leap/bitmask/services/eip/eipconfig.py | 3 +- src/leap/bitmask/services/eip/linuxvpnlauncher.py | 4 +-- src/leap/bitmask/services/eip/vpnlauncher.py | 4 +-- src/leap/bitmask/services/eip/vpnprocess.py | 9 +++--- .../bitmask/services/eip/windowsvpnlauncher.py | 5 ++-- src/leap/bitmask/services/mail/conductor.py | 5 ++-- src/leap/bitmask/services/mail/imap.py | 4 +-- src/leap/bitmask/services/mail/imapcontroller.py | 6 ++-- src/leap/bitmask/services/mail/plumber.py | 4 +-- src/leap/bitmask/services/mail/smtpbootstrapper.py | 4 +-- src/leap/bitmask/services/mail/smtpconfig.py | 4 +-- .../services/soledad/soledadbootstrapper.py | 4 +-- src/leap/bitmask/services/soledad/soledadconfig.py | 5 ++-- src/leap/bitmask/util/autostart.py | 4 +-- src/leap/bitmask/util/keyring_helpers.py | 5 ++-- src/leap/bitmask/util/polkit_agent.py | 4 +-- src/leap/bitmask/util/privilege_policies.py | 4 +-- src/leap/bitmask/util/requirement_checker.py | 6 ++-- 58 files changed, 139 insertions(+), 181 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index adab8652..543fa03a 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -39,19 +39,18 @@ # M:::::::::::~NMMM7???7MMMM:::::::::::::::::::::::NMMMI??I7MMMM:::::::::::::M # M::::::::::::::7MMMMMMM+:::::::::::::::::::::::::::?MMMMMMMZ:::::::::::::::M # (thanks to: http://www.glassgiant.com/ascii/) + import atexit import multiprocessing import os import sys - -from leap.bitmask.backend.backend_proxy import BackendProxy - from leap.bitmask import __version__ as VERSION +from leap.bitmask.backend.backend_proxy import BackendProxy +from leap.bitmask.backend_app import run_backend from leap.bitmask.config import flags from leap.bitmask.frontend_app import run_frontend -from leap.bitmask.backend_app import run_backend -from leap.bitmask.logs.utils import create_logger +from leap.bitmask.logs.utils import get_logger from leap.bitmask.platform_init.locks import we_are_the_one_and_only from leap.bitmask.services.mail import plumber from leap.bitmask.util import leap_argparse, flags_to_dict @@ -171,8 +170,8 @@ def start_app(): check_requirements() logger.info('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') - logger.info('Bitmask version %s', VERSION) - logger.info('leap.mail version %s', MAIL_VERSION) + logger.info('Bitmask version %s' % VERSION) + logger.info('leap.mail version %s' % MAIL_VERSION) logger.info('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') logger.info('Starting app') diff --git a/src/leap/bitmask/backend/backend.py b/src/leap/bitmask/backend/backend.py index fcbb19dc..4a98d146 100644 --- a/src/leap/bitmask/backend/backend.py +++ b/src/leap/bitmask/backend/backend.py @@ -17,7 +17,6 @@ # FIXME this is missing module documentation. It would be fine to say a couple # of lines about the whole backend architecture. - import json import os import time @@ -34,12 +33,12 @@ except ImportError: pass from leap.bitmask.backend.api import API, PING_REQUEST +from leap.bitmask.backend.signaler import Signaler from leap.bitmask.backend.utils import get_backend_certificates from leap.bitmask.config import flags -from leap.bitmask.backend.signaler import Signaler +from leap.bitmask.logs.utils import get_logger -import logging -logger = logging.getLogger(__name__) +logger = get_logger() class TxZmqREPConnection(object): diff --git a/src/leap/bitmask/backend/backend_proxy.py b/src/leap/bitmask/backend/backend_proxy.py index 8d6930d6..30b7c5d1 100644 --- a/src/leap/bitmask/backend/backend_proxy.py +++ b/src/leap/bitmask/backend/backend_proxy.py @@ -34,9 +34,9 @@ from leap.bitmask.backend.settings import Settings from leap.bitmask.backend.utils import generate_zmq_certificates_if_needed from leap.bitmask.backend.utils import get_backend_certificates from leap.bitmask.config import flags +from leap.bitmask.logs.utils import get_logger -import logging -logger = logging.getLogger(__name__) +logger = get_logger() class ZmqREQConnection(threading.Thread): diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index d602156d..b833bf59 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -17,11 +17,9 @@ """ Backend components """ - # TODO [ ] Get rid of all this deferToThread mess, or at least contain # all of it into its own threadpool. -import logging import os import socket import time @@ -38,9 +36,10 @@ from leap.bitmask.backend.settings import Settings, GATEWAY_AUTOMATIC from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.crypto.srpregister import SRPRegister +from leap.bitmask.logs.utils import get_logger from leap.bitmask.platform_init import IS_LINUX -from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper from leap.bitmask.provider.pinned import PinnedProviders +from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper from leap.bitmask.services import get_supported from leap.bitmask.services.eip import eipconfig from leap.bitmask.services.eip import get_openvpn_management @@ -66,7 +65,7 @@ from leap.keymanager import openpgp from leap.soledad.client.secrets import PassphraseTooShort from leap.soledad.client.secrets import NoStorageSecret -logger = logging.getLogger(__name__) +logger = get_logger() class ILEAPComponent(zope.interface.Interface): diff --git a/src/leap/bitmask/backend/leapbackend.py b/src/leap/bitmask/backend/leapbackend.py index 3b023563..cf45c4f8 100644 --- a/src/leap/bitmask/backend/leapbackend.py +++ b/src/leap/bitmask/backend/leapbackend.py @@ -17,16 +17,15 @@ """ Backend for everything """ -import logging - import zope.interface import zope.proxy from leap.bitmask.backend import components from leap.bitmask.backend.backend import Backend from leap.bitmask.backend.settings import Settings +from leap.bitmask.logs.utils import get_logger -logger = logging.getLogger(__name__) +logger = get_logger() ERROR_KEY = "error" PASSED_KEY = "passed" diff --git a/src/leap/bitmask/backend/settings.py b/src/leap/bitmask/backend/settings.py index ed70ca6b..dedfc13d 100644 --- a/src/leap/bitmask/backend/settings.py +++ b/src/leap/bitmask/backend/settings.py @@ -18,13 +18,13 @@ Backend settings """ import ConfigParser -import logging import os +from leap.bitmask.logs.utils import get_logger from leap.bitmask.util import get_path_prefix from leap.common.check import leap_assert, leap_assert_type -logger = logging.getLogger(__name__) +logger = get_logger() # We need this one available for the default decorator GATEWAY_AUTOMATIC = "Automatic" diff --git a/src/leap/bitmask/backend/signaler.py b/src/leap/bitmask/backend/signaler.py index aec2f606..c5335eb8 100644 --- a/src/leap/bitmask/backend/signaler.py +++ b/src/leap/bitmask/backend/signaler.py @@ -27,9 +27,9 @@ import zmq from leap.bitmask.backend.api import SIGNALS from leap.bitmask.backend.utils import get_frontend_certificates from leap.bitmask.config import flags +from leap.bitmask.logs.utils import get_logger -import logging -logger = logging.getLogger(__name__) +logger = get_logger() class Signaler(object): diff --git a/src/leap/bitmask/backend/signaler_qt.py b/src/leap/bitmask/backend/signaler_qt.py index b7f48d21..e3244934 100644 --- a/src/leap/bitmask/backend/signaler_qt.py +++ b/src/leap/bitmask/backend/signaler_qt.py @@ -33,9 +33,9 @@ except ImportError: from leap.bitmask.backend.api import SIGNALS from leap.bitmask.backend.utils import get_frontend_certificates from leap.bitmask.config import flags +from leap.bitmask.logs.utils import get_logger -import logging -logger = logging.getLogger(__name__) +logger = get_logger() class SignalerQt(QtCore.QObject): diff --git a/src/leap/bitmask/backend/utils.py b/src/leap/bitmask/backend/utils.py index b2674330..3b5effc5 100644 --- a/src/leap/bitmask/backend/utils.py +++ b/src/leap/bitmask/backend/utils.py @@ -17,7 +17,6 @@ """ Backend utilities to handle ZMQ certificates. """ -import logging import os import shutil import stat @@ -30,11 +29,12 @@ except ImportError: pass from leap.bitmask.config import flags +from leap.bitmask.logs.utils import get_logger from leap.bitmask.util import get_path_prefix from leap.common.files import mkdir_p from leap.common.check import leap_assert -logger = logging.getLogger(__name__) +logger = get_logger() KEYS_DIR = os.path.join(get_path_prefix(), 'leap', 'zmq_certificates') diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py index af71ba12..891575be 100644 --- a/src/leap/bitmask/backend_app.py +++ b/src/leap/bitmask/backend_app.py @@ -17,7 +17,6 @@ """ Start point for the Backend. """ -import logging import multiprocessing import signal @@ -26,11 +25,8 @@ from leap.common.events import server as event_server from leap.bitmask.backend.leapbackend import LeapBackend from leap.bitmask.backend.utils import generate_zmq_certificates from leap.bitmask.config import flags -from leap.bitmask.logs.utils import create_logger from leap.bitmask.util import dict_to_flags -logger = logging.getLogger(__name__) - def signal_handler(signum, frame): """ @@ -46,7 +42,7 @@ def signal_handler(signum, frame): # In the future we may need to do the stop in here when the frontend and # the backend are run separately (without multiprocessing) pname = multiprocessing.current_process().name - logger.debug("{0}: SIGNAL #{1} catched.".format(pname, signum)) + print "{0}: SIGNAL #{1} catched.".format(pname, signum) def run_backend(bypass_checks=False, flags_dict=None, frontend_pid=None): @@ -58,6 +54,12 @@ def run_backend(bypass_checks=False, flags_dict=None, frontend_pid=None): :param flags_dict: a dict containing the flag values set on app start. :type flags_dict: dict """ + # NOTE: this needs to be used here, within the call since this function is + # executed in a different process and it seems that the process/thread + # identification isn't working 100% + from leap.bitmask.logs.utils import get_logger + logger = get_logger() + # The backend is the one who always creates the certificates. Either if it # is run separately or in a process in the same app as the frontend. if flags.ZMQ_HAS_CURVE: @@ -84,5 +86,4 @@ def run_backend(bypass_checks=False, flags_dict=None, frontend_pid=None): if __name__ == '__main__': - logger = create_logger(debug=True) run_backend() diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index fd3e1592..484a8a25 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -18,14 +18,14 @@ QSettings abstraction. """ import os -import logging from PySide import QtCore from leap.common.check import leap_assert, leap_assert_type +from leap.bitmask.logs.utils import get_logger from leap.bitmask.util import get_path_prefix -logger = logging.getLogger(__name__) +logger = get_logger() def to_bool(val): diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py index 386c697d..f454bb40 100644 --- a/src/leap/bitmask/config/providerconfig.py +++ b/src/leap/bitmask/config/providerconfig.py @@ -18,18 +18,18 @@ """ Provider configuration """ -import logging import os from leap.bitmask import provider from leap.bitmask.config import flags from leap.bitmask.config.provider_spec import leap_provider_spec +from leap.bitmask.logs.utils import get_logger 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__) +logger = get_logger() class MissingCACert(Exception): diff --git a/src/leap/bitmask/crypto/certs.py b/src/leap/bitmask/crypto/certs.py index c3ca4efb..4b669376 100644 --- a/src/leap/bitmask/crypto/certs.py +++ b/src/leap/bitmask/crypto/certs.py @@ -17,17 +17,17 @@ """ Utilities for dealing with client certs """ -import logging import os from leap.bitmask.crypto.srpauth import SRPAuth +from leap.bitmask.logs.utils import get_logger from leap.bitmask.util.constants import REQUEST_TIMEOUT from leap.common.files import check_and_fix_urw_only from leap.common.files import mkdir_p from leap.common import certs as leap_certs -logger = logging.getLogger(__name__) +logger = get_logger() def download_client_cert(provider_config, path, session): diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 1e96030e..452bfa66 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -14,9 +14,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - import binascii -import logging import threading import sys @@ -33,14 +31,14 @@ from twisted.internet import threads from twisted.internet.defer import CancelledError from leap.bitmask.backend.settings import Settings +from leap.bitmask.logs.utils import get_logger 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 from leap.common.check import leap_assert from leap.common.events import emit, catalog - -logger = logging.getLogger(__name__) +logger = get_logger() class SRPAuthenticationError(Exception): diff --git a/src/leap/bitmask/crypto/srpregister.py b/src/leap/bitmask/crypto/srpregister.py index e3007b6c..baa15244 100644 --- a/src/leap/bitmask/crypto/srpregister.py +++ b/src/leap/bitmask/crypto/srpregister.py @@ -26,11 +26,12 @@ from PySide import QtCore from urlparse import urlparse from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.logs.utils import get_logger 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__) +logger = get_logger() class SRPRegisterImpl: diff --git a/src/leap/bitmask/frontend_app.py b/src/leap/bitmask/frontend_app.py index 55e188f7..fed24cfa 100644 --- a/src/leap/bitmask/frontend_app.py +++ b/src/leap/bitmask/frontend_app.py @@ -28,10 +28,10 @@ from PySide import QtCore, QtGui from leap.bitmask.config import flags from leap.bitmask.gui.mainwindow import MainWindow +from leap.bitmask.logs.utils import get_logger from leap.bitmask.util import dict_to_flags -import logging -logger = logging.getLogger(__name__) +logger = get_logger() def signal_handler(window, pid, signum, frame): diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py index 7d147b7b..2e315d18 100644 --- a/src/leap/bitmask/gui/advanced_key_management.py +++ b/src/leap/bitmask/gui/advanced_key_management.py @@ -17,14 +17,13 @@ """ Advanced Key Management """ -import logging - -from PySide import QtCore, QtGui +from PySide import QtGui +from leap.bitmask.logs.utils import get_logger from leap.bitmask.services import get_service_display_name, MX_SERVICE from ui_advanced_key_management import Ui_AdvancedKeyManagement -logger = logging.getLogger(__name__) +logger = get_logger() class AdvancedKeyManagement(QtGui.QDialog): diff --git a/src/leap/bitmask/gui/app.py b/src/leap/bitmask/gui/app.py index 46f276e1..02357b2b 100644 --- a/src/leap/bitmask/gui/app.py +++ b/src/leap/bitmask/gui/app.py @@ -18,15 +18,14 @@ A single App instances holds the signals that are shared among different frontend UI components. The App also keeps a reference to the backend object and the signaler get signals from the backend. """ -import logging - from PySide import QtCore, QtGui from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.backend.backend_proxy import BackendProxy from leap.bitmask.backend.leapsignaler import LeapSignaler +from leap.bitmask.logs.utils import get_logger -logger = logging.getLogger(__name__) +logger = get_logger() class App(QtGui.QWidget): diff --git a/src/leap/bitmask/gui/eip_preferenceswindow.py b/src/leap/bitmask/gui/eip_preferenceswindow.py index 8939c709..756e8adf 100644 --- a/src/leap/bitmask/gui/eip_preferenceswindow.py +++ b/src/leap/bitmask/gui/eip_preferenceswindow.py @@ -18,15 +18,14 @@ """ EIP Preferences window """ -import logging - from functools import partial from PySide import QtCore, QtGui from leap.bitmask.config.leapsettings import LeapSettings +from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui.ui_eippreferences import Ui_EIPPreferences -logger = logging.getLogger(__name__) +logger = get_logger() class EIPPreferencesWindow(QtGui.QDialog): diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 83490cac..8334c2ee 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -17,13 +17,12 @@ """ EIP Status Panel widget implementation """ -import logging - from datetime import datetime from functools import partial from PySide import QtCore, QtGui +from leap.bitmask.logs.utils import get_logger from leap.bitmask.services import get_service_display_name, EIP_SERVICE from leap.bitmask.platform_init import IS_LINUX from leap.bitmask.util.averages import RateMovingAverage @@ -32,7 +31,7 @@ from leap.common.check import leap_assert_type from ui_eip_status import Ui_EIPStatus QtDelayedCall = QtCore.QTimer.singleShot -logger = logging.getLogger(__name__) +logger = get_logger() class EIPStatusWidget(QtGui.QWidget): diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index 716a8609..756dd63c 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -30,8 +30,6 @@ The login sequence is the following: - on success: _authentication_finished """ -import logging - from keyring.errors import InitError as KeyringInitError from PySide import QtCore, QtGui from ui_login import Ui_LoginWidget @@ -41,6 +39,7 @@ from ui_login import Ui_LoginWidget from leap.bitmask.backend.leapbackend import ERROR_KEY, PASSED_KEY from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings +from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui.signaltracker import SignalTracker from leap.bitmask.util import make_address from leap.bitmask.util.credentials import USERNAME_REGEX @@ -48,7 +47,7 @@ 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__) +logger = get_logger() class LoginState(object): diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index a3a1be37..b5d1df81 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -17,10 +17,9 @@ """ Mail Status Panel widget implementation """ -import logging - from PySide import QtCore, QtGui +from leap.bitmask.logs.utils import get_logger from leap.bitmask.platform_init import IS_LINUX from leap.bitmask.services import get_service_display_name, MX_SERVICE from leap.common.check import leap_assert, leap_assert_type @@ -29,7 +28,7 @@ from leap.common.events import catalog from ui_mail_status import Ui_MailStatusWidget -logger = logging.getLogger(__name__) +logger = get_logger() class MailStatusWidget(QtGui.QWidget): diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 0df6ef84..a2cf7bca 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -17,7 +17,6 @@ """ Main window for Bitmask. """ -import logging import time from datetime import datetime @@ -34,10 +33,11 @@ from leap.bitmask import __version_hash__ as VERSION_HASH from leap.bitmask.backend.leapbackend import ERROR_KEY, PASSED_KEY from leap.bitmask.config import flags +from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement from leap.bitmask.gui.eip_status import EIPStatusWidget -from leap.bitmask.gui.loggerwindow import LoggerWindow +from leap.bitmask.gui.logwindow import LoggerWindow from leap.bitmask.gui.login import LoginWidget from leap.bitmask.gui.mail_status import MailStatusWidget from leap.bitmask.gui.preferenceswindow import PreferencesWindow @@ -60,7 +60,6 @@ from leap.bitmask.services import EIP_SERVICE, MX_SERVICE from leap.bitmask.util import autostart, make_address from leap.bitmask.util.keyring_helpers import has_keyring -from leap.bitmask.logs.leap_log_handler import LeapLogHandler from leap.common.events import register from leap.common.events import catalog @@ -70,7 +69,8 @@ from leap.mail.imap.service.imap import IMAP_PORT from ui_mainwindow import Ui_MainWindow QtDelayedCall = QtCore.QTimer.singleShot -logger = logging.getLogger(__name__) + +logger = get_logger() class MainWindow(QtGui.QMainWindow, SignalTracker): @@ -492,21 +492,6 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._wizard = None - def _get_leap_logging_handler(self): - """ - Gets the leap handler from the top level logger - - :return: a logging handler or None - :rtype: LeapLogHandler or None - """ - # TODO this can be a function, does not need - # to be a method. - leap_logger = logging.getLogger('leap') - for h in leap_logger.handlers: - if isinstance(h, LeapLogHandler): - return h - return None - def _show_logger_window(self): """ TRIGGERS: @@ -515,13 +500,8 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): Display the window with the history of messages logged until now and displays the new ones on arrival. """ - leap_log_handler = self._get_leap_logging_handler() - if leap_log_handler is None: - logger.error('Leap logger handler not found') - return - else: - lw = LoggerWindow(self, handler=leap_log_handler) - lw.show() + lw = LoggerWindow(self) + lw.show() def _show_AKM(self): """ diff --git a/src/leap/bitmask/gui/passwordwindow.py b/src/leap/bitmask/gui/passwordwindow.py index 88565829..94cf25da 100644 --- a/src/leap/bitmask/gui/passwordwindow.py +++ b/src/leap/bitmask/gui/passwordwindow.py @@ -19,14 +19,14 @@ Change password dialog window """ -from PySide import QtCore, QtGui -from leap.bitmask.util.credentials import password_checks +from PySide import QtGui +from leap.bitmask.logs.utils import get_logger +from leap.bitmask.util.credentials import password_checks from leap.bitmask.gui.ui_password_change import Ui_PasswordChange from leap.bitmask.gui.flashable import Flashable -import logging -logger = logging.getLogger(__name__) +logger = get_logger() class PasswordWindow(QtGui.QDialog, Flashable): diff --git a/src/leap/bitmask/gui/preferences_account_page.py b/src/leap/bitmask/gui/preferences_account_page.py index cab90eec..5661fb66 100644 --- a/src/leap/bitmask/gui/preferences_account_page.py +++ b/src/leap/bitmask/gui/preferences_account_page.py @@ -16,16 +16,16 @@ """ Widget for "account" preferences """ -import logging - from functools import partial from PySide import QtCore, QtGui + +from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui.ui_preferences_account_page import Ui_PreferencesAccountPage from leap.bitmask.gui.passwordwindow import PasswordWindow from leap.bitmask.services import get_service_display_name -logger = logging.getLogger(__name__) +logger = get_logger() class PreferencesAccountPage(QtGui.QWidget): diff --git a/src/leap/bitmask/gui/preferences_email_page.py b/src/leap/bitmask/gui/preferences_email_page.py index 80e8d93e..3087f343 100644 --- a/src/leap/bitmask/gui/preferences_email_page.py +++ b/src/leap/bitmask/gui/preferences_email_page.py @@ -16,12 +16,12 @@ """ Widget for "email" preferences """ -import logging +from PySide import QtGui -from PySide import QtCore, QtGui +from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui.ui_preferences_email_page import Ui_PreferencesEmailPage -logger = logging.getLogger(__name__) +logger = get_logger() class PreferencesEmailPage(QtGui.QWidget): diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index daad08b0..a71f4e5c 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -18,18 +18,17 @@ """ Preferences window """ -import logging - from PySide import QtCore, QtGui from leap.bitmask.services import EIP_SERVICE, MX_SERVICE +from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui.ui_preferences import Ui_Preferences from leap.bitmask.gui.preferences_account_page import PreferencesAccountPage from leap.bitmask.gui.preferences_vpn_page import PreferencesVpnPage from leap.bitmask.gui.preferences_email_page import PreferencesEmailPage -logger = logging.getLogger(__name__) +logger = get_logger() class PreferencesWindow(QtGui.QDialog): diff --git a/src/leap/bitmask/gui/signaltracker.py b/src/leap/bitmask/gui/signaltracker.py index 0e3b2dce..3dfcfe18 100644 --- a/src/leap/bitmask/gui/signaltracker.py +++ b/src/leap/bitmask/gui/signaltracker.py @@ -14,11 +14,11 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import logging - from PySide import QtCore -logger = logging.getLogger(__name__) +from leap.bitmask.logs.utils import get_logger + +logger = get_logger() class SignalTracker(QtCore.QObject): diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py index 91f1f605..ab48b756 100644 --- a/src/leap/bitmask/gui/statemachines.py +++ b/src/leap/bitmask/gui/statemachines.py @@ -17,16 +17,15 @@ """ State machines for the Bitmask app. """ -import logging - from PySide import QtCore from PySide.QtCore import QStateMachine, QState, Signal from PySide.QtCore import QObject from leap.bitmask.services import connections from leap.common.check import leap_assert_type +from leap.bitmask.logs.utils import get_logger -logger = logging.getLogger(__name__) +logger = get_logger() _tr = QObject().tr diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index c66c6269..c60d967b 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -17,7 +17,6 @@ """ First run wizard """ -import logging import random from functools import partial @@ -30,6 +29,7 @@ from leap.bitmask.backend.leapbackend import ERROR_KEY, PASSED_KEY from leap.bitmask.config import flags from leap.bitmask.config.leapsettings import LeapSettings +from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui.signaltracker import SignalTracker from leap.bitmask.services import get_service_display_name, get_supported from leap.bitmask.util.credentials import password_checks, username_checks @@ -39,7 +39,7 @@ from leap.bitmask.util.keyring_helpers import has_keyring from ui_wizard import Ui_Wizard QtDelayedCall = QtCore.QTimer.singleShot -logger = logging.getLogger(__name__) +logger = get_logger() class Wizard(QtGui.QWizard, SignalTracker): diff --git a/src/leap/bitmask/logs/utils.py b/src/leap/bitmask/logs/utils.py index 72efae97..e0a5fba3 100644 --- a/src/leap/bitmask/logs/utils.py +++ b/src/leap/bitmask/logs/utils.py @@ -12,7 +12,7 @@ import logbook from logbook.more import ColorizedStderrHandler -def get_logger(debug=True, logfile=None, replace_stdout=True): +def get_logger(): level = logbook.WARNING if flags.DEBUG: level = logbook.NOTSET diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index be8a7ab9..eb892cce 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -17,7 +17,6 @@ """ Platform-dependant initialization code. """ -import logging import os import platform import stat @@ -32,9 +31,9 @@ from leap.bitmask.services.eip import get_vpn_launcher from leap.bitmask.services.eip.darwinvpnlauncher import DarwinVPNLauncher from leap.bitmask.util import first from leap.bitmask.util.privilege_policies import LinuxPolicyChecker +from leap.bitmask.logs.utils import get_logger - -logger = logging.getLogger(__name__) +logger = get_logger() # NOTE we could use a deferToThread here, but should # be aware of this bug: http://www.themacaque.com/?p=1067 diff --git a/src/leap/bitmask/platform_init/locks.py b/src/leap/bitmask/platform_init/locks.py index eff900fa..203d367b 100644 --- a/src/leap/bitmask/platform_init/locks.py +++ b/src/leap/bitmask/platform_init/locks.py @@ -17,11 +17,11 @@ """ Utilities for handling multi-platform file locking mechanisms """ -import logging import errno import os import platform +from leap.bitmask.logs.utils import get_logger from leap.bitmask.platform_init import IS_WIN, IS_UNIX from leap.common.events import emit, catalog @@ -37,7 +37,7 @@ else: # WINDOWS from leap.bitmask.util import get_modification_ts, update_modification_ts -logger = logging.getLogger(__name__) +logger = get_logger() if IS_UNIX: diff --git a/src/leap/bitmask/provider/pinned.py b/src/leap/bitmask/provider/pinned.py index 09fcc52c..ea1788eb 100644 --- a/src/leap/bitmask/provider/pinned.py +++ b/src/leap/bitmask/provider/pinned.py @@ -17,13 +17,12 @@ """ Pinned Providers """ -import logging - +from leap.bitmask.logs.utils import get_logger from leap.bitmask.provider import pinned_calyx from leap.bitmask.provider import pinned_demobitmask from leap.bitmask.provider import pinned_riseup -logger = logging.getLogger(__name__) +logger = get_logger() class PinnedProviders(object): diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index efba29f9..28944d89 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -17,7 +17,6 @@ """ Provider bootstrapping """ -import logging import socket import os import sys @@ -28,6 +27,7 @@ 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.logs.utils import get_logger from leap.bitmask.provider import get_provider_path from leap.bitmask.provider.pinned import PinnedProviders from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper @@ -38,7 +38,7 @@ from leap.common.certs import get_digest 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__) +logger = get_logger() class UnsupportedProviderAPI(Exception): diff --git a/src/leap/bitmask/services/__init__.py b/src/leap/bitmask/services/__init__.py index ba12ba4e..54426669 100644 --- a/src/leap/bitmask/services/__init__.py +++ b/src/leap/bitmask/services/__init__.py @@ -17,7 +17,6 @@ """ Services module. """ -import logging import os import sys @@ -25,6 +24,7 @@ from PySide import QtCore from leap.bitmask.config import flags from leap.bitmask.crypto.srpauth import SRPAuth +from leap.bitmask.logs.utils import get_logger from leap.bitmask.util.constants import REQUEST_TIMEOUT from leap.bitmask.util.privilege_policies import is_missing_policy_permissions from leap.bitmask.util.request_helpers import get_content @@ -34,7 +34,7 @@ from leap.common.check import leap_assert from leap.common.config.baseconfig import BaseConfig from leap.common.files import get_mtime -logger = logging.getLogger(__name__) +logger = get_logger() EIP_SERVICE = u"openvpn" diff --git a/src/leap/bitmask/services/abstractbootstrapper.py b/src/leap/bitmask/services/abstractbootstrapper.py index 77929b75..6a08f475 100644 --- a/src/leap/bitmask/services/abstractbootstrapper.py +++ b/src/leap/bitmask/services/abstractbootstrapper.py @@ -18,8 +18,6 @@ """ Abstract bootstrapper implementation """ -import logging - import requests from functools import partial @@ -30,9 +28,10 @@ from twisted.python import log from twisted.internet import threads from twisted.internet.defer import CancelledError +from leap.bitmask.logs.utils import get_logger from leap.common.check import leap_assert, leap_assert_type -logger = logging.getLogger(__name__) +logger = get_logger() class AbstractBootstrapper(QtCore.QObject): diff --git a/src/leap/bitmask/services/eip/conductor.py b/src/leap/bitmask/services/eip/conductor.py index 3fc88724..3386dddf 100644 --- a/src/leap/bitmask/services/eip/conductor.py +++ b/src/leap/bitmask/services/eip/conductor.py @@ -20,10 +20,9 @@ EIP Conductor module. This handles Qt Signals and triggers the calls to the backend, where the VPNProcess has been initialized. """ -import logging - from PySide import QtCore +from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui import statemachines from leap.bitmask.services import EIP_SERVICE from leap.bitmask.services import get_service_display_name @@ -31,7 +30,7 @@ from leap.bitmask.services.eip.connection import EIPConnection from leap.bitmask.platform_init import IS_MAC QtDelayedCall = QtCore.QTimer.singleShot -logger = logging.getLogger(__name__) +logger = get_logger() class EIPConductor(object): diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py index f83e0170..17fc11c2 100644 --- a/src/leap/bitmask/services/eip/darwinvpnlauncher.py +++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py @@ -19,15 +19,15 @@ Darwin VPN launcher implementation. """ import commands import getpass -import logging import os import sys +from leap.bitmask.logs.utils import get_logger from leap.bitmask.services.eip.vpnlauncher import VPNLauncher from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException from leap.bitmask.util import get_path_prefix -logger = logging.getLogger(__name__) +logger = get_logger() class EIPNoTunKextLoaded(VPNLauncherException): diff --git a/src/leap/bitmask/services/eip/eipbootstrapper.py b/src/leap/bitmask/services/eip/eipbootstrapper.py index f78113bc..7a331d71 100644 --- a/src/leap/bitmask/services/eip/eipbootstrapper.py +++ b/src/leap/bitmask/services/eip/eipbootstrapper.py @@ -17,11 +17,11 @@ """ EIP bootstrapping """ -import logging import os from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.certs import download_client_cert +from leap.bitmask.logs.utils import get_logger from leap.bitmask.services import download_service_config from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.services.eip.eipconfig import EIPConfig @@ -29,7 +29,7 @@ from leap.common import certs as leap_certs from leap.common.check import leap_assert, leap_assert_type from leap.common.files import check_and_fix_urw_only -logger = logging.getLogger(__name__) +logger = get_logger() class EIPBootstrapper(AbstractBootstrapper): diff --git a/src/leap/bitmask/services/eip/eipconfig.py b/src/leap/bitmask/services/eip/eipconfig.py index d5947eb1..b1d08393 100644 --- a/src/leap/bitmask/services/eip/eipconfig.py +++ b/src/leap/bitmask/services/eip/eipconfig.py @@ -26,12 +26,13 @@ import ipaddr from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.logs.utils import get_logger from leap.bitmask.services import ServiceConfig from leap.bitmask.services.eip.eipspec import get_schema from leap.bitmask.util import get_path_prefix from leap.common.check import leap_assert, leap_assert_type -logger = logging.getLogger(__name__) +logger = get_logger() def get_eipconfig_path(domain, relative=True): diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py index a3ab408b..cf14a8f9 100644 --- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -18,11 +18,11 @@ Linux VPN launcher implementation. """ import commands -import logging import os import sys from leap.bitmask.config import flags +from leap.bitmask.logs.utils import get_logger from leap.bitmask.util.privilege_policies import LinuxPolicyChecker from leap.bitmask.util.privilege_policies import NoPolkitAuthAgentAvailable from leap.bitmask.util.privilege_policies import NoPkexecAvailable @@ -31,7 +31,7 @@ from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException from leap.bitmask.util import get_path_prefix, force_eval from leap.bitmask.util import first -logger = logging.getLogger(__name__) +logger = get_logger() COM = commands diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py index 7793d624..c48f857c 100644 --- a/src/leap/bitmask/services/eip/vpnlauncher.py +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -19,7 +19,6 @@ Platform independant VPN launcher interface. """ import getpass import hashlib -import logging import os import stat @@ -27,6 +26,7 @@ from abc import ABCMeta, abstractmethod from functools import partial from leap.bitmask.config import flags +from leap.bitmask.logs.utils import get_logger from leap.bitmask.backend.settings import Settings, GATEWAY_AUTOMATIC from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.platform_init import IS_LINUX @@ -35,7 +35,7 @@ from leap.bitmask.util import force_eval from leap.common.check import leap_assert, leap_assert_type -logger = logging.getLogger(__name__) +logger = get_logger() class VPNLauncherException(Exception): diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 3e46418c..586b50f5 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -18,7 +18,6 @@ VPN Manager, spawned in a custom processProtocol. """ import commands -import logging import os import shutil import socket @@ -39,6 +38,7 @@ except ImportError: from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.logs.utils import get_logger from leap.bitmask.services.eip import get_vpn_launcher from leap.bitmask.services.eip import linuxvpnlauncher from leap.bitmask.services.eip.eipconfig import EIPConfig @@ -47,13 +47,12 @@ from leap.bitmask.util import first, force_eval from leap.bitmask.platform_init import IS_MAC, IS_LINUX from leap.common.check import leap_assert, leap_assert_type -logger = logging.getLogger(__name__) -vpnlog = logging.getLogger('leap.openvpn') - from twisted.internet import defer, protocol, reactor from twisted.internet import error as internet_error from twisted.internet.task import LoopingCall +logger = get_logger() + class VPNObserver(object): """ @@ -884,7 +883,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): """ # truncate the newline line = data[:-1] - vpnlog.info(line) + logger.info(line) self._vpn_observer.watch(line) def processExited(self, reason): diff --git a/src/leap/bitmask/services/eip/windowsvpnlauncher.py b/src/leap/bitmask/services/eip/windowsvpnlauncher.py index 3f1ed43b..aaa3e45f 100644 --- a/src/leap/bitmask/services/eip/windowsvpnlauncher.py +++ b/src/leap/bitmask/services/eip/windowsvpnlauncher.py @@ -17,12 +17,11 @@ """ Windows VPN launcher implementation. """ -import logging - +from leap.bitmask.logs.utils import get_logger from leap.bitmask.services.eip.vpnlauncher import VPNLauncher from leap.common.check import leap_assert -logger = logging.getLogger(__name__) +logger = get_logger() class WindowsVPNLauncher(VPNLauncher): diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py index 2f1d20e6..68197d9d 100644 --- a/src/leap/bitmask/services/mail/conductor.py +++ b/src/leap/bitmask/services/mail/conductor.py @@ -17,9 +17,8 @@ """ Mail Services Conductor """ -import logging - from leap.bitmask.config import flags +from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui import statemachines from leap.bitmask.services.mail import connection as mail_connection from leap.bitmask.services.mail.emailfirewall import get_email_firewall @@ -28,7 +27,7 @@ from leap.common.events import catalog from leap.common.events import register as leap_register -logger = logging.getLogger(__name__) +logger = get_logger() class IMAPControl(object): diff --git a/src/leap/bitmask/services/mail/imap.py b/src/leap/bitmask/services/mail/imap.py index 30646a63..5934756d 100644 --- a/src/leap/bitmask/services/mail/imap.py +++ b/src/leap/bitmask/services/mail/imap.py @@ -17,16 +17,16 @@ """ Initialization of imap service """ -import logging import os import sys +from leap.bitmask.logs.utils import get_logger from leap.mail.constants import INBOX_NAME from leap.mail.imap.service import imap from leap.mail.incoming.service import IncomingMail, INCOMING_CHECK_PERIOD from twisted.python import log -logger = logging.getLogger(__name__) +logger = get_logger() # The name of the environment variable that has to be # set to override the default time value, in seconds. diff --git a/src/leap/bitmask/services/mail/imapcontroller.py b/src/leap/bitmask/services/mail/imapcontroller.py index d374ac29..e5313477 100644 --- a/src/leap/bitmask/services/mail/imapcontroller.py +++ b/src/leap/bitmask/services/mail/imapcontroller.py @@ -17,12 +17,10 @@ """ IMAP service controller. """ -import logging - +from leap.bitmask.logs.utils import get_logger from leap.bitmask.services.mail import imap - -logger = logging.getLogger(__name__) +logger = get_logger() class IMAPController(object): diff --git a/src/leap/bitmask/services/mail/plumber.py b/src/leap/bitmask/services/mail/plumber.py index 58625db5..43203f0c 100644 --- a/src/leap/bitmask/services/mail/plumber.py +++ b/src/leap/bitmask/services/mail/plumber.py @@ -30,6 +30,7 @@ from twisted.internet import defer from leap.bitmask.backend.settings import Settings from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.logs.utils import get_logger 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 @@ -37,8 +38,7 @@ from leap.bitmask.util import flatten, get_path_prefix from leap.mail.imap.account import IMAPAccount from leap.soledad.client import Soledad -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) +logger = get_logger() def initialize_soledad(uuid, email, passwd, diff --git a/src/leap/bitmask/services/mail/smtpbootstrapper.py b/src/leap/bitmask/services/mail/smtpbootstrapper.py index 6f45469b..cd871803 100644 --- a/src/leap/bitmask/services/mail/smtpbootstrapper.py +++ b/src/leap/bitmask/services/mail/smtpbootstrapper.py @@ -17,11 +17,11 @@ """ SMTP bootstrapping """ -import logging import os from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.certs import download_client_cert +from leap.bitmask.logs.utils import get_logger from leap.bitmask.services import download_service_config from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.services.mail.smtpconfig import SMTPConfig @@ -31,7 +31,7 @@ from leap.common import certs as leap_certs from leap.common.check import leap_assert from leap.common.files import check_and_fix_urw_only -logger = logging.getLogger(__name__) +logger = get_logger() class NoSMTPHosts(Exception): diff --git a/src/leap/bitmask/services/mail/smtpconfig.py b/src/leap/bitmask/services/mail/smtpconfig.py index 09f90314..2d8de411 100644 --- a/src/leap/bitmask/services/mail/smtpconfig.py +++ b/src/leap/bitmask/services/mail/smtpconfig.py @@ -17,16 +17,16 @@ """ SMTP configuration """ -import logging import os from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.logs.utils import get_logger from leap.bitmask.services import ServiceConfig from leap.bitmask.services.mail.smtpspec import get_schema from leap.bitmask.util import get_path_prefix from leap.common.check import leap_assert, leap_assert_type -logger = logging.getLogger(__name__) +logger = get_logger() class SMTPConfig(ServiceConfig): diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 9864e4e1..242bfb3d 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -17,7 +17,6 @@ """ Soledad bootstrapping """ -import logging import os import socket import sys @@ -32,6 +31,7 @@ from pysqlcipher.dbapi2 import ProgrammingError as sqlcipher_ProgrammingError from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpauth import SRPAuth +from leap.bitmask.logs.utils import get_logger from leap.bitmask.services import download_service_config from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.services.soledad.soledadconfig import SoledadConfig @@ -46,7 +46,7 @@ from leap.soledad.common.errors import InvalidAuthTokenError from leap.soledad.client import Soledad from leap.soledad.client.secrets import BootstrapSequenceError -logger = logging.getLogger(__name__) +logger = get_logger() """ These mocks are replicated from imap tests and the repair utility. diff --git a/src/leap/bitmask/services/soledad/soledadconfig.py b/src/leap/bitmask/services/soledad/soledadconfig.py index d3cc7da4..8052bcdb 100644 --- a/src/leap/bitmask/services/soledad/soledadconfig.py +++ b/src/leap/bitmask/services/soledad/soledadconfig.py @@ -17,12 +17,11 @@ """ Soledad configuration """ -import logging - +from leap.bitmask.logs.utils import get_logger from leap.bitmask.services import ServiceConfig from leap.bitmask.services.soledad.soledadspec import get_schema -logger = logging.getLogger(__name__) +logger = get_logger() class SoledadConfig(ServiceConfig): diff --git a/src/leap/bitmask/util/autostart.py b/src/leap/bitmask/util/autostart.py index d7a8afb8..2000b9f5 100644 --- a/src/leap/bitmask/util/autostart.py +++ b/src/leap/bitmask/util/autostart.py @@ -17,14 +17,14 @@ """ Helpers to enable/disable bitmask's autostart. """ -import logging import os from leap.bitmask.config import flags +from leap.bitmask.logs.utils import get_logger from leap.bitmask.platform_init import IS_LINUX from leap.common.files import mkdir_p -logger = logging.getLogger(__name__) +logger = get_logger() DESKTOP_ENTRY = """\ diff --git a/src/leap/bitmask/util/keyring_helpers.py b/src/leap/bitmask/util/keyring_helpers.py index 2b729b65..d81f39b1 100644 --- a/src/leap/bitmask/util/keyring_helpers.py +++ b/src/leap/bitmask/util/keyring_helpers.py @@ -17,8 +17,6 @@ """ Keyring helpers. """ -import logging - try: import keyring from keyring.backends.file import EncryptedKeyring, PlaintextKeyring @@ -35,7 +33,8 @@ except Exception: keyring = None -logger = logging.getLogger(__name__) +from leap.bitmask.logs.utils import get_logger +logger = get_logger() def _get_keyring_with_fallback(): diff --git a/src/leap/bitmask/util/polkit_agent.py b/src/leap/bitmask/util/polkit_agent.py index e512bffa..f6c7b4ca 100644 --- a/src/leap/bitmask/util/polkit_agent.py +++ b/src/leap/bitmask/util/polkit_agent.py @@ -17,14 +17,14 @@ """ Daemonizes polkit authentication agent. """ -import logging import os import subprocess import daemon # TODO --- logger won't work when daemoninzed. Log to syslog instead? -logger = logging.getLogger(__name__) +from leap.bitmask.logs.utils import get_logger +logger = get_logger() POLKIT_PATHS = ( '/usr/lib/lxpolkit/lxpolkit', diff --git a/src/leap/bitmask/util/privilege_policies.py b/src/leap/bitmask/util/privilege_policies.py index 2acc63cf..fd8c7c8e 100644 --- a/src/leap/bitmask/util/privilege_policies.py +++ b/src/leap/bitmask/util/privilege_policies.py @@ -19,7 +19,6 @@ Helpers to determine if the needed policies for privilege escalation are operative under this client run. """ import commands -import logging import os import subprocess import platform @@ -28,10 +27,11 @@ import time from abc import ABCMeta, abstractmethod from leap.bitmask.config import flags +from leap.bitmask.logs.utils import get_logger from leap.common.check import leap_assert from leap.common.files import which -logger = logging.getLogger(__name__) +logger = get_logger() class NoPolkitAuthAgentAvailable(Exception): diff --git a/src/leap/bitmask/util/requirement_checker.py b/src/leap/bitmask/util/requirement_checker.py index 37e8e693..99ef81b4 100644 --- a/src/leap/bitmask/util/requirement_checker.py +++ b/src/leap/bitmask/util/requirement_checker.py @@ -18,9 +18,7 @@ """ Utility to check the needed requirements. """ - import os -import logging from pkg_resources import (DistributionNotFound, get_distribution, @@ -28,7 +26,9 @@ from pkg_resources import (DistributionNotFound, resource_stream, VersionConflict) -logger = logging.getLogger(__name__) +from leap.bitmask.logs.utils import get_logger + +logger = get_logger() def get_requirements(): -- cgit v1.2.3 From d75e1395c9ff3ffcb0663122140be393c7ae6b60 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 19 Jun 2015 12:00:06 -0300 Subject: [feat] support new psutil API API to get the children has changed on latest psutil, this takes care of it. --- src/leap/bitmask/app.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 543fa03a..aa4e304b 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -73,7 +73,15 @@ def kill_the_children(): parent = psutil.Process(me) print "Killing all the children processes..." - for child in parent.get_children(recursive=True): + children = None + try: + # for psutil 0.2.x + children = parent.get_children(recursive=True) + except: + # for psutil 0.3.x + children = parent.children(recursive=True) + + for child in children: try: child.terminate() except Exception as exc: -- cgit v1.2.3 From 239a0ec845d53b7a0a1af5c27b5eea956ab6459a Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 19 Jun 2015 14:00:28 -0300 Subject: [feat] handle twisted/logging logs with logbook Forward Twisted logs to logging and use logbook to handle logging logs. Store the bitmask logs on the config folder. --- src/leap/bitmask/backend_app.py | 12 +++++++-- src/leap/bitmask/logs/safezmqhandler.py | 13 ++++++++++ src/leap/bitmask/logs/utils.py | 45 ++++++++++++++++++++++++++++----- 3 files changed, 61 insertions(+), 9 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py index 891575be..011f59fb 100644 --- a/src/leap/bitmask/backend_app.py +++ b/src/leap/bitmask/backend_app.py @@ -25,6 +25,7 @@ from leap.common.events import server as event_server from leap.bitmask.backend.leapbackend import LeapBackend from leap.bitmask.backend.utils import generate_zmq_certificates from leap.bitmask.config import flags +from leap.bitmask.logs.utils import get_logger from leap.bitmask.util import dict_to_flags @@ -54,11 +55,18 @@ def run_backend(bypass_checks=False, flags_dict=None, frontend_pid=None): :param flags_dict: a dict containing the flag values set on app start. :type flags_dict: dict """ + # In the backend, we want all the components to log into logbook + # that is: logging handlers and twisted logs + from logbook.compat import redirect_logging + from twisted.python.log import PythonLoggingObserver + redirect_logging() + observer = PythonLoggingObserver() + observer.start() + # NOTE: this needs to be used here, within the call since this function is # executed in a different process and it seems that the process/thread # identification isn't working 100% - from leap.bitmask.logs.utils import get_logger - logger = get_logger() + logger = get_logger() # noqa # The backend is the one who always creates the certificates. Either if it # is run separately or in a process in the same app as the frontend. diff --git a/src/leap/bitmask/logs/safezmqhandler.py b/src/leap/bitmask/logs/safezmqhandler.py index 7aac6a6a..4f7aca9b 100644 --- a/src/leap/bitmask/logs/safezmqhandler.py +++ b/src/leap/bitmask/logs/safezmqhandler.py @@ -39,6 +39,19 @@ class SafeZMQHandler(ZeroMQHandler): def __init__(self, uri=None, level=NOTSET, filter=None, bubble=False, context=None, multi=False): + """ + Safe zmq handler constructor that calls the ZeroMQHandler constructor + and does some extra initializations. + """ + # The current `SafeZMQHandler` uses the `ZeroMQHandler` constructor + # which creates a socket each time. + # The purpose of the `self._sockets` attribute is to prevent cases in + # which we use the same logger in different threads. For instance when + # we (in the same file) `deferToThread` a method/function, we are using + # the same logger/socket without calling get_logger again. + # If we want to reuse the socket, we need to rewrite this constructor + # 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) diff --git a/src/leap/bitmask/logs/utils.py b/src/leap/bitmask/logs/utils.py index e0a5fba3..413f6a75 100644 --- a/src/leap/bitmask/logs/utils.py +++ b/src/leap/bitmask/logs/utils.py @@ -1,18 +1,47 @@ -import logging +# -*- coding: utf-8 -*- +# utils.py +# Copyright (C) 2013, 2014, 2015 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 . +""" +Logs utilities +""" + +import os import sys from leap.bitmask.config import flags from leap.bitmask.logs import LOG_FORMAT from leap.bitmask.logs.log_silencer import SelectiveSilencerFilter from leap.bitmask.logs.safezmqhandler import SafeZMQHandler -from leap.bitmask.logs.streamtologger import StreamToLogger +# from leap.bitmask.logs.streamtologger import StreamToLogger from leap.bitmask.platform_init import IS_WIN +from leap.bitmask.util import get_path_prefix import logbook from logbook.more import ColorizedStderrHandler +BITMASK_LOG_FILE = os.path.join(get_path_prefix(), "leap", 'bitmask.log') + + def get_logger(): + """ + Push to the app stack the needed handlers and return a Logger object. + + :rtype: logbook.Logger + """ level = logbook.WARNING if flags.DEBUG: level = logbook.NOTSET @@ -27,8 +56,9 @@ def get_logger(): level=level, filter=silencer.filter) zmq_handler.push_application() - file_handler = logbook.FileHandler('bitmask.log', format_string=LOG_FORMAT, - bubble=True, filter=silencer.filter) + file_handler = logbook.FileHandler(BITMASK_LOG_FILE, + format_string=LOG_FORMAT, bubble=True, + filter=silencer.filter) file_handler.push_application() # don't use simple stream, go for colored log handler instead @@ -46,7 +76,7 @@ def get_logger(): return logger -def replace_stdout_stderr_with_logging(logger): +def replace_stdout_stderr_with_logging(logger=None): """ NOTE: we are not using this right now (see commented lines on app.py), @@ -61,8 +91,9 @@ def replace_stdout_stderr_with_logging(logger): # Disabling this on windows since it breaks ALL THE THINGS # The issue for this is #4149 if not IS_WIN: - sys.stdout = StreamToLogger(logger, logging.DEBUG) - sys.stderr = StreamToLogger(logger, logging.ERROR) + # logger = get_logger() + # sys.stdout = StreamToLogger(logger, logbook.NOTSET) + # sys.stderr = StreamToLogger(logger, logging.ERROR) # Replace twisted's logger to use our custom output. from twisted.python import log -- cgit v1.2.3 From 8793f3ef6862ddd5a01008b2e12d1f8d910fd261 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 19 Jun 2015 17:50:10 -0300 Subject: [feat] add log rotation feature Rotate bitmask.log file on each start. --- src/leap/bitmask/app.py | 2 +- src/leap/bitmask/logs/utils.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index aa4e304b..ca7eb1d3 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -145,7 +145,7 @@ def start_app(): flags.DEBUG = opts.debug - logger = get_logger() + logger = get_logger(perform_rollover=True) # NOTE: since we are not using this right now, the code that replaces the # stdout needs to be reviewed when we enable this again diff --git a/src/leap/bitmask/logs/utils.py b/src/leap/bitmask/logs/utils.py index 413f6a75..d3aa322b 100644 --- a/src/leap/bitmask/logs/utils.py +++ b/src/leap/bitmask/logs/utils.py @@ -36,7 +36,7 @@ from logbook.more import ColorizedStderrHandler BITMASK_LOG_FILE = os.path.join(get_path_prefix(), "leap", 'bitmask.log') -def get_logger(): +def get_logger(perform_rollover=False): """ Push to the app stack the needed handlers and return a Logger object. @@ -56,9 +56,13 @@ def get_logger(): level=level, filter=silencer.filter) zmq_handler.push_application() - file_handler = logbook.FileHandler(BITMASK_LOG_FILE, - format_string=LOG_FORMAT, bubble=True, - filter=silencer.filter) + file_handler = logbook.RotatingFileHandler( + BITMASK_LOG_FILE, format_string=LOG_FORMAT, bubble=True, + filter=silencer.filter, max_size=sys.maxint) + + if perform_rollover: + file_handler.perform_rollover() + file_handler.push_application() # don't use simple stream, go for colored log handler instead -- cgit v1.2.3 From 8de9387861bbb2f450befa78f4343b0a294b96f2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 22 Jun 2015 12:07:18 -0400 Subject: [feat] log lsb_release info if available if the lsb_release utility is present on system, log part of its output so we can have more information about the platform bitmask is running on. - Resolves: #7162 - Releases: 0.9.0 --- src/leap/bitmask/app.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'src/leap') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index ca7eb1d3..032a9e52 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -41,6 +41,7 @@ # (thanks to: http://www.glassgiant.com/ascii/) import atexit +import commands import multiprocessing import os import sys @@ -117,6 +118,17 @@ def do_mail_plumbing(opts): # XXX catch when import is used w/o acct +def log_lsb_release_info(logger): + """ + Attempt to log distribution info from the lsb_release utility + """ + if commands.getoutput('which lsb_release'): + distro_info = commands.getoutput('lsb_release -a').split('\n')[-4:] + logger.info("LSB Release info:") + for line in distro_info: + logger.info(line) + + def start_app(): """ Starts the main event loop and launches the main window. @@ -180,6 +192,7 @@ def start_app(): logger.info('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') logger.info('Bitmask version %s' % VERSION) logger.info('leap.mail version %s' % MAIL_VERSION) + log_lsb_release_info(logger) logger.info('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') logger.info('Starting app') -- cgit v1.2.3 From e7bda574b308c08dc5a61941b850442c43a17bd4 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 22 Jun 2015 16:58:19 -0300 Subject: [bug] make sure log path exists The logger is the first thing to be created and on a first run the config path won't exist. This way we make sure the path always exists. --- src/leap/bitmask/logs/utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/logs/utils.py b/src/leap/bitmask/logs/utils.py index d3aa322b..87d33876 100644 --- a/src/leap/bitmask/logs/utils.py +++ b/src/leap/bitmask/logs/utils.py @@ -28,12 +28,17 @@ from leap.bitmask.logs.safezmqhandler import SafeZMQHandler # from leap.bitmask.logs.streamtologger import StreamToLogger from leap.bitmask.platform_init import IS_WIN from leap.bitmask.util import get_path_prefix +from leap.common.files import mkdir_p import logbook from logbook.more import ColorizedStderrHandler -BITMASK_LOG_FILE = os.path.join(get_path_prefix(), "leap", 'bitmask.log') +# NOTE: make sure that the folder exists, the logger is created before saving +# settings on the first run. +_base = os.path.join(get_path_prefix(), "leap") +mkdir_p(_base) +BITMASK_LOG_FILE = os.path.join(_base, 'bitmask.log') def get_logger(perform_rollover=False): -- cgit v1.2.3 From fc65820f9e9a9a39434bbef3f5e9251436d9b458 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 25 Jun 2015 19:57:52 -0300 Subject: [bug] log contains exported PGP private key Due to #7139 (the logbook log centralizer) logs from 3rd party libs are included in the centralized logs with lots of debug information and we don't want that. We use the existing silencer to exclude logs that are not from leap modules. - Resolves: #7185 --- src/leap/bitmask/logs/log_silencer.py | 82 ++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 26 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/logs/log_silencer.py b/src/leap/bitmask/logs/log_silencer.py index 540532cb..da95e9b1 100644 --- a/src/leap/bitmask/logs/log_silencer.py +++ b/src/leap/bitmask/logs/log_silencer.py @@ -18,24 +18,31 @@ Filter for leap logs. """ import os -import re from leap.bitmask.util import get_path_prefix class SelectiveSilencerFilter(object): """ - Configurable filter for root leap logger. + Configurable log filter for a Logbook logger. - If you want to ignore components from the logging, just add them, - one by line, to ~/.config/leap/leap.dev.conf + To include certain logs add them to: + ~/.config/leap/leap_log_inclusion.dev.conf + + To exclude certain logs add them to: + ~/.config/leap/leap_log_exclusion.dev.conf + + The log filtering is based on how the module name starts. + In case of no inclusion or exclusion files are detected the default rules + will be used. """ # TODO we can augment this by properly parsing the log-silencer file # and having different sections: ignore, levels, ... # TODO use ConfigParser to unify sections [log-ignore] [log-debug] etc - CONFIG_NAME = "leap.dev.conf" + INCLUSION_CONFIG_FILE = "leap_log_inclusion.dev.conf" + EXCLUSION_CONFIG_FILE = "leap_log_exclusion.dev.conf" # Components to be completely silenced in the main bitmask logs. # You probably should think twice before adding a component to @@ -43,36 +50,47 @@ class SelectiveSilencerFilter(object): # only in those cases in which we gain more from silencing them than from # having their logs into the main log file that the user will likely send # to us. - SILENCER_RULES = ( + EXCLUSION_RULES = ( 'leap.common.events', 'leap.common.decorators', ) + # This tuple list the module names that we want to display, any different + # namespace will be filtered out. + INCLUSION_RULES = ( + '__main__', + 'leap.', # right now we just want to include logs from leap modules + 'twisted.', + ) + def __init__(self): """ Tries to load silencer rules from the default path, or load from the SILENCER_RULES tuple if not found. """ - self.rules = None - if os.path.isfile(self._rules_path): - self.rules = self._load_rules() - if not self.rules: - self.rules = self.SILENCER_RULES - - @property - def _rules_path(self): - """ - The configuration file for custom ignore rules. - """ - return os.path.join(get_path_prefix(), "leap", self.CONFIG_NAME) + self._inclusion_path = os.path.join(get_path_prefix(), "leap", + self.INCLUSION_CONFIG_FILE) + + self._exclusion_path = os.path.join(get_path_prefix(), "leap", + self.EXCLUSION_CONFIG_FILE) + + self._load_rules() def _load_rules(self): """ - Loads a list of paths to be ignored from the logging. + Load the inclusion and exclusion rules from the config files. """ - lines = open(self._rules_path).readlines() - return map(lambda line: re.sub('\s', '', line), - lines) + try: + with open(self._inclusion_path) as f: + self._inclusion_rules = f.read().splitlines() + except IOError: + self._inclusion_rules = self.INCLUSION_RULES + + try: + with open(self._exclusion_path) as f: + self._exclusion_rules = f.read().splitlines() + except IOError: + self._exclusion_rules = self.EXCLUSION_RULES def filter(self, record, handler): """ @@ -83,13 +101,25 @@ class SelectiveSilencerFilter(object): :returns: a bool indicating whether the record should be logged or not. :rtype: bool """ - if not self.rules: - return True + if not self._inclusion_rules and not self._exclusion_rules: + return True # do not filter if there are no rules + logger_path = record.module if logger_path is None: - return True + return True # we can't filter if there is no module info - for path in self.rules: + # exclude paths that ARE NOT listed in ANY of the inclusion rules + match = False + for path in self._inclusion_rules: + if logger_path.startswith(path): + match = True + + if not match: + return False + + # exclude paths that ARE listed in the exclusion rules + for path in self._exclusion_rules: if logger_path.startswith(path): return False + return True -- cgit v1.2.3 From 82f21a9fa05dc204be1f4c2b23d67f83acbe07ce Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 30 Jun 2015 00:15:23 -0400 Subject: [style] fix typo s/self/sync --- src/leap/bitmask/services/soledad/soledadbootstrapper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 242bfb3d..1825d317 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -615,7 +615,7 @@ class Syncer(object): return self._callback_deferred def _try_sync(self): - logger.debug("BOOTSTRAPPER: trying to self Soledad....") + logger.debug("BOOTSTRAPPER: trying to sync Soledad....") # pass defer_decryption=False to get inline decryption # for debugging. self._sync_deferred = self._soledad.sync(defer_decryption=True) @@ -632,7 +632,7 @@ class Syncer(object): def _error(self, failure): self._timeout_delayed_call.cancel() if failure.check(InvalidAuthTokenError): - logger.error('Invalid auth token while trying to self Soledad') + logger.error('Invalid auth token while trying to sync Soledad') self._signaler.signal( self._signaler.soledad_invalid_auth_token) self._callback_deferred.fail(failure) -- cgit v1.2.3 From 2612f7af2aa627ba4c4d680d15f54ede6402b4eb Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 6 Jul 2015 19:01:08 -0300 Subject: [bug] run zmq log subscriber in background Prevent locks caused by the zmq log handler reaching the zmq's HWM (High water mark / buffer limit) and causing some components to block until the buffer is empty (running the zmq handler for instance). We run the zmq handler in the background all the time to prevent this. - Resolves: #7222 --- src/leap/bitmask/gui/logwindow.py | 119 +------------------------------------ src/leap/bitmask/gui/mainwindow.py | 5 +- src/leap/bitmask/logs/utils.py | 108 +++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 117 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/gui/logwindow.py b/src/leap/bitmask/gui/logwindow.py index 123f14cc..718269c9 100644 --- a/src/leap/bitmask/gui/logwindow.py +++ b/src/leap/bitmask/gui/logwindow.py @@ -23,93 +23,15 @@ import cgi from PySide import QtCore, QtGui import logbook -from logbook.queues import ZeroMQSubscriber from ui_loggerwindow import Ui_LoggerWindow -from leap.bitmask.logs import LOG_FORMAT -from leap.bitmask.logs.utils import get_logger +from leap.bitmask.logs.utils import get_logger, LOG_CONTROLLER from leap.bitmask.util.constants import PASTEBIN_API_DEV_KEY from leap.bitmask.util import pastebin logger = get_logger() -# log history global variable used to store received logs through different -# opened instances of this window -_LOGS_HISTORY = [] - - -class QtLogHandler(logbook.Handler, logbook.StringFormatterHandlerMixin): - """ - Custom log handler which emits a log record with the message properly - formatted using a Qt Signal. - """ - - class _QtSignaler(QtCore.QObject): - """ - inline class used to hold the `new_log` Signal, if this is used - directly in the outside class it fails due how PySide works. - - This is the message we get if not use this method: - TypeError: Error when calling the metaclass bases - metaclass conflict: the metaclass of a derived class must be a - (non-strict) subclass of the metaclasses of all its bases - - """ - new_log = QtCore.Signal(object) - - def emit(self, data): - """ - emit the `new_log` Signal with the given `data` parameter. - - :param data: the data to emit along with the signal. - :type data: object - """ - # WARNING: the new-style connection does NOT work because PySide - # translates the emit method to self.emit, and that collides with - # the emit method for logging.Handler - # self.new_log.emit(log_item) - QtCore.QObject.emit(self, QtCore.SIGNAL('new_log(PyObject)'), data) - - def __init__(self, level=logbook.NOTSET, format_string=None, - encoding=None, filter=None, bubble=False): - - logbook.Handler.__init__(self, level, filter, bubble) - logbook.StringFormatterHandlerMixin.__init__(self, format_string) - - self.qt = self._QtSignaler() - - def __enter__(self): - return logbook.Handler.__enter__(self) - - def __exit__(self, exc_type, exc_value, tb): - return logbook.Handler.__exit__(self, exc_type, exc_value, tb) - - def emit(self, record): - """ - Emit the specified logging record using a Qt Signal. - Also add it to the history in order to be able to access it later. - - :param record: the record to emit - :type record: logbook.LogRecord - """ - global _LOGS_HISTORY - record.msg = self.format(record) - # NOTE: not optimal approach, we may want to look at - # bisect.insort with a custom approach to use key or - # http://code.activestate.com/recipes/577197-sortedcollection/ - # Sort logs on arrival, logs transmitted over zmq may arrive unsorted. - _LOGS_HISTORY.append(record) - _LOGS_HISTORY = sorted(_LOGS_HISTORY, key=lambda r: r.time) - - # XXX: emitting the record on arrival does not allow us to sort here so - # in the GUI the logs may arrive with with some time sort problem. - # We should implement a sort-on-arrive for the log window. - # Maybe we should switch to a tablewidget item that sort automatically - # by timestamp. - # As a user workaround you can close/open the log window - self.qt.emit(record) - class LoggerWindow(QtGui.QDialog): """ @@ -147,19 +69,8 @@ class LoggerWindow(QtGui.QDialog): self._set_logs_to_display() - self._my_handler = QtLogHandler(format_string=LOG_FORMAT) - self._my_handler.qt.new_log.connect(self._add_log_line) - + LOG_CONTROLLER.new_log.connect(self._add_log_line) self._load_history() - self._connect_to_logbook() - - def _connect_to_logbook(self): - """ - Run in the background the log receiver. - """ - subscriber = ZeroMQSubscriber('tcp://127.0.0.1:5000', multi=True) - self._logbook_controller = subscriber.dispatch_in_background( - self._my_handler) def _add_log_line(self, log): """ @@ -201,7 +112,7 @@ class LoggerWindow(QtGui.QDialog): self._set_logs_to_display() self.ui.txtLogHistory.clear() current_history = [] - for record in _LOGS_HISTORY: + for record in LOG_CONTROLLER.get_logs(): self._add_log_line(record) current_history.append(record.msg) @@ -334,27 +245,3 @@ class LoggerWindow(QtGui.QDialog): self._paste_thread = QtCore.QThread() self._paste_thread.run = lambda: do_pastebin() self._paste_thread.start() - - def closeEvent(self, e): - """ - Disconnect logger on close. - """ - self._disconnect_logger() - e.accept() - - def reject(self): - """ - Disconnect logger on reject. - """ - self._disconnect_logger() - QtGui.QDialog.reject(self) - - def _disconnect_logger(self): - """ - Stop the background thread that receives messages through zmq, also - close the subscriber socket. - This allows us to re-create the subscriber when we reopen this window - without getting an error at trying to connect twice to the zmq port. - """ - self._logbook_controller.stop() - self._logbook_controller.subscriber.close() diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index a2cf7bca..9f6c77cd 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -33,7 +33,7 @@ from leap.bitmask import __version_hash__ as VERSION_HASH from leap.bitmask.backend.leapbackend import ERROR_KEY, PASSED_KEY from leap.bitmask.config import flags -from leap.bitmask.logs.utils import get_logger +from leap.bitmask.logs.utils import get_logger, LOG_CONTROLLER from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement from leap.bitmask.gui.eip_status import EIPStatusWidget @@ -740,6 +740,8 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._show_hide_unsupported_services() + LOG_CONTROLLER.start_logbook_subscriber() + # XXX - HACK, kind of... # With the 1ms QTimer.singleShot call we schedule the call right after # other signals waiting for the qt reactor to take control. @@ -1707,6 +1709,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._leap_signaler.stop() + LOG_CONTROLLER.stop_logbook_subscriber() self._backend.stop() time.sleep(0.05) # give the thread a little time to finish. diff --git a/src/leap/bitmask/logs/utils.py b/src/leap/bitmask/logs/utils.py index 87d33876..f0e820b2 100644 --- a/src/leap/bitmask/logs/utils.py +++ b/src/leap/bitmask/logs/utils.py @@ -30,8 +30,11 @@ from leap.bitmask.platform_init import IS_WIN from leap.bitmask.util import get_path_prefix from leap.common.files import mkdir_p +from PySide import QtCore + import logbook from logbook.more import ColorizedStderrHandler +from logbook.queues import ZeroMQSubscriber # NOTE: make sure that the folder exists, the logger is created before saving @@ -107,3 +110,108 @@ def replace_stdout_stderr_with_logging(logger=None): # Replace twisted's logger to use our custom output. from twisted.python import log log.startLogging(sys.stdout) + + +class QtLogHandler(logbook.Handler, logbook.StringFormatterHandlerMixin): + """ + Custom log handler which emits a log record with the message properly + formatted using a Qt Signal. + """ + + class _QtSignaler(QtCore.QObject): + """ + inline class used to hold the `new_log` Signal, if this is used + directly in the outside class it fails due how PySide works. + + This is the message we get if not use this method: + TypeError: Error when calling the metaclass bases + metaclass conflict: the metaclass of a derived class must be a + (non-strict) subclass of the metaclasses of all its bases + + """ + new_log = QtCore.Signal(object) + + def emit(self, data): + """ + emit the `new_log` Signal with the given `data` parameter. + + :param data: the data to emit along with the signal. + :type data: object + """ + # WARNING: the new-style connection does NOT work because PySide + # translates the emit method to self.emit, and that collides with + # the emit method for logging.Handler + # self.new_log.emit(log_item) + QtCore.QObject.emit(self, QtCore.SIGNAL('new_log(PyObject)'), data) + + def __init__(self, level=logbook.NOTSET, format_string=None, + encoding=None, filter=None, bubble=False): + + logbook.Handler.__init__(self, level, filter, bubble) + logbook.StringFormatterHandlerMixin.__init__(self, format_string) + + self.qt = self._QtSignaler() + self.logs = [] + + def __enter__(self): + return logbook.Handler.__enter__(self) + + def __exit__(self, exc_type, exc_value, tb): + return logbook.Handler.__exit__(self, exc_type, exc_value, tb) + + def emit(self, record): + """ + Emit the specified logging record using a Qt Signal. + Also add it to the history in order to be able to access it later. + + :param record: the record to emit + :type record: logbook.LogRecord + """ + global _LOGS_HISTORY + record.msg = self.format(record) + # NOTE: not optimal approach, we may want to look at + # bisect.insort with a custom approach to use key or + # http://code.activestate.com/recipes/577197-sortedcollection/ + # Sort logs on arrival, logs transmitted over zmq may arrive unsorted. + self.logs.append(record) + self.logs = sorted(self.logs, key=lambda r: r.time) + + # XXX: emitting the record on arrival does not allow us to sort here so + # in the GUI the logs may arrive with with some time sort problem. + # We should implement a sort-on-arrive for the log window. + # Maybe we should switch to a tablewidget item that sort automatically + # by timestamp. + # As a user workaround you can close/open the log window + self.qt.emit(record) + + +class _LogController(object): + def __init__(self): + self._qt_handler = QtLogHandler(format_string=LOG_FORMAT) + self.new_log = self._qt_handler.qt.new_log + + def start_logbook_subscriber(self): + """ + Run in the background the log receiver. + """ + subscriber = ZeroMQSubscriber('tcp://127.0.0.1:5000', multi=True) + self._logbook_controller = subscriber.dispatch_in_background( + self._qt_handler) + + def stop_logbook_subscriber(self): + """ + Stop the background thread that receives messages through zmq, also + close the subscriber socket. + This allows us to re-create the subscriber when we reopen this window + without getting an error at trying to connect twice to the zmq port. + """ + self._logbook_controller.stop() + self._logbook_controller.subscriber.close() + + def get_logs(self): + return self._qt_handler.logs + +# use a global variable to store received logs through different opened +# instances of the log window as well as to containing the logbook background +# handle. +LOG_CONTROLLER = _LogController() -- cgit v1.2.3 From 65bdd263322fbce871e2804fbc6cbccb33fb53b8 Mon Sep 17 00:00:00 2001 From: kwadronaut Date: Sun, 19 Jul 2015 17:26:59 +0200 Subject: [style] grammar fix - Resolves: #7284 --- src/leap/bitmask/gui/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 9f6c77cd..22af9de2 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -985,7 +985,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): "manager or download it from " "addons.mozilla.org.".format(thunderbird_extension_url)) manual_text = self.tr( - "Alternately, you can manually configure " + "Alternatively, you can manually configure " "your mail client to use Bitmask Email with these options:") manual_imap = self.tr("IMAP: localhost, port {0}".format(IMAP_PORT)) manual_smtp = self.tr("SMTP: localhost, port {0}".format(smtp_port)) -- cgit v1.2.3 From 0d9c53c4ec182c6bf3452506351258b61bb1f739 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 8 Jul 2015 16:15:09 -0300 Subject: [bug] prevent logbook subscriber to fail Fix: If the logbook controller is not started the stop call will fail. Trying to start it twice fails. --- src/leap/bitmask/logs/utils.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/logs/utils.py b/src/leap/bitmask/logs/utils.py index f0e820b2..683fb542 100644 --- a/src/leap/bitmask/logs/utils.py +++ b/src/leap/bitmask/logs/utils.py @@ -188,15 +188,17 @@ class QtLogHandler(logbook.Handler, logbook.StringFormatterHandlerMixin): class _LogController(object): def __init__(self): self._qt_handler = QtLogHandler(format_string=LOG_FORMAT) + self._logbook_controller = None self.new_log = self._qt_handler.qt.new_log def start_logbook_subscriber(self): """ Run in the background the log receiver. """ - subscriber = ZeroMQSubscriber('tcp://127.0.0.1:5000', multi=True) - self._logbook_controller = subscriber.dispatch_in_background( - self._qt_handler) + if self._logbook_controller is None: + subscriber = ZeroMQSubscriber('tcp://127.0.0.1:5000', multi=True) + self._logbook_controller = subscriber.dispatch_in_background( + self._qt_handler) def stop_logbook_subscriber(self): """ @@ -205,8 +207,10 @@ class _LogController(object): This allows us to re-create the subscriber when we reopen this window without getting an error at trying to connect twice to the zmq port. """ - self._logbook_controller.stop() - self._logbook_controller.subscriber.close() + if self._logbook_controller is not None: + self._logbook_controller.stop() + self._logbook_controller.subscriber.close() + self._logbook_controller = None def get_logs(self): return self._qt_handler.logs -- cgit v1.2.3 From bc6c85b14452684c19a215ba7974924e7f1fd3af Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 20 Jul 2015 15:05:46 -0400 Subject: [bug] start the events server when reactor is running otherwise, events are not correctly registered - Resolves: #7149 --- src/leap/bitmask/backend_app.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py index 011f59fb..1040fbf8 100644 --- a/src/leap/bitmask/backend_app.py +++ b/src/leap/bitmask/backend_app.py @@ -86,7 +86,12 @@ def run_backend(bypass_checks=False, flags_dict=None, frontend_pid=None): # start the events server # This is not needed for the standalone bundle since the launcher takes # care of it. - event_server.ensure_server() + try: + from twisted.internet import reactor + reactor.callWhenRunning(reactor.callLater, 0, + event_server.ensure_server) + except Exception as e: + logger.error("Could not ensure server: %r" % (e,)) backend = LeapBackend(bypass_checks=bypass_checks, frontend_pid=frontend_pid) -- cgit v1.2.3 From 6188c4f9c5d7ebf9aad1673ad6d08d6d9541afbb Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 20 Jul 2015 15:08:09 -0400 Subject: [refactor] minor reordering of events registration Also, do not raise assertionerror, log warning instead. --- src/leap/bitmask/gui/mail_status.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index b5d1df81..bd7e549c 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -71,27 +71,23 @@ class MailStatusWidget(QtGui.QWidget): register(event=catalog.KEYMANAGER_LOOKING_FOR_KEY, callback=self._mail_handle_keymanager_events) - register(event=catalog.KEYMANAGER_KEY_FOUND, callback=self._mail_handle_keymanager_events) - - # register(event=catalog.KEYMANAGER_KEY_NOT_FOUND, - # callback=self._mail_handle_keymanager_events) - + register(event=catalog.KEYMANAGER_KEY_NOT_FOUND, + callback=self._mail_handle_keymanager_events) register(event=catalog.KEYMANAGER_STARTED_KEY_GENERATION, callback=self._mail_handle_keymanager_events) - register(event=catalog.KEYMANAGER_FINISHED_KEY_GENERATION, callback=self._mail_handle_keymanager_events) - register(event=catalog.KEYMANAGER_DONE_UPLOADING_KEYS, callback=self._mail_handle_keymanager_events) register(event=catalog.SOLEDAD_DONE_DOWNLOADING_KEYS, callback=self._mail_handle_soledad_events) - register(event=catalog.SOLEDAD_DONE_UPLOADING_KEYS, callback=self._mail_handle_soledad_events) + register(event=catalog.SOLEDAD_INVALID_AUTH_TOKEN, + callback=self.set_soledad_invalid_auth_token) register(event=catalog.MAIL_UNREAD_MESSAGES, callback=self._mail_handle_imap_events) @@ -100,9 +96,6 @@ class MailStatusWidget(QtGui.QWidget): register(event=catalog.SMTP_SERVICE_STARTED, callback=self._mail_handle_imap_events) - register(event=catalog.SOLEDAD_INVALID_AUTH_TOKEN, - callback=self.set_soledad_invalid_auth_token) - self._soledad_event.connect( self._mail_handle_soledad_events_slot) self._imap_event.connect( @@ -296,8 +289,9 @@ class MailStatusWidget(QtGui.QWidget): ext_status = self.tr("Initial sync in progress, please wait...") elif event == catalog.KEYMANAGER_KEY_FOUND: ext_status = self.tr("Found key! Starting mail...") - # elif event == catalog.KEYMANAGER_KEY_NOT_FOUND: - # ext_status = self.tr("Key not found!") + elif event == catalog.KEYMANAGER_KEY_NOT_FOUND: + ext_status = self.tr( + "Key not found...") elif event == catalog.KEYMANAGER_STARTED_KEY_GENERATION: ext_status = self.tr( "Generating new key, this may take a few minutes.") @@ -306,10 +300,7 @@ class MailStatusWidget(QtGui.QWidget): elif event == catalog.KEYMANAGER_DONE_UPLOADING_KEYS: ext_status = self.tr("Starting mail...") else: - leap_assert(False, - "Don't know how to handle this state: %s" - % (event)) - + logger.warning("don't know to to handle %s" % (event,)) self._set_mail_status(ext_status, ready=1) def _mail_handle_smtp_events(self, event): -- cgit v1.2.3 From 6b2d0f7424f08423bcdbb99002d8eacbc460fc68 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 24 Jul 2015 16:16:38 -0300 Subject: [style] pep8 comments --- src/leap/bitmask/util/leap_argparse.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index 383e5f8a..70feb61c 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -78,20 +78,20 @@ def build_parser(): # XXX not yet updated to new mail api for mail 0.4.0 # parser.add_argument('--acct', metavar="user@provider", - #nargs='?', - #action="store", dest="acct", - #help='Manipulate mailboxes for this account') + # nargs='?', + # 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. Use with --acct') + # action="store_true", dest="repair", + # help='Repair mailboxes for a given account. ' + # 'Use when upgrading versions after a schema ' + # 'change. Use with --acct') # parser.add_argument('--import-maildir', metavar="/path/to/Maildir", - #nargs='?', - #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') + # nargs='?', + # 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 59bc70465d706aae380682c94cac006d80646b90 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 24 Jul 2015 16:22:26 -0300 Subject: [feat] enable '--danger' for stable versions Is useful for new providers to be able to use the stable Bitmask version with this flag to be able to use custom certificates. - Resolves: #7250 --- src/leap/bitmask/util/leap_argparse.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py index 70feb61c..bfff503e 100644 --- a/src/leap/bitmask/util/leap_argparse.py +++ b/src/leap/bitmask/util/leap_argparse.py @@ -19,8 +19,6 @@ Parses the command line arguments passed to the application. """ import argparse -from leap.bitmask import IS_RELEASE_VERSION - def build_parser(): """ @@ -93,11 +91,10 @@ def build_parser(): # '--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 " - "bootstraping, for debugging development servers. " - "Use at your own risk!") - parser.add_argument('--danger', action="store_true", help=help_text) + help_text = ("INSECURE: 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) # optional cert file used to check domains with self signed certs. parser.add_argument('--ca-cert-file', metavar="/path/to/cacert.pem", @@ -132,8 +129,4 @@ def get_options(): parser = build_parser() opts, unknown = parser.parse_known_args() - # we add this option manually since it's not defined for 'release version' - if IS_RELEASE_VERSION: - opts.danger = False - return opts -- cgit v1.2.3 From 37a86b18242ccd27db41254e2f51b05b05509c8a Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Sun, 26 Jul 2015 15:37:51 +0200 Subject: [feat] Move the updater code from the launcher The new pyinstaller method will rule out the launcher, we need the updater working inside bitmask client. - Closes: #7291 --- src/leap/bitmask/backend_app.py | 27 ++++--- src/leap/bitmask/updater.py | 174 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 12 deletions(-) create mode 100644 src/leap/bitmask/updater.py (limited to 'src/leap') diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py index 1040fbf8..dcd08d97 100644 --- a/src/leap/bitmask/backend_app.py +++ b/src/leap/bitmask/backend_app.py @@ -20,6 +20,8 @@ Start point for the Backend. import multiprocessing import signal +from twisted.internet import reactor + from leap.common.events import server as event_server from leap.bitmask.backend.leapbackend import LeapBackend @@ -80,23 +82,24 @@ def run_backend(bypass_checks=False, flags_dict=None, frontend_pid=None): if flags_dict is not None: dict_to_flags(flags_dict) - # HACK we should be able to run the ensure_server anyway but right now it - # breaks if we run it twice. - if not flags.STANDALONE: - # start the events server - # This is not needed for the standalone bundle since the launcher takes - # care of it. - try: - from twisted.internet import reactor - reactor.callWhenRunning(reactor.callLater, 0, - event_server.ensure_server) - except Exception as e: - logger.error("Could not ensure server: %r" % (e,)) + reactor.callWhenRunning(start_events_and_updater, logger) backend = LeapBackend(bypass_checks=bypass_checks, frontend_pid=frontend_pid) backend.run() +def start_events_and_updater(logger): + event_server.ensure_server() + + if flags.STANDALONE: + try: + from leap.bitmask.updater import Updater + updater = Updater() + updater.start() + except ImportError: + logger.error("Updates are not enabled in this distribution.") + + if __name__ == '__main__': run_backend() diff --git a/src/leap/bitmask/updater.py b/src/leap/bitmask/updater.py new file mode 100644 index 00000000..c35eff5f --- /dev/null +++ b/src/leap/bitmask/updater.py @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- +# updater.py +# Copyright (C) 2014, 2015 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 . +""" +Updater check and download loop. +""" +import os +import shutil +import platform +import time +import threading +import ConfigParser +import tuf.client.updater + +from leap.bitmask.logs.utils import get_logger +from leap.common.events import emit, catalog + + +logger = get_logger() + + +""" +Supported platforms. + +Maps platform names from `platform.system() + "-" + platform.machine()` to the +platform names we use in the repos. +""" +bundles_per_platform = { + "Linux-i386": "linux-i386", + "Linux-i686": "linux-i386", + "Linux-x86_64": "linux-x86_64", +} + +CONFIG_PATH = "launcher.conf" +GENERAL_SECTION = "General" +DELAY_KEY = "updater_delay" + + +class Updater(threading.Thread): + def __init__(self): + """ + Initialize the list of mirrors, paths and other TUF dependencies from + the config file + """ + config = ConfigParser.ConfigParser() + config.read(CONFIG_PATH) + + if config.has_section(GENERAL_SECTION) and \ + config.has_option(GENERAL_SECTION, DELAY_KEY): + self.delay = config.getint(GENERAL_SECTION, DELAY_KEY) + else: + self.delay = 60 + + self._load_mirrors(config) + if not self.mirrors: + logger.error("No updater mirrors found (missing or not well " + "formed launcher.conf)") + + self.bundle_path = os.getcwd() + self.source_path = self.bundle_path + self.dest_path = os.path.join(self.bundle_path, 'tmp') + self.update_path = os.path.join(self.bundle_path, 'updates') + + tuf.conf.ssl_certificates = "./lib/leap/common/cacert.pem" + + threading.Thread.__init__(self) + self.daemon = True + + def run(self): + """ + Check for updates + """ + if not self.mirrors: + return + + while True: + try: + tuf.conf.repository_directory = os.path.join(self.bundle_path, + 'repo') + + updater = tuf.client.updater.Updater('leap-updater', + self.mirrors) + updater.refresh() + + targets = updater.all_targets() + updated_targets = updater.updated_targets(targets, + self.source_path) + if updated_targets: + logger.info("There is updates needed. Start downloading " + "updates.") + for target in updated_targets: + updater.download_target(target, self.dest_path) + self._set_permissions(target) + if os.path.isdir(self.dest_path): + if os.path.isdir(self.update_path): + shutil.rmtree(self.update_path) + shutil.move(self.dest_path, self.update_path) + filepath = sorted([f['filepath'] for f in updated_targets]) + emit(catalog.UPDATER_NEW_UPDATES, + ", ".join(filepath)) + logger.info("Updates ready: %s" % (filepath,)) + return + except NotImplemented as e: + logger.error("NotImplemented: %s" % (e,)) + return + except Exception as e: + logger.error("An unexpected error has occurred while " + "updating: %s" % (e,)) + finally: + time.sleep(self.delay) + + def _load_mirrors(self, config): + """ + Retrieve the mirrors from config and place them in self.mirrors + + :param config: parsed configuration file + :type config: ConfigParser + """ + self.mirrors = {} + for section in config.sections(): + if section[:6] != 'Mirror': + continue + url_prefix = config.get(section, 'url_prefix') + metadata_path = self._repo_path() + '/metadata' + targets_path = self._repo_path() + '/targets' + self.mirrors[section[7:]] = {'url_prefix': url_prefix, + 'metadata_path': metadata_path, + 'targets_path': targets_path, + 'confined_target_dirs': ['']} + + def _set_permissions(self, target): + """ + Walk over all the targets and set the rigt permissions on each file. + The permisions are stored in the custom field 'file_permissions' of the + TUF's targets.json + + :param target: the already parsed target json + :type target: tuf.formats.TARGETFILES_SCHEMA + """ + file_permissions_str = target["fileinfo"]["custom"]["file_permissions"] + file_permissions = int(file_permissions_str, 8) + filepath = target['filepath'] + if filepath[0] == '/': + filepath = filepath[1:] + file_path = os.path.join(self.dest_path, filepath) + os.chmod(file_path, file_permissions) + + def _repo_path(self): + """ + Find the remote repo path deneding on the 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 + supported by the updater. + """ + system = platform.system() + "-" + platform.machine() + if system not in bundles_per_platform: + raise NotImplementedError("Platform %s not supported" % (system,)) + return bundles_per_platform[system] -- cgit v1.2.3 From 877e92d8698510b243b32d45e9c46f25e6f04615 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Fri, 7 Aug 2015 12:50:36 +0200 Subject: [test] fix SRP tests The tests where using deferToThread to run without need, I remove it and now it's easier to debug when one test fails. - Resolves: #7343 --- src/leap/bitmask/crypto/srpregister.py | 5 +- src/leap/bitmask/crypto/tests/test_srpregister.py | 64 ++++++++--------------- 2 files changed, 26 insertions(+), 43 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/crypto/srpregister.py b/src/leap/bitmask/crypto/srpregister.py index baa15244..7a216847 100644 --- a/src/leap/bitmask/crypto/srpregister.py +++ b/src/leap/bitmask/crypto/srpregister.py @@ -73,8 +73,9 @@ class SRPRegisterImpl: :param password: password for this username :type password: str - :returns: if the registration went ok or not. - :rtype: bool + :returns: if the registration went ok or not, and the returned status + code of of the request + :rtype: (bool, int) """ username = username.lower().encode('utf-8') diff --git a/src/leap/bitmask/crypto/tests/test_srpregister.py b/src/leap/bitmask/crypto/tests/test_srpregister.py index 4d6e7be3..c019a60c 100644 --- a/src/leap/bitmask/crypto/tests/test_srpregister.py +++ b/src/leap/bitmask/crypto/tests/test_srpregister.py @@ -26,9 +26,8 @@ import os import sys from mock import MagicMock -from nose.twistedtools import reactor, deferred +from nose.twistedtools import reactor from twisted.python import log -from twisted.internet import threads from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto import srpregister, srpauth @@ -111,10 +110,10 @@ class SRPTestCase(unittest.TestCase): raise ImproperlyConfiguredError( "Could not load test provider config") - register = srpregister.SRPRegister(provider_config=provider) - self.assertEquals(register._port, "443") + register = srpregister.SRPRegister(provider_config=provider, + register_path="users") + self.assertEquals(register._srp_register._port, "443") - @deferred() def test_wrong_cert(self): provider = ProviderConfig() loaded = provider.load(path=os.path.join( @@ -129,13 +128,11 @@ class SRPTestCase(unittest.TestCase): raise ImproperlyConfiguredError( "Could not load test provider config") - register = srpregister.SRPRegister(provider_config=provider) - d = threads.deferToThread(register.register_user, "foouser_firsttime", - "barpass") - d.addCallback(self.assertFalse) - return d + register = srpregister.SRPRegister(provider_config=provider, + register_path="users") + ok = register.register_user("foouser_firsttime", "barpass") + self.assertFalse(ok) - @deferred() def test_register_user(self): """ Checks if the registration of an unused name works as expected when @@ -143,31 +140,17 @@ class SRPTestCase(unittest.TestCase): when we request a user that is taken. """ # pristine registration - d = threads.deferToThread(self.register.register_user, - "foouser_firsttime", - "barpass") - d.addCallback(self.assertTrue) - return d + ok = self.register.register_user("foouser_firsttime", "barpass") + self.assertTrue(ok) - @deferred() def test_second_register_user(self): # second registration attempt with the same user should return errors - d = threads.deferToThread(self.register.register_user, - "foouser_second", - "barpass") - d.addCallback(self.assertTrue) - - # FIXME currently we are catching this in an upper layer, - # we could bring the error validation to the SRPRegister class - def register_wrapper(_): - return threads.deferToThread(self.register.register_user, - "foouser_second", - "barpass") - d.addCallback(register_wrapper) - d.addCallback(self.assertFalse) - return d - - @deferred() + ok = self.register.register_user("foouser_second", "barpass") + self.assertTrue(ok) + + ok = self.register.register_user("foouser_second", "barpass") + self.assertFalse(ok) + def test_correct_http_uri(self): """ Checks that registration autocorrect http uris to https ones. @@ -187,15 +170,14 @@ class SRPTestCase(unittest.TestCase): raise ImproperlyConfiguredError( "Could not load test provider config") - register = srpregister.SRPRegister(provider_config=provider) + register = srpregister.SRPRegister(provider_config=provider, + register_path="users") # ... and we check that we're correctly taking the HTTPS protocol # instead - reg_uri = register._get_registration_uri() + reg_uri = register._srp_register._get_registration_uri() self.assertEquals(reg_uri, HTTPS_URI) - register._get_registration_uri = MagicMock(return_value=HTTPS_URI) - d = threads.deferToThread(register.register_user, "test_failhttp", - "barpass") - d.addCallback(self.assertTrue) - - return d + register._srp_register._get_registration_uri = MagicMock( + return_value=HTTPS_URI) + ok = register.register_user("test_failhttp", "barpass") + self.assertTrue(ok) -- cgit v1.2.3 From 657005c72abdd478d1f4809e9984d401cbbbb845 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 5 Aug 2015 18:54:58 -0700 Subject: [feat] add util function fo find location when frozen --- src/leap/bitmask/util/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src/leap') diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py index e8eddd64..9853803a 100644 --- a/src/leap/bitmask/util/__init__.py +++ b/src/leap/bitmask/util/__init__.py @@ -20,6 +20,7 @@ Some small and handy functions. import datetime import itertools import os +import sys from leap.bitmask.config import flags from leap.common.config import get_path_prefix as common_get_path_prefix @@ -154,3 +155,14 @@ def flags_to_dict(): values = dict((i, getattr(flags, i)) for i in items) return values + +def here(module=None): + if getattr(sys, 'frozen', False): + # we are running in a |PyInstaller| bundle + return sys._MEIPASS + else: + dirname = os.path.dirname + if module: + return dirname(module.__file__) + else: + return dirname(__file__) -- cgit v1.2.3 From f5e150a4fe87b60316d278de6b4ead08357f2dba Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 5 Aug 2015 18:57:36 -0700 Subject: [feat] add path to gpg binary in the bundle --- src/leap/bitmask/services/soledad/soledadbootstrapper.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 1825d317..57ae3849 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -37,7 +37,8 @@ from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.services.soledad.soledadconfig import SoledadConfig 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.bitmask.util import here +from leap.bitmask.platform_init import IS_WIN, IS_MAC from leap.common.check import leap_assert, leap_assert_type, leap_check from leap.common.files import which from leap.keymanager import KeyManager, openpgp @@ -439,6 +440,9 @@ class SoledadBootstrapper(AbstractBootstrapper): except IndexError as e: logger.debug("Couldn't find the gpg binary!") logger.exception(e) + if IS_MAC: + gpgbin = os.path.abspath( + os.path.join(here(), "apps", "mail", "gpg")) leap_check(gpgbin is not None, "Could not find gpg binary") return gpgbin -- cgit v1.2.3 From 109af6218886ac4cdaf0d915675b1e912c68341e Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 5 Aug 2015 18:59:52 -0700 Subject: [bug] schedule signal emitting in reactor thread --- src/leap/bitmask/services/abstractbootstrapper.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/services/abstractbootstrapper.py b/src/leap/bitmask/services/abstractbootstrapper.py index 6a08f475..191309ba 100644 --- a/src/leap/bitmask/services/abstractbootstrapper.py +++ b/src/leap/bitmask/services/abstractbootstrapper.py @@ -25,7 +25,7 @@ from functools import partial from PySide import QtCore from twisted.python import log -from twisted.internet import threads +from twisted.internet import threads, reactor from twisted.internet.defer import CancelledError from leap.bitmask.logs.utils import get_logger @@ -155,7 +155,8 @@ class AbstractBootstrapper(QtCore.QObject): data = {self.PASSED_KEY: True, self.ERROR_KEY: ""} if isinstance(signal, basestring): if self._signaler is not None: - self._signaler.signal(signal, data) + reactor.callFromThread( + self._signaler.signal, signal, data) else: logger.warning("Tried to notify but no signaler found") else: -- cgit v1.2.3 From 7f50b9944833715da2d4a58b126722dc3a9a7bc2 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 5 Aug 2015 19:02:45 -0700 Subject: [doc] clarify that backend lives in a subprocess --- src/leap/bitmask/backend_app.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/leap') diff --git a/src/leap/bitmask/backend_app.py b/src/leap/bitmask/backend_app.py index dcd08d97..1300ed05 100644 --- a/src/leap/bitmask/backend_app.py +++ b/src/leap/bitmask/backend_app.py @@ -51,6 +51,8 @@ def signal_handler(signum, frame): def run_backend(bypass_checks=False, flags_dict=None, frontend_pid=None): """ Run the backend for the application. + This is called from the main app.py entrypoint, and is run in a child + subprocess. :param bypass_checks: whether we should bypass the checks or not :type bypass_checks: bool -- cgit v1.2.3 From 154109b9b08a782e2a7f6b6dddbe33e576b2b8a0 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 5 Aug 2015 19:14:03 -0700 Subject: [bug] raise the maxfiles limit in OSX due to zmq, we are hitting the limit and getting app crashes. - Resolves: #7319 --- src/leap/bitmask/app.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 032a9e52..be1fc424 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -39,13 +39,19 @@ # M:::::::::::~NMMM7???7MMMM:::::::::::::::::::::::NMMMI??I7MMMM:::::::::::::M # M::::::::::::::7MMMMMMM+:::::::::::::::::::::::::::?MMMMMMMZ:::::::::::::::M # (thanks to: http://www.glassgiant.com/ascii/) - import atexit import commands import multiprocessing import os +import platform import sys +if platform.system() == "Darwin": + # We need to tune maximum number of files, due to zmq usage + # we hit the limit. + import resource + resource.setrlimit(resource.RLIMIT_NOFILE, (4096, 10240)) + from leap.bitmask import __version__ as VERSION from leap.bitmask.backend.backend_proxy import BackendProxy from leap.bitmask.backend_app import run_backend -- cgit v1.2.3 From f220d32702eddbd18be167b2742bb60043f60a45 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 5 Aug 2015 19:17:43 -0700 Subject: [bug] workaround wrong qtplugins path rewritten in the libs this is a bug in macholib, there's a missing letter in the plugins path, so unless we fix this they cannot be loaded from the bundle. See: https://bitbucket.org/pydica/pyside-setup/commits/4b8be97e5a00b577fe30ce9aa7e5723ff2a66f94 Quoting from http://code.activestate.com/lists/pythonmac-sig/23278/: """ The problem might be this line: @rpath/Contents/mageformats/libqtiff.dylib The "i" from "imageformats" is missing! This _might_ be related to the unusual case that "libqtiff" has no path at all, or something else is funny, and we end up with a name that will not be found at all. Then the loader finds the plugin in the installed Qt, which causes it to load everything again from there. """ --- src/leap/bitmask/app.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index be1fc424..a2e2aa1a 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -60,7 +60,7 @@ from leap.bitmask.frontend_app import run_frontend from leap.bitmask.logs.utils import get_logger from leap.bitmask.platform_init.locks import we_are_the_one_and_only from leap.bitmask.services.mail import plumber -from leap.bitmask.util import leap_argparse, flags_to_dict +from leap.bitmask.util import leap_argparse, flags_to_dict, here from leap.bitmask.util.requirement_checker import check_requirements from leap.mail import __version__ as MAIL_VERSION @@ -68,7 +68,6 @@ from leap.mail import __version__ as MAIL_VERSION import codecs codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) - import psutil @@ -134,6 +133,15 @@ 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 + # in the libs. + if sys.platform in ('win32', 'darwin'): + from PySide import QtCore + plugins_path = os.path.join(os.path.dirname(here(QtCore)), 'plugins') + QtCore.QCoreApplication.setLibraryPaths([plugins_path]) + def start_app(): """ @@ -200,7 +208,6 @@ def start_app(): logger.info('leap.mail version %s' % MAIL_VERSION) log_lsb_release_info(logger) logger.info('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') - logger.info('Starting app') backend_running = BackendProxy().check_online() @@ -220,6 +227,7 @@ def start_app(): backend_process.start() backend_pid = backend_process.pid + fix_qtplugins_path() run_frontend(options, flags_dict, backend_pid=backend_pid) -- cgit v1.2.3 From 612deb39f759d9c5a86b78fc012b8a8b56fcb838 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 7 Aug 2015 15:28:15 -0300 Subject: [feat] add soledad sync progress to the UI Register to Soledad's sync (send and receive) events and display the progress in the UI. - Resolves: #7353 --- src/leap/bitmask/gui/mail_status.py | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index bd7e549c..1a38c8cf 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -35,7 +35,7 @@ class MailStatusWidget(QtGui.QWidget): """ Status widget that displays the state of the LEAP Mail service """ - _soledad_event = QtCore.Signal(object) + _soledad_event = QtCore.Signal(object, object) _smtp_event = QtCore.Signal(object) _imap_event = QtCore.Signal(object, object) _keymanager_event = QtCore.Signal(object) @@ -86,6 +86,10 @@ class MailStatusWidget(QtGui.QWidget): callback=self._mail_handle_soledad_events) register(event=catalog.SOLEDAD_DONE_UPLOADING_KEYS, callback=self._mail_handle_soledad_events) + register(event=catalog.SOLEDAD_SYNC_RECEIVE_STATUS, + callback=self._mail_handle_soledad_events) + register(event=catalog.SOLEDAD_SYNC_SEND_STATUS, + callback=self._mail_handle_soledad_events) register(event=catalog.SOLEDAD_INVALID_AUTH_TOKEN, callback=self.set_soledad_invalid_auth_token) @@ -228,11 +232,11 @@ class MailStatusWidget(QtGui.QWidget): :param event: The event that triggered the callback. :type event: str :param content: The content of the event. - :type content: list + :type content: dict """ - self._soledad_event.emit(event) + self._soledad_event.emit(event, content) - def _mail_handle_soledad_events_slot(self, event): + def _mail_handle_soledad_events_slot(self, event, content): """ TRIGGERS: _mail_handle_soledad_events @@ -241,21 +245,44 @@ class MailStatusWidget(QtGui.QWidget): :param event: The event that triggered the callback. :type event: str + :param content: The content of the event. + :type content: dict """ self._set_mail_status(self.tr("Starting..."), ready=1) ext_status = "" + ready = None if event == catalog.SOLEDAD_DONE_UPLOADING_KEYS: ext_status = self.tr("Soledad has started...") + ready = 1 elif event == catalog.SOLEDAD_DONE_DOWNLOADING_KEYS: ext_status = self.tr("Soledad is starting, please wait...") + ready = 1 + elif event == catalog.SOLEDAD_SYNC_RECEIVE_STATUS: + sync_progress = content['received'] * 100 / content['total'] + if sync_progress < 100: + ext_status = self.tr("Sync: downloading ({0:02}%)") + ext_status = ext_status.format(sync_progress) + else: + ext_status = self.tr("Sync: download completed.") + + ready = 2 + elif event == catalog.SOLEDAD_SYNC_SEND_STATUS: + sync_progress = content['sent'] * 100 / content['total'] + if sync_progress < 100: + ext_status = self.tr("Sync: uploading ({0:02}%)") + ext_status = ext_status.format(sync_progress) + else: + ext_status = self.tr("Sync: upload complete.") + + ready = 2 else: leap_assert(False, "Don't know how to handle this state: %s" % (event)) - self._set_mail_status(ext_status, ready=1) + self._set_mail_status(ext_status, ready=ready) def _mail_handle_keymanager_events(self, event, content): """ -- cgit v1.2.3 From bb808bb42046eb71411b2a4528e1820801428a7a Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 12 Aug 2015 12:17:30 -0400 Subject: [bug] fail more clearly if we got incorrect version string on the sumo tarball, the _version file for leap/bitmask submodule wasn't being frozen, and hence BITMASK_VERSION was None. this was breaking the provider tests w/o any clear error message. - Related: #7322 --- src/leap/bitmask/provider/__init__.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/provider/__init__.py b/src/leap/bitmask/provider/__init__.py index 89ff5d95..4385a92f 100644 --- a/src/leap/bitmask/provider/__init__.py +++ b/src/leap/bitmask/provider/__init__.py @@ -17,6 +17,7 @@ """ Provider utilities. """ +import logging import os from pkg_resources import parse_version @@ -24,6 +25,8 @@ from pkg_resources import parse_version from leap.bitmask import __short_version__ as BITMASK_VERSION from leap.common.check import leap_assert +logger = logging.getLogger(__name__) + # The currently supported API versions by the client. SUPPORTED_APIS = ["1"] @@ -62,4 +65,12 @@ def supports_client(minimum_version): :returns: True if that version is supported or False otherwise. :return type: bool """ - return parse_version(minimum_version) <= parse_version(BITMASK_VERSION) + try: + min_ver = parse_version(minimum_version) + cur_ver = parse_version(BITMASK_VERSION) + supported = min_ver <= cur_ver + except TypeError as exc: + logger.error("Error while parsing versions") + logger.exception(exc) + supported = False + return supported -- cgit v1.2.3 From 182dd2738f077079a47d3f56033b78dac9204986 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 23 Jul 2015 18:45:08 -0300 Subject: [bug] handle eip-config not providing locations Is valid for a provider not to provide locations for their gateways. - Resolves: #7281 --- src/leap/bitmask/backend/components.py | 4 +++- src/leap/bitmask/services/eip/eipconfig.py | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/backend/components.py b/src/leap/bitmask/backend/components.py index b833bf59..5f34d290 100644 --- a/src/leap/bitmask/backend/components.py +++ b/src/leap/bitmask/backend/components.py @@ -628,7 +628,9 @@ class EIP(object): # this only works for selecting the first gateway, as we're # currently doing. ccodes = gateway_selector.get_gateways_country_code() - gateway_ccode = ccodes[gateways[0]] + gateway_ccode = '' # '' instead of None due to needed signal argument + if ccodes is not None: + gateway_ccode = ccodes[gateways[0]] self._signaler.signal(self._signaler.eip_get_gateway_country_code, gateway_ccode) diff --git a/src/leap/bitmask/services/eip/eipconfig.py b/src/leap/bitmask/services/eip/eipconfig.py index b1d08393..43328af9 100644 --- a/src/leap/bitmask/services/eip/eipconfig.py +++ b/src/leap/bitmask/services/eip/eipconfig.py @@ -161,12 +161,17 @@ class VPNGatewaySelector(object): def get_gateways_country_code(self): """ Return a dict with ipaddress -> country code mapping. + Return None if there are no locations specified. - :rtype: dict + :rtype: dict or None """ country_codes = {} locations = self._eipconfig.get_locations() + + if not locations: + return + gateways = self._eipconfig.get_gateways() for idx, gateway in enumerate(gateways): -- cgit v1.2.3 From 9a21cc06cfc4024c881b4ba59c10c69e7de90fe9 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 20 Apr 2015 17:09:43 -0300 Subject: [bug] handle disabled registration, error 403. If the user wants to register a new account we check whether the provider allows registration or not right after getting the provider.json file and show an error msg on the wizard if not allowed. Also, there is a new signal to handle the error raised by the server if a registration attempt is made but is rejected with error 403. - Resolves: #6594 --- src/leap/bitmask/backend/api.py | 1 + src/leap/bitmask/backend/leapsignaler.py | 1 + src/leap/bitmask/config/providerconfig.py | 10 ++++++++++ src/leap/bitmask/crypto/srpregister.py | 3 +++ src/leap/bitmask/gui/wizard.py | 31 +++++++++++++++++++++++++++++++ 5 files changed, 46 insertions(+) (limited to 'src/leap') diff --git a/src/leap/bitmask/backend/api.py b/src/leap/bitmask/backend/api.py index 3f6c0ad1..48aa2090 100644 --- a/src/leap/bitmask/backend/api.py +++ b/src/leap/bitmask/backend/api.py @@ -146,6 +146,7 @@ SIGNALS = ( "srp_password_change_badpw", "srp_password_change_error", "srp_password_change_ok", + "srp_registration_disabled", "srp_registration_failed", "srp_registration_finished", "srp_registration_taken", diff --git a/src/leap/bitmask/backend/leapsignaler.py b/src/leap/bitmask/backend/leapsignaler.py index c0fdffdc..1ac51f5e 100644 --- a/src/leap/bitmask/backend/leapsignaler.py +++ b/src/leap/bitmask/backend/leapsignaler.py @@ -109,6 +109,7 @@ class LeapSignaler(SignalerQt): srp_password_change_badpw = QtCore.Signal() srp_password_change_error = QtCore.Signal() srp_password_change_ok = QtCore.Signal() + srp_registration_disabled = QtCore.Signal() srp_registration_failed = QtCore.Signal() srp_registration_finished = QtCore.Signal() srp_registration_taken = QtCore.Signal() diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py index f454bb40..d972b280 100644 --- a/src/leap/bitmask/config/providerconfig.py +++ b/src/leap/bitmask/config/providerconfig.py @@ -69,6 +69,7 @@ class ProviderConfig(BaseConfig): details["description"] = config.get_description(lang=lang) details["enrollment_policy"] = config.get_enrollment_policy() details["services"] = config.get_services() + details["allow_registration"] = config.get_allow_registration() services = [] for service in config.get_services(): @@ -177,6 +178,15 @@ class ProviderConfig(BaseConfig): services = self._safe_get_value("services") return services + def get_allow_registration(self): + """ + Return whether the registration is allowed or not in the provider. + + :rtype: bool + """ + service = self._safe_get_value("service") + return service['allow_registration'] + def get_ca_cert_path(self, about_to_download=False): """ Returns the path to the certificate for the current provider. diff --git a/src/leap/bitmask/crypto/srpregister.py b/src/leap/bitmask/crypto/srpregister.py index 7a216847..9bf19377 100644 --- a/src/leap/bitmask/crypto/srpregister.py +++ b/src/leap/bitmask/crypto/srpregister.py @@ -153,6 +153,7 @@ class SRPRegister(QtCore.QObject): STATUS_OK = (200, 201) STATUS_TAKEN = 422 + STATUS_FORBIDDEN = 403 def __init__(self, signaler=None, provider_config=None, register_path="users"): @@ -204,6 +205,8 @@ class SRPRegister(QtCore.QObject): self._signaler.signal(self._signaler.srp_registration_finished) elif status_code == self.STATUS_TAKEN: self._signaler.signal(self._signaler.srp_registration_taken) + elif status_code == self.STATUS_FORBIDDEN: + self._signaler.signal(self._signaler.srp_registration_disabled) else: self._signaler.signal(self._signaler.srp_registration_failed) diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index c60d967b..5145d8b6 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -385,6 +385,19 @@ class Wizard(QtGui.QWizard, SignalTracker): self._set_register_status(error_msg, error=True) self.ui.btnRegister.setEnabled(True) + def _registration_disabled(self): + """ + TRIGGERS: + self._backend.signaler.srp_registration_disabled + + The registration is disabled in the current provider. + """ + self._username = self._password = None + + error_msg = self.tr("The registration is disabled for this provider.") + self._set_register_status(error_msg, error=True) + self.ui.btnRegister.setEnabled(True) + def _registration_taken(self): """ TRIGGERS: @@ -581,6 +594,22 @@ class Wizard(QtGui.QWizard, SignalTracker): :type details: dict """ self._provider_details = details + self._check_registration_allowed() + + def _check_registration_allowed(self): + """ + Check whether the provider allows new users registration or not. + If it is not allowed we display a message and prevent the user moving + forward on the wizard. + """ + if self._show_register: # user wants to register a new account + if not self._provider_details['allow_registration']: + logger.debug("Registration not allowed") + status = ("" + + self.tr("The provider has disabled registration") + + "") + self.ui.lblProviderSelectStatus.setText(status) + self.button(QtGui.QWizard.NextButton).setEnabled(False) def _download_ca_cert(self, data): """ @@ -686,6 +715,7 @@ class Wizard(QtGui.QWizard, SignalTracker): self._skip_provider_checks(skip) else: self._enable_check(reset=False) + self._check_registration_allowed() if pageId == self.SETUP_PROVIDER_PAGE: if not self._provider_setup_ok: @@ -765,5 +795,6 @@ class Wizard(QtGui.QWizard, SignalTracker): conntrack(sig.prov_check_api_certificate, self._check_api_certificate) conntrack(sig.srp_registration_finished, self._registration_finished) + conntrack(sig.srp_registration_disabled, self._registration_disabled) conntrack(sig.srp_registration_failed, self._registration_failed) conntrack(sig.srp_registration_taken, self._registration_taken) -- cgit v1.2.3 From eb5184845818d9440423251450c6bdcc44e5156d Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 21 Aug 2015 13:37:25 -0400 Subject: [feat] allow to disable EIP on build --- src/leap/bitmask/_components.py | 6 + src/leap/bitmask/gui/mainwindow.py | 295 +++++++++++++---------- src/leap/bitmask/gui/preferences_account_page.py | 7 +- src/leap/bitmask/gui/preferenceswindow.py | 9 +- src/leap/bitmask/gui/wizard.py | 7 + 5 files changed, 194 insertions(+), 130 deletions(-) create mode 100644 src/leap/bitmask/_components.py (limited to 'src/leap') diff --git a/src/leap/bitmask/_components.py b/src/leap/bitmask/_components.py new file mode 100644 index 00000000..9be0e6bc --- /dev/null +++ b/src/leap/bitmask/_components.py @@ -0,0 +1,6 @@ +""" +Enabled Modules in Bitmask. +Change these values for builds of the client with only one module enabled. +""" +HAS_EIP = True +HAS_MAIL = True diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 22af9de2..44a048e0 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -36,10 +36,8 @@ from leap.bitmask.config import flags from leap.bitmask.logs.utils import get_logger, LOG_CONTROLLER from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement -from leap.bitmask.gui.eip_status import EIPStatusWidget from leap.bitmask.gui.logwindow import LoggerWindow from leap.bitmask.gui.login import LoginWidget -from leap.bitmask.gui.mail_status import MailStatusWidget from leap.bitmask.gui.preferenceswindow import PreferencesWindow from leap.bitmask.gui.signaltracker import SignalTracker from leap.bitmask.gui.systray import SysTray @@ -53,11 +51,6 @@ from leap.bitmask.platform_init import locks from leap.bitmask.platform_init.initializers import init_platform from leap.bitmask.platform_init.initializers import init_signals -from leap.bitmask.services.eip import conductor as eip_conductor -from leap.bitmask.services.mail import conductor as mail_conductor - -from leap.bitmask.services import EIP_SERVICE, MX_SERVICE - from leap.bitmask.util import autostart, make_address from leap.bitmask.util.keyring_helpers import has_keyring @@ -68,6 +61,19 @@ from leap.mail.imap.service.imap import IMAP_PORT from ui_mainwindow import Ui_MainWindow +from leap.bitmask._components import HAS_EIP, HAS_MAIL + +if HAS_EIP: + from leap.bitmask.gui.eip_status import EIPStatusWidget + from leap.bitmask.services.eip import conductor as eip_conductor + from leap.bitmask.services import EIP_SERVICE + +if HAS_MAIL: + from leap.bitmask.gui.mail_status import MailStatusWidget + from leap.bitmask.services.mail import conductor as mail_conductor + from leap.bitmask.services import MX_SERVICE + + QtDelayedCall = QtCore.QTimer.singleShot logger = get_logger() @@ -78,17 +84,19 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): Main window for login and presenting status updates to the user """ # Signals - eip_needs_login = QtCore.Signal([]) + new_updates = QtCore.Signal(object) raise_window = QtCore.Signal([]) soledad_ready = QtCore.Signal([]) all_services_stopped = QtCore.Signal() - # We use this flag to detect abnormal terminations - user_stopped_eip = False + if HAS_EIP: + eip_needs_login = QtCore.Signal([]) + # 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_START_TIMEOUT = 60000 # in milliseconds + # We give EIP some time to come up before starting soledad anyway + EIP_START_TIMEOUT = 60000 # in milliseconds # We give the services some time to a halt before forcing quit. SERVICES_STOP_TIMEOUT = 3000 # in milliseconds @@ -131,9 +139,10 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._leap_signaler, self) self.ui.loginLayout.addWidget(self._login_widget) - # Mail Widget - self._mail_status = MailStatusWidget(self) - self.ui.mailLayout.addWidget(self._mail_status) + if HAS_MAIL: + # Mail Widget + self._mail_status = MailStatusWidget(self) + self.ui.mailLayout.addWidget(self._mail_status) # Provider List self._providers = Providers(self.ui.cmbProviders) @@ -150,41 +159,43 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._providers.connect_provider_changed(self._on_provider_changed) - # EIP Control redux ######################################### - self._eip_conductor = eip_conductor.EIPConductor( - self._settings, self._backend, self._leap_signaler) - self._eip_status = EIPStatusWidget(self, self._eip_conductor, - self._leap_signaler) - - init_signals.eip_missing_helpers.connect( - self._disable_eip_missing_helpers) - - self.ui.eipLayout.addWidget(self._eip_status) - - # XXX we should get rid of the circular refs - # conductor <-> status, right now keeping state on the widget ifself. - self._eip_conductor.add_eip_widget(self._eip_status) - - self._eip_conductor.connect_signals() - self._eip_conductor.qtsigs.connecting_signal.connect( - self._on_eip_connecting) - self._eip_conductor.qtsigs.connected_signal.connect( - self._on_eip_connection_connected) - self._eip_conductor.qtsigs.disconnected_signal.connect( - self._on_eip_connection_disconnected) - self._eip_conductor.qtsigs.connected_signal.connect( - self._maybe_run_soledad_setup_checks) + if HAS_EIP: + # EIP Control redux ######################################### + self._eip_conductor = eip_conductor.EIPConductor( + self._settings, self._backend, self._leap_signaler) + self._eip_status = EIPStatusWidget(self, self._eip_conductor, + self._leap_signaler) + + init_signals.eip_missing_helpers.connect( + self._disable_eip_missing_helpers) + + self.ui.eipLayout.addWidget(self._eip_status) + + # XXX we should get rid of the circular refs + # conductor <-> status, + # right now keeping state on the widget ifself. + self._eip_conductor.add_eip_widget(self._eip_status) + + self._eip_conductor.connect_signals() + self._eip_conductor.qtsigs.connecting_signal.connect( + self._on_eip_connecting) + self._eip_conductor.qtsigs.connected_signal.connect( + self._on_eip_connection_connected) + self._eip_conductor.qtsigs.disconnected_signal.connect( + self._on_eip_connection_disconnected) + self._eip_conductor.qtsigs.connected_signal.connect( + self._maybe_run_soledad_setup_checks) + + self.eip_needs_login.connect(self._eip_status.disable_eip_start) + self.eip_needs_login.connect(self._disable_eip_start_action) + + # XXX all this info about state should move to eip conductor too + self._already_started_eip = False + self._trying_to_start_eip = False self._login_widget.login_offline_finished.connect( self._maybe_run_soledad_setup_checks) - self.eip_needs_login.connect(self._eip_status.disable_eip_start) - self.eip_needs_login.connect(self._disable_eip_start_action) - - # XXX all this info about state should move to eip conductor too - self._already_started_eip = False - self._trying_to_start_eip = False - self._soledad_started = False # This is created once we have a valid provider config @@ -216,6 +227,7 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._on_provider_changed) # Action item hidden since we don't provide stable mail yet. + # TODO enable for 0.9.0 release?? # self.ui.action_advanced_key_management.triggered.connect( # self._show_AKM) @@ -227,11 +239,16 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._systray = None # XXX separate actions into a different module. - self._action_mail_status = QtGui.QAction(self.tr("Mail is OFF"), self) - self._mail_status.set_action_mail_status(self._action_mail_status) + if HAS_MAIL: + self._action_mail_status = QtGui.QAction( + self.tr("Mail is OFF"), self) + self._mail_status.set_action_mail_status( + self._action_mail_status) - self._action_eip_startstop = QtGui.QAction("", self) - self._eip_status.set_action_eip_startstop(self._action_eip_startstop) + if HAS_EIP: + self._action_eip_startstop = QtGui.QAction("", self) + self._eip_status.set_action_eip_startstop( + self._action_eip_startstop) self._action_visible = QtGui.QAction(self.tr("Show Main Window"), self) self._action_visible.triggered.connect(self._ensure_visible) @@ -269,19 +286,21 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._start_hidden = start_hidden self._backend_pid = backend_pid - self._mail_conductor = mail_conductor.MailConductor(self._backend) - self._mail_conductor.connect_mail_signals(self._mail_status) + if HAS_MAIL: + self._mail_conductor = mail_conductor.MailConductor(self._backend) + self._mail_conductor.connect_mail_signals(self._mail_status) if not init_platform(): self.quit() return # start event machines from within the eip and mail conductors - # TODO should encapsulate all actions into one object - self._eip_conductor.start_eip_machine( - action=self._action_eip_startstop) - self._mail_conductor.start_mail_machine() + if HAS_EIP: + self._eip_conductor.start_eip_machine( + action=self._action_eip_startstop) + if HAS_MAIL: + self._mail_conductor.start_mail_machine() if self._first_run(): self._wizard_firstrun = True @@ -360,17 +379,17 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): # here. sig.srp_not_logged_in_error.connect(self._not_logged_in_error) - # EIP start signals ============================================== - self._eip_conductor.connect_backend_signals() - sig.eip_can_start.connect(self._backend_can_start_eip) - sig.eip_cannot_start.connect(self._backend_cannot_start_eip) + if HAS_EIP: + # EIP start signals ============================================== + self._eip_conductor.connect_backend_signals() + sig.eip_can_start.connect(self._backend_can_start_eip) + sig.eip_cannot_start.connect(self._backend_cannot_start_eip) - sig.eip_dns_error.connect(self._eip_dns_error) + sig.eip_dns_error.connect(self._eip_dns_error) - sig.eip_get_gateway_country_code.connect(self._set_eip_provider) - sig.eip_no_gateway.connect(self._set_eip_provider) - - # ================================================================== + sig.eip_get_gateway_country_code.connect(self._set_eip_provider) + sig.eip_no_gateway.connect(self._set_eip_provider) + # ================================================================== # Soledad signals # TODO delegate connection to soledad bootstrapper @@ -488,7 +507,8 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._login_widget.set_password(possible_password) self._login() else: - self.eip_needs_login.emit() + if HAS_EIP: + self.eip_needs_login.emit() self._wizard = None @@ -566,22 +586,26 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): self._backend_cannot_start_eip() return - if EIP_SERVICE not in self.app.settings.get_enabled_services(domain): - self._eip_conductor.terminate() + services_enabled = self.app.settings.get_enabled_services(domain) - def hide(): - self.app.backend.eip_can_start(domain=domain) + if HAS_EIP: + if EIP_SERVICE not in services_enabled: + self._eip_conductor.terminate() - QtDelayedCall(100, hide) - # ^^ VERY VERY Hacky, but with the simple state machine, - # there is no way to signal 'disconnect and then disable' + def hide(): + self.app.backend.eip_can_start(domain=domain) - else: - self._trying_to_start_eip = self.app.settings.get_autostart_eip() - if not self._trying_to_start_eip: - self._backend.eip_setup(provider=domain, skip_network=True) - # check if EIP can start (will trigger widget update) - self.app.backend.eip_can_start(domain=domain) + QtDelayedCall(100, hide) + # ^^ VERY VERY Hacky, but with the simple state machine, + # there is no way to signal 'disconnect and then disable' + + else: + settings = self.app.settings + self._trying_to_start_eip = settings.get_autostart_eip() + if not self._trying_to_start_eip: + self._backend.eip_setup(provider=domain, skip_network=True) + # check if EIP can start (will trigger widget update) + self.app.backend.eip_can_start(domain=domain) def _backend_can_start_eip(self): """ @@ -633,15 +657,16 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): if default_provider is not None: enabled_services = settings.get_enabled_services(default_provider) - if EIP_SERVICE in enabled_services: - # we don't have a usable provider - # so the user needs to log in first - self._eip_status.disable_eip_start() - else: - self._eip_status.disable_eip_start() - # NOTE: we shouldn't be setting the message here. - if not self._eip_status.missing_helpers: - self._eip_status.set_eip_status(self.tr("Disabled")) + if HAS_EIP: + if EIP_SERVICE in enabled_services: + # we don't have a usable provider + # so the user needs to log in first + self._eip_status.disable_eip_start() + else: + self._eip_status.disable_eip_start() + # NOTE: we shouldn't be setting the message here. + if not self._eip_status.missing_helpers: + self._eip_status.set_eip_status(self.tr("Disabled")) # this state flag is responsible for deferring the login # so we must update it, otherwise we're in a deadlock. @@ -779,12 +804,13 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): only, the mail widget won't be displayed. """ providers = self._settings.get_configured_providers() - self._backend.provider_get_all_services(providers=providers) def _provider_get_all_services(self, services): - self._set_eip_visible(EIP_SERVICE in services) - self._set_mx_visible(MX_SERVICE in services) + if HAS_EIP: + self._set_eip_visible(EIP_SERVICE in services) + if HAS_MAIL: + self._set_mx_visible(MX_SERVICE in services) def _set_mx_visible(self, visible): """ @@ -837,23 +863,29 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): systrayMenu.addAction(self._action_visible) systrayMenu.addSeparator() - eip_status_label = u"{0}: {1}".format( - self._eip_conductor.eip_name, self.tr("OFF")) - self._eip_menu = eip_menu = systrayMenu.addMenu(eip_status_label) - eip_menu.addAction(self._action_eip_startstop) - self._eip_status.set_eip_status_menu(eip_menu) - systrayMenu.addSeparator() - systrayMenu.addAction(self._action_mail_status) - systrayMenu.addSeparator() + if HAS_EIP: + eip_status_label = u"{0}: {1}".format( + self._eip_conductor.eip_name, self.tr("OFF")) + self._eip_menu = eip_menu = systrayMenu.addMenu(eip_status_label) + eip_menu.addAction(self._action_eip_startstop) + self._eip_status.set_eip_status_menu(eip_menu) + systrayMenu.addSeparator() + if HAS_MAIL: + systrayMenu.addAction(self._action_mail_status) + systrayMenu.addSeparator() systrayMenu.addAction(self.ui.action_quit) self._systray = SysTray(self) self._systray.setContextMenu(systrayMenu) - self._systray.setIcon(self._eip_status.ERROR_ICON_TRAY) + + if HAS_EIP: + self._systray.setIcon(self._eip_status.ERROR_ICON_TRAY) self._systray.setVisible(True) self._systray.activated.connect(self._tray_activated) - self._mail_status.set_systray(self._systray) - self._eip_status.set_systray(self._systray) + if HAS_EIP: + self._eip_status.set_systray(self._systray) + if HAS_MAIL: + self._mail_status.set_systray(self._systray) if self._start_hidden: hello = lambda: self._systray.showMessage( @@ -1093,8 +1125,11 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): # TODO: we should handle the case that EIP is autostarting since we # won't get a warning until EIP has fully started. # TODO: we need to add a check for the mail status (smtp/imap/soledad) - something_runing = (self._login_widget.get_logged_user() is not None or - self._already_started_eip) + + something_runing = self._login_widget.get_logged_user() is not None + if HAS_EIP: + something_runing = something_runing or self._already_started_eip + provider = self._providers.get_selected_provider() self._login_widget.set_provider(provider) @@ -1175,25 +1210,32 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): user = self._login_widget.get_logged_user() # XXX the widget now gives us the full user id. # this is confusing. - # domain = self._providers.get_selected_provider() - # full_user_id = make_address(user, domain) - # XXX the casting to str (needed by smtp gateway) should be done - # in a better place. - self._mail_conductor.userid = str(user) - self._start_eip_bootstrap() self.ui.action_create_new_account.setEnabled(True) - # if soledad/mail is enabled: - if MX_SERVICE in self._enabled_services: - btn_enabled = self._login_widget.set_logout_btn_enabled - btn_enabled(False) - sig = self._leap_signaler - sig.soledad_bootstrap_failed.connect(lambda: btn_enabled(True)) - sig.soledad_bootstrap_finished.connect(lambda: btn_enabled(True)) - - if MX_SERVICE not in self._provider_details['services']: - self._set_mx_visible(False) + if HAS_EIP: + self._start_eip_bootstrap() + if HAS_MAIL: + # XXX the casting to str (needed by smtp gateway) should be done + # in a better place. + self._mail_conductor.userid = str(user) + if MX_SERVICE in self._enabled_services: + btn_enabled = self._login_widget.set_logout_btn_enabled + btn_enabled(False) + sig = self._leap_signaler + sig.soledad_bootstrap_failed.connect( + lambda: btn_enabled(True)) + sig.soledad_bootstrap_finished.connect( + lambda: btn_enabled(True)) + + if MX_SERVICE not in self._provider_details['services']: + self._set_mx_visible(False) + + if not HAS_EIP: + # This has to be worked out in Bitmask 0.10. + # Since EIP won't start, we need to trigger + # the soledad setup service from here. + self._maybe_run_soledad_setup_checks() def _on_user_logged_out(self): """ @@ -1203,8 +1245,9 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): Switch the stackedWidget back to the login stage after logging out """ - self._mail_conductor.stop_mail_services() - self._mail_status.mail_state_disabled() + if HAS_MAIL: + self._mail_conductor.stop_mail_services() + self._mail_status.mail_state_disabled() self._show_hide_unsupported_services() def _start_eip_bootstrap(self): @@ -1465,9 +1508,9 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): missing_helpers = self._eip_status.missing_helpers already_started = self._already_started_eip - can_start = (should_start - and not already_started - and not missing_helpers) + can_start = (should_start and + not already_started and + not missing_helpers) if can_start: if self._eip_status.is_cold_start: @@ -1504,7 +1547,9 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): msg = self.tr("Disabled") self._eip_status.disable_eip_start() self._eip_status.set_eip_status(msg) + # eip will not start, so we start soledad anyway + # XXX This is the entry point for soledad startup. self._maybe_run_soledad_setup_checks() def _finish_eip_bootstrap(self, data): diff --git a/src/leap/bitmask/gui/preferences_account_page.py b/src/leap/bitmask/gui/preferences_account_page.py index 5661fb66..da9da14d 100644 --- a/src/leap/bitmask/gui/preferences_account_page.py +++ b/src/leap/bitmask/gui/preferences_account_page.py @@ -21,9 +21,10 @@ from functools import partial from PySide import QtCore, QtGui from leap.bitmask.logs.utils import get_logger -from leap.bitmask.gui.ui_preferences_account_page import Ui_PreferencesAccountPage +from leap.bitmask.gui import ui_preferences_account_page as ui_pref from leap.bitmask.gui.passwordwindow import PasswordWindow from leap.bitmask.services import get_service_display_name +from leap.bitmask._components import HAS_EIP logger = get_logger() @@ -42,7 +43,7 @@ class PreferencesAccountPage(QtGui.QWidget): :type app: App """ QtGui.QWidget.__init__(self, parent) - self.ui = Ui_PreferencesAccountPage() + self.ui = ui_pref.Ui_PreferencesAccountPage() self.ui.setupUi(self) self.account = account @@ -120,6 +121,8 @@ class PreferencesAccountPage(QtGui.QWidget): # add one checkbox per service and set the current value # from what is saved in settings. for service in services: + if not HAS_EIP and service == "openvpn": + continue try: checkbox = QtGui.QCheckBox( get_service_display_name(service), self) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index a71f4e5c..baa71252 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -20,7 +20,8 @@ Preferences window """ from PySide import QtCore, QtGui -from leap.bitmask.services import EIP_SERVICE, MX_SERVICE +from leap.bitmask.services import EIP_SERVICE +from leap.bitmask._components import HAS_EIP from leap.bitmask.logs.utils import get_logger from leap.bitmask.gui.ui_preferences import Ui_Preferences @@ -120,7 +121,8 @@ class PreferencesWindow(QtGui.QDialog): """ Adds the pages for the different configuration categories. """ - self._account_page = PreferencesAccountPage(self, self.account, self.app) + self._account_page = PreferencesAccountPage( + self, self.account, self.app) self._vpn_page = PreferencesVpnPage(self, self.account, self.app) self._email_page = PreferencesEmailPage(self, self.account, self.app) @@ -178,6 +180,7 @@ class PreferencesWindow(QtGui.QDialog): if account != self.account: return - self._vpn_item.setHidden(not EIP_SERVICE in services) + if HAS_EIP: + self._vpn_item.setHidden(EIP_SERVICE not in services) # self._email_item.setHidden(not MX_SERVICE in services) # ^^ disable email for now, there is nothing there yet. diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 5145d8b6..abaf2108 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -35,6 +35,7 @@ from leap.bitmask.services import get_service_display_name, get_supported from leap.bitmask.util.credentials import password_checks, username_checks from leap.bitmask.util.credentials import USERNAME_REGEX from leap.bitmask.util.keyring_helpers import has_keyring +from leap.bitmask._components import HAS_EIP from ui_wizard import Ui_Wizard @@ -690,6 +691,12 @@ class Wizard(QtGui.QWizard, SignalTracker): checkbox.stateChanged.connect( partial(self._service_selection_changed, service)) checkbox.setChecked(True) + + if service == "openvpn" and not HAS_EIP: + # this is a mail-only build, we disable eip. + checkbox.setEnabled(False) + checkbox.setChecked(False) + self._shown_services.add(service) except ValueError: logger.error( -- cgit v1.2.3 From 28d176a2376d3ab41812ca31601d5b5195086d34 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 24 Aug 2015 15:24:16 -0400 Subject: [pkg] add icon for mail-only builds --- src/leap/bitmask/gui/mainwindow.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/leap') diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 44a048e0..312048ba 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -73,11 +73,13 @@ if HAS_MAIL: from leap.bitmask.services.mail import conductor as mail_conductor from leap.bitmask.services import MX_SERVICE - QtDelayedCall = QtCore.QTimer.singleShot logger = get_logger() +if not HAS_EIP: + BITMASK_MAIL_ONLY_ICON = ":/images/menubar-mask-icon.png" + class MainWindow(QtGui.QMainWindow, SignalTracker): """ @@ -874,11 +876,16 @@ class MainWindow(QtGui.QMainWindow, SignalTracker): systrayMenu.addAction(self._action_mail_status) systrayMenu.addSeparator() systrayMenu.addAction(self.ui.action_quit) + self._systray = SysTray(self) self._systray.setContextMenu(systrayMenu) if HAS_EIP: self._systray.setIcon(self._eip_status.ERROR_ICON_TRAY) + else: + mail_status_icon = QtGui.QPixmap(BITMASK_MAIL_ONLY_ICON) + self._systray.setIcon(mail_status_icon) + self._systray.setVisible(True) self._systray.activated.connect(self._tray_activated) -- cgit v1.2.3