summaryrefslogtreecommitdiff
path: root/src/leap
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap')
-rw-r--r--src/leap/bitmask/backend.py23
-rw-r--r--src/leap/bitmask/gui/ui/wizard.ui2
-rw-r--r--src/leap/bitmask/gui/wizard.py110
-rw-r--r--src/leap/bitmask/provider/pinned.py73
-rw-r--r--src/leap/bitmask/provider/pinned_demobitmask.py115
-rw-r--r--src/leap/bitmask/provider/providerbootstrapper.py9
6 files changed, 286 insertions, 46 deletions
diff --git a/src/leap/bitmask/backend.py b/src/leap/bitmask/backend.py
index 3c97c797..7ebe7f97 100644
--- a/src/leap/bitmask/backend.py
+++ b/src/leap/bitmask/backend.py
@@ -38,6 +38,7 @@ from leap.bitmask.crypto.srpauth import SRPAuth
from leap.bitmask.crypto.srpregister import SRPRegister
from leap.bitmask.platform_init import IS_LINUX
from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper
+from leap.bitmask.provider.pinned import PinnedProviders
from leap.bitmask.services import get_supported
from leap.bitmask.services.eip import eipconfig
from leap.bitmask.services.eip import get_openvpn_management
@@ -275,6 +276,16 @@ class Provider(object):
self._signaler.PROV_GET_DETAILS,
self._provider_config.get_light_config(domain, lang))
+ def get_pinned_providers(self):
+ """
+ Signal the list of pinned provider domains.
+
+ Signals:
+ prov_get_pinned_providers -> list of provider domains
+ """
+ self._signaler.signal(
+ self._signaler.PROV_GET_PINNED_PROVIDERS,
+ PinnedProviders.domains())
class Register(object):
"""
@@ -1127,6 +1138,7 @@ class Signaler(QtCore.QObject):
prov_get_all_services = QtCore.Signal(object)
prov_get_supported_services = QtCore.Signal(object)
prov_get_details = QtCore.Signal(object)
+ prov_get_pinned_providers = QtCore.Signal(object)
prov_cancelled_setup = QtCore.Signal(object)
@@ -1240,6 +1252,7 @@ class Signaler(QtCore.QObject):
PROV_GET_ALL_SERVICES = "prov_get_all_services"
PROV_GET_SUPPORTED_SERVICES = "prov_get_supported_services"
PROV_GET_DETAILS = "prov_get_details"
+ PROV_GET_PINNED_PROVIDERS = "prov_get_pinned_providers"
SRP_REGISTRATION_FINISHED = "srp_registration_finished"
SRP_REGISTRATION_FAILED = "srp_registration_failed"
@@ -1340,6 +1353,7 @@ class Signaler(QtCore.QObject):
self.PROV_GET_ALL_SERVICES,
self.PROV_GET_SUPPORTED_SERVICES,
self.PROV_GET_DETAILS,
+ self.PROV_GET_PINNED_PROVIDERS,
self.SRP_REGISTRATION_FINISHED,
self.SRP_REGISTRATION_FAILED,
@@ -1681,6 +1695,15 @@ class Backend(object):
"""
self._call_queue.put(("provider", "get_details", None, domain, lang))
+ def provider_get_pinned_providers(self):
+ """
+ Signal the pinned providers.
+
+ Signals:
+ prov_get_pinned_providers -> list of provider domains
+ """
+ self._call_queue.put(("provider", "get_pinned_providers", None))
+
def user_register(self, provider, username, password):
"""
Register a user using the domain and password given as parameters.
diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui
index 6c592522..8c52897d 100644
--- a/src/leap/bitmask/gui/ui/wizard.ui
+++ b/src/leap/bitmask/gui/ui/wizard.ui
@@ -38,7 +38,7 @@
<property name="options">
<set>QWizard::IndependentPages</set>
</property>
- <widget class="QWizardPage" name="introduction_page">
+ <widget class="WizardPage" name="introduction_page">
<property name="title">
<string>Welcome</string>
</property>
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index 4d774907..05cfbed3 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -33,6 +33,7 @@ from leap.bitmask.util.keyring_helpers import has_keyring
from ui_wizard import Ui_Wizard
+QtDelayedCall = QtCore.QTimer.singleShot
logger = logging.getLogger(__name__)
@@ -64,6 +65,8 @@ class Wizard(QtGui.QWizard):
self.ui = Ui_Wizard()
self.ui.setupUi(self)
+ self._connected_signals = []
+
self.setPixmap(QtGui.QWizard.LogoPixmap,
QtGui.QPixmap(":/images/mask-icon.png"))
@@ -79,8 +82,8 @@ class Wizard(QtGui.QWizard):
self._use_existing_provider = False
self.ui.grpCheckProvider.setVisible(False)
- self.ui.btnCheck.clicked.connect(self._check_provider)
- self.ui.lnProvider.returnPressed.connect(self._check_provider)
+ self._connect_and_track(self.ui.btnCheck.clicked, self._check_provider)
+ self._connect_and_track(self.ui.lnProvider.returnPressed, self._check_provider)
self._backend = backend
self._backend_connect()
@@ -95,24 +98,25 @@ class Wizard(QtGui.QWizard):
self._provider_select_defer = None
self._provider_setup_defer = None
- self.currentIdChanged.connect(self._current_id_changed)
+ self._connect_and_track(self.currentIdChanged, self._current_id_changed)
- self.ui.lnProvider.textChanged.connect(self._enable_check)
- self.ui.rbNewProvider.toggled.connect(
+ self._connect_and_track(self.ui.lnProvider.textChanged, self._enable_check)
+ self._connect_and_track(self.ui.rbNewProvider.toggled,
lambda x: self._enable_check())
- self.ui.cbProviders.currentIndexChanged[int].connect(
+ self._connect_and_track(self.ui.cbProviders.currentIndexChanged[int],
self._reset_provider_check)
- self.ui.lblUser.returnPressed.connect(
+ self._connect_and_track(self.ui.lblUser.returnPressed,
self._focus_password)
- self.ui.lblPassword.returnPressed.connect(
+ self._connect_and_track(self.ui.lblPassword.returnPressed,
self._focus_second_password)
- self.ui.lblPassword2.returnPressed.connect(
+ self._connect_and_track(self.ui.lblPassword2.returnPressed,
self._register)
- self.ui.btnRegister.clicked.connect(
+ self._connect_and_track(self.ui.btnRegister.clicked,
self._register)
- self.ui.rbExistingProvider.toggled.connect(self._skip_provider_checks)
+ self._connect_and_track(self.ui.rbExistingProvider.toggled,
+ self._skip_provider_checks)
usernameRe = QtCore.QRegExp(USERNAME_REGEX)
self.ui.lblUser.setValidator(
@@ -137,7 +141,19 @@ class Wizard(QtGui.QWizard):
self._provider_checks_ok = False
self._provider_setup_ok = False
- self.finished.connect(self._wizard_finished)
+ self._connect_and_track(self.finished, self._wizard_finished)
+
+ def _connect_and_track(self, signal, method):
+ """
+ Helper to connect signals and keep track of them.
+
+ :param signal: the signal to connect to.
+ :type signal: QtCore.Signal
+ :param method: the method to call when the signal is triggered.
+ :type method: callable, Slot or Signal
+ """
+ self._connected_signals.append((signal, method))
+ signal.connect(method)
@QtCore.Slot()
def _wizard_finished(self):
@@ -153,28 +169,35 @@ class Wizard(QtGui.QWizard):
self._provider_setup_ok = False
self.ui.lnProvider.setText('')
self.ui.grpCheckProvider.setVisible(False)
- self._backend_disconnect()
+ self._disconnect_tracked()
def _load_configured_providers(self):
"""
Loads the configured providers into the wizard providers combo box.
"""
+ self._backend.provider_get_pinned_providers()
+
+ def _load_configured_providers_with_pinned(self, pinned):
+ """
+ Once we have the pinned providers from the backend, we
+ continue setting everything up
+
+ :param pinned: list of pinned providers
+ :type pinned: list of str
+ """
ls = LeapSettings()
providers = ls.get_configured_providers()
- if not providers:
+ if not providers and not pinned:
self.ui.rbExistingProvider.setEnabled(False)
self.ui.label_8.setEnabled(False) # 'https://' label
self.ui.cbProviders.setEnabled(False)
return
- pinned = []
user_added = []
# separate pinned providers from user added ones
for p in providers:
- if ls.is_pinned_provider(p):
- pinned.append(p)
- else:
+ if p not in pinned:
user_added.append(p)
if user_added:
@@ -191,6 +214,9 @@ class Wizard(QtGui.QWizard):
# 'Use existing provider' option.
self.ui.rbExistingProvider.setChecked(True)
+ # We need to set it as complete explicitly
+ self.page(self.INTRO_PAGE).set_completed()
+
def get_domain(self):
return self._domain
@@ -727,36 +753,30 @@ class Wizard(QtGui.QWizard):
Connects all the backend signals with the wizard.
"""
sig = self._backend.signaler
- sig.prov_name_resolution.connect(self._name_resolution)
- sig.prov_https_connection.connect(self._https_connection)
- sig.prov_download_provider_info.connect(self._download_provider_info)
- sig.prov_get_details.connect(self._provider_get_details)
+ conntrack = self._connect_and_track
+ conntrack(sig.prov_name_resolution, self._name_resolution)
+ conntrack(sig.prov_https_connection, self._https_connection)
+ conntrack(sig.prov_download_provider_info,
+ self._download_provider_info)
+ conntrack(sig.prov_get_details, self._provider_get_details)
+ conntrack(sig.prov_get_pinned_providers,
+ self._load_configured_providers_with_pinned)
- sig.prov_download_ca_cert.connect(self._download_ca_cert)
- sig.prov_check_ca_fingerprint.connect(self._check_ca_fingerprint)
- sig.prov_check_api_certificate.connect(self._check_api_certificate)
+ conntrack(sig.prov_download_ca_cert, self._download_ca_cert)
+ conntrack(sig.prov_check_ca_fingerprint, self._check_ca_fingerprint)
+ conntrack(sig.prov_check_api_certificate, self._check_api_certificate)
- sig.srp_registration_finished.connect(self._registration_finished)
- sig.srp_registration_failed.connect(self._registration_failed)
- sig.srp_registration_taken.connect(self._registration_taken)
+ conntrack(sig.srp_registration_finished, self._registration_finished)
+ conntrack(sig.srp_registration_failed, self._registration_failed)
+ conntrack(sig.srp_registration_taken, self._registration_taken)
- def _backend_disconnect(self):
+ def _disconnect_tracked(self):
"""
This method is called when the wizard dialog is closed.
- We disconnect all the backend signals in here.
+ We disconnect all the signals in here.
"""
- sig = self._backend.signaler
- try:
- # disconnect backend signals
- sig.prov_name_resolution.disconnect(self._name_resolution)
- sig.prov_https_connection.disconnect(self._https_connection)
- sig.prov_download_provider_info.disconnect(
- self._download_provider_info)
-
- sig.prov_download_ca_cert.disconnect(self._download_ca_cert)
- sig.prov_check_ca_fingerprint.disconnect(
- self._check_ca_fingerprint)
- sig.prov_check_api_certificate.disconnect(
- self._check_api_certificate)
- except RuntimeError:
- pass # Signal was not connected
+ for signal, method in self._connected_signals:
+ try:
+ signal.disconnect(method)
+ except RuntimeError:
+ pass # Signal was not connected
diff --git a/src/leap/bitmask/provider/pinned.py b/src/leap/bitmask/provider/pinned.py
new file mode 100644
index 00000000..d9b74c3d
--- /dev/null
+++ b/src/leap/bitmask/provider/pinned.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+# pinned.py
+# Copyright (C) 2013-2014 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/>.
+"""
+Pinned Providers
+"""
+import logging
+
+from leap.bitmask.provider import pinned_demobitmask
+
+logger = logging.getLogger(__name__)
+
+
+class PinnedProviders(object):
+ """
+ Represents the providers that are pinned in Bitmask
+ """
+
+ CONFIG_KEY = "config"
+ CACERT_KEY = "cacert"
+
+ PROVIDERS = {
+ pinned_demobitmask.DOMAIN: {
+ CONFIG_KEY: pinned_demobitmask.PROVIDER_JSON,
+ CACERT_KEY: pinned_demobitmask.CACERT_PEM,
+ }
+ }
+
+ def __init__(self):
+ pass
+
+ @classmethod
+ def domains(self):
+ """
+ Return the domains that are pinned in here
+
+ :rtype: list of str
+ """
+ return self.PROVIDERS.keys()
+
+ @classmethod
+ def save_hardcoded(self, domain, provider_path, cacert_path):
+ """
+ Save the pinned content for provider.json and cacert.pem to
+ the specified paths
+
+ :param domain: domain of the pinned provider
+ :type domain: str
+ :param provider_path: path where the pinned provider.json will
+ be saved
+ :type provider_path: str
+ :param cacert_path: path where the pinned cacert.pem will be
+ saved
+ :type cacert_path: str
+ """
+ with open(provider_path, "w") as f:
+ f.write(self.PROVIDERS[domain][self.CONFIG_KEY])
+
+ with open(cacert_path, "w") as f:
+ f.write(self.PROVIDERS[domain][self.CACERT_KEY])
diff --git a/src/leap/bitmask/provider/pinned_demobitmask.py b/src/leap/bitmask/provider/pinned_demobitmask.py
new file mode 100644
index 00000000..9c699a4c
--- /dev/null
+++ b/src/leap/bitmask/provider/pinned_demobitmask.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+# pinned_demobitmask.py
+# Copyright (C) 2013-2014 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/>.
+"""
+Pinned provider.json and cacert.pem for demo.bitmask.net
+"""
+
+DOMAIN = "demo.bitmask.net"
+
+PROVIDER_JSON = """
+{
+ "api_uri": "https://api.demo.bitmask.net:4430",
+ "api_version": "1",
+ "ca_cert_fingerprint": "SHA256: 0f17c033115f6b76ff67871872303ff65034efe7dd1b910062ca323eb4da5c7e",
+ "ca_cert_uri": "https://demo.bitmask.net/ca.crt",
+ "default_language": "en",
+ "description": {
+ "el": "demo.bitmask.net allows you to test the Bitmask application. User accounts may be periodically deleted.",
+ "en": "demo.bitmask.net allows you to test the Bitmask application. User accounts may be periodically deleted.",
+ "es": "demo.bitmask.net allows you to test the Bitmask application. User accounts may be periodically deleted."
+ },
+ "domain": "demo.bitmask.net",
+ "enrollment_policy": "open",
+ "languages": [
+ "en"
+ ],
+ "name": {
+ "en": "Bitmask"
+ },
+ "service": {
+ "allow_anonymous": true,
+ "allow_free": true,
+ "allow_limited_bandwidth": false,
+ "allow_paid": true,
+ "allow_registration": true,
+ "allow_unlimited_bandwidth": true,
+ "bandwidth_limit": 102400,
+ "default_service_level": 1,
+ "levels": [
+ {
+ "id": 1,
+ "name": "free",
+ "storage": 50
+ },
+ {
+ "id": 2,
+ "name": "basic",
+ "rate": [
+ "US$10",
+ "\u20ac10"
+ ],
+ "storage": 1000
+ },
+ {
+ "id": 3,
+ "name": "pro",
+ "rate": [
+ "US$20",
+ "\u20ac20"
+ ],
+ "storage": 10000
+ }
+ ]
+ },
+ "services": [
+ "openvpn"
+ ]
+}
+"""
+
+CACERT_PEM = """-----BEGIN CERTIFICATE-----
+MIIFbzCCA1egAwIBAgIBATANBgkqhkiG9w0BAQ0FADBKMRgwFgYDVQQDDA9CaXRt
+YXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNVBAsME2h0dHBzOi8v
+Yml0bWFzay5uZXQwHhcNMTIxMTA2MDAwMDAwWhcNMjIxMTA2MDAwMDAwWjBKMRgw
+FgYDVQQDDA9CaXRtYXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNV
+BAsME2h0dHBzOi8vYml0bWFzay5uZXQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQC1eV4YvayaU+maJbWrD4OHo3d7S1BtDlcvkIRS1Fw3iYDjsyDkZxai
+dHp4EUasfNQ+EVtXUvtk6170EmLco6Elg8SJBQ27trE6nielPRPCfX3fQzETRfvB
+7tNvGw4Jn2YKiYoMD79kkjgyZjkJ2r/bEHUSevmR09BRp86syHZerdNGpXYhcQ84
+CA1+V+603GFIHnrP+uQDdssW93rgDNYu+exT+Wj6STfnUkugyjmPRPjL7wh0tzy+
+znCeLl4xiV3g9sjPnc7r2EQKd5uaTe3j71sDPF92KRk0SSUndREz+B1+Dbe/RGk4
+MEqGFuOzrtsgEhPIX0hplhb0Tgz/rtug+yTT7oJjBa3u20AAOQ38/M99EfdeJvc4
+lPFF1XBBLh6X9UKF72an2NuANiX6XPySnJgZ7nZ09RiYZqVwu/qt3DfvLfhboq+0
+bQvLUPXrVDr70onv5UDjpmEA/cLmaIqqrduuTkFZOym65/PfAPvpGnt7crQj/Ibl
+DEDYZQmP7AS+6zBjoOzNjUGE5r40zWAR1RSi7zliXTu+yfsjXUIhUAWmYR6J3KxB
+lfsiHBQ+8dn9kC3YrUexWoOqBiqJOAJzZh5Y1tqgzfh+2nmHSB2dsQRs7rDRRlyy
+YMbkpzL9ZsOUO2eTP1mmar6YjCN+rggYjRrX71K2SpBG6b1zZxOG+wIDAQABo2Aw
+XjAdBgNVHQ4EFgQUuYGDLL2sswnYpHHvProt1JU+D48wDgYDVR0PAQH/BAQDAgIE
+MAwGA1UdEwQFMAMBAf8wHwYDVR0jBBgwFoAUuYGDLL2sswnYpHHvProt1JU+D48w
+DQYJKoZIhvcNAQENBQADggIBADeG67vaFcbITGpi51264kHPYPEWaXUa5XYbtmBl
+cXYyB6hY5hv/YNuVGJ1gWsDmdeXEyj0j2icGQjYdHRfwhrbEri+h1EZOm1cSBDuY
+k/P5+ctHyOXx8IE79DBsZ6IL61UKIaKhqZBfLGYcWu17DVV6+LT+AKtHhOrv3TSj
+RnAcKnCbKqXLhUPXpK0eTjPYS2zQGQGIhIy9sQXVXJJJsGrPgMxna1Xw2JikBOCG
+htD/JKwt6xBmNwktH0GI/LVtVgSp82Clbn9C4eZN9E5YbVYjLkIEDhpByeC71QhX
+EIQ0ZR56bFuJA/CwValBqV/G9gscTPQqd+iETp8yrFpAVHOW+YzSFbxjTEkBte1J
+aF0vmbqdMAWLk+LEFPQRptZh0B88igtx6tV5oVd+p5IVRM49poLhuPNJGPvMj99l
+mlZ4+AeRUnbOOeAEuvpLJbel4rhwFzmUiGoeTVoPZyMevWcVFq6BMkS+jRR2w0jK
+G6b0v5XDHlcFYPOgUrtsOBFJVwbutLvxdk6q37kIFnWCd8L3kmES5q4wjyFK47Co
+Ja8zlx64jmMZPg/t3wWqkZgXZ14qnbyG5/lGsj5CwVtfDljrhN0oCWK1FZaUmW3d
+69db12/g4f6phldhxiWuGC/W6fCW5kre7nmhshcltqAJJuU47iX+DarBFiIj816e
+yV8e
+-----END CERTIFICATE-----"""
diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py
index 6cdfe4f4..8c96a8b5 100644
--- a/src/leap/bitmask/provider/providerbootstrapper.py
+++ b/src/leap/bitmask/provider/providerbootstrapper.py
@@ -29,6 +29,7 @@ from leap.bitmask import util
from leap.bitmask.config import flags
from leap.bitmask.config.providerconfig import ProviderConfig, MissingCACert
from leap.bitmask.provider import get_provider_path
+from leap.bitmask.provider.pinned import PinnedProviders
from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper
from leap.bitmask.util.constants import REQUEST_TIMEOUT
from leap.bitmask.util.request_helpers import get_content
@@ -176,6 +177,14 @@ class ProviderBootstrapper(AbstractBootstrapper):
provider_json = os.path.join(util.get_path_prefix(),
get_provider_path(domain))
+ if domain in PinnedProviders.domains() and \
+ not os.path.exists(provider_json):
+ mkdir_p(os.path.join(os.path.dirname(provider_json),
+ "keys", "ca"))
+ cacert = os.path.join(os.path.dirname(provider_json),
+ "keys", "ca", "cacert.pem")
+ PinnedProviders.save_hardcoded(domain, provider_json, cacert)
+
mtime = get_mtime(provider_json)
if self._download_if_needed and mtime: