From ee8fbbdc2f3dbccea3a830b40e9eb0be5b392d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 6 Mar 2013 15:38:05 -0300 Subject: Add EIP service --- src/leap/services/eip/providerbootstrapper.py | 520 ++++++++++++++++++++++++++ 1 file changed, 520 insertions(+) create mode 100644 src/leap/services/eip/providerbootstrapper.py (limited to 'src/leap/services/eip/providerbootstrapper.py') diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py new file mode 100644 index 00000000..babcd47b --- /dev/null +++ b/src/leap/services/eip/providerbootstrapper.py @@ -0,0 +1,520 @@ +# -*- coding: utf-8 -*- +# providerbootstrapper.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 . + +""" +Provider bootstrapping +""" + +import requests +import logging +import socket +import os +import errno + +from OpenSSL import crypto +from PySide import QtGui, QtCore + +from leap.config.providerconfig import ProviderConfig + +logger = logging.getLogger(__name__) + + +class ProviderBootstrapper(QtCore.QThread): + """ + 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) + https_connection = QtCore.Signal(dict) + download_provider_info = QtCore.Signal(dict) + + download_ca_cert = QtCore.Signal(dict) + check_ca_fingerprint = QtCore.Signal(dict) + check_api_certificate = QtCore.Signal(dict) + + def __init__(self): + QtCore.QThread.__init__(self) + + self._checks = [] + self._checks_lock = QtCore.QMutex() + + self._should_quit = False + self._should_quit_lock = QtCore.QMutex() + + # **************************************************** # + # 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 + + def get_should_quit(self): + """ + Returns wether this thread should quit + + @rtype: bool + @return: True if the thread should terminate itself, Flase otherwise + """ + + QtCore.QMutexLocker(self._should_quit_lock) + return self._should_quit + + def set_should_quit(self): + """ + Sets the should_quit flag to True so that this thread + terminates the first chance it gets + """ + QtCore.QMutexLocker(self._should_quit_lock) + self._should_quit = True + self.wait() + + def start(self): + """ + Starts the thread and resets the should_quit flag + """ + with QtCore.QMutexLocker(self._should_quit_lock): + self._should_quit = False + + QtCore.QThread.start(self) + + def _should_proceed_provider(self): + """ + Returns False if provider.json already exists for the given + domain. True otherwise + + @rtype: bool + """ + if not self._download_if_needed: + return True + + # We don't really need a provider config at this stage, just + # the path prefix + return not os.path.exists(os.path.join(ProviderConfig() + .get_path_prefix(), + "leap", + "providers", + self._domain, + "provider.json")) + + 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 + """ + + 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,) + + 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): + """ + Checks that https is working and that the provided certificate + checks out + + @return: True if the checks passed, False otherwise + @rtype: bool + """ + + 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 + + try: + res = self._session.get("https://%s" % (self._domain,)) + res.raise_for_status() + https_data[self.PASSED_KEY] = True + except Exception as e: + https_data[self.ERROR_KEY] = "%s" % (e,) + + 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): + """ + Downloads the provider.json defition + + @return: True if the checks passed, False otherwise + @rtype: bool + """ + 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: "" + } + + if not self._should_proceed_provider(): + download_data[self.PASSED_KEY] = True + self.download_provider_info.emit(download_data) + return True + + try: + res = self._session.get("https://%s/%s" % (self._domain, + "provider.json")) + res.raise_for_status() + + provider_definition = res.content + + provider_config = ProviderConfig() + provider_config.load(data=provider_definition) + 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, domain, download_if_needed=False): + """ + Populates the check queue + + @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 + """ + assert domain and len(domain) > 0, "We need a domain!" + + self._domain = domain + self._download_if_needed = download_if_needed + + QtCore.QMutexLocker(self._checks_lock) + self._checks = [ + self._check_name_resolution, + self._check_https, + self._download_provider_info + ] + + def _should_proceed_cert(self): + """ + Returns False if the certificate already exists for the given + provider. True otherwise + + @rtype: bool + """ + assert self._provider_config, "We need a provider config!" + + if not self._download_if_needed: + return True + + return not os.path.exists(self._provider_config + .get_ca_cert_path(about_to_download=True)) + + def _download_ca_cert(self): + """ + Downloads the CA cert that is going to be used for the api URL + + @return: True if the checks passed, False otherwise + @rtype: bool + """ + + assert self._provider_config, "Cannot download the ca cert " + \ + "without a provider config!" + + 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(): + download_ca_cert_data[self.PASSED_KEY] = True + self.download_ca_cert.emit(download_ca_cert_data) + return True + + try: + res = self._session.get(self._provider_config.get_ca_cert_uri()) + res.raise_for_status() + + cert_path = self._provider_config.get_ca_cert_path( + about_to_download=True) + + cert_dir = os.path.dirname(cert_path) + + try: + os.makedirs(cert_dir) + except OSError as e: + if e.errno == errno.EEXIST and os.path.isdir(cert_dir): + pass + else: + raise + + with open(cert_path, "w") as f: + f.write(res.content) + + 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): + """ + Checks the CA cert fingerprint against the one provided in the + json definition + + @return: True if the checks passed, False otherwise + @rtype: bool + """ + assert self._provider_config, "Cannot check the ca cert " + \ + "without a provider config!" + + logger.debug("Checking ca fingerprint for %s and cert %s" % + (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 + + try: + parts = self._provider_config.get_ca_cert_fingerprint().split(":") + 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() + + assert len(cert_data) > 0, "Could not read certificate data" + + x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert_data) + digest = x509.digest(method).replace(":", "").lower() + + assert digest == fingerprint, \ + "Downloaded certificate has a different fingerprint!" + + check_ca_fingerprint_data[self.PASSED_KEY] = True + except Exception as e: + check_ca_fingerprint_data[self.ERROR_KEY] = "%s" % (e,) + + 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): + """ + 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 + """ + assert self._provider_config, "Cannot check the ca cert " + \ + "without a provider config!" + + logger.debug("Checking api certificate for %s and cert %s" % + (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 + + 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,) + + 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, provider_config, + download_if_needed=False): + """ + Starts the checks needed for a new provider setup + + @param provider_config: Provider configuration + @type provider_config: ProviderConfig + @param download_if_needed: if True, makes the checks do not + overwrite already downloaded data + @type download_if_needed: bool + """ + assert provider_config, "We need a provider config!" + assert isinstance(provider_config, ProviderConfig), "Expected " + \ + "ProviderConfig type, not %r" % (type(provider_config),) + + self._provider_config = provider_config + self._download_if_needed = download_if_needed + + QtCore.QMutexLocker(self._checks_lock) + self._checks = [ + self._download_ca_cert, + self._check_ca_fingerprint, + self._check_api_certificate + ] + + def run(self): + """ + Main run loop for this thread. Executes the checks. + """ + shouldContinue = False + while True: + if self.get_should_quit(): + logger.debug("Quitting provider bootstrap thread") + return + checkSomething = False + with QtCore.QMutexLocker(self._checks_lock): + if len(self._checks) > 0: + check = self._checks.pop(0) + shouldContinue = check() + checkSomething = True + if not shouldContinue: + logger.debug("Something went wrong with the checks, " + "clearing...") + self._checks = [] + checkSomething = False + if not checkSomething: + self.usleep(self.IDLE_SLEEP_INTERVAL) + + +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_thread = args[0] + bootstrapper_thread.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_thread = ProviderBootstrapper() + + sigint = partial(sigint_handler, bootstrapper_thread) + signal.signal(signal.SIGINT, sigint) + + timer = QtCore.QTimer() + timer.start(500) + timer.timeout.connect(lambda: None) + app.connect(app, QtCore.SIGNAL("aboutToQuit()"), + bootstrapper_thread.set_should_quit) + w = QtGui.QWidget() + w.resize(100, 100) + w.show() + + bootstrapper_thread.start() + bootstrapper_thread.run_provider_select_checks("bitmask.net") + + provider_config = ProviderConfig() + if provider_config.load(os.path.join("leap", + "providers", + "bitmask.net", + "provider.json")): + bootstrapper_thread.run_provider_setup_checks(provider_config) + + sys.exit(app.exec_()) -- cgit v1.2.3 From 751638b4eb8208e1eaa1beaaed284da6b412bca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 7 Mar 2013 19:05:11 -0300 Subject: Change asserts for a custom leap_assert method Also: - Make SRPAuth and the Bootstrappers be a QObject instead of a QThread so we can use them inside another more generic thread - Add a generic CheckerThread that runs checks or whatever operation as long as it returns a boolean value - Closes the whole application if the wizard is rejected at the first run - Do not fail when the config directory doesn't exist - Set the wizard pixmap logo as LEAP's logo - Improve wizard checks - Make SRPRegister play nice with the CheckerThread --- src/leap/services/eip/providerbootstrapper.py | 137 ++++++++------------------ 1 file changed, 42 insertions(+), 95 deletions(-) (limited to 'src/leap/services/eip/providerbootstrapper.py') diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index babcd47b..ecdc4e07 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -29,11 +29,13 @@ from OpenSSL import crypto from PySide import QtGui, QtCore from leap.config.providerconfig import ProviderConfig +from leap.util.check import leap_assert, leap_assert_type +from leap.util.checkerthread import CheckerThread logger = logging.getLogger(__name__) -class ProviderBootstrapper(QtCore.QThread): +class ProviderBootstrapper(QtCore.QObject): """ Given a provider URL performs a series of checks and emits signals after they are passed. @@ -56,13 +58,7 @@ class ProviderBootstrapper(QtCore.QThread): check_api_certificate = QtCore.Signal(dict) def __init__(self): - QtCore.QThread.__init__(self) - - self._checks = [] - self._checks_lock = QtCore.QMutex() - - self._should_quit = False - self._should_quit_lock = QtCore.QMutex() + QtCore.QObject.__init__(self) # **************************************************** # # Dependency injection helpers, override this for more @@ -75,35 +71,6 @@ class ProviderBootstrapper(QtCore.QThread): self._provider_config = None self._download_if_needed = False - def get_should_quit(self): - """ - Returns wether this thread should quit - - @rtype: bool - @return: True if the thread should terminate itself, Flase otherwise - """ - - QtCore.QMutexLocker(self._should_quit_lock) - return self._should_quit - - def set_should_quit(self): - """ - Sets the should_quit flag to True so that this thread - terminates the first chance it gets - """ - QtCore.QMutexLocker(self._should_quit_lock) - self._should_quit = True - self.wait() - - def start(self): - """ - Starts the thread and resets the should_quit flag - """ - with QtCore.QMutexLocker(self._should_quit_lock): - self._should_quit = False - - QtCore.QThread.start(self) - def _should_proceed_provider(self): """ Returns False if provider.json already exists for the given @@ -131,7 +98,7 @@ class ProviderBootstrapper(QtCore.QThread): @rtype: bool """ - assert self._domain, "Cannot check DNS without a domain" + leap_assert(self._domain, "Cannot check DNS without a domain") logger.debug("Checking name resolution for %s" % (self._domain)) @@ -162,7 +129,7 @@ class ProviderBootstrapper(QtCore.QThread): @rtype: bool """ - assert self._domain, "Cannot check HTTPS without a domain" + leap_assert(self._domain, "Cannot check HTTPS without a domain") logger.debug("Checking https for %s" % (self._domain)) @@ -193,7 +160,8 @@ class ProviderBootstrapper(QtCore.QThread): @return: True if the checks passed, False otherwise @rtype: bool """ - assert self._domain, "Cannot download provider info without a domain" + leap_assert(self._domain, + "Cannot download provider info without a domain") logger.debug("Downloading provider info for %s" % (self._domain)) @@ -230,7 +198,8 @@ class ProviderBootstrapper(QtCore.QThread): return download_data[self.PASSED_KEY] - def run_provider_select_checks(self, domain, download_if_needed=False): + def run_provider_select_checks(self, checker, + domain, download_if_needed=False): """ Populates the check queue @@ -243,17 +212,16 @@ class ProviderBootstrapper(QtCore.QThread): @return: True if the checks passed, False otherwise @rtype: bool """ - assert domain and len(domain) > 0, "We need a domain!" + leap_assert(domain and len(domain) > 0, "We need a domain!") self._domain = domain self._download_if_needed = download_if_needed - QtCore.QMutexLocker(self._checks_lock) - self._checks = [ + checker.add_checks([ self._check_name_resolution, self._check_https, self._download_provider_info - ] + ]) def _should_proceed_cert(self): """ @@ -262,7 +230,7 @@ class ProviderBootstrapper(QtCore.QThread): @rtype: bool """ - assert self._provider_config, "We need a provider config!" + leap_assert(self._provider_config, "We need a provider config!") if not self._download_if_needed: return True @@ -278,8 +246,8 @@ class ProviderBootstrapper(QtCore.QThread): @rtype: bool """ - assert self._provider_config, "Cannot download the ca cert " + \ - "without a provider config!" + leap_assert(self._provider_config, "Cannot download the ca cert " + "without a provider config!") logger.debug("Downloading ca cert for %s at %s" % (self._domain, self._provider_config.get_ca_cert_uri())) @@ -331,8 +299,8 @@ class ProviderBootstrapper(QtCore.QThread): @return: True if the checks passed, False otherwise @rtype: bool """ - assert self._provider_config, "Cannot check the ca cert " + \ - "without a provider config!" + leap_assert(self._provider_config, "Cannot check the ca cert " + "without a provider config!") logger.debug("Checking ca fingerprint for %s and cert %s" % (self._domain, @@ -350,7 +318,7 @@ class ProviderBootstrapper(QtCore.QThread): try: parts = self._provider_config.get_ca_cert_fingerprint().split(":") - assert len(parts) == 2, "Wrong fingerprint format" + leap_assert(len(parts) == 2, "Wrong fingerprint format") method = parts[0].strip() fingerprint = parts[1].strip() @@ -358,13 +326,13 @@ class ProviderBootstrapper(QtCore.QThread): with open(self._provider_config.get_ca_cert_path()) as f: cert_data = f.read() - assert len(cert_data) > 0, "Could not read certificate data" + leap_assert(len(cert_data) > 0, "Could not read certificate data") x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert_data) digest = x509.digest(method).replace(":", "").lower() - assert digest == fingerprint, \ - "Downloaded certificate has a different fingerprint!" + leap_assert(digest == fingerprint, + "Downloaded certificate has a different fingerprint!") check_ca_fingerprint_data[self.PASSED_KEY] = True except Exception as e: @@ -384,8 +352,8 @@ class ProviderBootstrapper(QtCore.QThread): @return: True if the checks passed, False otherwise @rtype: bool """ - assert self._provider_config, "Cannot check the ca cert " + \ - "without a provider config!" + leap_assert(self._provider_config, "Cannot check the ca cert " + "without a provider config!") logger.debug("Checking api certificate for %s and cert %s" % (self._provider_config.get_api_uri(), @@ -418,7 +386,8 @@ class ProviderBootstrapper(QtCore.QThread): return check_api_certificate_data[self.PASSED_KEY] - def run_provider_setup_checks(self, provider_config, + def run_provider_setup_checks(self, checker, + provider_config, download_if_needed=False): """ Starts the checks needed for a new provider setup @@ -429,43 +398,17 @@ class ProviderBootstrapper(QtCore.QThread): overwrite already downloaded data @type download_if_needed: bool """ - assert provider_config, "We need a provider config!" - assert isinstance(provider_config, ProviderConfig), "Expected " + \ - "ProviderConfig type, not %r" % (type(provider_config),) + leap_assert(provider_config, "We need a provider config!") + leap_assert_type(provider_config, ProviderConfig) self._provider_config = provider_config self._download_if_needed = download_if_needed - QtCore.QMutexLocker(self._checks_lock) - self._checks = [ + checker.add_checks([ self._download_ca_cert, self._check_ca_fingerprint, self._check_api_certificate - ] - - def run(self): - """ - Main run loop for this thread. Executes the checks. - """ - shouldContinue = False - while True: - if self.get_should_quit(): - logger.debug("Quitting provider bootstrap thread") - return - checkSomething = False - with QtCore.QMutexLocker(self._checks_lock): - if len(self._checks) > 0: - check = self._checks.pop(0) - shouldContinue = check() - checkSomething = True - if not shouldContinue: - logger.debug("Something went wrong with the checks, " - "clearing...") - self._checks = [] - checkSomething = False - if not checkSomething: - self.usleep(self.IDLE_SLEEP_INTERVAL) - + ]) if __name__ == "__main__": import sys @@ -476,8 +419,8 @@ if __name__ == "__main__": def sigint_handler(*args, **kwargs): logger.debug('SIGINT catched. shutting down...') - bootstrapper_thread = args[0] - bootstrapper_thread.set_should_quit() + bootstrapper_checks = args[0] + bootstrapper_checks.set_should_quit() QtGui.QApplication.quit() def signal_tester(d): @@ -493,28 +436,32 @@ if __name__ == "__main__": console.setFormatter(formatter) logger.addHandler(console) - bootstrapper_thread = ProviderBootstrapper() + bootstrapper_checks = ProviderBootstrapper() + + checker = CheckerThread() + checker.start() - sigint = partial(sigint_handler, bootstrapper_thread) + 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()"), - bootstrapper_thread.set_should_quit) + checker.set_should_quit) w = QtGui.QWidget() w.resize(100, 100) w.show() - bootstrapper_thread.start() - bootstrapper_thread.run_provider_select_checks("bitmask.net") + 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_thread.run_provider_setup_checks(provider_config) + bootstrapper_checks.run_provider_setup_checks(checker, + provider_config) sys.exit(app.exec_()) -- cgit v1.2.3 From 2da60cd0f78378fdcb8f6364a798720281b34b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 12 Mar 2013 09:56:05 -0300 Subject: Check and try to fix certificate permissions --- src/leap/services/eip/providerbootstrapper.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'src/leap/services/eip/providerbootstrapper.py') diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index ecdc4e07..0e9f8563 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -31,6 +31,7 @@ from PySide import QtGui, QtCore from leap.config.providerconfig import ProviderConfig from leap.util.check import leap_assert, leap_assert_type from leap.util.checkerthread import CheckerThread +from leap.util.files import check_and_fix_urw_only logger = logging.getLogger(__name__) @@ -258,9 +259,16 @@ class ProviderBootstrapper(QtCore.QObject): } if not self._should_proceed_cert(): - download_ca_cert_data[self.PASSED_KEY] = True + 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 True + return download_ca_cert_data[self.PASSED_KEY] try: res = self._session.get(self._provider_config.get_ca_cert_uri()) @@ -282,6 +290,8 @@ class ProviderBootstrapper(QtCore.QObject): with open(cert_path, "w") as f: f.write(res.content) + 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,) -- cgit v1.2.3 From 70c402fe170ca4e01159b03739b7cacda7b0dfd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 13 Mar 2013 09:43:13 -0300 Subject: Add mtime check for existing json definitions before download Also, wait for threads to finish when quitting --- src/leap/services/eip/providerbootstrapper.py | 59 ++++++++++++--------------- 1 file changed, 25 insertions(+), 34 deletions(-) (limited to 'src/leap/services/eip/providerbootstrapper.py') diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index 0e9f8563..f1a917f0 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -31,7 +31,7 @@ from PySide import QtGui, QtCore from leap.config.providerconfig import ProviderConfig from leap.util.check import leap_assert, leap_assert_type from leap.util.checkerthread import CheckerThread -from leap.util.files import check_and_fix_urw_only +from leap.util.files import check_and_fix_urw_only, get_mtime logger = logging.getLogger(__name__) @@ -72,25 +72,6 @@ class ProviderBootstrapper(QtCore.QObject): self._provider_config = None self._download_if_needed = False - def _should_proceed_provider(self): - """ - Returns False if provider.json already exists for the given - domain. True otherwise - - @rtype: bool - """ - if not self._download_if_needed: - return True - - # We don't really need a provider config at this stage, just - # the path prefix - return not os.path.exists(os.path.join(ProviderConfig() - .get_path_prefix(), - "leap", - "providers", - self._domain, - "provider.json")) - def _check_name_resolution(self): """ Checks that the name resolution for the provider name works @@ -171,24 +152,34 @@ class ProviderBootstrapper(QtCore.QObject): self.ERROR_KEY: "" } - if not self._should_proceed_provider(): - download_data[self.PASSED_KEY] = True - self.download_provider_info.emit(download_data) - return True - 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")) + "provider.json"), + headers=headers) res.raise_for_status() - provider_definition = res.content - - provider_config = ProviderConfig() - provider_config.load(data=provider_definition) - provider_config.save(["leap", - "providers", - self._domain, - "provider.json"]) + # Not modified + if res.status_code == 304: + logger.debug("Provider definition has not been modified") + else: + provider_definition = res.content + + provider_config = ProviderConfig() + provider_config.load(data=provider_definition) + provider_config.save(["leap", + "providers", + self._domain, + "provider.json"]) download_data[self.PASSED_KEY] = True except Exception as e: -- cgit v1.2.3 From 0ff122cf9fd0a76871093b595910fb7c0d3bfe85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 13 Mar 2013 10:05:58 -0300 Subject: Pass mtime to pluggableconfig's load Also add a request_helpers file to util where all the helper methods for handling requests should go --- src/leap/services/eip/providerbootstrapper.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/leap/services/eip/providerbootstrapper.py') diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index f1a917f0..4fdd9b8d 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -32,6 +32,7 @@ from leap.config.providerconfig import ProviderConfig from leap.util.check import leap_assert, leap_assert_type from leap.util.checkerthread import CheckerThread from leap.util.files import check_and_fix_urw_only, get_mtime +from leap.util.request_helpers import get_content logger = logging.getLogger(__name__) @@ -172,10 +173,10 @@ class ProviderBootstrapper(QtCore.QObject): if res.status_code == 304: logger.debug("Provider definition has not been modified") else: - provider_definition = res.content + provider_definition, mtime = get_content(res) provider_config = ProviderConfig() - provider_config.load(data=provider_definition) + provider_config.load(data=provider_definition, mtime=mtime) provider_config.save(["leap", "providers", self._domain, -- cgit v1.2.3 From 12d2835c7d1f3c3d11eaa587b2196c104e6859e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 13 Mar 2013 10:21:15 -0300 Subject: Add mkdir_p method to util.files --- src/leap/services/eip/providerbootstrapper.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'src/leap/services/eip/providerbootstrapper.py') diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index 4fdd9b8d..df56110e 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -31,7 +31,7 @@ from PySide import QtGui, QtCore from leap.config.providerconfig import ProviderConfig from leap.util.check import leap_assert, leap_assert_type from leap.util.checkerthread import CheckerThread -from leap.util.files import check_and_fix_urw_only, get_mtime +from leap.util.files import check_and_fix_urw_only, get_mtime, mkdir_p from leap.util.request_helpers import get_content logger = logging.getLogger(__name__) @@ -271,13 +271,7 @@ class ProviderBootstrapper(QtCore.QObject): cert_dir = os.path.dirname(cert_path) - try: - os.makedirs(cert_dir) - except OSError as e: - if e.errno == errno.EEXIST and os.path.isdir(cert_dir): - pass - else: - raise + mkdir_p(cert_dir) with open(cert_path, "w") as f: f.write(res.content) -- cgit v1.2.3 From a12906958e4d117daaf45bd42e7383d2344ea463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 13 Mar 2013 10:38:25 -0300 Subject: Add util.certs and abstract digest there --- src/leap/services/eip/providerbootstrapper.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src/leap/services/eip/providerbootstrapper.py') diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index df56110e..dc87a1bd 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -23,9 +23,7 @@ import requests import logging import socket import os -import errno -from OpenSSL import crypto from PySide import QtGui, QtCore from leap.config.providerconfig import ProviderConfig @@ -33,6 +31,7 @@ from leap.util.check import leap_assert, leap_assert_type from leap.util.checkerthread import CheckerThread from leap.util.files import check_and_fix_urw_only, get_mtime, mkdir_p from leap.util.request_helpers import get_content +from leap.util.certs import get_digest logger = logging.getLogger(__name__) @@ -324,8 +323,7 @@ class ProviderBootstrapper(QtCore.QObject): leap_assert(len(cert_data) > 0, "Could not read certificate data") - x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert_data) - digest = x509.digest(method).replace(":", "").lower() + digest = get_digest(cert_data, method) leap_assert(digest == fingerprint, "Downloaded certificate has a different fingerprint!") -- cgit v1.2.3 From fdc1d749a859cefd325e1de712f90eba79d3f678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 13 Mar 2013 14:15:02 -0300 Subject: Display a more related error message for https wizard checks Differentiate between SSLError and other type of errors, so to its clear when the certificate fails and when the connection fails or other kind of problems --- src/leap/services/eip/providerbootstrapper.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/leap/services/eip/providerbootstrapper.py') diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index dc87a1bd..82c62f90 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -127,8 +127,14 @@ class ProviderBootstrapper(QtCore.QObject): res = self._session.get("https://%s" % (self._domain,)) 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: - https_data[self.ERROR_KEY] = "%s" % (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) -- cgit v1.2.3 From 4459619c0d0597447119c67fb7267caae028103f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 13 Mar 2013 14:17:08 -0300 Subject: Document checker parameter in providerbootstrapper --- src/leap/services/eip/providerbootstrapper.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/leap/services/eip/providerbootstrapper.py') diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index 82c62f90..40ec55c5 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -201,6 +201,8 @@ class ProviderBootstrapper(QtCore.QObject): """ 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 -- cgit v1.2.3 From d0dfad6ac2af360de6421ce74a6831b5b81ad019 Mon Sep 17 00:00:00 2001 From: kali Date: Thu, 14 Mar 2013 07:08:31 +0900 Subject: namespace leap + leap.common split leap is a namespace package from here on. common folder will be deleted and moved to leap_pycommon repository. --- src/leap/services/eip/providerbootstrapper.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/leap/services/eip/providerbootstrapper.py') diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index 40ec55c5..778d5149 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -18,20 +18,20 @@ """ Provider bootstrapping """ - -import requests import logging import socket import os +import requests + from PySide import QtGui, 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.check import leap_assert, leap_assert_type from leap.util.checkerthread import CheckerThread -from leap.util.files import check_and_fix_urw_only, get_mtime, mkdir_p from leap.util.request_helpers import get_content -from leap.util.certs import get_digest logger = logging.getLogger(__name__) -- cgit v1.2.3 From 1fbf6db1276c5bca41c4cfbcc90818d9605c1938 Mon Sep 17 00:00:00 2001 From: Tomas Touceda Date: Fri, 12 Apr 2013 14:07:15 -0300 Subject: Add --danger option to not validate the first hop of certificates This is intended to be used while testing, not in production --- src/leap/services/eip/providerbootstrapper.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'src/leap/services/eip/providerbootstrapper.py') diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index 778d5149..f5559143 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -58,7 +58,14 @@ class ProviderBootstrapper(QtCore.QObject): check_ca_fingerprint = QtCore.Signal(dict) check_api_certificate = QtCore.Signal(dict) - def __init__(self): + def __init__(self, bypass_checks=False): + """ + Constructor for provider bootstrapper object + + @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) # **************************************************** # @@ -71,6 +78,7 @@ class ProviderBootstrapper(QtCore.QObject): self._domain = None self._provider_config = None self._download_if_needed = False + self._bypass_checks = bypass_checks def _check_name_resolution(self): """ @@ -124,7 +132,8 @@ class ProviderBootstrapper(QtCore.QObject): # system to work try: - res = self._session.get("https://%s" % (self._domain,)) + 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: @@ -171,7 +180,8 @@ class ProviderBootstrapper(QtCore.QObject): res = self._session.get("https://%s/%s" % (self._domain, "provider.json"), - headers=headers) + headers=headers, + verify=not self._bypass_checks) res.raise_for_status() # Not modified @@ -270,7 +280,8 @@ class ProviderBootstrapper(QtCore.QObject): return download_ca_cert_data[self.PASSED_KEY] try: - res = self._session.get(self._provider_config.get_ca_cert_uri()) + 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( -- cgit v1.2.3 From 2dae2703fb8c2ae7e721ce83020c0dd10ff9ca33 Mon Sep 17 00:00:00 2001 From: kali Date: Fri, 3 May 2013 02:59:22 +0900 Subject: updated documentation * documentation reviewed after rewrite, ready for 0.2.1 * updated docstrings format to fit sphinx autodoc --- src/leap/services/eip/providerbootstrapper.py | 59 +++++++++++++-------------- 1 file changed, 29 insertions(+), 30 deletions(-) (limited to 'src/leap/services/eip/providerbootstrapper.py') diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index f5559143..734d3867 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -62,9 +62,9 @@ class ProviderBootstrapper(QtCore.QObject): """ Constructor for provider bootstrapper object - @param bypass_checks: Set to true if the app should bypass + :param bypass_checks: Set to true if the app should bypass first round of checks for CA certificates at bootstrap - @type bypass_checks: bool + :type bypass_checks: bool """ QtCore.QObject.__init__(self) @@ -84,8 +84,8 @@ class ProviderBootstrapper(QtCore.QObject): """ Checks that the name resolution for the provider name works - @return: True if the checks passed, False otherwise - @rtype: bool + :return: True if the checks passed, False otherwise + :rtype: bool """ leap_assert(self._domain, "Cannot check DNS without a domain") @@ -115,8 +115,8 @@ class ProviderBootstrapper(QtCore.QObject): Checks that https is working and that the provided certificate checks out - @return: True if the checks passed, False otherwise - @rtype: bool + :return: True if the checks passed, False otherwise + :rtype: bool """ leap_assert(self._domain, "Cannot check HTTPS without a domain") @@ -154,8 +154,8 @@ class ProviderBootstrapper(QtCore.QObject): """ Downloads the provider.json defition - @return: True if the checks passed, False otherwise - @rtype: bool + :return: True if the checks passed, False otherwise + :rtype: bool """ leap_assert(self._domain, "Cannot download provider info without a domain") @@ -211,16 +211,15 @@ class ProviderBootstrapper(QtCore.QObject): """ 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 + :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 + :return: True if the checks passed, False otherwise + :rtype: bool """ leap_assert(domain and len(domain) > 0, "We need a domain!") @@ -238,7 +237,7 @@ class ProviderBootstrapper(QtCore.QObject): Returns False if the certificate already exists for the given provider. True otherwise - @rtype: bool + :rtype: bool """ leap_assert(self._provider_config, "We need a provider config!") @@ -252,8 +251,8 @@ class ProviderBootstrapper(QtCore.QObject): """ Downloads the CA cert that is going to be used for the api URL - @return: True if the checks passed, False otherwise - @rtype: bool + :return: True if the checks passed, False otherwise + :rtype: bool """ leap_assert(self._provider_config, "Cannot download the ca cert " @@ -310,8 +309,8 @@ class ProviderBootstrapper(QtCore.QObject): Checks the CA cert fingerprint against the one provided in the json definition - @return: True if the checks passed, False otherwise - @rtype: bool + :return: True if the checks passed, False otherwise + :rtype: bool """ leap_assert(self._provider_config, "Cannot check the ca cert " "without a provider config!") @@ -362,8 +361,8 @@ class ProviderBootstrapper(QtCore.QObject): 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 + :return: True if the checks passed, False otherwise + :rtype: bool """ leap_assert(self._provider_config, "Cannot check the ca cert " "without a provider config!") @@ -403,13 +402,13 @@ class ProviderBootstrapper(QtCore.QObject): provider_config, download_if_needed=False): """ - Starts the checks needed for a new provider setup + Starts the checks needed for a new provider setup. - @param provider_config: Provider configuration - @type provider_config: ProviderConfig - @param download_if_needed: if True, makes the checks do not - overwrite already downloaded data - @type download_if_needed: bool + :param provider_config: Provider configuration + :type provider_config: ProviderConfig + + :param download_if_needed: if True, makes the checks do not overwrite already downloaded data. + :type download_if_needed: bool """ leap_assert(provider_config, "We need a provider config!") leap_assert_type(provider_config, ProviderConfig) -- cgit v1.2.3 From 884d0e0f4dbba34b6f6f5afe6e27390a7606a7fa Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 29 May 2013 04:02:43 +0900 Subject: make tests pass & fix pep8 --- src/leap/services/eip/providerbootstrapper.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'src/leap/services/eip/providerbootstrapper.py') diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index 734d3867..289d212b 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -209,13 +209,16 @@ class ProviderBootstrapper(QtCore.QObject): def run_provider_select_checks(self, checker, domain, download_if_needed=False): """ - Populates the check queue + 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 + + :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 @@ -407,7 +410,8 @@ class ProviderBootstrapper(QtCore.QObject): :param provider_config: Provider configuration :type provider_config: ProviderConfig - :param download_if_needed: if True, makes the checks do not overwrite already downloaded data. + :param download_if_needed: if True, makes the checks do not + overwrite already downloaded data. :type download_if_needed: bool """ leap_assert(provider_config, "We need a provider config!") -- cgit v1.2.3 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/eip/providerbootstrapper.py | 384 +++++++------------------- 1 file changed, 95 insertions(+), 289 deletions(-) (limited to 'src/leap/services/eip/providerbootstrapper.py') 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) -- cgit v1.2.3 From 364d31999dbc488b5f99d81a0480c67ef248a515 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 10 Jun 2013 15:39:20 -0300 Subject: Check provider api version for compatibility --- src/leap/services/eip/providerbootstrapper.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'src/leap/services/eip/providerbootstrapper.py') diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index 1339e086..e099eee7 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -32,10 +32,19 @@ from leap.common.check import leap_assert, leap_assert_type from leap.config.providerconfig import ProviderConfig from leap.util.request_helpers import get_content from leap.services.abstractbootstrapper import AbstractBootstrapper +from leap.provider.supportedapis import SupportedAPIs + logger = logging.getLogger(__name__) +class UnsupportedProviderAPI(Exception): + """ + Raised when attempting to use a provider with an incompatible API. + """ + pass + + class ProviderBootstrapper(AbstractBootstrapper): """ Given a provider URL performs a series of checks and emits signals @@ -142,6 +151,18 @@ class ProviderBootstrapper(AbstractBootstrapper): self._domain, "provider.json"]) + api_version = provider_config.get_api_version() + if SupportedAPIs.supports(api_version): + logger.debug("Provider definition has been modified") + else: + api_supported = ', '.join(self._supported_api_versions) + error = ('Unsupported provider API version. ' + 'Supported versions are: {}. ' + 'Found: {}.').format(api_supported, api_version) + + logger.error(error) + raise UnsupportedProviderAPI(error) + def run_provider_select_checks(self, domain, download_if_needed=False): """ Populates the check queue. -- cgit v1.2.3 From b1429c296ee852d941a0c88e976631d03140ddee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 27 Jun 2013 09:59:57 -0300 Subject: Fix minor bugs and return defer in the run_* methods --- src/leap/services/eip/providerbootstrapper.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/leap/services/eip/providerbootstrapper.py') diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py index e099eee7..754d0643 100644 --- a/src/leap/services/eip/providerbootstrapper.py +++ b/src/leap/services/eip/providerbootstrapper.py @@ -155,7 +155,7 @@ class ProviderBootstrapper(AbstractBootstrapper): if SupportedAPIs.supports(api_version): logger.debug("Provider definition has been modified") else: - api_supported = ', '.join(self._supported_api_versions) + api_supported = ', '.join(SupportedAPIs.SUPPORTED_APIS) error = ('Unsupported provider API version. ' 'Supported versions are: {}. ' 'Found: {}.').format(api_supported, api_version) @@ -185,7 +185,7 @@ class ProviderBootstrapper(AbstractBootstrapper): (self._download_provider_info, self.download_provider_info) ] - self.addCallbackChain(cb_chain) + return self.addCallbackChain(cb_chain) def _should_proceed_cert(self): """ @@ -217,6 +217,7 @@ class ProviderBootstrapper(AbstractBootstrapper): check_and_fix_urw_only( self._provider_config .get_ca_cert_path(about_to_download=True)) + return res = self._session.get(self._provider_config.get_ca_cert_uri(), verify=not self._bypass_checks) @@ -307,4 +308,4 @@ class ProviderBootstrapper(AbstractBootstrapper): (self._check_api_certificate, self.check_api_certificate) ] - self.addCallbackChain(cb_chain) + return self.addCallbackChain(cb_chain) -- cgit v1.2.3