summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changes/feature-4636_warn-user-if-outdated-app2
-rw-r--r--src/leap/bitmask/app.py3
-rw-r--r--src/leap/bitmask/backend.py16
-rw-r--r--src/leap/bitmask/config/flags.py9
-rw-r--r--src/leap/bitmask/gui/mainwindow.py72
-rw-r--r--src/leap/bitmask/provider/__init__.py33
-rw-r--r--src/leap/bitmask/provider/providerbootstrapper.py45
-rw-r--r--src/leap/bitmask/provider/supportedapis.py38
-rw-r--r--src/leap/bitmask/provider/tests/test_providerbootstrapper.py10
-rw-r--r--src/leap/bitmask/util/leap_argparse.py8
10 files changed, 152 insertions, 84 deletions
diff --git a/changes/feature-4636_warn-user-if-outdated-app b/changes/feature-4636_warn-user-if-outdated-app
new file mode 100644
index 00000000..d4bd6eb7
--- /dev/null
+++ b/changes/feature-4636_warn-user-if-outdated-app
@@ -0,0 +1,2 @@
+- Warn the user on incompatible api error.
+- Warn the user if is using an old app version. Closes #4636.
diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py
index b16a51aa..d50743d6 100644
--- a/src/leap/bitmask/app.py
+++ b/src/leap/bitmask/app.py
@@ -200,6 +200,9 @@ def main():
from leap.common.config.baseconfig import BaseConfig
flags.STANDALONE = standalone
flags.MAIL_LOGFILE = mail_logfile
+ flags.APP_VERSION_CHECK = opts.app_version_check
+ flags.API_VERSION_CHECK = opts.api_version_check
+
BaseConfig.standalone = standalone
logger = add_logger_handlers(debug, logfile)
diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py
index 8a289a79..6b29d4b3 100644
--- a/src/leap/bitmask/backend.py
+++ b/src/leap/bitmask/backend.py
@@ -194,6 +194,9 @@ class Signaler(QtCore.QObject):
prov_problem_with_provider = QtCore.Signal(object)
+ prov_unsupported_client = QtCore.Signal(object)
+ prov_unsupported_api = QtCore.Signal(object)
+
# These will exist both in the backend and the front end.
# The frontend might choose to not "interpret" all the signals
# from the backend, but the backend needs to have all the signals
@@ -204,7 +207,9 @@ class Signaler(QtCore.QObject):
PROV_DOWNLOAD_CA_CERT_KEY = "prov_download_ca_cert"
PROV_CHECK_CA_FINGERPRINT_KEY = "prov_check_ca_fingerprint"
PROV_CHECK_API_CERTIFICATE_KEY = "prov_check_api_certificate"
- PROV_PROV_PROBLEM_WITH_PROVIER_KEY = "prov_problem_with_provider"
+ PROV_PROBLEM_WITH_PROVIDER_KEY = "prov_problem_with_provider"
+ PROV_UNSUPPORTED_CLIENT = "prov_unsupported_client"
+ PROV_UNSUPPORTED_API = "prov_unsupported_api"
def __init__(self):
"""
@@ -220,7 +225,9 @@ class Signaler(QtCore.QObject):
self.PROV_DOWNLOAD_CA_CERT_KEY,
self.PROV_CHECK_CA_FINGERPRINT_KEY,
self.PROV_CHECK_API_CERTIFICATE_KEY,
- self.PROV_PROV_PROBLEM_WITH_PROVIER_KEY
+ self.PROV_PROBLEM_WITH_PROVIDER_KEY,
+ self.PROV_UNSUPPORTED_CLIENT,
+ self.PROV_UNSUPPORTED_API
]
for sig in signals:
@@ -243,6 +250,11 @@ class Signaler(QtCore.QObject):
# will do zmq.send_multipart, and the frontend version will be
# similar to this
log.msg("Signaling %s :: %s" % (key, data))
+
+ # for some reason emitting 'None' gives a segmentation fault.
+ if data is None:
+ data = ''
+
try:
self._signals[key].emit(data)
except KeyError:
diff --git a/src/leap/bitmask/config/flags.py b/src/leap/bitmask/config/flags.py
index ba1b65b9..b1576c32 100644
--- a/src/leap/bitmask/config/flags.py
+++ b/src/leap/bitmask/config/flags.py
@@ -32,3 +32,12 @@ WARNING: You should NOT use this kind of flags unless you're sure of what
STANDALONE = False
MAIL_LOGFILE = None
+
+# The APP/API version check flags are used to provide a way to skip
+# that checks.
+# This can be used for:
+# - allow the use of a client that is not compatible with a provider.
+# - use a development version of the client with an older version number
+# since it's not released yet, and it is compatible with a newer provider.
+APP_VERSION_CHECK = True
+API_VERSION_CHECK = True
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index f954006d..8c512ad2 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -21,7 +21,6 @@ import logging
from PySide import QtCore, QtGui
from datetime import datetime
-from functools import partial
from twisted.internet import threads
from zope.proxy import ProxyBase, setProxiedObject
@@ -362,23 +361,20 @@ class MainWindow(QtGui.QMainWindow):
"""
Helper to connect to backend signals
"""
- self._backend.signaler.prov_name_resolution.connect(
- self._intermediate_stage)
- self._backend.signaler.prov_https_connection.connect(
- self._intermediate_stage)
- self._backend.signaler.prov_download_ca_cert.connect(
- self._intermediate_stage)
+ sig = self._backend.signaler
+ sig.prov_name_resolution.connect(self._intermediate_stage)
+ sig.prov_https_connection.connect(self._intermediate_stage)
+ sig.prov_download_ca_cert.connect(self._intermediate_stage)
- self._backend.signaler.prov_download_provider_info.connect(
- self._load_provider_config)
- self._backend.signaler.prov_check_api_certificate.connect(
- self._provider_config_loaded)
+ sig.prov_download_provider_info.connect(self._load_provider_config)
+ sig.prov_check_api_certificate.connect(self._provider_config_loaded)
# Only used at login, no need to disconnect this like we do
# with the other
- self._backend.signaler.prov_problem_with_provider.connect(
- partial(self._login_widget.set_status,
- self.tr("Unable to login: Problem with provider")))
+ sig.prov_problem_with_provider.connect(self._login_problem_provider)
+
+ sig.prov_unsupported_client.connect(self._needs_update)
+ sig.prov_unsupported_api.connect(self._incompatible_api)
def _backend_disconnect(self):
"""
@@ -387,17 +383,13 @@ class MainWindow(QtGui.QMainWindow):
Some signals are emitted from the wizard, and we want to
ignore those.
"""
- self._backend.signaler.prov_name_resolution.disconnect(
- self._intermediate_stage)
- self._backend.signaler.prov_https_connection.disconnect(
- self._intermediate_stage)
- self._backend.signaler.prov_download_ca_cert.disconnect(
- self._intermediate_stage)
+ sig = self._backend.signaler
+ sig.prov_name_resolution.disconnect(self._intermediate_stage)
+ sig.prov_https_connection.disconnect(self._intermediate_stage)
+ sig.prov_download_ca_cert.disconnect(self._intermediate_stage)
- self._backend.signaler.prov_download_provider_info.disconnect(
- self._load_provider_config)
- self._backend.signaler.prov_check_api_certificate.disconnect(
- self._provider_config_loaded)
+ sig.prov_download_provider_info.disconnect(self._load_provider_config)
+ sig.prov_check_api_certificate.disconnect(self._provider_config_loaded)
def _rejected_wizard(self):
"""
@@ -856,6 +848,28 @@ class MainWindow(QtGui.QMainWindow):
"<a href='https://leap.se'>More about LEAP"
"</a>") % (VERSION, VERSION_HASH[:10], greet))
+ def _needs_update(self):
+ """
+ Display a warning dialog to inform the user that the app needs update.
+ """
+ url = "https://dl.bitmask.net/"
+ msg = self.tr(
+ "The current client version is not supported "
+ "by this provider.<br>"
+ "Please update to latest version.<br><br>"
+ "You can get the latest version from {0}").format(url)
+ QtGui.QMessageBox.warning(self, self.tr("Update Needed"), msg)
+
+ def _incompatible_api(self):
+ """
+ Display a warning dialog to inform the user that the provider has an
+ incompatible API.
+ """
+ msg = self.tr(
+ "This provider is not compatible with the client.<br><br>"
+ "Error: API version incompatible.")
+ QtGui.QMessageBox.warning(self, self.tr("Incompatible Provider"), msg)
+
def changeEvent(self, e):
"""
Reimplements the changeEvent method to minimize to tray
@@ -923,11 +937,17 @@ class MainWindow(QtGui.QMainWindow):
selected_provider = self._login_widget.get_selected_provider()
self._backend.provider_bootstrap(selected_provider)
else:
- self._login_widget.set_status(
- self.tr("Unable to login: Problem with provider"))
logger.error(data[self._backend.ERROR_KEY])
self._login_widget.set_enabled(True)
+ def _login_problem_provider(self):
+ """
+ Warns the user about a problem with the provider during login.
+ """
+ self._login_widget.set_status(
+ self.tr("Unable to login: Problem with provider"))
+ self._login_widget.set_enabled(True)
+
def _login(self):
"""
SLOT
diff --git a/src/leap/bitmask/provider/__init__.py b/src/leap/bitmask/provider/__init__.py
index 53587d65..89ff5d95 100644
--- a/src/leap/bitmask/provider/__init__.py
+++ b/src/leap/bitmask/provider/__init__.py
@@ -15,12 +15,20 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
-Module initialization for leap.bitmask.provider
+Provider utilities.
"""
import os
+
+from pkg_resources import parse_version
+
+from leap.bitmask import __short_version__ as BITMASK_VERSION
from leap.common.check import leap_assert
+# The currently supported API versions by the client.
+SUPPORTED_APIS = ["1"]
+
+
def get_provider_path(domain):
"""
Returns relative path for provider config.
@@ -32,3 +40,26 @@ def get_provider_path(domain):
"""
leap_assert(domain is not None, "get_provider_path: We need a domain")
return os.path.join("leap", "providers", domain, "provider.json")
+
+
+def supports_api(api_version):
+ """
+ :param api_version: the version number of the api that we need to check
+ :type api_version: str
+
+ :returns: if that version is supported or not.
+ :return type: bool
+ """
+ return api_version in SUPPORTED_APIS
+
+
+def supports_client(minimum_version):
+ """
+ :param minimum_version: the version number of the client that
+ we need to check.
+ :type minimum_version: str
+
+ :returns: True if that version is supported or False otherwise.
+ :return type: bool
+ """
+ return parse_version(minimum_version) <= parse_version(BITMASK_VERSION)
diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py
index 947ba0c9..531d255e 100644
--- a/src/leap/bitmask/provider/providerbootstrapper.py
+++ b/src/leap/bitmask/provider/providerbootstrapper.py
@@ -24,12 +24,13 @@ import sys
import requests
+from leap.bitmask.config import flags
from leap.bitmask.config.providerconfig import ProviderConfig, MissingCACert
from leap.bitmask.util.request_helpers import get_content
from leap.bitmask import util
from leap.bitmask.util.constants import REQUEST_TIMEOUT
from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper
-from leap.bitmask.provider.supportedapis import SupportedAPIs
+from leap.bitmask import provider
from leap.common import ca_bundle
from leap.common.certs import get_digest
from leap.common.files import check_and_fix_urw_only, get_mtime, mkdir_p
@@ -45,6 +46,14 @@ class UnsupportedProviderAPI(Exception):
pass
+class UnsupportedClientVersionError(Exception):
+ """
+ Raised when attempting to use a provider with an older
+ client than supported.
+ """
+ pass
+
+
class WrongFingerprint(Exception):
"""
Raised when a fingerprint comparison does not match.
@@ -59,6 +68,8 @@ class ProviderBootstrapper(AbstractBootstrapper):
If a check fails, the subsequent checks are not executed
"""
+ MIN_CLIENT_VERSION = 'x-minimum-client-version'
+
def __init__(self, signaler=None, bypass_checks=False):
"""
Constructor for provider bootstrapper object
@@ -187,6 +198,8 @@ class ProviderBootstrapper(AbstractBootstrapper):
res.raise_for_status()
logger.debug("Request status code: {0}".format(res.status_code))
+ min_client_version = res.headers.get(self.MIN_CLIENT_VERSION, '0')
+
# Not modified
if res.status_code == 304:
logger.debug("Provider definition has not been modified")
@@ -194,6 +207,12 @@ class ProviderBootstrapper(AbstractBootstrapper):
# end refactor, more or less...
# XXX Watch out, have to check the supported api yet.
else:
+ if flags.APP_VERSION_CHECK:
+ if not provider.supports_client(min_client_version):
+ self._signaler.signal(
+ self._signaler.PROV_UNSUPPORTED_CLIENT)
+ raise UnsupportedClientVersionError()
+
provider_definition, mtime = get_content(res)
provider_config = ProviderConfig()
@@ -201,17 +220,19 @@ class ProviderBootstrapper(AbstractBootstrapper):
provider_config.save(["leap", "providers",
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(SupportedAPIs.SUPPORTED_APIS)
- error = ('Unsupported provider API version. '
- 'Supported versions are: {0}. '
- 'Found: {1}.').format(api_supported, api_version)
-
- logger.error(error)
- raise UnsupportedProviderAPI(error)
+ if flags.API_VERSION_CHECK:
+ api_version = provider_config.get_api_version()
+ if provider.supports_api(api_version):
+ logger.debug("Provider definition has been modified")
+ else:
+ api_supported = ', '.join(provider.SUPPORTED_APIS)
+ error = ('Unsupported provider API version. '
+ 'Supported versions are: {0}. '
+ 'Found: {1}.').format(api_supported, api_version)
+
+ logger.error(error)
+ self._signaler.signal(self._signaler.PROV_UNSUPPORTED_API)
+ raise UnsupportedProviderAPI(error)
def run_provider_select_checks(self, domain, download_if_needed=False):
"""
diff --git a/src/leap/bitmask/provider/supportedapis.py b/src/leap/bitmask/provider/supportedapis.py
deleted file mode 100644
index 3e650ba2..00000000
--- a/src/leap/bitmask/provider/supportedapis.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-# supportedapis.py
-# Copyright (C) 2013 LEAP
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""
-API Support check.
-"""
-
-
-class SupportedAPIs(object):
- """
- Class responsible of checking for API compatibility.
- """
- SUPPORTED_APIS = ["1"]
-
- @classmethod
- def supports(self, api_version):
- """
- :param api_version: the version number of the api that we need to check
- :type api_version: str
-
- :returns: if that version is supported or not.
- :return type: bool
- """
- return api_version in self.SUPPORTED_APIS
diff --git a/src/leap/bitmask/provider/tests/test_providerbootstrapper.py b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py
index d8336fec..6cf3e469 100644
--- a/src/leap/bitmask/provider/tests/test_providerbootstrapper.py
+++ b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py
@@ -36,17 +36,17 @@ from nose.twistedtools import deferred, reactor
from twisted.internet import threads
from requests.models import Response
+from leap.bitmask import provider
+from leap.bitmask import util
+from leap.bitmask.backend import Signaler
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.crypto.tests import fake_provider
from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper
from leap.bitmask.provider.providerbootstrapper import UnsupportedProviderAPI
from leap.bitmask.provider.providerbootstrapper import WrongFingerprint
-from leap.bitmask.provider.supportedapis import SupportedAPIs
-from leap.bitmask.backend import Signaler
-from leap.bitmask import util
from leap.common.files import mkdir_p
-from leap.common.testing.https_server import where
from leap.common.testing.basetest import BaseLeapTest
+from leap.common.testing.https_server import where
class ProviderBootstrapperTest(BaseLeapTest):
@@ -489,7 +489,7 @@ class ProviderBootstrapperActiveTest(unittest.TestCase):
'leap.bitmask.config.providerconfig.ProviderConfig.get_ca_cert_path',
lambda x: where('cacert.pem'))
def test_download_provider_info_unsupported_api(self):
- self._setup_provider_config_with(SupportedAPIs.SUPPORTED_APIS[0],
+ self._setup_provider_config_with(provider.SUPPORTED_APIS[0],
tempfile.mkdtemp())
self._setup_providerbootstrapper(False)
self._produce_dummy_provider_json()
diff --git a/src/leap/bitmask/util/leap_argparse.py b/src/leap/bitmask/util/leap_argparse.py
index 6703b600..280573f1 100644
--- a/src/leap/bitmask/util/leap_argparse.py
+++ b/src/leap/bitmask/util/leap_argparse.py
@@ -62,6 +62,14 @@ Launches Bitmask""", epilog=epilog)
help='Repair mailboxes for a given account. '
'Use when upgrading versions after a schema '
'change.')
+ parser.add_argument('-N', '--no-app-version-check', default=True,
+ action="store_false", dest="app_version_check",
+ help='Skip the app version compatibility check with '
+ 'the provider.')
+ parser.add_argument('-M', '--no-api-version-check', default=True,
+ action="store_false", dest="api_version_check",
+ help='Skip the api version compatibility check with '
+ 'the provider.')
# Not in use, we might want to reintroduce them.
#parser.add_argument('-i', '--no-provider-checks',