summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changes/feature-3505_preferences-select-gateway-manually1
-rw-r--r--src/leap/bitmask/config/leapsettings.py36
-rw-r--r--src/leap/bitmask/gui/preferenceswindow.py156
-rw-r--r--src/leap/bitmask/gui/ui/preferences.ui199
-rw-r--r--src/leap/bitmask/services/eip/eipconfig.py41
-rw-r--r--src/leap/bitmask/services/eip/vpnlaunchers.py12
6 files changed, 336 insertions, 109 deletions
diff --git a/changes/feature-3505_preferences-select-gateway-manually b/changes/feature-3505_preferences-select-gateway-manually
new file mode 100644
index 00000000..de414e6b
--- /dev/null
+++ b/changes/feature-3505_preferences-select-gateway-manually
@@ -0,0 +1 @@
+ o Add option to select gateway manually in the preferences panel. Closes #3505.
diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py
index ca94129e..ad67b29e 100644
--- a/src/leap/bitmask/config/leapsettings.py
+++ b/src/leap/bitmask/config/leapsettings.py
@@ -66,6 +66,10 @@ class LeapSettings(object):
REMEMBER_KEY = "RememberUserAndPass"
DEFAULTPROVIDER_KEY = "DefaultProvider"
ALERTMISSING_KEY = "AlertMissingScripts"
+ GATEWAY_KEY = "Gateway"
+
+ # values
+ GATEWAY_AUTOMATIC = "Automatic"
def __init__(self, standalone=False):
"""
@@ -137,6 +141,38 @@ class LeapSettings(object):
return providers
+ def get_selected_gateway(self, provider):
+ """
+ Returns the configured gateway for the given provider.
+
+ :param provider: provider domain
+ :type provider: str
+
+ :rtype: str
+ """
+ leap_assert(len(provider) > 0, "We need a nonempty provider")
+ gateway_key = "{0}/{1}".format(provider, self.GATEWAY_KEY)
+ gateway = self._settings.value(gateway_key, self.GATEWAY_AUTOMATIC)
+
+ return gateway
+
+ def set_selected_gateway(self, provider, gateway):
+ """
+ Saves the configured gateway for the given provider
+
+ :param provider: provider domain
+ :type provider: str
+
+ :param gateway: gateway to use as default
+ :type gateway: str
+ """
+
+ leap_assert(len(provider) > 0, "We need a nonempty provider")
+ leap_assert_type(gateway, (str, unicode))
+
+ gateway_key = "{0}/{1}".format(provider, self.GATEWAY_KEY)
+ self._settings.setValue(gateway_key, gateway)
+
def get_enabled_services(self, provider):
"""
Returns a list of enabled services for the given provider
diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py
index 17e12304..c3c3490b 100644
--- a/src/leap/bitmask/gui/preferenceswindow.py
+++ b/src/leap/bitmask/gui/preferenceswindow.py
@@ -30,6 +30,7 @@ from leap.bitmask.crypto.srpauth import SRPAuthBadPassword
from leap.bitmask.util.password import basic_password_checks
from leap.bitmask.services import get_supported
from leap.bitmask.config.providerconfig import ProviderConfig
+from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector
from leap.bitmask.services import get_service_display_name
logger = logging.getLogger(__name__)
@@ -54,6 +55,7 @@ class PreferencesWindow(QtGui.QDialog):
:type standalone: bool
"""
QtGui.QDialog.__init__(self, parent)
+ self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic")
self._srp_auth = srp_auth
self._settings = leap_settings
@@ -65,14 +67,19 @@ class PreferencesWindow(QtGui.QDialog):
self.ui.setupUi(self)
self.ui.lblPasswordChangeStatus.setVisible(False)
self.ui.lblProvidersServicesStatus.setVisible(False)
+ self.ui.lblProvidersGatewayStatus.setVisible(False)
self._selected_services = set()
- self._provider_config = ProviderConfig()
# Connections
self.ui.pbChangePassword.clicked.connect(self._change_password)
self.ui.cbProvidersServices.currentIndexChanged[unicode].connect(
self._populate_services)
+ self.ui.cbProvidersGateway.currentIndexChanged[unicode].connect(
+ self._populate_gateways)
+
+ self.ui.cbGateways.currentIndexChanged[unicode].connect(
+ lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False))
if not self._settings.get_configured_providers():
self.ui.gbEnabledServices.setEnabled(False)
@@ -207,6 +214,9 @@ class PreferencesWindow(QtGui.QDialog):
:param status: status message to display, can be HTML
:type status: str
+ :param success: is set to True if we should display the
+ message as green
+ :type success: bool
"""
if success:
status = "<font color='green'><b>%s</b></font>" % (status,)
@@ -214,6 +224,28 @@ class PreferencesWindow(QtGui.QDialog):
self.ui.lblProvidersServicesStatus.setVisible(True)
self.ui.lblProvidersServicesStatus.setText(status)
+ def _set_providers_gateway_status(self, status, success=False,
+ error=False):
+ """
+ Sets the status label for the gateway change.
+
+ :param status: status message to display, can be HTML
+ :type status: str
+ :param success: is set to True if we should display the
+ message as green
+ :type success: bool
+ :param error: is set to True if we should display the
+ message as red
+ :type error: bool
+ """
+ if success:
+ status = "<font color='green'><b>%s</b></font>" % (status,)
+ elif error:
+ status = "<font color='red'><b>%s</b></font>" % (status,)
+
+ self.ui.lblProvidersGatewayStatus.setVisible(True)
+ self.ui.lblProvidersGatewayStatus.setText(status)
+
def _add_configured_providers(self):
"""
Add the client's configured providers to the providers combo boxes.
@@ -250,7 +282,7 @@ class PreferencesWindow(QtGui.QDialog):
"""
SLOT
TRIGGERS:
- self.ui.cbProvidersServices.clicked
+ self.ui.cbProvidersServices.currentIndexChanged[unicode]
Loads the services that the provider provides into the UI for
the user to enable or disable.
@@ -261,10 +293,11 @@ class PreferencesWindow(QtGui.QDialog):
# We hide the maybe-visible status label after a change
self.ui.lblProvidersServicesStatus.setVisible(False)
- provider_config_path = os.path.join(
- "leap", "providers", domain, "provider.json")
+ if not domain:
+ return
- if not domain or not self._provider_config.load(provider_config_path):
+ provider_config = self._get_provider_config(domain)
+ if provider_config is None:
return
# set the proper connection for the 'save' button
@@ -276,7 +309,7 @@ class PreferencesWindow(QtGui.QDialog):
save_services = partial(self._save_enabled_services, domain)
self.ui.pbSaveServices.clicked.connect(save_services)
- services = get_supported(self._provider_config.get_services())
+ services = get_supported(provider_config.get_services())
services_conf = self._settings.get_enabled_services(domain)
# discard changes if other provider is selected
@@ -307,7 +340,11 @@ class PreferencesWindow(QtGui.QDialog):
def _save_enabled_services(self, provider):
"""
- Saves the new settings to the configuration file.
+ SLOT
+ TRIGGERS:
+ self.ui.pbSaveServices.clicked
+
+ Saves the new enabled services settings to the configuration file.
:param provider: the provider config that we need to save.
:type provider: str
@@ -319,3 +356,108 @@ class PreferencesWindow(QtGui.QDialog):
"Services settings for provider '{0}' saved.".format(provider))
logger.debug(msg)
self._set_providers_services_status(msg, success=True)
+
+ def _get_provider_config(self, domain):
+ """
+ Helper to return a valid Provider Config from the domain name.
+
+ :param domain: the domain name of the provider.
+ :type domain: str
+
+ :rtype: ProviderConfig or None if there is a problem loading the config
+ """
+ provider_config = ProviderConfig()
+ provider_config_path = os.path.join(
+ "leap", "providers", domain, "provider.json")
+
+ if not provider_config.load(provider_config_path):
+ provider_config = None
+
+ return provider_config
+
+ def _save_selected_gateway(self, provider):
+ """
+ SLOT
+ TRIGGERS:
+ self.ui.pbSaveGateway.clicked
+
+ Saves the new gateway setting to the configuration file.
+
+ :param provider: the provider config that we need to save.
+ :type provider: str
+ """
+ gateway = self.ui.cbGateways.currentText()
+
+ if gateway == self.AUTOMATIC_GATEWAY_LABEL:
+ gateway = self._settings.GATEWAY_AUTOMATIC
+ else:
+ idx = self.ui.cbGateways.currentIndex()
+ gateway = self.ui.cbGateways.itemData(idx)
+
+ self._settings.set_selected_gateway(provider, gateway)
+
+ msg = self.tr(
+ "Gateway settings for provider '{0}' saved.".format(provider))
+ logger.debug(msg)
+ self._set_providers_gateway_status(msg, success=True)
+
+ def _populate_gateways(self, domain):
+ """
+ SLOT
+ TRIGGERS:
+ self.ui.cbProvidersGateway.currentIndexChanged[unicode]
+
+ Loads the gateways that the provider provides into the UI for
+ the user to select.
+
+ :param domain: the domain of the provider to load gateways from.
+ :type domain: str
+ """
+ # We hide the maybe-visible status label after a change
+ self.ui.lblProvidersGatewayStatus.setVisible(False)
+
+ if not domain:
+ return
+
+ try:
+ # disconnect prevoiusly connected save method
+ self.ui.pbSaveGateway.clicked.disconnect()
+ except RuntimeError:
+ pass # Signal was not connected
+
+ # set the proper connection for the 'save' button
+ save_gateway = partial(self._save_selected_gateway, domain)
+ self.ui.pbSaveGateway.clicked.connect(save_gateway)
+
+ eip_config = EIPConfig()
+ provider_config = self._get_provider_config(domain)
+
+ eip_config_path = os.path.join("leap", "providers",
+ domain, "eip-service.json")
+ api_version = provider_config.get_api_version()
+ eip_config.set_api_version(api_version)
+ eip_loaded = eip_config.load(eip_config_path)
+
+ if not eip_loaded or provider_config is None:
+ self._set_providers_gateway_status(
+ self.tr("There was a problem with configuration files."),
+ error=True)
+ return
+
+ gateways = VPNGatewaySelector(eip_config).get_gateways_list()
+ logger.debug(gateways)
+
+ self.ui.cbGateways.clear()
+ self.ui.cbGateways.addItem(self.AUTOMATIC_GATEWAY_LABEL)
+
+ # Add the available gateways and
+ # select the one stored in configuration file.
+ selected_gateway = self._settings.get_selected_gateway(domain)
+ index = 0
+ for idx, (gw_name, gw_ip) in enumerate(gateways):
+ gateway = "{0} ({1})".format(gw_name, gw_ip)
+ self.ui.cbGateways.addItem(gateway, gw_ip)
+ if gw_ip == selected_gateway:
+ index = idx + 1
+
+ self.ui.cbGateways.setCurrentIndex(index)
diff --git a/src/leap/bitmask/gui/ui/preferences.ui b/src/leap/bitmask/gui/ui/preferences.ui
index b59990b1..e66a2d68 100644
--- a/src/leap/bitmask/gui/ui/preferences.ui
+++ b/src/leap/bitmask/gui/ui/preferences.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>453</width>
- <height>463</height>
+ <width>503</width>
+ <height>529</height>
</rect>
</property>
<property name="windowTitle">
@@ -18,80 +18,77 @@
<normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset>
</property>
<layout class="QGridLayout" name="gridLayout_3">
- <item row="0" column="0">
- <widget class="QGroupBox" name="gbPasswordChange">
+ <item row="5" column="0">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="0">
+ <widget class="QGroupBox" name="gbGatewaySelector">
<property name="enabled">
- <bool>false</bool>
+ <bool>true</bool>
</property>
<property name="title">
- <string>Password Change</string>
+ <string>Select gateway for provider</string>
</property>
- <layout class="QFormLayout" name="formLayout">
- <property name="fieldGrowthPolicy">
- <enum>QFormLayout::ExpandingFieldsGrow</enum>
- </property>
- <item row="0" column="0">
- <widget class="QLabel" name="lblCurrentPassword">
- <property name="text">
- <string>&amp;Current password:</string>
- </property>
- <property name="buddy">
- <cstring>leCurrentPassword</cstring>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QLineEdit" name="leCurrentPassword">
- <property name="echoMode">
- <enum>QLineEdit::Password</enum>
- </property>
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1" colspan="2">
+ <widget class="QComboBox" name="cbProvidersGateway">
+ <item>
+ <property name="text">
+ <string>&lt;Select provider&gt;</string>
+ </property>
+ </item>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QLabel" name="lblNewPassword">
+ <item row="0" column="0">
+ <widget class="QLabel" name="lblSelectProvider">
<property name="text">
- <string>&amp;New password:</string>
+ <string>&amp;Select provider:</string>
</property>
<property name="buddy">
- <cstring>leNewPassword</cstring>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLineEdit" name="leNewPassword">
- <property name="echoMode">
- <enum>QLineEdit::Password</enum>
+ <cstring>cbProvidersGateway</cstring>
</property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QLabel" name="lblNewPassword2">
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
<property name="text">
- <string>&amp;Re-enter new password:</string>
- </property>
- <property name="buddy">
- <cstring>leNewPassword2</cstring>
+ <string>Select gateway:</string>
</property>
</widget>
</item>
- <item row="2" column="1">
- <widget class="QLineEdit" name="leNewPassword2">
- <property name="echoMode">
- <enum>QLineEdit::Password</enum>
- </property>
+ <item row="1" column="1" colspan="2">
+ <widget class="QComboBox" name="cbGateways">
+ <item>
+ <property name="text">
+ <string>Automatic</string>
+ </property>
+ </item>
</widget>
</item>
- <item row="4" column="1">
- <widget class="QPushButton" name="pbChangePassword">
+ <item row="5" column="2">
+ <widget class="QPushButton" name="pbSaveGateway">
<property name="text">
- <string>Change</string>
+ <string>Save this provider settings</string>
</property>
</widget>
</item>
- <item row="3" column="0" colspan="2">
- <widget class="QLabel" name="lblPasswordChangeStatus">
+ <item row="2" column="0" colspan="3">
+ <widget class="QLabel" name="lblProvidersGatewayStatus">
<property name="text">
- <string>&lt;Password change status&gt;</string>
+ <string>&lt; Providers Gateway Status &gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -101,7 +98,7 @@
</layout>
</widget>
</item>
- <item row="2" column="0">
+ <item row="4" column="0">
<widget class="QGroupBox" name="gbEnabledServices">
<property name="title">
<string>Enabled services</string>
@@ -158,69 +155,89 @@
</layout>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QGroupBox" name="gbGatewaySelector">
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="gbPasswordChange">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
- <string>Select gateway for provider</string>
- </property>
- <property name="checkable">
- <bool>false</bool>
+ <string>Password Change</string>
</property>
- <layout class="QGridLayout" name="gridLayout">
+ <layout class="QFormLayout" name="formLayout">
+ <property name="fieldGrowthPolicy">
+ <enum>QFormLayout::ExpandingFieldsGrow</enum>
+ </property>
<item row="0" column="0">
- <widget class="QLabel" name="lblSelectProvider">
+ <widget class="QLabel" name="lblCurrentPassword">
<property name="text">
- <string>&amp;Select provider:</string>
+ <string>&amp;Current password:</string>
</property>
<property name="buddy">
- <cstring>cbProvidersGateway</cstring>
+ <cstring>leCurrentPassword</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
- <widget class="QComboBox" name="cbProvidersGateway">
- <item>
- <property name="text">
- <string>&lt;Select provider&gt;</string>
- </property>
- </item>
+ <widget class="QLineEdit" name="leCurrentPassword">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
</widget>
</item>
<item row="1" column="0">
- <widget class="QLabel" name="label">
+ <widget class="QLabel" name="lblNewPassword">
<property name="text">
- <string>Select gateway:</string>
+ <string>&amp;New password:</string>
+ </property>
+ <property name="buddy">
+ <cstring>leNewPassword</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
- <widget class="QComboBox" name="cbGateways">
- <item>
- <property name="text">
- <string>Automatic</string>
- </property>
- </item>
+ <widget class="QLineEdit" name="leNewPassword">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="lblNewPassword2">
+ <property name="text">
+ <string>&amp;Re-enter new password:</string>
+ </property>
+ <property name="buddy">
+ <cstring>leNewPassword2</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="leNewPassword2">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QPushButton" name="pbChangePassword">
+ <property name="text">
+ <string>Change</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QLabel" name="lblPasswordChangeStatus">
+ <property name="text">
+ <string>&lt;Password change status&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
</widget>
</item>
</layout>
</widget>
</item>
- <item row="3" column="0">
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
</layout>
</widget>
<resources>
diff --git a/src/leap/bitmask/services/eip/eipconfig.py b/src/leap/bitmask/services/eip/eipconfig.py
index 843e7397..1f49f9cd 100644
--- a/src/leap/bitmask/services/eip/eipconfig.py
+++ b/src/leap/bitmask/services/eip/eipconfig.py
@@ -62,11 +62,12 @@ class VPNGatewaySelector(object):
self._eipconfig = eipconfig
- def get_gateways(self):
+ def get_gateways_list(self):
"""
- Returns the 4 best gateways, sorted by timezone proximity.
+ Returns the existing gateways, sorted by timezone proximity.
- :rtype: list of IPv4Address or IPv6Address object.
+ :rtype: list of tuples (location, ip)
+ (str, IPv4Address or IPv6Address object)
"""
gateways_timezones = []
locations = self._eipconfig.get_locations()
@@ -77,19 +78,35 @@ class VPNGatewaySelector(object):
gateway_distance = 99 # if hasn't location -> should go last
if gateway_location is not None:
- gw_offset = int(locations[gateway['location']]['timezone'])
+ timezone = locations[gateway['location']]['timezone']
+ gateway_name = locations[gateway['location']].get('name', None)
+ if gateway_name is not None:
+ gateway_location = gateway_name
+
+ gw_offset = int(timezone)
if gw_offset in self.equivalent_timezones:
gw_offset = self.equivalent_timezones[gw_offset]
gateway_distance = self._get_timezone_distance(gw_offset)
ip = self._eipconfig.get_gateway_ip(idx)
- gateways_timezones.append((ip, gateway_distance))
+ gateways_timezones.append((ip, gateway_distance, gateway_location))
- gateways_timezones = sorted(gateways_timezones,
- key=lambda gw: gw[1])[:4]
+ gateways_timezones = sorted(gateways_timezones, key=lambda gw: gw[1])
+
+ gateways = []
+ for ip, distance, location in gateways_timezones:
+ gateways.append((location, ip))
+
+ return gateways
- gateways = [ip for ip, dist in gateways_timezones]
+ def get_gateways(self):
+ """
+ Returns the 4 best gateways, sorted by timezone proximity.
+
+ :rtype: list of IPv4Address or IPv6Address object.
+ """
+ gateways = [ip for location, ip in self.get_gateways_list()][:4]
return gateways
def _get_timezone_distance(self, offset):
@@ -246,7 +263,8 @@ if __name__ == "__main__":
console.setFormatter(formatter)
logger.addHandler(console)
- eipconfig = EIPConfig('1')
+ eipconfig = EIPConfig()
+ eipconfig.set_api_version('1')
try:
eipconfig.get_clusters()
@@ -255,9 +273,14 @@ if __name__ == "__main__":
print "Safe value getting is working"
if eipconfig.load("leap/providers/bitmask.net/eip-service.json"):
+ print "EIPConfig methods"
print eipconfig.get_clusters()
print eipconfig.get_gateways()
print eipconfig.get_locations()
print eipconfig.get_openvpn_configuration()
print eipconfig.get_serial()
print eipconfig.get_version()
+ print "VPNGatewaySelector methods"
+ gws = VPNGatewaySelector(eipconfig)
+ print gws.get_gateways()
+ print gws.get_gateways_list()
diff --git a/src/leap/bitmask/services/eip/vpnlaunchers.py b/src/leap/bitmask/services/eip/vpnlaunchers.py
index f8c51ad8..15221c48 100644
--- a/src/leap/bitmask/services/eip/vpnlaunchers.py
+++ b/src/leap/bitmask/services/eip/vpnlaunchers.py
@@ -33,6 +33,7 @@ except ImportError:
from abc import ABCMeta, abstractmethod
from functools import partial
+from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector
from leap.bitmask.util import first
@@ -414,8 +415,15 @@ class LinuxVPNLauncher(VPNLauncher):
if openvpn_verb is not None:
args += ['--verb', '%d' % (openvpn_verb,)]
- gateway_selector = VPNGatewaySelector(eipconfig)
- gateways = gateway_selector.get_gateways()
+ gateways = []
+ leap_settings = LeapSettings(ProviderConfig.standalone)
+ gateway_conf = leap_settings.get_selected_gateway()
+
+ if gateway_conf == leap_settings.GATEWAY_AUTOMATIC:
+ gateway_selector = VPNGatewaySelector(eipconfig)
+ gateways = gateway_selector.get_gateways()
+ else:
+ gateways = [gateway_conf]
if not gateways:
logger.error('No gateway was found!')