summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/leap/config/leapsettings.py19
-rw-r--r--src/leap/crypto/srpauth.py84
-rw-r--r--src/leap/gui/mainwindow.py89
-rw-r--r--src/leap/gui/ui/wizard.ui3
-rw-r--r--src/leap/gui/wizard.py21
-rw-r--r--src/leap/provider/__init__.py0
-rw-r--r--src/leap/provider/supportedapis.py38
-rw-r--r--src/leap/services/eip/providerbootstrapper.py21
8 files changed, 208 insertions, 67 deletions
diff --git a/src/leap/config/leapsettings.py b/src/leap/config/leapsettings.py
index 59a0a16d..006be851 100644
--- a/src/leap/config/leapsettings.py
+++ b/src/leap/config/leapsettings.py
@@ -65,6 +65,7 @@ class LeapSettings(object):
AUTOLOGIN_KEY = "AutoLogin"
PROPERPROVIDER_KEY = "ProperProvider"
REMEMBER_KEY = "RememberUserAndPass"
+ DEFAULTPROVIDER_KEY = "DefaultProvider"
def __init__(self, standalone=False):
"""
@@ -230,3 +231,21 @@ class LeapSettings(object):
"""
leap_assert_type(properprovider, bool)
self._settings.setValue(self.PROPERPROVIDER_KEY, properprovider)
+
+ def get_defaultprovider(self):
+ """
+ Returns the default provider to be used for autostarting EIP
+
+ :rtype: str or None
+ """
+ return self._settings.value(self.DEFAULTPROVIDER_KEY, None)
+
+ def set_defaultprovider(self, provider):
+ """
+ Sets the default provider to be used for autostarting EIP
+
+ :param provider: provider to use
+ :type provider: str
+ """
+ leap_assert(len(provider) > 0, "We cannot save an empty provider")
+ self._settings.setValue(self.DEFAULTPROVIDER_KEY, provider)
diff --git a/src/leap/crypto/srpauth.py b/src/leap/crypto/srpauth.py
index 28086279..52267b3b 100644
--- a/src/leap/crypto/srpauth.py
+++ b/src/leap/crypto/srpauth.py
@@ -25,10 +25,10 @@ import json
#this error is raised from requests
from simplejson.decoder import JSONDecodeError
-from PySide import QtCore, QtGui
+from PySide import QtCore
+from twisted.internet import threads
from leap.common.check import leap_assert
-from leap.config.providerconfig import ProviderConfig
from leap.util.request_helpers import get_content
from leap.common.events import signal as events_signal
from leap.common.events import events_pb2 as proto
@@ -124,13 +124,15 @@ class SRPAuth(QtCore.QObject):
self._srp_a = A
- def _start_authentication(self, username, password):
+ def _start_authentication(self, _, username, password):
"""
Sends the first request for authentication to retrieve the
salt and B parameter
Might raise SRPAuthenticationError
+ :param _: IGNORED, output from the previous callback (None)
+ :type _: IGNORED
:param username: username to login
:type username: str
:param password: password for the username
@@ -187,17 +189,15 @@ class SRPAuth(QtCore.QObject):
return salt, B
- def _process_challenge(self, salt, B, username):
+ def _process_challenge(self, salt_B, username):
"""
Given the salt and B processes the auth challenge and
generates the M2 parameter
Might throw SRPAuthenticationError
- :param salt: salt for the username
- :type salt: str
- :param B: B SRP parameter
- :type B: str
+ :param salt_B: salt and B parameters for the username
+ :type salt_B: tuple
:param username: username for this session
:type username: str
@@ -206,6 +206,7 @@ class SRPAuth(QtCore.QObject):
"""
logger.debug("Processing challenge...")
try:
+ salt, B = salt_B
unhex_salt = self._safe_unhexlify(salt)
unhex_B = self._safe_unhexlify(B)
except TypeError as e:
@@ -254,9 +255,14 @@ class SRPAuth(QtCore.QObject):
(auth_result.status_code,))
json_content = json.loads(content)
- M2 = json_content.get("M2", None)
- uid = json_content.get("id", None)
- token = json_content.get("token", None)
+
+ try:
+ M2 = json_content.get("M2", None)
+ uid = json_content.get("id", None)
+ token = json_content.get("token", None)
+ except Exception as e:
+ logger.error(e)
+ raise Exception("Something went wrong with the login")
events_signal(proto.CLIENT_UID, content=uid)
@@ -318,17 +324,22 @@ class SRPAuth(QtCore.QObject):
:type username: str
:param password: password for this user
:type password: str
+
+ :returns: A defer on a different thread
+ :rtype: twisted.internet.defer.Deferred
"""
leap_assert(self.get_session_id() is None, "Already logged in")
- self._authentication_preprocessing(username, password)
- salt, B = self._start_authentication(username, password)
- M2 = self._process_challenge(salt, B, username)
+ d = threads.deferToThread(self._authentication_preprocessing,
+ username=username,
+ password=password)
- self._verify_session(M2)
+ d.addCallback(self._start_authentication, username=username,
+ password=password)
+ d.addCallback(self._process_challenge, username=username)
+ d.addCallback(self._verify_session)
- leap_assert(self.get_session_id(), "Something went wrong because"
- " we don't have the auth cookie afterwards")
+ return d
def logout(self):
"""
@@ -388,10 +399,6 @@ class SRPAuth(QtCore.QObject):
authentication_finished = QtCore.Signal(bool, str)
logout_finished = QtCore.Signal(bool, str)
- DO_NOTHING = 0
- DO_LOGIN = 1
- DO_LOGOUT = 2
-
def __init__(self, provider_config):
"""
Creates a singleton instance if needed
@@ -406,8 +413,6 @@ class SRPAuth(QtCore.QObject):
# Store instance reference as the only member in the handle
self.__dict__['_SRPAuth__instance'] = SRPAuth.__instance
- self._should_login = self.DO_NOTHING
- self._should_login_lock = QtCore.QMutex()
self._username = None
self._password = None
@@ -423,16 +428,31 @@ class SRPAuth(QtCore.QObject):
:type password: str
"""
- try:
- self.__instance.authenticate(username, password)
+ d = self.__instance.authenticate(username, password)
+ d.addCallback(self._gui_notify)
+ d.addErrback(self._errback)
+ return d
- logger.debug("Successful login!")
- self.authentication_finished.emit(True, self.tr("Succeeded"))
- return True
- except Exception as e:
- logger.error("Error logging in %s" % (e,))
- self.authentication_finished.emit(False, "%s" % (e,))
- return False
+ def _gui_notify(self, _):
+ """
+ Callback that notifies the UI with the proper signal.
+
+ :param _: IGNORED, output from the previous callback (None)
+ :type _: IGNORED
+ """
+ logger.debug("Successful login!")
+ self.authentication_finished.emit(True, self.tr("Succeeded"))
+
+ def _errback(self, failure):
+ """
+ General errback for the whole login process. Will notify the
+ UI with the proper signal.
+
+ :param failure: Failure object captured from a callback.
+ :type failure: twisted.python.failure.Failure
+ """
+ logger.error("Error logging in %s" % (failure,))
+ self.authentication_finished.emit(False, "%s" % (failure,))
def get_session_id(self):
return self.__instance.get_session_id()
diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py
index 2cad6df3..a7d88aee 100644
--- a/src/leap/gui/mainwindow.py
+++ b/src/leap/gui/mainwindow.py
@@ -218,12 +218,11 @@ class MainWindow(QtGui.QMainWindow):
self._really_quit = False
self._systray = None
- self._vpn_systray = None
- self._action_eip_status = QtGui.QAction(self.tr("Encryption is OFF"),
+ self._action_eip_status = QtGui.QAction(self.tr("Encrypted internet is OFF"),
self)
self._action_eip_status.setEnabled(False)
- self._action_eip_startstop = QtGui.QAction(self.tr("Stop"), self)
+ self._action_eip_startstop = QtGui.QAction(self.tr("Turn encryption ON"), self)
self._action_eip_startstop.triggered.connect(
self._stop_eip)
self._action_eip_write = QtGui.QAction(
@@ -235,7 +234,7 @@ class MainWindow(QtGui.QMainWindow):
"%12.2f Kb" % (0.0,), self)
self._action_eip_read.setEnabled(False)
- self._action_visible = QtGui.QAction(self.tr("Hide"), self)
+ self._action_visible = QtGui.QAction(self.tr("Hide Main Window"), self)
self._action_visible.triggered.connect(self._toggle_visible)
self._enabled_services = []
@@ -260,6 +259,8 @@ class MainWindow(QtGui.QMainWindow):
self._soledad = None
self._keymanager = None
+ self._login_defer = None
+
self._smtp_config = SMTPConfig()
if self._first_run():
@@ -378,6 +379,12 @@ class MainWindow(QtGui.QMainWindow):
if self._wizard:
possible_username = self._wizard.get_username()
possible_password = self._wizard.get_password()
+
+ # select the configured provider in the combo box
+ domain = self._wizard.get_domain()
+ provider_index = self.ui.cmbProviders.findText(domain)
+ self.ui.cmbProviders.setCurrentIndex(provider_index)
+
self.ui.chkRemember.setChecked(self._wizard.get_remember())
self._enabled_services = list(self._wizard.get_services())
self._settings.set_enabled_services(
@@ -400,8 +407,21 @@ class MainWindow(QtGui.QMainWindow):
saved_user = self._settings.get_user()
auto_login = self._settings.get_autologin()
+ try:
+ username, domain = saved_user.split('@')
+ except (ValueError, AttributeError) as e:
+ # if the saved_user does not contain an '@' or its None
+ logger.error('Username@provider malformed. %r' % (e, ))
+ saved_user = None
+
if saved_user is not None:
- self.ui.lnUser.setText(saved_user)
+ # fill the username
+ self.ui.lnUser.setText(username)
+
+ # select the configured provider in the combo box
+ provider_index = self.ui.cmbProviders.findText(domain)
+ self.ui.cmbProviders.setCurrentIndex(provider_index)
+
self.ui.chkRemember.setChecked(True)
self.ui.chkAutoLogin.setEnabled(self.ui.chkRemember
.isEnabled())
@@ -435,22 +455,15 @@ class MainWindow(QtGui.QMainWindow):
systrayMenu.addAction(self.ui.action_sign_out)
systrayMenu.addSeparator()
systrayMenu.addAction(self.ui.action_quit)
+ systrayMenu.addSeparator()
+ systrayMenu.addAction(self._action_eip_status)
+ systrayMenu.addAction(self._action_eip_startstop)
self._systray = QtGui.QSystemTrayIcon(self)
self._systray.setContextMenu(systrayMenu)
- self._systray.setIcon(QtGui.QIcon(self.LOGGED_OUT_ICON))
+ self._systray.setIcon(QtGui.QIcon(self.ERROR_ICON))
self._systray.setVisible(True)
self._systray.activated.connect(self._toggle_visible)
- vpn_systrayMenu = QtGui.QMenu(self)
- vpn_systrayMenu.addAction(self._action_eip_status)
- vpn_systrayMenu.addAction(self._action_eip_startstop)
- vpn_systrayMenu.addAction(self._action_eip_read)
- vpn_systrayMenu.addAction(self._action_eip_write)
- self._vpn_systray = QtGui.QSystemTrayIcon(self)
- self._vpn_systray.setContextMenu(vpn_systrayMenu)
- self._vpn_systray.setIcon(QtGui.QIcon(self.ERROR_ICON))
- self._vpn_systray.setVisible(False)
-
def _toggle_visible(self, reason=None):
"""
SLOT
@@ -594,7 +607,7 @@ class MainWindow(QtGui.QMainWindow):
:param status: status message
:type status: str
"""
- self._vpn_systray.setToolTip(status)
+ self._systray.setToolTip(status)
if error:
status = "<font color='red'><b>%s</b></font>" % (status,)
self.ui.lblEIPStatus.setText(status)
@@ -694,13 +707,16 @@ class MainWindow(QtGui.QMainWindow):
self._login_set_enabled(False)
if self.ui.chkRemember.isChecked():
+ # in the keyring and in the settings
+ # we store the value 'usename@provider'
+ username_domain = (username + '@' + provider).encode("utf8")
try:
keyring.set_password(self.KEYRING_KEY,
- username.encode("utf8"),
+ username_domain,
password.encode("utf8"))
# Only save the username if it was saved correctly in
# the keyring
- self._settings.set_user(username)
+ self._settings.set_user(username_domain)
except Exception as e:
logger.error("Problem saving data to keyring. %r"
% (e,))
@@ -728,10 +744,7 @@ class MainWindow(QtGui.QMainWindow):
self._srp_auth.logout_finished.connect(
self._done_logging_out)
- auth_partial = partial(self._srp_auth.authenticate,
- username,
- password)
- threads.deferToThread(auth_partial)
+ self._login_defer = self._srp_auth.authenticate(username, password)
else:
self._set_status(data[self._provider_bootstrapper.ERROR_KEY])
self._login_set_enabled(True)
@@ -751,6 +764,7 @@ class MainWindow(QtGui.QMainWindow):
# "Succeeded" message and then we switch to the EIP status
# panel
QtCore.QTimer.singleShot(1000, self._switch_to_status)
+ self._login_defer = None
else:
self._login_set_enabled(True)
@@ -760,7 +774,6 @@ class MainWindow(QtGui.QMainWindow):
triggers the eip bootstrapping
"""
self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX)
- self._systray.setIcon(self.LOGGED_IN_ICON)
self._soledad_bootstrapper.run_soledad_setup_checks(
self._provider_config,
@@ -883,17 +896,29 @@ class MainWindow(QtGui.QMainWindow):
return host, port
def _start_eip(self):
+ """
+ SLOT
+ TRIGGERS:
+ self.ui.btnEipStartStop.clicked
+ self._action_eip_startstop.triggered
+ or called from _finish_eip_bootstrap
+
+ Starts EIP
+ """
try:
host, port = self._get_socket_host()
self._vpn.start(eipconfig=self._eip_config,
providerconfig=self._provider_config,
socket_host=host,
socket_port=port)
- self.ui.btnEipStartStop.setText(self.tr("Stop EIP"))
+
+ self._settings.set_defaultprovider(
+ self._provider_config.get_domain())
+ self.ui.btnEipStartStop.setText(self.tr("Turn Encryption OFF"))
self.ui.btnEipStartStop.disconnect(self)
self.ui.btnEipStartStop.clicked.connect(
self._stop_eip)
- self._action_eip_startstop.setText(self.tr("Stop"))
+ self._action_eip_startstop.setText(self.tr("Turn Encryption OFF"))
self._action_eip_startstop.disconnect(self)
self._action_eip_startstop.triggered.connect(
self._stop_eip)
@@ -922,11 +947,11 @@ class MainWindow(QtGui.QMainWindow):
self._vpn.terminate()
self._set_eip_status(self.tr("EIP has stopped"))
self._set_eip_status_icon("error")
- self.ui.btnEipStartStop.setText(self.tr("Start EIP"))
+ self.ui.btnEipStartStop.setText(self.tr("Turn Encryption ON"))
self.ui.btnEipStartStop.disconnect(self)
self.ui.btnEipStartStop.clicked.connect(
self._start_eip)
- self._action_eip_startstop.setText(self.tr("Start"))
+ self._action_eip_startstop.setText(self.tr("Turn Encryption ON"))
self._action_eip_startstop.disconnect(self)
self._action_eip_startstop.triggered.connect(
self._start_eip)
@@ -942,7 +967,6 @@ class MainWindow(QtGui.QMainWindow):
if self._provider_config.provides_eip() and \
self._enabled_services.count(self.OPENVPN_SERVICE) > 0:
- self._vpn_systray.setVisible(True)
self._eip_bootstrapper.run_eip_setup_checks(
self._provider_config,
download_if_needed=True)
@@ -967,12 +991,13 @@ class MainWindow(QtGui.QMainWindow):
if status in ("WAIT", "AUTH", "GET_CONFIG",
"RECONNECTING", "ASSIGN_IP"):
selected_pixmap = self.CONNECTING_ICON
+ tray_message = self.tr("Turning Encryption ON")
elif status in ("CONNECTED"):
tray_message = self.tr("Encryption is ON")
selected_pixmap = self.CONNECTED_ICON
self.ui.lblVPNStatusIcon.setPixmap(selected_pixmap)
- self._vpn_systray.setIcon(QtGui.QIcon(selected_pixmap))
+ self._systray.setIcon(QtGui.QIcon(selected_pixmap))
self._action_eip_status.setText(tray_message)
def _update_vpn_state(self, data):
@@ -1073,7 +1098,6 @@ class MainWindow(QtGui.QMainWindow):
Switches the stackedWidget back to the login stage after
logging out
"""
- self._systray.setIcon(self.LOGGED_OUT_ICON)
self.ui.action_sign_out.setEnabled(False)
self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX)
self.ui.lnPassword.setText("")
@@ -1169,6 +1193,9 @@ class MainWindow(QtGui.QMainWindow):
self.close()
+ if self._login_defer:
+ self._login_defer.cancel()
+
if self._quit_callback:
self._quit_callback()
logger.debug('Bye.')
diff --git a/src/leap/gui/ui/wizard.ui b/src/leap/gui/ui/wizard.ui
index 96cf4621..4b9cab1c 100644
--- a/src/leap/gui/ui/wizard.ui
+++ b/src/leap/gui/ui/wizard.ui
@@ -696,6 +696,9 @@
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
</widget>
</item>
</layout>
diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py
index 405752ef..d03427db 100644
--- a/src/leap/gui/wizard.py
+++ b/src/leap/gui/wizard.py
@@ -154,6 +154,9 @@ class Wizard(QtGui.QWizard):
self.page(self.FINISH_PAGE).setButtonText(
QtGui.QWizard.FinishButton, self.tr("Connect"))
+ def get_domain(self):
+ return self._domain
+
def get_username(self):
return self._username
@@ -275,6 +278,9 @@ class Wizard(QtGui.QWizard):
self.ui.lblPassword2.clearFocus()
self._set_registration_fields_visibility(False)
+
+ # Allow the user to remember his password
+ self.ui.chkRemember.setVisible(True)
self.ui.chkRemember.setEnabled(True)
self.page(self.REGISTER_USER_PAGE).set_completed()
@@ -389,10 +395,11 @@ class Wizard(QtGui.QWizard):
if not passed:
status = self.tr("<font color='red'><b>Non-existent "
"provider</b></font>")
+ else:
+ self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON)
self.ui.lblProviderSelectStatus.setText(status)
self.ui.btnCheck.setEnabled(not passed)
self.ui.lnProvider.setEnabled(not passed)
- self.ui.lblHTTPS.setPixmap(self.QUESTION_ICON)
def _https_connection(self, data):
"""
@@ -408,9 +415,10 @@ class Wizard(QtGui.QWizard):
status = self.tr("<font color='red'><b>%s</b></font>") \
% (data[self._provider_bootstrapper.ERROR_KEY])
self.ui.lblProviderSelectStatus.setText(status)
+ else:
+ self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON)
self.ui.btnCheck.setEnabled(not passed)
self.ui.lnProvider.setEnabled(not passed)
- self.ui.lblProviderInfo.setPixmap(self.QUESTION_ICON)
def _download_provider_info(self, data):
"""
@@ -451,7 +459,9 @@ class Wizard(QtGui.QWizard):
Sets the status for the download of the CA certificate check
"""
self._complete_task(data, self.ui.lblDownloadCaCert)
- self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON)
+ passed = data[self._provider_bootstrapper.PASSED_KEY]
+ if passed:
+ self.ui.lblCheckCaFpr.setPixmap(self.QUESTION_ICON)
def _check_ca_fingerprint(self, data):
"""
@@ -461,7 +471,9 @@ class Wizard(QtGui.QWizard):
Sets the status for the CA fingerprint check
"""
self._complete_task(data, self.ui.lblCheckCaFpr)
- self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON)
+ passed = data[self._provider_bootstrapper.PASSED_KEY]
+ if passed:
+ self.ui.lblCheckApiCert.setPixmap(self.QUESTION_ICON)
def _check_api_certificate(self, data):
"""
@@ -566,6 +578,7 @@ class Wizard(QtGui.QWizard):
"%s") %
(self._provider_config
.get_name(),))
+ self.ui.chkRemember.setVisible(False)
if pageId == self.SERVICES_PAGE:
self._populate_services()
diff --git a/src/leap/provider/__init__.py b/src/leap/provider/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/leap/provider/__init__.py
diff --git a/src/leap/provider/supportedapis.py b/src/leap/provider/supportedapis.py
new file mode 100644
index 00000000..3e650ba2
--- /dev/null
+++ b/src/leap/provider/supportedapis.py
@@ -0,0 +1,38 @@
+# -*- 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/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.