diff options
-rw-r--r-- | changes/feature-3505_preferences-select-gateway-manually | 1 | ||||
-rw-r--r-- | src/leap/bitmask/config/leapsettings.py | 36 | ||||
-rw-r--r-- | src/leap/bitmask/gui/preferenceswindow.py | 156 | ||||
-rw-r--r-- | src/leap/bitmask/gui/ui/preferences.ui | 199 | ||||
-rw-r--r-- | src/leap/bitmask/services/eip/eipconfig.py | 41 | ||||
-rw-r--r-- | src/leap/bitmask/services/eip/vpnlaunchers.py | 12 |
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>&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><Select provider></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>&New password:</string> + <string>&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>&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><Password change status></string> + <string>< Providers Gateway Status ></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>&Select provider:</string> + <string>&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><Select provider></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>&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>&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><Password change status></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!') |