From 4c726c1531abfe288604eaa4c1d347e85bed81eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 3 Jun 2013 15:02:41 -0300 Subject: Use Twisted's deferToThread and Deferreds to handle parallel tasks This removes CheckerThread --- src/leap/services/abstractbootstrapper.py | 155 +++++++++ src/leap/services/eip/eipbootstrapper.py | 264 +++++----------- src/leap/services/eip/providerbootstrapper.py | 384 ++++++----------------- src/leap/services/eip/vpnlaunchers.py | 2 +- src/leap/services/mail/smtpbootstrapper.py | 142 ++++----- src/leap/services/soledad/soledadbootstrapper.py | 233 +++++--------- 6 files changed, 466 insertions(+), 714 deletions(-) create mode 100644 src/leap/services/abstractbootstrapper.py (limited to 'src/leap/services') diff --git a/src/leap/services/abstractbootstrapper.py b/src/leap/services/abstractbootstrapper.py new file mode 100644 index 00000000..bce03e6b --- /dev/null +++ b/src/leap/services/abstractbootstrapper.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +# abstractbootstrapper.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 . + +""" +Abstract bootstrapper implementation +""" +import logging + +import requests + +from PySide import QtCore +from twisted.internet import threads +from leap.common.check import leap_assert, leap_assert_type + +logger = logging.getLogger(__name__) + + +class AbstractBootstrapper(QtCore.QObject): + """ + Abstract Bootstrapper that implements the needed deferred callbacks + """ + + PASSED_KEY = "passed" + ERROR_KEY = "error" + + def __init__(self, bypass_checks=False): + """ + Constructor for the abstract bootstrapper + + :param bypass_checks: Set to true if the app should bypass + first round of checks for CA + certificates at bootstrap + :type bypass_checks: bool + """ + QtCore.QObject.__init__(self) + + leap_assert(self._gui_errback.im_func == \ + AbstractBootstrapper._gui_errback.im_func, + "Cannot redefine _gui_errback") + leap_assert(self._errback.im_func == \ + AbstractBootstrapper._errback.im_func, + "Cannot redefine _errback") + leap_assert(self._gui_notify.im_func == \ + AbstractBootstrapper._gui_notify.im_func, + "Cannot redefine _gui_notify") + + # **************************************************** # + # Dependency injection helpers, override this for more + # granular testing + self._fetcher = requests + # **************************************************** # + + self._session = self._fetcher.session() + self._bypass_checks = bypass_checks + self._signal_to_emit = None + self._err_msg = None + + def _gui_errback(self, failure): + """ + Errback used to notify the GUI of a problem, it should be used + as the last errback of the whole chain. + + Traps all exceptions if a signal is defined, otherwise it just + lets it continue. + + NOTE: This method is final, it should not be redefined. + + :param failure: failure object that Twisted generates + :type failure: twisted.python.failure.Failure + """ + if self._signal_to_emit: + err_msg = self._err_msg \ + if self._err_msg is not None \ + else str(failure.value) + self._signal_to_emit.emit({ + self.PASSED_KEY: False, + self.ERROR_KEY: err_msg + }) + failure.trap(Exception) + + def _errback(self, failure, signal=None): + """ + Regular errback used for the middle of the chain. If it's + executed, the first one will set the signal to emit as + failure. + + NOTE: This method is final, it should not be redefined. + + :param failure: failure object that Twisted generates + :type failure: twisted.python.failure.Failure + :param signal: Signal to emit if it fails here first + :type signal: QtCore.SignalInstance + + :returns: failure object that Twisted generates + :rtype: twisted.python.failure.Failure + """ + if self._signal_to_emit is None: + self._signal_to_emit = signal + return failure + + def _gui_notify(self, _, signal=None): + """ + Callback used to notify the GUI of a success. Will emit signal + if specified + + NOTE: This method is final, it should not be redefined. + + :param _: IGNORED. Returned from the previous callback + :type _: IGNORED + :param signal: Signal to emit if it fails here first + :type signal: QtCore.SignalInstance + """ + if signal: + logger.debug("Emitting %s" % (signal,)) + signal.emit({self.PASSED_KEY: True, self.ERROR_KEY: ""}) + + def addCallbackChain(self, callbacks): + """ + Creates a callback/errback chain on another thread using + deferToThread and adds the _gui_errback to the end to notify + the GUI on an error. + + :param callbacks: List of tuples of callbacks and the signal + associated to that callback + :type callbacks: list(tuple(func, func)) + """ + leap_assert_type(callbacks, list) + + self._signal_to_emit = None + self._err_msg = None + + d = None + for cb, sig in callbacks: + if d is None: + d = threads.deferToThread(cb) + else: + d.addCallback(cb) + d.addErrback(self._errback, signal=sig) + d.addCallback(self._gui_notify, signal=sig) + d.addErrback(self._gui_errback) + diff --git a/src/leap/services/eip/eipbootstrapper.py b/src/leap/services/eip/eipbootstrapper.py index a881f235..7216bb80 100644 --- a/src/leap/services/eip/eipbootstrapper.py +++ b/src/leap/services/eip/eipbootstrapper.py @@ -22,9 +22,7 @@ EIP bootstrapping import logging import os -import requests - -from PySide import QtGui, QtCore +from PySide import QtCore from leap.common.check import leap_assert, leap_assert_type from leap.common.certs import is_valid_pemfile, should_redownload @@ -32,49 +30,34 @@ from leap.common.files import check_and_fix_urw_only, get_mtime, mkdir_p from leap.config.providerconfig import ProviderConfig from leap.crypto.srpauth import SRPAuth from leap.services.eip.eipconfig import EIPConfig -from leap.util.checkerthread import CheckerThread from leap.util.request_helpers import get_content +from leap.services.abstractbootstrapper import AbstractBootstrapper logger = logging.getLogger(__name__) -class EIPBootstrapper(QtCore.QObject): +class EIPBootstrapper(AbstractBootstrapper): """ Sets up EIP for a provider a series of checks and emits signals after they are passed. If a check fails, the subsequent checks are not executed """ - PASSED_KEY = "passed" - ERROR_KEY = "error" - - IDLE_SLEEP_INTERVAL = 100 - # All dicts returned are of the form # {"passed": bool, "error": str} download_config = QtCore.Signal(dict) download_client_certificate = QtCore.Signal(dict) def __init__(self): - QtCore.QObject.__init__(self) + AbstractBootstrapper.__init__(self) - # **************************************************** # - # Dependency injection helpers, override this for more - # granular testing - self._fetcher = requests - # **************************************************** # - - self._session = self._fetcher.session() self._provider_config = None self._eip_config = None self._download_if_needed = False - def _download_config(self): + def _download_config(self, *args): """ Downloads the EIP config for the given provider - - :return: True if the checks passed, False otherwise - :rtype: bool """ leap_assert(self._provider_config, @@ -83,65 +66,47 @@ class EIPBootstrapper(QtCore.QObject): logger.debug("Downloading EIP config for %s" % (self._provider_config.get_domain(),)) - download_config_data = { - self.PASSED_KEY: False, - self.ERROR_KEY: "" - } - self._eip_config = EIPConfig() - try: - headers = {} - mtime = get_mtime(os.path.join(self._eip_config - .get_path_prefix(), - "leap", - "providers", - self._provider_config.get_domain(), - "eip-service.json")) - - if self._download_if_needed and mtime: - headers['if-modified-since'] = mtime - - # there is some confusion with this uri, - # it's in 1/config/eip, config/eip and config/1/eip... - config_uri = "%s/%s/config/eip-service.json" % ( - self._provider_config.get_api_uri(), - self._provider_config.get_api_version()) - logger.debug('Downloading eip config from: %s' % config_uri) - - res = self._session.get(config_uri, - verify=self._provider_config - .get_ca_cert_path(), - headers=headers) - res.raise_for_status() - - # Not modified - if res.status_code == 304: - logger.debug("EIP definition has not been modified") - else: - eip_definition, mtime = get_content(res) - - self._eip_config.load(data=eip_definition, mtime=mtime) - self._eip_config.save(["leap", + headers = {} + mtime = get_mtime(os.path.join(self._eip_config + .get_path_prefix(), + "leap", "providers", self._provider_config.get_domain(), - "eip-service.json"]) - - download_config_data[self.PASSED_KEY] = True - except Exception as e: - download_config_data[self.ERROR_KEY] = "%s" % (e,) - - logger.debug("Emitting download_config %s" % (download_config_data,)) - self.download_config.emit(download_config_data) - - return download_config_data[self.PASSED_KEY] - - def _download_client_certificates(self): + "eip-service.json")) + + if self._download_if_needed and mtime: + headers['if-modified-since'] = mtime + + # there is some confusion with this uri, + # it's in 1/config/eip, config/eip and config/1/eip... + config_uri = "%s/%s/config/eip-service.json" % ( + self._provider_config.get_api_uri(), + self._provider_config.get_api_version()) + logger.debug('Downloading eip config from: %s' % config_uri) + + res = self._session.get(config_uri, + verify=self._provider_config + .get_ca_cert_path(), + headers=headers) + res.raise_for_status() + + # Not modified + if res.status_code == 304: + logger.debug("EIP definition has not been modified") + else: + eip_definition, mtime = get_content(res) + + self._eip_config.load(data=eip_definition, mtime=mtime) + self._eip_config.save(["leap", + "providers", + self._provider_config.get_domain(), + "eip-service.json"]) + + def _download_client_certificates(self, *args): """ Downloads the EIP client certificate for the given provider - - :return: True if the checks passed, False otherwise - :rtype: bool """ leap_assert(self._provider_config, "We need a provider configuration!") leap_assert(self._eip_config, "We need an eip configuration!") @@ -149,11 +114,6 @@ class EIPBootstrapper(QtCore.QObject): logger.debug("Downloading EIP client certificate for %s" % (self._provider_config.get_domain(),)) - download_cert = { - self.PASSED_KEY: False, - self.ERROR_KEY: "" - } - client_cert_path = self._eip_config.\ get_client_cert_path(self._provider_config, about_to_download=True) @@ -164,56 +124,39 @@ class EIPBootstrapper(QtCore.QObject): if self._download_if_needed and \ os.path.exists(client_cert_path): - try: - check_and_fix_urw_only(client_cert_path) - download_cert[self.PASSED_KEY] = True - except Exception as e: - download_cert[self.PASSED_KEY] = False - download_cert[self.ERROR_KEY] = "%s" % (e,) - self.download_client_certificate.emit(download_cert) - return download_cert[self.PASSED_KEY] - - try: - srp_auth = SRPAuth(self._provider_config) - session_id = srp_auth.get_session_id() - cookies = None - if session_id: - cookies = {"_session_id": session_id} - cert_uri = "%s/%s/cert" % ( - self._provider_config.get_api_uri(), - self._provider_config.get_api_version()) - logger.debug('getting cert from uri: %s' % cert_uri) - res = self._session.get(cert_uri, - verify=self._provider_config - .get_ca_cert_path(), - cookies=cookies) - res.raise_for_status() - client_cert = res.content - - # TODO: check certificate validity - - if not is_valid_pemfile(client_cert): - raise Exception(self.tr("The downloaded certificate is not a " - "valid PEM file")) - - mkdir_p(os.path.dirname(client_cert_path)) - - with open(client_cert_path, "w") as f: - f.write(client_cert) - check_and_fix_urw_only(client_cert_path) - - download_cert[self.PASSED_KEY] = True - except Exception as e: - download_cert[self.ERROR_KEY] = "%s" % (e,) - - logger.debug("Emitting download_client_certificates %s" % - (download_cert,)) - self.download_client_certificate.emit(download_cert) - - return download_cert[self.PASSED_KEY] - - def run_eip_setup_checks(self, checker, + return + + srp_auth = SRPAuth(self._provider_config) + session_id = srp_auth.get_session_id() + cookies = None + if session_id: + cookies = {"_session_id": session_id} + cert_uri = "%s/%s/cert" % ( + self._provider_config.get_api_uri(), + self._provider_config.get_api_version()) + logger.debug('getting cert from uri: %s' % cert_uri) + res = self._session.get(cert_uri, + verify=self._provider_config + .get_ca_cert_path(), + cookies=cookies) + res.raise_for_status() + client_cert = res.content + + # TODO: check certificate validity + + if not is_valid_pemfile(client_cert): + raise Exception(self.tr("The downloaded certificate is not a " + "valid PEM file")) + + mkdir_p(os.path.dirname(client_cert_path)) + + with open(client_cert_path, "w") as f: + f.write(client_cert) + + check_and_fix_urw_only(client_cert_path) + + def run_eip_setup_checks(self, provider_config, download_if_needed=False): """ @@ -228,60 +171,9 @@ class EIPBootstrapper(QtCore.QObject): self._provider_config = provider_config self._download_if_needed = download_if_needed - checker.add_checks([ - self._download_config, - self._download_client_certificates - ]) - - -if __name__ == "__main__": - import sys - from functools import partial - app = QtGui.QApplication(sys.argv) - - import signal - - def sigint_handler(*args, **kwargs): - logger.debug('SIGINT catched. shutting down...') - checker = args[0] - checker.set_should_quit() - QtGui.QApplication.quit() - - def signal_tester(d): - print d - - logger = logging.getLogger(name='leap') - logger.setLevel(logging.DEBUG) - console = logging.StreamHandler() - console.setLevel(logging.DEBUG) - formatter = logging.Formatter( - '%(asctime)s ' - '- %(name)s - %(levelname)s - %(message)s') - console.setFormatter(formatter) - logger.addHandler(console) - - eip_checks = EIPBootstrapper() - checker = CheckerThread() - - sigint = partial(sigint_handler, checker) - signal.signal(signal.SIGINT, sigint) - - timer = QtCore.QTimer() - timer.start(500) - timer.timeout.connect(lambda: None) - app.connect(app, QtCore.SIGNAL("aboutToQuit()"), - checker.set_should_quit) - w = QtGui.QWidget() - w.resize(100, 100) - w.show() - - checker.start() - - provider_config = ProviderConfig() - if provider_config.load(os.path.join("leap", - "providers", - "bitmask.net", - "provider.json")): - eip_checks.run_eip_setup_checks(checker, provider_config) - - sys.exit(app.exec_()) + cb_chain = [ + (self._download_config, self.download_config), + (self._download_client_certificates, self.download_client_certificate) + ] + + self.addCallbackChain(cb_chain) diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index 289d212b..1339e086 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -24,30 +24,25 @@ import os import requests -from PySide import QtGui, QtCore +from PySide import QtCore from leap.common.certs import get_digest from leap.common.files import check_and_fix_urw_only, get_mtime, mkdir_p from leap.common.check import leap_assert, leap_assert_type from leap.config.providerconfig import ProviderConfig -from leap.util.checkerthread import CheckerThread from leap.util.request_helpers import get_content +from leap.services.abstractbootstrapper import AbstractBootstrapper logger = logging.getLogger(__name__) -class ProviderBootstrapper(QtCore.QObject): +class ProviderBootstrapper(AbstractBootstrapper): """ Given a provider URL performs a series of checks and emits signals after they are passed. If a check fails, the subsequent checks are not executed """ - PASSED_KEY = "passed" - ERROR_KEY = "error" - - IDLE_SLEEP_INTERVAL = 100 - # All dicts returned are of the form # {"passed": bool, "error": str} name_resolution = QtCore.Signal(dict) @@ -66,68 +61,34 @@ class ProviderBootstrapper(QtCore.QObject): first round of checks for CA certificates at bootstrap :type bypass_checks: bool """ - QtCore.QObject.__init__(self) + AbstractBootstrapper.__init__(self, bypass_checks) - # **************************************************** # - # Dependency injection helpers, override this for more - # granular testing - self._fetcher = requests - # **************************************************** # - - self._session = self._fetcher.session() self._domain = None self._provider_config = None self._download_if_needed = False - self._bypass_checks = bypass_checks def _check_name_resolution(self): """ Checks that the name resolution for the provider name works - - :return: True if the checks passed, False otherwise - :rtype: bool """ - leap_assert(self._domain, "Cannot check DNS without a domain") logger.debug("Checking name resolution for %s" % (self._domain)) - name_resolution_data = { - self.PASSED_KEY: False, - self.ERROR_KEY: "" - } - # We don't skip this check, since it's basic for the whole # system to work - try: - socket.gethostbyname(self._domain) - name_resolution_data[self.PASSED_KEY] = True - except socket.gaierror as e: - name_resolution_data[self.ERROR_KEY] = "%s" % (e,) + socket.gethostbyname(self._domain) - logger.debug("Emitting name_resolution %s" % (name_resolution_data,)) - self.name_resolution.emit(name_resolution_data) - - return name_resolution_data[self.PASSED_KEY] - - def _check_https(self): + def _check_https(self, *args): """ Checks that https is working and that the provided certificate checks out - - :return: True if the checks passed, False otherwise - :rtype: bool """ leap_assert(self._domain, "Cannot check HTTPS without a domain") logger.debug("Checking https for %s" % (self._domain)) - https_data = { - self.PASSED_KEY: False, - self.ERROR_KEY: "" - } - # We don't skip this check, since it's basic for the whole # system to work @@ -135,105 +96,75 @@ class ProviderBootstrapper(QtCore.QObject): res = self._session.get("https://%s" % (self._domain,), verify=not self._bypass_checks) res.raise_for_status() - https_data[self.PASSED_KEY] = True - except requests.exceptions.SSLError as e: - logger.error("%s" % (e,)) - https_data[self.ERROR_KEY] = self.tr("Provider certificate could " - "not verify") - except Exception as e: - logger.error("%s" % (e,)) - https_data[self.ERROR_KEY] = self.tr("Provider does not support " - "HTTPS") - - logger.debug("Emitting https_connection %s" % (https_data,)) - self.https_connection.emit(https_data) - - return https_data[self.PASSED_KEY] - - def _download_provider_info(self): + except requests.exceptions.SSLError: + self._err_msg = self.tr("Provider certificate could " + "not be verified") + raise + except Exception: + self._err_msg = self.tr("Provider does not support HTTPS") + raise + + def _download_provider_info(self, *args): """ Downloads the provider.json defition - - :return: True if the checks passed, False otherwise - :rtype: bool """ leap_assert(self._domain, "Cannot download provider info without a domain") logger.debug("Downloading provider info for %s" % (self._domain)) - download_data = { - self.PASSED_KEY: False, - self.ERROR_KEY: "" - } - - try: - headers = {} - mtime = get_mtime(os.path.join(ProviderConfig() - .get_path_prefix(), - "leap", - "providers", - self._domain, - "provider.json")) - if self._download_if_needed and mtime: - headers['if-modified-since'] = mtime - - res = self._session.get("https://%s/%s" % (self._domain, - "provider.json"), - headers=headers, - verify=not self._bypass_checks) - res.raise_for_status() - - # Not modified - if res.status_code == 304: - logger.debug("Provider definition has not been modified") - else: - provider_definition, mtime = get_content(res) - - provider_config = ProviderConfig() - provider_config.load(data=provider_definition, mtime=mtime) - provider_config.save(["leap", - "providers", - self._domain, - "provider.json"]) - - download_data[self.PASSED_KEY] = True - except Exception as e: - download_data[self.ERROR_KEY] = "%s" % (e,) - - logger.debug("Emitting download_provider_info %s" % (download_data,)) - self.download_provider_info.emit(download_data) - - return download_data[self.PASSED_KEY] - - def run_provider_select_checks(self, checker, - domain, download_if_needed=False): + headers = {} + mtime = get_mtime(os.path.join(ProviderConfig() + .get_path_prefix(), + "leap", + "providers", + self._domain, + "provider.json")) + if self._download_if_needed and mtime: + headers['if-modified-since'] = mtime + + res = self._session.get("https://%s/%s" % (self._domain, + "provider.json"), + headers=headers, + verify=not self._bypass_checks) + res.raise_for_status() + + # Not modified + if res.status_code == 304: + logger.debug("Provider definition has not been modified") + else: + provider_definition, mtime = get_content(res) + + provider_config = ProviderConfig() + provider_config.load(data=provider_definition, mtime=mtime) + provider_config.save(["leap", + "providers", + self._domain, + "provider.json"]) + + def run_provider_select_checks(self, domain, download_if_needed=False): """ Populates the check queue. - :param checker: checker thread to be used to run this check - :type checker: CheckerThread - :param domain: domain to check :type domain: str :param download_if_needed: if True, makes the checks do not overwrite already downloaded data :type download_if_needed: bool - - :return: True if the checks passed, False otherwise - :rtype: bool """ leap_assert(domain and len(domain) > 0, "We need a domain!") self._domain = domain self._download_if_needed = download_if_needed - checker.add_checks([ - self._check_name_resolution, - self._check_https, - self._download_provider_info - ]) + cb_chain = [ + (self._check_name_resolution, self.name_resolution), + (self._check_https, self.https_connection), + (self._download_provider_info, self.download_provider_info) + ] + + self.addCallbackChain(cb_chain) def _should_proceed_cert(self): """ @@ -250,12 +181,9 @@ class ProviderBootstrapper(QtCore.QObject): return not os.path.exists(self._provider_config .get_ca_cert_path(about_to_download=True)) - def _download_ca_cert(self): + def _download_ca_cert(self, *args): """ Downloads the CA cert that is going to be used for the api URL - - :return: True if the checks passed, False otherwise - :rtype: bool """ leap_assert(self._provider_config, "Cannot download the ca cert " @@ -264,56 +192,28 @@ class ProviderBootstrapper(QtCore.QObject): logger.debug("Downloading ca cert for %s at %s" % (self._domain, self._provider_config.get_ca_cert_uri())) - download_ca_cert_data = { - self.PASSED_KEY: False, - self.ERROR_KEY: "" - } - if not self._should_proceed_cert(): - try: - check_and_fix_urw_only( - self._provider_config - .get_ca_cert_path(about_to_download=True)) - download_ca_cert_data[self.PASSED_KEY] = True - except Exception as e: - download_ca_cert_data[self.PASSED_KEY] = False - download_ca_cert_data[self.ERROR_KEY] = "%s" % (e,) - self.download_ca_cert.emit(download_ca_cert_data) - return download_ca_cert_data[self.PASSED_KEY] - - try: - res = self._session.get(self._provider_config.get_ca_cert_uri(), - verify=not self._bypass_checks) - res.raise_for_status() - - cert_path = self._provider_config.get_ca_cert_path( - about_to_download=True) - - cert_dir = os.path.dirname(cert_path) + check_and_fix_urw_only( + self._provider_config + .get_ca_cert_path(about_to_download=True)) - mkdir_p(cert_dir) + res = self._session.get(self._provider_config.get_ca_cert_uri(), + verify=not self._bypass_checks) + res.raise_for_status() - with open(cert_path, "w") as f: - f.write(res.content) + cert_path = self._provider_config.get_ca_cert_path( + about_to_download=True) + cert_dir = os.path.dirname(cert_path) + mkdir_p(cert_dir) + with open(cert_path, "w") as f: + f.write(res.content) - check_and_fix_urw_only(cert_path) + check_and_fix_urw_only(cert_path) - download_ca_cert_data[self.PASSED_KEY] = True - except Exception as e: - download_ca_cert_data[self.ERROR_KEY] = "%s" % (e,) - - logger.debug("Emitting download_ca_cert %s" % (download_ca_cert_data,)) - self.download_ca_cert.emit(download_ca_cert_data) - - return download_ca_cert_data[self.PASSED_KEY] - - def _check_ca_fingerprint(self): + def _check_ca_fingerprint(self, *args): """ Checks the CA cert fingerprint against the one provided in the json definition - - :return: True if the checks passed, False otherwise - :rtype: bool """ leap_assert(self._provider_config, "Cannot check the ca cert " "without a provider config!") @@ -322,50 +222,27 @@ class ProviderBootstrapper(QtCore.QObject): (self._domain, self._provider_config.get_ca_cert_path())) - check_ca_fingerprint_data = { - self.PASSED_KEY: False, - self.ERROR_KEY: "" - } - if not self._should_proceed_cert(): - check_ca_fingerprint_data[self.PASSED_KEY] = True - self.check_ca_fingerprint.emit(check_ca_fingerprint_data) - return True + return - try: - parts = self._provider_config.get_ca_cert_fingerprint().split(":") - leap_assert(len(parts) == 2, "Wrong fingerprint format") - - method = parts[0].strip() - fingerprint = parts[1].strip() - cert_data = None - with open(self._provider_config.get_ca_cert_path()) as f: - cert_data = f.read() - - leap_assert(len(cert_data) > 0, "Could not read certificate data") - - digest = get_digest(cert_data, method) + parts = self._provider_config.get_ca_cert_fingerprint().split(":") + leap_assert(len(parts) == 2, "Wrong fingerprint format") - leap_assert(digest == fingerprint, - "Downloaded certificate has a different fingerprint!") + method = parts[0].strip() + fingerprint = parts[1].strip() + cert_data = None + with open(self._provider_config.get_ca_cert_path()) as f: + cert_data = f.read() - check_ca_fingerprint_data[self.PASSED_KEY] = True - except Exception as e: - check_ca_fingerprint_data[self.ERROR_KEY] = "%s" % (e,) + leap_assert(len(cert_data) > 0, "Could not read certificate data") + digest = get_digest(cert_data, method) + leap_assert(digest == fingerprint, + "Downloaded certificate has a different fingerprint!") - logger.debug("Emitting check_ca_fingerprint %s" % - (check_ca_fingerprint_data,)) - self.check_ca_fingerprint.emit(check_ca_fingerprint_data) - - return check_ca_fingerprint_data[self.PASSED_KEY] - - def _check_api_certificate(self): + def _check_api_certificate(self, *args): """ Tries to make an API call with the downloaded cert and checks if it validates against it - - :return: True if the checks passed, False otherwise - :rtype: bool """ leap_assert(self._provider_config, "Cannot check the ca cert " "without a provider config!") @@ -374,34 +251,17 @@ class ProviderBootstrapper(QtCore.QObject): (self._provider_config.get_api_uri(), self._provider_config.get_ca_cert_path())) - check_api_certificate_data = { - self.PASSED_KEY: False, - self.ERROR_KEY: "" - } - if not self._should_proceed_cert(): - check_api_certificate_data[self.PASSED_KEY] = True - self.check_api_certificate.emit(check_api_certificate_data) - return True + return - try: - test_uri = "%s/%s/cert" % (self._provider_config.get_api_uri(), - self._provider_config.get_api_version()) - res = self._session.get(test_uri, - verify=self._provider_config - .get_ca_cert_path()) - res.raise_for_status() - check_api_certificate_data[self.PASSED_KEY] = True - except Exception as e: - check_api_certificate_data[self.ERROR_KEY] = "%s" % (e,) + test_uri = "%s/%s/cert" % (self._provider_config.get_api_uri(), + self._provider_config.get_api_version()) + res = self._session.get(test_uri, + verify=self._provider_config + .get_ca_cert_path()) + res.raise_for_status() - logger.debug("Emitting check_api_certificate %s" % - (check_api_certificate_data,)) - self.check_api_certificate.emit(check_api_certificate_data) - - return check_api_certificate_data[self.PASSED_KEY] - - def run_provider_setup_checks(self, checker, + def run_provider_setup_checks(self, provider_config, download_if_needed=False): """ @@ -420,64 +280,10 @@ class ProviderBootstrapper(QtCore.QObject): self._provider_config = provider_config self._download_if_needed = download_if_needed - checker.add_checks([ - self._download_ca_cert, - self._check_ca_fingerprint, - self._check_api_certificate - ]) - -if __name__ == "__main__": - import sys - from functools import partial - app = QtGui.QApplication(sys.argv) - - import signal - - def sigint_handler(*args, **kwargs): - logger.debug('SIGINT catched. shutting down...') - bootstrapper_checks = args[0] - bootstrapper_checks.set_should_quit() - QtGui.QApplication.quit() - - def signal_tester(d): - print d - - logger = logging.getLogger(name='leap') - logger.setLevel(logging.DEBUG) - console = logging.StreamHandler() - console.setLevel(logging.DEBUG) - formatter = logging.Formatter( - '%(asctime)s ' - '- %(name)s - %(levelname)s - %(message)s') - console.setFormatter(formatter) - logger.addHandler(console) - - bootstrapper_checks = ProviderBootstrapper() - - checker = CheckerThread() - checker.start() - - sigint = partial(sigint_handler, checker) - signal.signal(signal.SIGINT, sigint) - - timer = QtCore.QTimer() - timer.start(500) - timer.timeout.connect(lambda: None) - app.connect(app, QtCore.SIGNAL("aboutToQuit()"), - checker.set_should_quit) - w = QtGui.QWidget() - w.resize(100, 100) - w.show() - - bootstrapper_checks.run_provider_select_checks(checker, - "bitmask.net") - - provider_config = ProviderConfig() - if provider_config.load(os.path.join("leap", - "providers", - "bitmask.net", - "provider.json")): - bootstrapper_checks.run_provider_setup_checks(checker, - provider_config) - - sys.exit(app.exec_()) + cb_chain = [ + (self._download_ca_cert, self.download_ca_cert), + (self._check_ca_fingerprint, self.check_ca_fingerprint), + (self._check_api_certificate, self.check_api_certificate) + ] + + self.addCallbackChain(cb_chain) diff --git a/src/leap/services/eip/vpnlaunchers.py b/src/leap/services/eip/vpnlaunchers.py index addad959..0691e121 100644 --- a/src/leap/services/eip/vpnlaunchers.py +++ b/src/leap/services/eip/vpnlaunchers.py @@ -132,7 +132,7 @@ def _is_auth_agent_running(): """ polkit_gnome = 'ps aux | grep polkit-[g]nome-authentication-agent-1' polkit_kde = 'ps aux | grep polkit-[k]de-authentication-agent-1' - return (len(commands.getoutput(polkit_gnome) > 0) or + return (len(commands.getoutput(polkit_gnome)) > 0 or len(commands.getoutput(polkit_kde)) > 0) diff --git a/src/leap/services/mail/smtpbootstrapper.py b/src/leap/services/mail/smtpbootstrapper.py index 6e0a0a47..64bf3153 100644 --- a/src/leap/services/mail/smtpbootstrapper.py +++ b/src/leap/services/mail/smtpbootstrapper.py @@ -22,8 +22,6 @@ SMTP bootstrapping import logging import os -import requests - from PySide import QtCore from leap.common.check import leap_assert, leap_assert_type @@ -31,44 +29,30 @@ from leap.common.files import get_mtime from leap.config.providerconfig import ProviderConfig from leap.crypto.srpauth import SRPAuth from leap.util.request_helpers import get_content +from leap.services.abstractbootstrapper import AbstractBootstrapper logger = logging.getLogger(__name__) -class SMTPBootstrapper(QtCore.QObject): +class SMTPBootstrapper(AbstractBootstrapper): """ SMTP init procedure """ - PASSED_KEY = "passed" - ERROR_KEY = "error" - - IDLE_SLEEP_INTERVAL = 100 - # All dicts returned are of the form # {"passed": bool, "error": str} download_config = QtCore.Signal(dict) def __init__(self): - QtCore.QObject.__init__(self) + AbstractBootstrapper.__init__(self) - # **************************************************** # - # Dependency injection helpers, override this for more - # granular testing - self._fetcher = requests - # **************************************************** # - - self._session = self._fetcher.session() self._provider_config = None self._smtp_config = None self._download_if_needed = False - def _download_config(self): + def _download_config(self, *args): """ Downloads the SMTP config for the given provider - - :return: True if everything went as expected, False otherwise - :rtype: bool """ leap_assert(self._provider_config, @@ -77,79 +61,59 @@ class SMTPBootstrapper(QtCore.QObject): logger.debug("Downloading SMTP config for %s" % (self._provider_config.get_domain(),)) - download_config_data = { - self.PASSED_KEY: False, - self.ERROR_KEY: "" - } - - try: - headers = {} - mtime = get_mtime(os.path.join(self._smtp_config - .get_path_prefix(), - "leap", - "providers", - self._provider_config.get_domain(), - "smtp-service.json")) - - if self._download_if_needed and mtime: - headers['if-modified-since'] = mtime - - # there is some confusion with this uri, - config_uri = "%s/%s/config/smtp-service.json" % ( - self._provider_config.get_api_uri(), - self._provider_config.get_api_version()) - logger.debug('Downloading SMTP config from: %s' % config_uri) - - srp_auth = SRPAuth(self._provider_config) - session_id = srp_auth.get_session_id() - cookies = None - if session_id: - cookies = {"_session_id": session_id} - - res = self._session.get(config_uri, - verify=self._provider_config - .get_ca_cert_path(), - headers=headers, - cookies=cookies) - res.raise_for_status() - - # Not modified - if res.status_code == 304: - logger.debug("SMTP definition has not been modified") - self._smtp_config.load(os.path.join( - "leap", "providers", - self._provider_config.get_domain(), - "smtp-service.json")) - else: - smtp_definition, mtime = get_content(res) - - self._smtp_config.load(data=smtp_definition, mtime=mtime) - self._smtp_config.save(["leap", - "providers", - self._provider_config.get_domain(), - "smtp-service.json"]) - - download_config_data[self.PASSED_KEY] = True - except Exception as e: - download_config_data[self.PASSED_KEY] = False - download_config_data[self.ERROR_KEY] = "%s" % (e,) - - logger.debug("Emitting download_config %s" % (download_config_data,)) - self.download_config.emit(download_config_data) - - return download_config_data[self.PASSED_KEY] + headers = {} + mtime = get_mtime(os.path.join(self._smtp_config + .get_path_prefix(), + "leap", + "providers", + self._provider_config.get_domain(), + "smtp-service.json")) + + if self._download_if_needed and mtime: + headers['if-modified-since'] = mtime + + # there is some confusion with this uri, + config_uri = "%s/%s/config/smtp-service.json" % ( + self._provider_config.get_api_uri(), + self._provider_config.get_api_version()) + logger.debug('Downloading SMTP config from: %s' % config_uri) + + srp_auth = SRPAuth(self._provider_config) + session_id = srp_auth.get_session_id() + cookies = None + if session_id: + cookies = {"_session_id": session_id} + + res = self._session.get(config_uri, + verify=self._provider_config + .get_ca_cert_path(), + headers=headers, + cookies=cookies) + res.raise_for_status() + + # Not modified + if res.status_code == 304: + logger.debug("SMTP definition has not been modified") + self._smtp_config.load(os.path.join("leap", + "providers", + self._provider_config.get_domain(), + "smtp-service.json")) + else: + smtp_definition, mtime = get_content(res) + + self._smtp_config.load(data=smtp_definition, mtime=mtime) + self._smtp_config.save(["leap", + "providers", + self._provider_config.get_domain(), + "smtp-service.json"]) def run_smtp_setup_checks(self, - checker, provider_config, smtp_config, download_if_needed=False): """ Starts the checks needed for a new smtp setup - :param checker: Object that executes actions in a different - thread - :type checker: leap.util.checkerthread.CheckerThread :param provider_config: Provider configuration :type provider_config: ProviderConfig :param smtp_config: SMTP configuration to populate @@ -164,6 +128,8 @@ class SMTPBootstrapper(QtCore.QObject): self._smtp_config = smtp_config self._download_if_needed = download_if_needed - checker.add_checks([ - self._download_config - ]) + cb_chain = [ + (self._download_config, self.download_config), + ] + + self.addCallbackChain(cb_chain) diff --git a/src/leap/services/soledad/soledadbootstrapper.py b/src/leap/services/soledad/soledadbootstrapper.py index eea9b0d5..2635a7e6 100644 --- a/src/leap/services/soledad/soledadbootstrapper.py +++ b/src/leap/services/soledad/soledadbootstrapper.py @@ -22,10 +22,7 @@ Soledad bootstrapping import logging import os -import requests - from PySide import QtCore -from mock import Mock from leap.common.check import leap_assert, leap_assert_type from leap.common.files import get_mtime @@ -36,39 +33,29 @@ from leap.crypto.srpauth import SRPAuth from leap.services.soledad.soledadconfig import SoledadConfig from leap.util.request_helpers import get_content from leap.soledad import Soledad +from leap.services.abstractbootstrapper import AbstractBootstrapper logger = logging.getLogger(__name__) -class SoledadBootstrapper(QtCore.QObject): +class SoledadBootstrapper(AbstractBootstrapper): """ Soledad init procedure """ - PASSED_KEY = "passed" - ERROR_KEY = "error" SOLEDAD_KEY = "soledad" KEYMANAGER_KEY = "keymanager" PUBKEY_KEY = "user[public_key]" - IDLE_SLEEP_INTERVAL = 100 - # All dicts returned are of the form # {"passed": bool, "error": str} download_config = QtCore.Signal(dict) gen_key = QtCore.Signal(dict) def __init__(self): - QtCore.QObject.__init__(self) - - # **************************************************** # - # Dependency injection helpers, override this for more - # granular testing - self._fetcher = requests - # **************************************************** # + AbstractBootstrapper.__init__(self) - self._session = self._fetcher.session() self._provider_config = None self._soledad_config = None self._keymanager = None @@ -76,6 +63,14 @@ class SoledadBootstrapper(QtCore.QObject): self._user = "" self._password = "" + @property + def keymanager(self): + return self._keymanager + + @property + def soledad(self): + return self._soledad + def _load_and_sync_soledad(self, srp_auth): """ Once everthing is in the right place, we instantiate and sync @@ -92,7 +87,8 @@ class SoledadBootstrapper(QtCore.QObject): local_db_path = "%s/%s.db" % (prefix, uuid) # TODO: use the proper URL - server_url = 'https://mole.dev.bitmask.net:2424/user-%s' % (uuid,) + #server_url = 'https://mole.dev.bitmask.net:2424/user-%s' % (uuid,) + server_url = 'https://gadwall.dev.bitmask.net:1111/user-%s' % (uuid,) # server_url = self._soledad_config.get_hosts(...) cert_file = self._provider_config.get_ca_cert_path() @@ -109,9 +105,6 @@ class SoledadBootstrapper(QtCore.QObject): def _download_config(self): """ Downloads the Soledad config for the given provider - - :return: True if everything went as expected, False otherwise - :rtype: bool """ leap_assert(self._provider_config, @@ -120,150 +113,84 @@ class SoledadBootstrapper(QtCore.QObject): logger.debug("Downloading Soledad config for %s" % (self._provider_config.get_domain(),)) - download_config_data = { - self.PASSED_KEY: False, - self.ERROR_KEY: "" - } - self._soledad_config = SoledadConfig() - try: - headers = {} - mtime = get_mtime(os.path.join(self._soledad_config - .get_path_prefix(), - "leap", - "providers", - self._provider_config.get_domain(), - "soledad-service.json")) - - if self._download_if_needed and mtime: - headers['if-modified-since'] = mtime - - # there is some confusion with this uri, - config_uri = "%s/%s/config/soledad-service.json" % ( - self._provider_config.get_api_uri(), - self._provider_config.get_api_version()) - logger.debug('Downloading soledad config from: %s' % config_uri) - - srp_auth = SRPAuth(self._provider_config) - session_id = srp_auth.get_session_id() - cookies = None - if session_id: - cookies = {"_session_id": session_id} - - res = self._session.get(config_uri, - verify=self._provider_config - .get_ca_cert_path(), - headers=headers, - cookies=cookies) - res.raise_for_status() - - # Not modified - if res.status_code == 304: - logger.debug("Soledad definition has not been modified") - else: - soledad_definition, mtime = get_content(res) - - self._soledad_config.load(data=soledad_definition, mtime=mtime) - self._soledad_config.save(["leap", - "providers", - self._provider_config.get_domain(), - "soledad-service.json"]) - - self._load_and_sync_soledad(srp_auth) - - download_config_data[self.PASSED_KEY] = True - except Exception as e: - download_config_data[self.PASSED_KEY] = False - download_config_data[self.ERROR_KEY] = "%s" % (e,) - - logger.debug("Emitting download_config %s" % (download_config_data,)) - self.download_config.emit(download_config_data) - - return download_config_data[self.PASSED_KEY] + headers = {} + mtime = get_mtime(os.path.join(self._soledad_config + .get_path_prefix(), + "leap", + "providers", + self._provider_config.get_domain(), + "soledad-service.json")) + + if self._download_if_needed and mtime: + headers['if-modified-since'] = mtime + + # there is some confusion with this uri, + config_uri = "%s/%s/config/soledad-service.json" % ( + self._provider_config.get_api_uri(), + self._provider_config.get_api_version()) + logger.debug('Downloading soledad config from: %s' % config_uri) + + srp_auth = SRPAuth(self._provider_config) + session_id = srp_auth.get_session_id() + cookies = None + if session_id: + cookies = {"_session_id": session_id} + + res = self._session.get(config_uri, + verify=self._provider_config + .get_ca_cert_path(), + headers=headers, + cookies=cookies) + res.raise_for_status() + + # Not modified + if res.status_code == 304: + logger.debug("Soledad definition has not been modified") + else: + soledad_definition, mtime = get_content(res) + + self._soledad_config.load(data=soledad_definition, mtime=mtime) + self._soledad_config.save(["leap", + "providers", + self._provider_config.get_domain(), + "soledad-service.json"]) + + self._load_and_sync_soledad(srp_auth) def _gen_key(self): """ Generates the key pair if needed, uploads it to the webapp and nickserver - - :return: True if everything is done successfully, False - otherwise - :rtype: bool """ leap_assert(self._provider_config, "We need a provider configuration!") - # XXX Sanitize this address = "%s@%s" % (self._user, self._provider_config.get_domain()) logger.debug("Retrieving key for %s" % (address,)) - genkey_data = { - self.PASSED_KEY: False, - self.ERROR_KEY: "", - self.SOLEDAD_KEY: None, - self.KEYMANAGER_KEY: None - } - + srp_auth = SRPAuth(self._provider_config) + self._keymanager = KeyManager( + address, + "https://%s:6425" % (self._provider_config.get_domain()), + self._soledad, + #token=srp_auth.get_token(), # TODO: enable token usage + session_id=srp_auth.get_session_id(), + ca_cert_path=self._provider_config.get_ca_cert_path(), + api_uri=self._provider_config.get_api_uri(), + api_version=self._provider_config.get_api_version(), + uid=srp_auth.get_uid()) try: - srp_auth = SRPAuth(self._provider_config) - self._keymanager = KeyManager( - address, - "https://nickserver", # TODO: nickserver url, none for now - self._soledad, - token=srp_auth.get_token()) - self._keymanager._fetcher.put = Mock() - try: - self._keymanager.get_key(address, openpgp.OpenPGPKey, - private=True, fetch_remote=False) - except KeyNotFound: - logger.debug( - "Key not found. Generating key for %s" % (address,)) - self._keymanager.gen_key(openpgp.OpenPGPKey) - - logger.debug("Key generated successfully.") - - cookies = None - session_id = srp_auth.get_session_id() - if session_id: - cookies = {"_session_id": session_id} - - key_uri = "%s/%s/users/%s.json" % ( - self._provider_config.get_api_uri(), - self._provider_config.get_api_version(), - srp_auth.get_uid()) - - logger.debug("Uploading public key to %s" % (key_uri,)) - - pubkey = self._keymanager.get_key( - address, openpgp.OpenPGPKey, - private=False, fetch_remote=False) - key_data = { - self.PUBKEY_KEY: pubkey.key_data, - } - - # TODO: check if uploaded before uploading it - key_result = self._session.put(key_uri, - data=key_data, - verify=self._provider_config - .get_ca_cert_path(), - cookies=cookies) - key_result.raise_for_status() - genkey_data[self.PASSED_KEY] = True - genkey_data[self.SOLEDAD_KEY] = self._soledad - genkey_data[self.KEYMANAGER_KEY] = self._keymanager - except Exception as e: - genkey_data[self.PASSED_KEY] = False - genkey_data[self.ERROR_KEY] = "%s" % (e,) - - logger.debug("Emitting gen_key %s" % (genkey_data,)) - self.gen_key.emit(genkey_data) - - return genkey_data[self.PASSED_KEY] + self._keymanager.get_key(address, openpgp.OpenPGPKey, + private=True, fetch_remote=False) + except KeyNotFound: + logger.debug("Key not found. Generating key for %s" % (address,)) + self._keymanager.gen_key(openpgp.OpenPGPKey) + logger.debug("Key generated successfully.") def run_soledad_setup_checks(self, - checker, provider_config, user, password, @@ -273,6 +200,10 @@ class SoledadBootstrapper(QtCore.QObject): :param provider_config: Provider configuration :type provider_config: ProviderConfig + :param user: User's login + :type user: str + :param password: User's password + :type password: str """ leap_assert_type(provider_config, ProviderConfig) @@ -281,7 +212,9 @@ class SoledadBootstrapper(QtCore.QObject): self._user = user self._password = password - checker.add_checks([ - self._download_config, - self._gen_key - ]) + cb_chain = [ + (self._download_config, self.download_config), + (self._gen_key, self.gen_key) + ] + + self.addCallbackChain(cb_chain) -- cgit v1.2.3