From 637a527e24c8a755cdabb9901b9af417160aeeb2 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 20 Sep 2013 17:44:15 -0300 Subject: Add missing changelog and remove ramaining files. --- CHANGELOG | 1 + changes/bug-3778_fix-path-prefix-helper | 1 - changes/bug-3825-include-resources | 1 - changes/bug_3803-do-not-install-resolv-update-globally | 1 - changes/bug_fix-first | 1 - 5 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 changes/bug-3778_fix-path-prefix-helper delete mode 100644 changes/bug-3825-include-resources delete mode 100644 changes/bug_3803-do-not-install-resolv-update-globally delete mode 100644 changes/bug_fix-first diff --git a/CHANGELOG b/CHANGELOG index 15db6cd2..ac1aceeb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,6 +27,7 @@ - Move the EIP action menu items under the EIP status submenu tree. o Adds --version flag. Closes: #3816 o Refactors EIPConnection to use LEAPConnection state machine. Closes: #3900 + o Include resource files and ui in the distrubution tarball. Closes: #3825 0.3.2 Sep 6 -- the "no crashes or anything" release: o Fix up script in non-bundle linuces. Closes: #3450 diff --git a/changes/bug-3778_fix-path-prefix-helper b/changes/bug-3778_fix-path-prefix-helper deleted file mode 100644 index e7cec539..00000000 --- a/changes/bug-3778_fix-path-prefix-helper +++ /dev/null @@ -1 +0,0 @@ - o Fix path prefix helper for the bundle and add regresion tests. Closes #3778. diff --git a/changes/bug-3825-include-resources b/changes/bug-3825-include-resources deleted file mode 100644 index 083fd05d..00000000 --- a/changes/bug-3825-include-resources +++ /dev/null @@ -1 +0,0 @@ - o Include resource files and ui in the distrubution tarball. Closes: #3825 diff --git a/changes/bug_3803-do-not-install-resolv-update-globally b/changes/bug_3803-do-not-install-resolv-update-globally deleted file mode 100644 index f6e06d5f..00000000 --- a/changes/bug_3803-do-not-install-resolv-update-globally +++ /dev/null @@ -1 +0,0 @@ - o Do not try to install resolv-update globally. Closes: #3803 diff --git a/changes/bug_fix-first b/changes/bug_fix-first deleted file mode 100644 index 0ef5188c..00000000 --- a/changes/bug_fix-first +++ /dev/null @@ -1 +0,0 @@ - o Catch IndexError on `first` utility. -- cgit v1.2.3 From d5eb66a29c75e506e35437c6957b8775c78a9909 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 20 Sep 2013 17:08:10 -0400 Subject: reflowed and aligned. Don't worry, I have put together a script to make this. --- relnotes.txt | 105 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 51 insertions(+), 54 deletions(-) diff --git a/relnotes.txt b/relnotes.txt index 19cfb879..30f26f9c 100644 --- a/relnotes.txt +++ b/relnotes.txt @@ -1,104 +1,101 @@ ANNOUNCING Bitmask, the internet encryption toolkit, release 0.3.3 -The LEAP team is pleased to announce the immediate availability of -version 0.3.3 of Bitmask, the Internet Encryption Toolkit, codename +The LEAP team is pleased to announce the immediate availability of +version 0.3.3 of Bitmask, the Internet Encryption Toolkit, codename "the calm after the tempest". https://downloads.leap.se/client/ -LEAP (LEAP Encryption Access Project) develops a plan to secure everyday -communication, breaking down into discrete services. +LEAP (LEAP Encryption Access Project) develops a plan to secure +everyday communication, breaking down into discrete services. -Bitmask is the desktop client to connect to the services offered by the -LEAP Platform. In the current phase the supported services are Encrypted -Internet Proxy and Encrypted Mail. +Bitmask is the desktop client to connect to the services offered by the +LEAP Platform. In the current phase the supported services are +Encrypted Internet Proxy and Encrypted Mail. -The Encrypted Internet Proxy provides circumvention, location -anonymization, and traffic encryption in a hassle-free, automatically +The Encrypted Internet Proxy provides circumvention, location +anonymization, and traffic encryption in a hassle-free, automatically self-configuring fashion. -Encrypted Mail offers automatic encryption and decryption for both -outgoing and incoming email, adding public key cryptography to your mail -without you ever having to worry about key distribution or signature -verification. +Encrypted Mail offers automatic encryption and decryption for both +outgoing and incoming email, adding public key cryptography to your +mail without you ever having to worry about key distribution or +signature verification. -You can read about this and many other cool things in the user manual +You can read about this and many other cool things in the user manual and the developer notes, which can be found online at: http://bitmask.rtfd.org/ -WARNING: This is still part of a beta release of our software, a lot of -testing and auditing is still needed, so indeed use it, and feed us back, -fork it and contribute to its development, but by any means DO NOT trust -your life to it (yet!). - +WARNING: This is still part of a beta release of our software, a lot of +testing and auditing is still needed, so indeed use it, and feed us +back, fork it and contribute to its development, but by any means DO +NOT trust your life to it (yet!). WHAT CAN THIS VERSION OF BITMASK DO FOR ME? -Bitmask 0.3.3 is mostly a bugfix release, with some minor improvements. +Bitmask 0.3.3 is mostly a bugfix release, with some minor improvements. On this release, we have fixed many UI bugs, and have undergone internal -reorganizations in the code. This release also bumps the requirement -for Soledad, the encrypted data syncronization engine behind Bitmask, -which has experienced a backward-incompatible change. You can refer to -the CHANGELOG for the meat. +reorganizations in the code. This release also bumps the requirement for +Soledad, the encrypted data syncronization engine behind Bitmask, which +has experienced a backward-incompatible change. You can refer to the +CHANGELOG for the meat. -As always, you can connect to the Encrypted Internet Proxy service offered -by a provider of your choice, and enjoy a encrypted internet connection -that the spying eyes can only track back to your provider. +As always, you can connect to the Encrypted Internet Proxy service +offered by a provider of your choice, and enjoy a encrypted internet +connection that the spying eyes can only track back to your provider. -The Encrypted Mail services will run local SMTP and IMAP proxies that, -once you configure the mail client of your choice, will automatically +The Encrypted Mail services will run local SMTP and IMAP proxies that, +once you configure the mail client of your choice, will automatically encrypt and decrypt your email using GPG encryption under the hood. -If it is the first time you run Bitmask, the first run wizard will help -you registering an user with your selected provider, downloading all +If it is the first time you run Bitmask, the first run wizard will help +you registering an user with your selected provider, downloading all the config files needed to connect to the various LEAP services. - LICENSE -You may use Bitmask under the GNU General Public License, version 3 or, -at your option, any later version. See the file "LICENSE" for the terms +You may use Bitmask under the GNU General Public License, version 3 or, +at your option, any later version. See the file "LICENSE" for the terms of the GNU General Public License, version 3. -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library -under certain conditions as described in each individual source file, -and distribute linked combinations including the two. - +In addition, as a special exception, the copyright holders give +permission to link the code of portions of this program with the +OpenSSL library under certain conditions as described in each +individual source file, and distribute linked combinations including +the two. INSTALLATION -We distribute the current version of Bitmask as standalone bundles for -GNU/Linux and OSX, but it is likely that you are able to run it under -other systems, specially if you are skillful and patience is one of -your virtues. +We distribute the current version of Bitmask as standalone bundles for +GNU/Linux and OSX, but it is likely that you are able to run it under +other systems, specially if you are skillful and patience is one of your +virtues. Have a look at "docs/user/install.rst". -Packages will be soon provided for debian and ubuntu, and the release -of windows bundles will be resumed shortly. +Packages will be soon provided for debian and ubuntu, and the release of +windows bundles will be resumed shortly. -We will love to hear if you are interested in help making packages +We will love to hear if you are interested in help making packages available for any other system. - BUGS -You can send the bugs our way by pointing your telnet session to port -443 on https://leap.se/code. We will do our best to make them follow +You can send the bugs our way by pointing your telnet session to port +443 on https://leap.se/code. We will do our best to make them follow our intensive bug-reeducation program. - HACKING You can find us in the #leap-dev channel on the freenode network. -If you are lucky enough, you can also spot us drinking mate, sleepless -in night trains, rooftops, rainforests, lonely islands and, always, +If you are lucky enough, you can also spot us drinking mate, sleepless +in night trains, rooftops, rainforests, lonely islands and, always, beyond any border. - The LEAP team, -Sep 20, 2013 Somewhere in the middle of the intertubes. +Sep 20, 2013 +Somewhere in the middle of the intertubes. +EOF -- cgit v1.2.3 From 45c79b80eee3b116596054e80b7304f46a52ffea Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 20 Sep 2013 18:18:11 -0400 Subject: fix paths --- docs/release_checklist.wiki | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release_checklist.wiki b/docs/release_checklist.wiki index 19a19289..b46eb1f6 100644 --- a/docs/release_checklist.wiki +++ b/docs/release_checklist.wiki @@ -28,7 +28,7 @@ * [ ] Build and upload bundles * [ ] Use the scripts under pkg// to build the the bundles. * [ ] Sign them with gpg -a --sign --detach-sign - * [ ] Upload bundle and signature to web-uploads@salmon.leap.se:~/public/client//Bitmask--.(tar.bz2,dmg,zip) + * [ ] Upload bundle and signature to downloads.leap.se/client//Bitmask--.(tar.bz2,dmg,zip) * [ ] Update symbolic link for latest upload and signature: * [ ] ~/public/client/Bitmask--latest * [ ] ~/public/client/Bitmask--latest.asc -- cgit v1.2.3 From 267ddfb3190a11b3034a42da4f75e0b380a413c6 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 23 Sep 2013 10:49:22 -0300 Subject: Improve release checklist and build_bundle script. --- docs/release_checklist.wiki | 24 ++++++++++++++---------- pkg/linux/build_bundle.sh | 12 +++++++----- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/docs/release_checklist.wiki b/docs/release_checklist.wiki index b46eb1f6..fc99fdf0 100644 --- a/docs/release_checklist.wiki +++ b/docs/release_checklist.wiki @@ -12,18 +12,22 @@ * [ ] git fetch origin * [ ] git tag -l, and see the latest tagged version (unless it's not a minor version bump, in which case, just bump to it) * [ ] Checkout release-X.Y.Z (locally, never pushed) - * [ ] Fold in changes files into the CHANGELOG - - NOTE: For leap.soledad, the CHANGELOG entries should be divided per package (common, client, server). See older releases for reference. - - Helper bash line: for i in $(ls changes); do cat changes/$i; echo; done - * [ ] Update relnotes.txt if needed. - * [ ] git rm changes/* + * [ ] Update relnotes.txt in leap.bitmask if needed. + * [ ] Review pkg/requirements.pip for everything and update if needed (that's why the order). - - See whatever has been introduced in changes/VERSION_COMPAT - - Reset changes/VERSION_COMPAT + - See whatever has been introduced in changes/VERSION_COMPAT + - Reset changes/VERSION_COMPAT * [ ] git commit -av # we should add a commit message here... - * [ ] git checkout master && git pull origin master && git merge --no-ff release-X.Y.Z && git push origin master - * [ ] git tag -s X.Y.Z -m "Tag version X.Y.Z" # (note the -s so that it's a signed tag and -m to specify the message for the tag) - * [ ] git push origin X.Y.Z + + * [ ] Fold in changes files into the CHANGELOG + - NOTE: For leap.soledad, the CHANGELOG entries should be divided per package (common, client, server). See older releases for reference. + - Helper bash line: for i in $(ls changes); do cat changes/$i; echo; done + * [ ] git rm changes/feature*; git rm changes/bug* + * [ ] git commit -m "Fold in changes." + + * [ ] git checkout master && git pull origin master && git merge --no-ff release-X.Y.Z --no-edit + * [ ] git tag -s X.Y.Z -m "Tag version X.Y.Z" # (note the -s so that it's a signed tag and -m to specify the message for the tag) + * [ ] git push origin master; git push origin X.Y.Z * [ ] git checkout develop && git merge master && git push origin develop * [ ] Build and upload bundles * [ ] Use the scripts under pkg// to build the the bundles. diff --git a/pkg/linux/build_bundle.sh b/pkg/linux/build_bundle.sh index 520ff256..60151a80 100755 --- a/pkg/linux/build_bundle.sh +++ b/pkg/linux/build_bundle.sh @@ -34,16 +34,18 @@ BITMASK_BIN=$TEMPLATE_BUNDLE/bitmask BUNDLE_NAME=Bitmask-linux$ARCH-$VERSION # clean template -rm $TEMPLATE_BUNDLE/CHANGELOG -rm $TEMPLATE_BUNDLE/relnotes.txt +rm -f $TEMPLATE_BUNDLE/CHANGELOG +rm -f $TEMPLATE_BUNDLE/relnotes.txt rm -rf $TEMPLATE_BUNDLE/apps/leap rm -rf $TEMPLATE_BUNDLE/lib/leap/{common,keymanager,soledad,mail} # checkout the latest tag in all repos for repo in $REPOSITORIES; do cd $REPOS_ROOT/$repo - git fetch - # checkout to the latest annotated tag, supress 'detached head' warning + git checkout master + git pull --ff-only origin master && git fetch + git reset --hard origin/master # this avoids problems if you are in a commit far in the past + # checkout to the closest annotated tag, supress 'detached head' warning git checkout --quiet `git describe --abbrev=0` done @@ -82,7 +84,7 @@ cp src/launcher $BITMASK_BIN # copy launcher.py to template bundle # e.g. TEMPLATE_BUNDLE/Bitmask.app/Contents/MacOS/apps/ -cd $REPOS_ROOT/bitmask_client_launcher/src/ +cd $REPOS_ROOT/bitmask_launcher/src/ cp launcher.py $TEMPLATE_BUNDLE/apps/ # copy relnotes, joint changelog and LICENSE to TEMPLATE_BUNDLE -- cgit v1.2.3 From 394408c3d5d38d04e2135385afcbaa74c0d91450 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 24 Sep 2013 11:50:32 -0300 Subject: Move logger up to be available sooner. --- src/leap/bitmask/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 02b1693d..37e67e61 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -179,6 +179,9 @@ def main(): flags.STANDALONE = standalone BaseConfig.standalone = standalone + logger = add_logger_handlers(debug, logfile) + replace_stdout_stderr_with_logging(logger) + # And then we import all the other stuff from leap.bitmask.gui import locale_rc from leap.bitmask.gui import twisted_main @@ -190,9 +193,6 @@ def main(): # pylint: avoid unused import assert(locale_rc) - logger = add_logger_handlers(debug, logfile) - replace_stdout_stderr_with_logging(logger) - if not we_are_the_one_and_only(): # Bitmask is already running logger.warning("Tried to launch more than one instance " -- cgit v1.2.3 From 2fc512023f28210dc7957105b49cfdb8878965f9 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 24 Sep 2013 11:48:33 -0300 Subject: Only ensure server if we are running the app. This code reorder avoids to get an error message if we run some code when the only thing we want is to get the version. Closes #3914. --- changes/bug-3914_unhandled-error-on-version-flag | 1 + src/leap/bitmask/app.py | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 changes/bug-3914_unhandled-error-on-version-flag diff --git a/changes/bug-3914_unhandled-error-on-version-flag b/changes/bug-3914_unhandled-error-on-version-flag new file mode 100644 index 00000000..41e023a0 --- /dev/null +++ b/changes/bug-3914_unhandled-error-on-version-flag @@ -0,0 +1 @@ + o Avoid error message if --version flag is used. Closes #3914. diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 37e67e61..d5132731 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -152,12 +152,6 @@ def main(): """ Starts the main event loop and launches the main window. """ - try: - event_server.ensure_server(event_server.SERVER_PORT) - except Exception as e: - # We don't even have logger configured in here - print "Could not ensure server: %r" % (e,) - _, opts = leap_argparse.init_leapc_args() if opts.version: @@ -170,6 +164,12 @@ def main(): logfile = opts.log_file openvpn_verb = opts.openvpn_verb + try: + event_server.ensure_server(event_server.SERVER_PORT) + except Exception as e: + # We don't even have logger configured in here + print "Could not ensure server: %r" % (e,) + ############################################################# # Given how paths and bundling works, we need to delay the imports # of certain parts that depend on this path settings. -- cgit v1.2.3 From a073ad8e329a3d90427737ca698e7acaa9d11b8d Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 24 Sep 2013 13:00:47 -0300 Subject: Update standalone dependency usage. --- src/leap/bitmask/config/tests/test_leapsettings.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/leap/bitmask/config/tests/test_leapsettings.py b/src/leap/bitmask/config/tests/test_leapsettings.py index 18166923..b45abfdd 100644 --- a/src/leap/bitmask/config/tests/test_leapsettings.py +++ b/src/leap/bitmask/config/tests/test_leapsettings.py @@ -29,6 +29,7 @@ import mock from leap.common.testing.basetest import BaseLeapTest from leap.bitmask.config.leapsettings import LeapSettings +from leap.bitmask.config import flags class LeapSettingsTest(BaseLeapTest): @@ -44,6 +45,7 @@ class LeapSettingsTest(BaseLeapTest): """ Test that the config file IS NOT stored under the CWD. """ + flags.STANDALONE = False self._leapsettings = LeapSettings() with mock.patch('os.listdir') as os_listdir: # use this method only to spy where LeapSettings is looking for @@ -57,7 +59,8 @@ class LeapSettingsTest(BaseLeapTest): """ Test that the config file IS stored under the CWD. """ - self._leapsettings = LeapSettings(standalone=True) + flags.STANDALONE = True + self._leapsettings = LeapSettings() with mock.patch('os.listdir') as os_listdir: # use this method only to spy where LeapSettings is looking for self._leapsettings.get_configured_providers() -- cgit v1.2.3 From 924264776e8dab3c17c09a3188ae701edbaf797b Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 24 Sep 2013 13:01:34 -0300 Subject: Update path comparison for certs. We need to check if the path *after* the prefix is correct, assuming that the prefix methods works fine. --- src/leap/bitmask/config/tests/test_providerconfig.py | 12 ++++-------- src/leap/bitmask/services/eip/tests/test_eipconfig.py | 12 ++++-------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/leap/bitmask/config/tests/test_providerconfig.py b/src/leap/bitmask/config/tests/test_providerconfig.py index 7661a1ce..fe27e683 100644 --- a/src/leap/bitmask/config/tests/test_providerconfig.py +++ b/src/leap/bitmask/config/tests/test_providerconfig.py @@ -175,10 +175,9 @@ class ProviderConfigTest(BaseLeapTest): def test_get_ca_cert_path_as_expected(self): pc = self._provider_config - pc.get_path_prefix = Mock(return_value='test') provider_domain = sample_config['domain'] - expected_path = os.path.join('test', 'leap', 'providers', + expected_path = os.path.join('leap', 'providers', provider_domain, 'keys', 'ca', 'cacert.pem') @@ -186,24 +185,21 @@ class ProviderConfigTest(BaseLeapTest): os.path.exists = Mock(return_value=True) cert_path = pc.get_ca_cert_path() - self.assertEqual(cert_path, expected_path) + self.assertTrue(cert_path.endswith(expected_path)) def test_get_ca_cert_path_about_to_download(self): pc = self._provider_config - pc.get_path_prefix = Mock(return_value='test') provider_domain = sample_config['domain'] - expected_path = os.path.join('test', 'leap', 'providers', + expected_path = os.path.join('leap', 'providers', provider_domain, 'keys', 'ca', 'cacert.pem') cert_path = pc.get_ca_cert_path(about_to_download=True) - - self.assertEqual(cert_path, expected_path) + self.assertTrue(cert_path.endswith(expected_path)) def test_get_ca_cert_path_fails(self): pc = self._provider_config - pc.get_path_prefix = Mock(return_value='test') # mock 'get_domain' so we don't need to load a config provider_domain = 'test.provider.com' diff --git a/src/leap/bitmask/services/eip/tests/test_eipconfig.py b/src/leap/bitmask/services/eip/tests/test_eipconfig.py index 76305e83..4e340f4c 100644 --- a/src/leap/bitmask/services/eip/tests/test_eipconfig.py +++ b/src/leap/bitmask/services/eip/tests/test_eipconfig.py @@ -262,15 +262,13 @@ class EIPConfigTest(BaseLeapTest): def test_get_client_cert_path_as_expected(self): config = self._get_eipconfig() - config.get_path_prefix = Mock(return_value='test') - provider_config = ProviderConfig() # mock 'get_domain' so we don't need to load a config provider_domain = 'test.provider.com' provider_config.get_domain = Mock(return_value=provider_domain) - expected_path = os.path.join('test', 'leap', 'providers', + expected_path = os.path.join('leap', 'providers', provider_domain, 'keys', 'client', 'openvpn.pem') @@ -278,26 +276,24 @@ class EIPConfigTest(BaseLeapTest): os.path.exists = Mock(return_value=True) cert_path = config.get_client_cert_path(provider_config) - self.assertEqual(cert_path, expected_path) + self.assertTrue(cert_path.endswith(expected_path)) def test_get_client_cert_path_about_to_download(self): config = self._get_eipconfig() - config.get_path_prefix = Mock(return_value='test') - provider_config = ProviderConfig() # mock 'get_domain' so we don't need to load a config provider_domain = 'test.provider.com' provider_config.get_domain = Mock(return_value=provider_domain) - expected_path = os.path.join('test', 'leap', 'providers', + expected_path = os.path.join('leap', 'providers', provider_domain, 'keys', 'client', 'openvpn.pem') cert_path = config.get_client_cert_path( provider_config, about_to_download=True) - self.assertEqual(cert_path, expected_path) + self.assertTrue(cert_path.endswith(expected_path)) def test_get_client_cert_path_fails(self): config = self._get_eipconfig() -- cgit v1.2.3 From 5ba674d4397155092b8ebec217016d94e95e05e0 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 24 Sep 2013 13:38:21 -0400 Subject: improve bug reporting section --- docs/testers/howto.rst | 132 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 110 insertions(+), 22 deletions(-) diff --git a/docs/testers/howto.rst b/docs/testers/howto.rst index 61d38787..1e276f7d 100644 --- a/docs/testers/howto.rst +++ b/docs/testers/howto.rst @@ -5,34 +5,125 @@ Howto for Testers This document covers a how-to guide to: -#. Quickly fetching latest development code, and -#. Reporting bugs. +#. :ref:`Where and how report bugs for Bitmask `, and +#. :ref:`Quickly fetching latest development code `. Let's go! +.. _reporting_bugs: + +Reporting bugs +-------------- + +Report all the bugs you can find to us! If something is not quite working yet, +we really want to know. Reporting a bug to us is the best way to get it fixed +quickly, and get our unconditional gratitude. + +It is quick, easy, and probably the best way to contribute to Bitmask +development, other than submitting patches. + +.. admonition:: Reporting better bugs + + New to bug reporting? Here you have a `great document about this noble art + `_. + +Where to report bugs +^^^^^^^^^^^^^^^^^^^^ + +We use the `Bitmask Bug Tracker `_, +although you can also use `Github issues +`_. But we reaaaally prefer if you +sign up in the former to send your bugs our way. + +What to include in your bug report +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* The symptoms of the bug itself: what went wrong? What items appear broken, or + do not work as expected? Maybe an UI element that appears to freeze? +* The Bitmask version you are running. You can get it by doing `bitmask + --version`, or you can go to `Help -> About Bitmask` menu. +* The installation method you used: bundle? from source code? debian package? +* Your platform version and other details: Ubuntu 12.04? Debian unstable? + Windows 8? OSX 10.8.4? If relevant, your desktop system also (gnome, kde...) +* When does the bug appear? What actions trigger it? Does it always + happen, or is it sporadic? +* The exact error message, if any. +* Attachments of the log files, if possible (see section below). + +Also, try not to mix several issues in your bug report. If you are finding +several problems, it's better to issue a separate bug report for each one of +them. + +Attaching log files +^^^^^^^^^^^^^^^^^^^ + +If you can spend a little time getting them, please add some logs to the bug +report. They are **really** useful when it comes to debug a problem. To do it: + +Launch Bitmask in debug mode. Logs are way more verbose that way:: + + bitmask --debug + +Get your hand on the logs. You can achieve that either by clicking on the "Show +log" button, and saving to file, or directly by specifying the path to the +logfile in the command line invocation:: + + bitmask --debug --logfile /tmp/bitmask.log + +Attach the logfile to your bug report. + +Need human interaction? +^^^^^^^^^^^^^^^^^^^^^^^ + +You can also find us in the ``#leap-dev`` channel on the `freenode network +`_. If you do not have a IRC client at hand, you can +`enter the channel via web +`_. + + .. _fetchinglatest: Fetching latest development code --------------------------------- -To allow rapid testing in different platforms, we have put together a quick script that is able to fetch latest development code. It more or less does all the steps covered in the :ref:`Setting up a Work Enviroment ` section, only that in a more compact way suitable (ahem) also for non developers. +Normally, testing the latest :ref:`client bundles ` should be +enough. We are engaged in a two-week release cycle with minor releases that are +as stable as possible. + +However, if you want to test that some issue has *really* been fixed before the +next release is out (if you are testing a new provider, for instance), you are +encouraged to try out the latest in the development branch. If you do not know +how to do that, or you prefer an automated script, keep reading for a way to +painlessly fetch the latest development code. + +We have put together a script to allow rapid testing in different platforms for +the brave souls like you. It more or less does all the steps covered in the +:ref:`Setting up a Work Enviroment ` section, only that in a more +compact way suitable (ahem) also for non developers. .. note:: - In the near future, we will be using :ref:`standalone bundles ` with the ability to self-update. + At some point in the near future, we will be using :ref:`standalone bundles + ` with the ability to self-update. Install dependencies ^^^^^^^^^^^^^^^^^^^^ -First, install all the base dependencies plus git, virtualenv and development files needed to compile several extensions:: +First, install all the base dependencies plus git, virtualenv and development +files needed to compile several extensions:: apt-get install openvpn git-core python-dev python-pyside python-setuptools python-virtualenv -.. TODO Should review these dependencies. ^^ +.. TODO Should review these dependencies. I think python-sqlite is missing, we + have an issue for that^^ + +.. TODO we really should keep the dependencies in a single file that we are able to + include, to avoid phasing out. + Bootstrap script ^^^^^^^^^^^^^^^^ .. note:: - This will fetch the *develop* branch. If you want to test another branch, just change it in the line starting with *pip install...*. Alternatively, bug kali so she add an option branch to a decent script. + This will fetch the *develop* branch. If you want to test another branch, just change it in the line starting with *pip install...*. Alternatively, bug kali so she add an option branch to an improved script. .. note:: This script could make use of the after_install hook. Read http://pypi.python.org/pypi/virtualenv/ @@ -47,15 +138,22 @@ Download and source the following script in the parent folder where you want you Tada! If everything went well, you should be able to run bitmask by typing:: - bitmask + bitmask --debug Noticed that your prompt changed? That was *virtualenv*. Keep reading... Activating the virtualenv ^^^^^^^^^^^^^^^^^^^^^^^^^ -The above bootstrap script has fetched latest code inside a virtualenv, which is an isolated, *virtual* python local environment that avoids messing with your global paths. You will notice you are *inside* a virtualenv because you will see a modified prompt reminding it to you (*bitmask-testbuild* in this case). +The above bootstrap script has fetched latest code inside a virtualenv, which is +an isolated, *virtual* python local environment that avoids messing with your +global paths. You will notice you are *inside* a virtualenv because you will see +a modified prompt reminding it to you (*bitmask-testbuild* in this case). -Thus, if you forget to *activate your virtualenv*, bitmask will not run from the local path, and it will be looking for something else in your global path. So, **you have to remember to activate your virtualenv** each time that you open a new shell and want to execute the code you are testing. You can do this by typing:: +Thus, if you forget to *activate your virtualenv*, bitmask will not run from the +local path, and it will be looking for something else in your global path. So, +**you have to remember to activate your virtualenv** each time that you open a +new shell and want to execute the code you are testing. You can do this by +typing:: $ source bin/activate @@ -87,11 +185,11 @@ You should be able to cd into the downloaded repo and pull latest changes:: (bitmask-testbuild)$ cd src/bitmask (bitmask-testbuild)$ git pull origin develop -However, as a tester you are encouraged to run the whole bootstrap process from time to time to help us catching install and versioniing bugs too. +However, you are encouraged to run the whole bootstrapping process from time to time to help us catching install and versioning bugs too. Testing the packages ^^^^^^^^^^^^^^^^^^^^ -When we have a release candidate for the supported platforms (Debian stable, Ubuntu 12.04 by now), we will announce also the URI where you can download the rc for testing in your system. Stay tuned! +When we have a release candidate for the supported platforms, we will announce also the URI where you can download the rc for testing in your system. Stay tuned! Testing the status of translations ---------------------------------- @@ -104,13 +202,3 @@ If you want to check the current status of bitmask localization in a language ot for running Bitmask with the spanish locales. -Reporting bugs --------------- - -.. admonition:: Reporting better bugs - - There is a great text on the art of bug reporting, that can be found `online `_. - -.. TODO add a line with ref. to running Bitmask in debug mode... - -We use the `Bitmask Bug Tracker `_, although you can also use `Github issues `_. -- cgit v1.2.3 From b575f5ffd94189b505a4fbedfe1b5196c3185a80 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 25 Sep 2013 10:13:23 -0400 Subject: mock clibs in sphinx build Following the docs here: http://read-the-docs.readthedocs.org/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules --- docs/conf.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 3c908b2c..3cfd0b5d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,7 +11,33 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import sys +import os + + +class Mock(object): + def __init__(self, *args, **kwargs): + pass + + def __call__(self, *args, **kwargs): + return Mock() + + @classmethod + def __getattr__(cls, name): + if name in ('__file__', '__path__'): + return '/dev/null' + elif name[0] == name[0].upper(): + mockType = type(name, (), {}) + mockType.__module__ = __name__ + return mockType + else: + return Mock() + +MOCK_MODULES = ['pysqlite', 'pyopenssl', 'pycryptopp', 'pysqlcipher'] + #'google.protobuf' + +for mod_name in MOCK_MODULES: + sys.modules[mod_name] = Mock() # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the -- cgit v1.2.3 From 75cc4211ae959cad9500bb9ccec608129dc3f438 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 25 Sep 2013 10:29:32 -0400 Subject: add sphinx-pypi-upload helper package --- pkg/requirements-docs.pip | 1 + setup.cfg | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 setup.cfg diff --git a/pkg/requirements-docs.pip b/pkg/requirements-docs.pip index 6966869c..ad14d003 100644 --- a/pkg/requirements-docs.pip +++ b/pkg/requirements-docs.pip @@ -1 +1,2 @@ sphinx +Sphinx-PyPI-upload diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..6c1d4f05 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,8 @@ +[build_sphinx] +source-dir = docs/ +build-dir = docs/_build +all_files = 1 + +[upload_sphinx] +upload-dir = docs/_build/html +repository = https://pypi.python.org/pypi -- cgit v1.2.3 From b61eb20fc26c8426189a645f5aecc37644f4c8ee Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 25 Sep 2013 11:22:11 -0400 Subject: fix branching model picture --- docs/dev/workflow.rst | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/docs/dev/workflow.rst b/docs/dev/workflow.rst index 1e3bd4af..abd228c1 100644 --- a/docs/dev/workflow.rst +++ b/docs/dev/workflow.rst @@ -27,11 +27,14 @@ Git flow -------- We are basing our workflow on what is described in `A successful git branching model `_. -.. image:: https://leap.se/code/attachments/13/git-branching-model.png +.. image:: https://downloads.leap.se/pics/git-branching-model.png -The author of the aforementioned post has also a handy pdf version of it: `branching_model.pdf`_ +Vincent Driessen, the author of the aforementioned post has also a handy pdf version of it: `branching_model.pdf`_ -However, we use a setup in which each developer maintains her own feature branch in her private repo. After a code review, this feature branch is rebased onto the authoritative integration branch. Thus, the leapcode repo in leap.se (mirrored in github) only maintains the master and develop branches. +However, we use a slightly modified setup in which each developer maintains her +own feature branch in her private repo. After a code review, this feature branch +is rebased onto the authoritative integration branch. Thus, the leapcode repo in +leap.se (mirrored in github) only maintains the master and develop branches. A couple of tools that help to follow this process are `git-flow`_ and `git-sweep`_. @@ -50,30 +53,33 @@ All code ready to be merged into the integration branch is expected to: Using Github ------------ -Particularly for the Bitmask client, we are using Github. So you should fork the repo from `github`_ . Depending on what kind of work you are going to do (bug or feature) you should create a branch with the following name: +Particularly for the Bitmask client, we are using Github. So you should fork the repo from `github`_ . Depending on what kind of work you are going to do (bug or feature) you should **create a branch** with the following name: -`bug/some_descriptive_text` +``bug/some_descriptive_text`` or -`feature/some_descriptive_text` +``feature/some_descriptive_text`` -Do your work there, push it, and create a pull request against the develop branch in the leapcode owned repo. Either you should post the pull request in `#leap-dev` at `Freenode` or we will just notice it when it's created. +Do your work there, push it, and create a pull request against the develop branch in the main repo (the one owned by leapcode). Now you should wait until we see it, or you can try also posting your pull request in ``#leap-dev`` at `freenode `_. -Your code will get reviewed/discussed by someone else on the team, and say that you need to make some changes. What you would do is the following: +Your code will get reviewed/discussed by someone else on the team. In case that you need to make some changes, you would do the following:: git checkout - # edit what you need here ... +*Edit what you need here ...* + +Simple commit, this doesn't need a good commit message:: - # Simple commit, this doesn't need a good commit message git commit -avm "Fix" - # This will help you reorder your commits and squash them (so that the - # final commit list has good representative messages) +This will help you reorder your commits and squash them (so that the +final commit list has good representative messages):: + git rebase -i develop - # Since you've rewritten your history, you'll need a force push +Since you've rewritten your history, you'll need a force push:: + git push + This will update your pull request automatically, but it won't notify us about the update, so you should add a comment saying so, or re-pingthe reviewer. -- cgit v1.2.3 From 4280e6711af1381eca3c72db013caf3604e136a3 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 25 Sep 2013 12:44:30 -0400 Subject: fix repo in installing doc --- docs/user/install.rst | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/user/install.rst b/docs/user/install.rst index e5765302..bcac4883 100644 --- a/docs/user/install.rst +++ b/docs/user/install.rst @@ -88,14 +88,19 @@ Installing Bitmask is as simple as using `pip `_ Show me the code! ----------------- -You can get the code from LEAP public git repository :: +For the users that can find their way through python packages, +you can get the code from LEAP public git repository :: - $ git clone git://leap.se/bitmask_client + $ git clone https://leap.se/git/bitmask_client Or from the github mirror :: $ git clone git://github.com/leapcode/bitmask_client.git -Once you have grabbed a copy of the sources, you can install it into your site-packages easily :: +Once you have grabbed a copy of the sources, and installed all the base +dependencies, you can install it into your site-packages easily :: - $ pyton setup.py install + $ make # compile the resources + $ sudo python2 setup.py install + +Although, like always, it is a better idea to install things in a virtualenv. -- cgit v1.2.3 From b89cdc8b538efc6cb5ec49a0d97f96e0fb47b2f6 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 25 Sep 2013 14:08:34 -0400 Subject: update source strings so translators can go on --- data/ts/en_US.ts | 668 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 402 insertions(+), 266 deletions(-) diff --git a/data/ts/en_US.ts b/data/ts/en_US.ts index 250b58ce..224060f4 100644 --- a/data/ts/en_US.ts +++ b/data/ts/en_US.ts @@ -1,17 +1,20 @@ - EIPBootstrapper + DarwinVPNLauncher - - The downloaded certificate is not a valid PEM file + + No gateway was found! + + EIPBootstrapper + LinuxVPNLauncher - + No gateway was found! @@ -19,292 +22,337 @@ LoggerWindow - + Logs - + Debug - + Info - + Warning - + Error - + Critical - + Save to file - + Save As + + + Filter by: + + + + + Case Insensitive + + LoginWidget - + Form - + Create a new account - + <b>Provider:</b> - + Remember username and password - + <b>Username:</b> - + <b>Password:</b> - + Log In - - - MainWindow - - LEAP + + Other... + + + + + Cancel + + + MainWindow - + There are new updates available, please restart. - + More... - + Show Log - + &Session - + Help - - &Sign out + + &Quit - - &Quit + + &Help - - About &LEAP + + &Wizard - - &Help + + Show &logs - - &Wizard + + Hide Main Window - - Show &logs + + The following components will be updated: +%s - - No default provider + + Updates available - - Encrypted internet is OFF + + Preferences - - Turn ON + + Show Main Window - - Hide Main Window + + Please select a valid provider - - The LEAPClient app is ready to update, please restart the application. + + Please provide a valid username - - The following components will be updated: -%s + + Please provide a valid Password - - Updates available + + Logging in... - - Preferences + + We could not find any authentication agent in your system.<br/>Make sure you have <b>polkit-gnome-authentication-agent-1</b> running and try again. - - Show Main Window + + We could not find <b>pkexec</b> in your system. - - About LEAP - %s + + We could not find openvpn binary. - - version: <b>%s</b><br>LEAP is a non-profit dedicated to giving all internet users access to secure communication. Our focus is on adapting encryption technology to make it easy to use and widely available. <a href="https://leap.se">More about LEAP</a> + + OFF - - Could not load provider configuration. + + Starting... - - Please select a valid provider + + Not supported - - Please provide a valid username + + Disabled - - Please provide a valid Password + + Could not load Encrypted Internet Configuration. - - Logging in... + + Encrypted Internet could not be launched because you did not authenticate properly. - - Turn OFF + + Encrypted Internet finished in an unexpected manner! - - We could not find any authentication agent in your system.<br/>Make sure you have <b>polkit-gnome-authentication-agent-1</b> running and try again. + + Bitmask - - We could not find <b>pkexec</b> in your system. + + Log &out - - We could not find openvpn binary. + + About &Bitmask - - OFF + + Mail is OFF - - Starting... + + The Bitmask app is ready to update, please restart the application. - - Not supported + + Encrypted Internet is OFF - - Disabled + + About Bitmask - %s - - Could not load Encrypted Internet Configuration. + + Version: <b>%s</b><br><br>Bitmask is the Desktop client application for the LEAP platform, supporting encrypted internet proxy, secure email, and secure chat (coming soon).<br><br>LEAP is a non-profit dedicated to giving all internet users access to secure communication. Our focus is on adapting encryption technology to make it easy to use and widely available. <br><br><a href='https://leap.se'>More about LEAP</a> - - Encrypted Internet could not be launched because you did not authenticate properly. + + Unable to login: Problem with provider - - Encrypted Internet finished in an unexpected manner! + + Log in cancelled by the user. + + + + + Encrypted Internet cannot be started because the tuntap extension is not installed properly in your system. + + + + + Another openvpn instance is already running, and could not be stopped. + + + + + Another openvpn instance is already running, and could not be stopped because it was not launched by Bitmask. Please stop it and try again. + + + + + There was a problem with the provider + + + + + Something went wrong with the logout. + + + + + Unable to connect: Problem with provider ProviderBootstrapper - + Provider certificate could not be verified - + Provider does not support HTTPS @@ -312,7 +360,7 @@ SRPAuth - + Succeeded @@ -320,516 +368,604 @@ StatusPanel - + Form - + user@domain.org - + Encrypted Internet: - + Off - + Turn On - - 0.0 Kb + + ... - - ... + + 0 Unread Emails + + + + + Disabled + + + + + Encrypted Mail: + + + + + 0.0 KB/s StatusPanelWidget - + Turn OFF - + Turn ON - + ON - + Authenticating... - + Retrieving configuration... - + Waiting to start... - + Assigning IP - + Unable to start VPN, it's already running. - - Encryption is OFF + + All services are OFF - - Turning ON + + Encrypted Internet is {0} - - Encryption is ON + + Mail is {0} - - - Wizard - - LEAP First run + + Reconnecting... - - Welcome + + Encrypted Internet is OFF + + + + + Encrypted Internet is STARTING + + + + + Encrypted Internet is ON + + + + + OFF + + + + + Mail is OFF + + + + + Mail is ON - - This is the LEAP Client first run wizard + + Starting... + + + + + Soledad has started... + + + + + Soledad is starting, please wait... + + + + + Looking for key for this user + + + + + Found key! Starting mail... + + + + + Generating new key, please wait... + + + + + Finished generating key! + + + + + Starting mail... + + + + + SMTP has started... + + + + + SMTP failed to start, check the logs. + + + + + Failed + + + + + IMAP has started... + + + + + IMAP failed to start, check the logs. + + + + + %s Unread Emails + + + + + WindowsVPNLauncher + + + No gateway was found! + + + + + Wizard + + + Welcome - + Log In with my credentials - + <html><head/><body><p>Now we will guide you through some configuration that is needed before you can connect for the first time.</p><p>If you ever need to modify these options again, you can find the wizard in the <span style=" font-style:italic;">'Settings'</span> menu from the main window.</p><p>Do you want to <span style=" font-weight:600;">sign up</span> for a new account, or <span style=" font-weight:600;">log in</span> with an already existing username?</p></body></html> - + Sign up for a new account - + Provider selection - + Please enter the domain of the provider you want to use for your connection - + Check - + https:// - + Checking for a valid provider - + Getting provider information - - Can we stablish a secure connection? - - - - + Can we reach this provider? - + Provider Information - + Description of services offered by this provider - + Name - + Desc - + <b>Services offered:</b> - + services - + <b>Enrollment policy:</b> - + policy - + <b>URL:</b> - + URL - + <b>Description:</b> - + Provider setup - + Gathering configuration options for this provider - + We are downloading some bits that we need to establish a secure connection with the provider for the first time. - + Setting up provider - + Getting info from the Certificate Authority - + Do we trust this Certificate Authority? - + Establishing a trust relationship with this provider - + Register new user - + Register a new user with provider - + <b>Password:</b> - + <b>Re-enter password:</b> - + Register - - <b>User:</b> - - - - + Remember my username and password - + Service selection - + Please select the services you would like to have - - Congratulations! - - - - - You have successfully configured the LEAP Client. - - - - - Encrypted Internet - - - - - Encrypted Mail - - - - - (will need admin password to start) - - - - + &Next > - + Connect - - Passwords don't match - - - - - Password too short - - - - - Password too easy - - - - - Password equal to username - - - - + Starting registration... - + User %s successfully registered. - + Unknown error - + <font color='red'><b>Non-existent provider</b></font> - + <font color='red'><b>%s</b></font> - + Unable to load provider configuration - + <font color='red'><b>Not a valid provider</b></font> - + Services by %s - + Something went wrong while trying to load service %s - + Gathering configuration options for %s - + Description of services offered by %s - + Register a new user with %s - - - __impl - - Unknown user + + Bitmask first run + + + + + This is the Bitmask first run wizard + + + + + Can we establish a secure connection? - + + <b>Username:</b> + + + + + __impl + + The server did not send the salt parameter - + The server did not send the B parameter - + The data sent from the server had errors - + Could not connect to the server - - Wrong password - - - - + Unknown error (%s) - + Problem getting data from server - + Bad data from server - + Auth verification failed - + Session cookie verification failed - + There was a problem with authentication + + + Invalid username or password. + + msg - + Missing up/down scripts - + TAP Driver - - LEAPClient needs to install the necessary drivers for Encrypted Internet to work. Would you like to proceed? - - - - + Encrypted Internet uses VPN, which needs a TAP device installed and none has been found. This will ask for administrative privileges. - + TUN Driver - + Encrypted Internet uses VPN, which needs a kernel extension for a TUN device installed, and none has been found. This will ask for administrative privileges. + + + Problem installing files + + + + + Some of the files could not be copied. + + + + + Bitmask needs to install the necessary drivers for Encrypted Internet to work. Would you like to proceed? + + -- cgit v1.2.3 From 9ba31164f032c304e07a063a3be3160985478982 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 25 Sep 2013 18:53:49 -0300 Subject: Refactor to be consistent with other launchers. This is done in order to make them similar and them merge as much as code as possible. --- src/leap/bitmask/services/eip/vpnlaunchers.py | 78 ++++++++++++++------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/src/leap/bitmask/services/eip/vpnlaunchers.py b/src/leap/bitmask/services/eip/vpnlaunchers.py index daa0d81f..e27a48d9 100644 --- a/src/leap/bitmask/services/eip/vpnlaunchers.py +++ b/src/leap/bitmask/services/eip/vpnlaunchers.py @@ -132,7 +132,7 @@ class VPNLauncher(object): Same as missing_updown_scripts but does not check for exec bit. :rtype: list """ - leap_assert(kls.UPDOWN_FILES is not None, + leap_assert(kls.OTHER_FILES is not None, "Need to define OTHER_FILES for this particular " "auncher before calling this method") file_exist = partial(_has_other_files, warn=False) @@ -261,6 +261,7 @@ class LinuxVPNLauncher(VPNLauncher): OPENVPN_DOWN_ROOT_BASE, OPENVPN_DOWN_ROOT_FILE) + UP_SCRIPT = DOWN_SCRIPT = UP_DOWN_PATH UPDOWN_FILES = (UP_DOWN_PATH,) POLKIT_PATH = LinuxPolicyChecker.get_polkit_path() OTHER_FILES = (POLKIT_PATH, ) @@ -357,16 +358,17 @@ class LinuxVPNLauncher(VPNLauncher): "scripts will be run. DNS leaks are likely!") return None - def get_vpn_command(self, eipconfig=None, providerconfig=None, - socket_host=None, socket_port="unix", openvpn_verb=1): + def get_vpn_command(self, eipconfig, providerconfig, socket_host, + socket_port="unix", openvpn_verb=1): """ Returns the platform dependant vpn launching command. It will look for openvpn in the regular paths and algo in - path_prefix/apps/eip/ (in case standalone is set) + path_prefix/apps/eip/ (in case that standalone is set) Might raise: - VPNLauncherException, - OpenVPNNotFoundException. + EIPNoTunKextLoaded, + OpenVPNNotFoundException, + VPNLauncherException. :param eipconfig: eip configuration object :type eipconfig: EIPConfig @@ -387,12 +389,8 @@ class LinuxVPNLauncher(VPNLauncher): :return: A VPN command ready to be launched :rtype: list """ - leap_assert(eipconfig, "We need an eip config") leap_assert_type(eipconfig, EIPConfig) - leap_assert(providerconfig, "We need a provider config") leap_assert_type(providerconfig, ProviderConfig) - leap_assert(socket_host, "We need a socket host!") - leap_assert(socket_port, "We need a socket port!") kwargs = {} if flags.STANDALONE: @@ -400,18 +398,12 @@ class LinuxVPNLauncher(VPNLauncher): get_path_prefix(), "..", "apps", "eip") openvpn_possibilities = which(self.OPENVPN_BIN, **kwargs) - if len(openvpn_possibilities) == 0: raise OpenVPNNotFoundException() openvpn = first(openvpn_possibilities) args = [] - pkexec = self.maybe_pkexec() - if pkexec: - args.append(openvpn) - openvpn = first(pkexec) - args += [ '--setenv', "LEAPOPENVPN", "1" ] @@ -454,22 +446,23 @@ class LinuxVPNLauncher(VPNLauncher): ] openvpn_configuration = eipconfig.get_openvpn_configuration() - for key, value in openvpn_configuration.items(): args += ['--%s' % (key,), value] + user = getpass.getuser() + ############################################################## # The down-root plugin fails in some situations, so we don't # drop privs for the time being ############################################################## # args += [ - # '--user', getpass.getuser(), + # '--user', user, # '--group', grp.getgrgid(os.getgroups()[-1]).gr_name # ] if socket_port == "unix": # that's always the case for linux args += [ - '--management-client-user', getpass.getuser() + '--management-client-user', user ] args += [ @@ -478,37 +471,46 @@ class LinuxVPNLauncher(VPNLauncher): '--script-security', '2' ] - plugin_path = self.maybe_down_plugin() - # If we do not have the down plugin neither in the bundle - # nor in the system, we do not do updown scripts. The alternative - # is leaving the user without the ability to restore dns and routes - # to its original state. + if _has_updown_scripts(self.UP_SCRIPT): + args += [ + '--up', '\"%s\"' % (self.UP_SCRIPT,), + ] - if plugin_path and _has_updown_scripts(self.UP_DOWN_PATH): + if _has_updown_scripts(self.DOWN_SCRIPT): args += [ - '--up', self.UP_DOWN_PATH, - '--down', self.UP_DOWN_PATH, - ############################################################## - # For the time being we are disabling the usage of the - # down-root plugin, because it doesn't quite work as - # expected (i.e. it doesn't run route -del as root - # when finishing, so it fails to properly - # restart/quit) - ############################################################## - # '--plugin', plugin_path, - # '\'script_type=down %s\'' % self.UP_DOWN_PATH + '--down', '\"%s\"' % (self.DOWN_SCRIPT,) ] + ########################################################### + # For the time being we are disabling the usage of the + # down-root plugin, because it doesn't quite work as + # expected (i.e. it doesn't run route -del as root + # when finishing, so it fails to properly + # restart/quit) + ########################################################### + # if _has_updown_scripts(self.OPENVPN_DOWN_PLUGIN): + # args += [ + # '--plugin', self.OPENVPN_DOWN_ROOT, + # '\'%s\'' % self.DOWN_SCRIPT # for OSX + # '\'script_type=down %s\'' % self.DOWN_SCRIPT # for Linux + # ] + args += [ '--cert', eipconfig.get_client_cert_path(providerconfig), '--key', eipconfig.get_client_cert_path(providerconfig), '--ca', providerconfig.get_ca_cert_path() ] + command = [openvpn] + pkexec = self.maybe_pkexec() + if pkexec: + command.insert(0, first(pkexec)) + + command_and_args = command + args logger.debug("Running VPN with command:") - logger.debug("%s %s" % (openvpn, " ".join(args))) + logger.debug(" ".join(command_and_args)) - return [openvpn] + args + return command_and_args def get_vpn_env(self): """ -- cgit v1.2.3 From 6af326fd901243cc5e36ddbcecff0690d1abee5e Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sat, 28 Sep 2013 11:38:46 -0400 Subject: Make providerboostrapper take the verify path from our ca-bundle. Also, move module to a more logical placement, since provier boostrapping is not dependent on eip service. --- changes/VERSION_COMPAT | 1 + src/leap/bitmask/gui/mainwindow.py | 2 +- src/leap/bitmask/gui/wizard.py | 7 +- src/leap/bitmask/provider/providerbootstrapper.py | 368 ++++++++++++++ src/leap/bitmask/provider/tests/__init__.py | 0 .../provider/tests/test_providerbootstrapper.py | 556 ++++++++++++++++++++ .../bitmask/services/eip/providerbootstrapper.py | 340 ------------- .../eip/tests/test_providerbootstrapper.py | 560 --------------------- 8 files changed, 928 insertions(+), 906 deletions(-) create mode 100644 src/leap/bitmask/provider/providerbootstrapper.py create mode 100644 src/leap/bitmask/provider/tests/__init__.py create mode 100644 src/leap/bitmask/provider/tests/test_providerbootstrapper.py delete mode 100644 src/leap/bitmask/services/eip/providerbootstrapper.py delete mode 100644 src/leap/bitmask/services/eip/tests/test_providerbootstrapper.py diff --git a/changes/VERSION_COMPAT b/changes/VERSION_COMPAT index cc00ecf7..425478c8 100644 --- a/changes/VERSION_COMPAT +++ b/changes/VERSION_COMPAT @@ -8,3 +8,4 @@ # # BEGIN DEPENDENCY LIST ------------------------- # leap.foo.bar>=x.y.z +leap.common >= 0.3.4 # because the ca_bundle diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 200d68aa..d8d0ac21 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -36,9 +36,9 @@ from leap.bitmask.gui import statemachines from leap.bitmask.gui.statuspanel import StatusPanelWidget from leap.bitmask.gui.wizard import Wizard +from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper from leap.bitmask.services.eip.eipconfig import EIPConfig -from leap.bitmask.services.eip.providerbootstrapper import ProviderBootstrapper # XXX: Soledad might not work out of the box in Windows, issue #2932 from leap.bitmask.services.soledad.soledadbootstrapper import \ SoledadBootstrapper diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 45734b81..bb38b136 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -14,7 +14,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - """ First run wizard """ @@ -27,15 +26,13 @@ from functools import partial from PySide import QtCore, QtGui from twisted.internet import threads -from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpregister import SRPRegister -from leap.bitmask.util.privilege_policies import is_missing_policy_permissions +from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper +from leap.bitmask.services import get_service_display_name, get_supported from leap.bitmask.util.request_helpers import get_content from leap.bitmask.util.keyring_helpers import has_keyring from leap.bitmask.util.password import basic_password_checks -from leap.bitmask.services.eip.providerbootstrapper import ProviderBootstrapper -from leap.bitmask.services import get_service_display_name, get_supported from ui_wizard import Ui_Wizard diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py new file mode 100644 index 00000000..751da828 --- /dev/null +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -0,0 +1,368 @@ +# -*- coding: utf-8 -*- +# providerbootstrapper.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 . +""" +Provider bootstrapping +""" +import logging +import socket +import os + +import requests + +from PySide import QtCore + +from leap.bitmask.config.providerconfig import ProviderConfig, MissingCACert +from leap.bitmask.util.request_helpers import get_content +from leap.bitmask.util import get_path_prefix +from leap.bitmask.util.constants import REQUEST_TIMEOUT +from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper +from leap.bitmask.provider.supportedapis import SupportedAPIs +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 +from leap.common.check import leap_assert, leap_assert_type, leap_check + +logger = logging.getLogger(__name__) + + +class UnsupportedProviderAPI(Exception): + """ + Raised when attempting to use a provider with an incompatible API. + """ + pass + + +class WrongFingerprint(Exception): + """ + Raised when a fingerprint comparison does not match. + """ + pass + + +class ProviderBootstrapper(AbstractBootstrapper): + """ + Given a provider URL performs a series of checks and emits signals + after they are passed. + If a check fails, the subsequent checks are not executed + """ + + # All dicts returned are of the form + # {"passed": bool, "error": str} + name_resolution = QtCore.Signal(dict) + https_connection = QtCore.Signal(dict) + download_provider_info = QtCore.Signal(dict) + + download_ca_cert = QtCore.Signal(dict) + check_ca_fingerprint = QtCore.Signal(dict) + check_api_certificate = QtCore.Signal(dict) + + def __init__(self, bypass_checks=False): + """ + Constructor for provider bootstrapper object + + :param bypass_checks: Set to true if the app should bypass + first round of checks for CA certificates at bootstrap + :type bypass_checks: bool + """ + AbstractBootstrapper.__init__(self, bypass_checks) + + self._domain = None + self._provider_config = None + self._download_if_needed = False + + @property + def verify(self): + """ + Verify parameter for requests. + + :returns: either False, if checks are skipped, or the + path to the ca bundle. + :rtype: bool or str + """ + if self._bypass_checks: + verify = False + else: + verify = ca_bundle.where() + return verify + + def _check_name_resolution(self): + """ + Checks that the name resolution for the provider name works + """ + leap_assert(self._domain, "Cannot check DNS without a domain") + logger.debug("Checking name resolution for %s" % (self._domain)) + + # We don't skip this check, since it's basic for the whole + # system to work + # err --- but we can do it after a failure, to diagnose what went + # wrong. Right now we're just adding connection overhead. -- kali + socket.gethostbyname(self._domain) + + def _check_https(self, *args): + """ + Checks that https is working and that the provided certificate + checks out + """ + leap_assert(self._domain, "Cannot check HTTPS without a domain") + logger.debug("Checking https for %s" % (self._domain)) + + # We don't skip this check, since it's basic for the whole + # system to work. + # err --- but we can do it after a failure, to diagnose what went + # wrong. Right now we're just adding connection overhead. -- kali + + try: + res = self._session.get("https://%s" % (self._domain,), + verify=self.verify, + timeout=REQUEST_TIMEOUT) + res.raise_for_status() + except requests.exceptions.SSLError as exc: + logger.exception(exc) + self._err_msg = self.tr("Provider certificate could " + "not be verified") + raise + except Exception as exc: + # XXX careful!. The error might be also a SSL handshake + # timeout error, in which case we should retry a couple of times + # more, for cases where the ssl server gives high latencies. + logger.exception(exc) + self._err_msg = self.tr("Provider does not support HTTPS") + raise + + def _download_provider_info(self, *args): + """ + Downloads the provider.json defition + """ + leap_assert(self._domain, + "Cannot download provider info without a domain") + logger.debug("Downloading provider info for %s" % (self._domain)) + + # -------------------------------------------------------------- + # TODO factor out with the download routines in services. + # Watch out! We're handling the verify paramenter differently here. + + headers = {} + provider_json = os.path.join(get_path_prefix(), "leap", "providers", + self._domain, "provider.json") + mtime = get_mtime(provider_json) + + if self._download_if_needed and mtime: + headers['if-modified-since'] = mtime + + uri = "https://%s/%s" % (self._domain, "provider.json") + verify = self.verify + + if mtime: # the provider.json exists + # So, we're getting it from the api.* and checking against + # the provider ca. + try: + provider_config = ProviderConfig() + provider_config.load(provider_json) + uri = provider_config.get_api_uri() + '/provider.json' + verify = provider_config.get_ca_cert_path() + except MissingCACert: + # no ca? then download from main domain again. + pass + + logger.debug("Requesting for provider.json... " + "uri: {0}, verify: {1}, headers: {2}".format( + uri, verify, headers)) + res = self._session.get(uri, verify=verify, + headers=headers, timeout=REQUEST_TIMEOUT) + res.raise_for_status() + logger.debug("Request status code: {0}".format(res.status_code)) + + # Not modified + if res.status_code == 304: + logger.debug("Provider definition has not been modified") + # -------------------------------------------------------------- + # end refactor, more or less... + # XXX Watch out, have to check the supported api yet. + else: + provider_definition, mtime = get_content(res) + + provider_config = ProviderConfig() + provider_config.load(data=provider_definition, mtime=mtime) + provider_config.save(["leap", + "providers", + 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(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) + + def run_provider_select_checks(self, domain, download_if_needed=False): + """ + Populates the check queue. + + :param domain: domain to check + :type domain: str + + :param download_if_needed: if True, makes the checks do not + overwrite already downloaded data + :type download_if_needed: bool + """ + leap_assert(domain and len(domain) > 0, "We need a domain!") + + self._domain = ProviderConfig.sanitize_path_component(domain) + self._download_if_needed = download_if_needed + + cb_chain = [ + (self._check_name_resolution, self.name_resolution), + (self._check_https, self.https_connection), + (self._download_provider_info, self.download_provider_info) + ] + + return self.addCallbackChain(cb_chain) + + def _should_proceed_cert(self): + """ + Returns False if the certificate already exists for the given + provider. True otherwise + + :rtype: bool + """ + leap_assert(self._provider_config, "We need a provider config!") + + if not self._download_if_needed: + return True + + return not os.path.exists(self._provider_config + .get_ca_cert_path(about_to_download=True)) + + def _download_ca_cert(self, *args): + """ + Downloads the CA cert that is going to be used for the api URL + """ + # XXX maybe we can skip this step if + # we have a fresh one. + leap_assert(self._provider_config, "Cannot download the ca cert " + "without a provider config!") + + logger.debug("Downloading ca cert for %s at %s" % + (self._domain, self._provider_config.get_ca_cert_uri())) + + if not self._should_proceed_cert(): + check_and_fix_urw_only( + self._provider_config + .get_ca_cert_path(about_to_download=True)) + return + + res = self._session.get(self._provider_config.get_ca_cert_uri(), + verify=self.verify, + timeout=REQUEST_TIMEOUT) + res.raise_for_status() + + cert_path = self._provider_config.get_ca_cert_path( + about_to_download=True) + cert_dir = os.path.dirname(cert_path) + mkdir_p(cert_dir) + with open(cert_path, "w") as f: + f.write(res.content) + + check_and_fix_urw_only(cert_path) + + def _check_ca_fingerprint(self, *args): + """ + Checks the CA cert fingerprint against the one provided in the + json definition + """ + leap_assert(self._provider_config, "Cannot check the ca cert " + "without a provider config!") + + logger.debug("Checking ca fingerprint for %s and cert %s" % + (self._domain, + self._provider_config.get_ca_cert_path())) + + if not self._should_proceed_cert(): + return + + parts = self._provider_config.get_ca_cert_fingerprint().split(":") + + error_msg = "Wrong fingerprint format" + leap_check(len(parts) == 2, error_msg, WrongFingerprint) + + method = parts[0].strip() + fingerprint = parts[1].strip() + cert_data = None + with open(self._provider_config.get_ca_cert_path()) as f: + cert_data = f.read() + + leap_assert(len(cert_data) > 0, "Could not read certificate data") + digest = get_digest(cert_data, method) + + error_msg = "Downloaded certificate has a different fingerprint!" + leap_check(digest == fingerprint, error_msg, WrongFingerprint) + + def _check_api_certificate(self, *args): + """ + Tries to make an API call with the downloaded cert and checks + if it validates against it + """ + leap_assert(self._provider_config, "Cannot check the ca cert " + "without a provider config!") + + logger.debug("Checking api certificate for %s and cert %s" % + (self._provider_config.get_api_uri(), + self._provider_config.get_ca_cert_path())) + + if not self._should_proceed_cert(): + return + + test_uri = "%s/%s/cert" % (self._provider_config.get_api_uri(), + self._provider_config.get_api_version()) + res = self._session.get(test_uri, + verify=self._provider_config + .get_ca_cert_path(), + timeout=REQUEST_TIMEOUT) + res.raise_for_status() + + def run_provider_setup_checks(self, + provider_config, + download_if_needed=False): + """ + Starts the checks needed for a new provider setup. + + :param provider_config: Provider configuration + :type provider_config: ProviderConfig + + :param download_if_needed: if True, makes the checks do not + overwrite already downloaded data. + :type download_if_needed: bool + """ + leap_assert(provider_config, "We need a provider config!") + leap_assert_type(provider_config, ProviderConfig) + + self._provider_config = provider_config + self._download_if_needed = download_if_needed + + cb_chain = [ + (self._download_ca_cert, self.download_ca_cert), + (self._check_ca_fingerprint, self.check_ca_fingerprint), + (self._check_api_certificate, self.check_api_certificate) + ] + + return self.addCallbackChain(cb_chain) diff --git a/src/leap/bitmask/provider/tests/__init__.py b/src/leap/bitmask/provider/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/leap/bitmask/provider/tests/test_providerbootstrapper.py b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py new file mode 100644 index 00000000..9b47d60e --- /dev/null +++ b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py @@ -0,0 +1,556 @@ +# -*- coding: utf-8 -*- +# test_providerbootstrapper.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 . +""" +Tests for the Provider Boostrapper checks + +These will be whitebox tests since we want to make sure the private +implementation is checking what we expect. +""" +import os +import mock +import socket +import stat +import tempfile +import time +import requests +try: + import unittest2 as unittest +except ImportError: + import unittest + +from nose.twistedtools import deferred, reactor +from twisted.internet import threads +from requests.models import Response + +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.common.files import mkdir_p +from leap.common.testing.https_server import where +from leap.common.testing.basetest import BaseLeapTest + + +class ProviderBootstrapperTest(BaseLeapTest): + def setUp(self): + self.pb = ProviderBootstrapper() + + def tearDown(self): + pass + + def test_name_resolution_check(self): + # Something highly likely to success + self.pb._domain = "google.com" + self.pb._check_name_resolution() + # Something highly likely to fail + self.pb._domain = "uquhqweuihowquie.abc.def" + + # In python 2.7.4 raises socket.error + # In python 2.7.5 raises socket.gaierror + with self.assertRaises((socket.gaierror, socket.error)): + self.pb._check_name_resolution() + + @deferred() + def test_run_provider_select_checks(self): + self.pb._check_name_resolution = mock.MagicMock() + self.pb._check_https = mock.MagicMock() + self.pb._download_provider_info = mock.MagicMock() + + d = self.pb.run_provider_select_checks("somedomain") + + def check(*args): + self.pb._check_name_resolution.assert_called_once_with() + self.pb._check_https.assert_called_once_with(None) + self.pb._download_provider_info.assert_called_once_with(None) + d.addCallback(check) + return d + + @deferred() + def test_run_provider_setup_checks(self): + self.pb._download_ca_cert = mock.MagicMock() + self.pb._check_ca_fingerprint = mock.MagicMock() + self.pb._check_api_certificate = mock.MagicMock() + + d = self.pb.run_provider_setup_checks(ProviderConfig()) + + def check(*args): + self.pb._download_ca_cert.assert_called_once_with() + self.pb._check_ca_fingerprint.assert_called_once_with(None) + self.pb._check_api_certificate.assert_called_once_with(None) + d.addCallback(check) + return d + + def test_should_proceed_cert(self): + self.pb._provider_config = mock.Mock() + self.pb._provider_config.get_ca_cert_path = mock.MagicMock( + return_value=where("cacert.pem")) + + self.pb._download_if_needed = False + self.assertTrue(self.pb._should_proceed_cert()) + + self.pb._download_if_needed = True + self.assertFalse(self.pb._should_proceed_cert()) + + self.pb._provider_config.get_ca_cert_path = mock.MagicMock( + return_value=where("somefilethatdoesntexist.pem")) + self.assertTrue(self.pb._should_proceed_cert()) + + def _check_download_ca_cert(self, should_proceed): + """ + Helper to check different paths easily for the download ca + cert check + + :param should_proceed: sets the _should_proceed_cert in the + provider bootstrapper being tested + :type should_proceed: bool + + :returns: The contents of the certificate, the expected + content depending on should_proceed, and the mode of + the file to be checked by the caller + :rtype: tuple of str, str, int + """ + old_content = "NOT THE NEW CERT" + new_content = "NEW CERT" + new_cert_path = os.path.join(tempfile.mkdtemp(), + "mynewcert.pem") + + with open(new_cert_path, "w") as c: + c.write(old_content) + + self.pb._provider_config = mock.Mock() + self.pb._provider_config.get_ca_cert_path = mock.MagicMock( + return_value=new_cert_path) + self.pb._domain = "somedomain" + + self.pb._should_proceed_cert = mock.MagicMock( + return_value=should_proceed) + + read = None + content_to_check = None + mode = None + + with mock.patch('requests.models.Response.content', + new_callable=mock.PropertyMock) as \ + content: + content.return_value = new_content + response_obj = Response() + response_obj.raise_for_status = mock.MagicMock() + + self.pb._session.get = mock.MagicMock(return_value=response_obj) + self.pb._download_ca_cert() + with open(new_cert_path, "r") as nc: + read = nc.read() + if should_proceed: + content_to_check = new_content + else: + content_to_check = old_content + mode = stat.S_IMODE(os.stat(new_cert_path).st_mode) + + os.unlink(new_cert_path) + return read, content_to_check, mode + + def test_download_ca_cert_no_saving(self): + read, expected_read, mode = self._check_download_ca_cert(False) + self.assertEqual(read, expected_read) + self.assertEqual(mode, int("600", 8)) + + def test_download_ca_cert_saving(self): + read, expected_read, mode = self._check_download_ca_cert(True) + self.assertEqual(read, expected_read) + self.assertEqual(mode, int("600", 8)) + + def test_check_ca_fingerprint_skips(self): + self.pb._provider_config = mock.Mock() + self.pb._provider_config.get_ca_cert_fingerprint = mock.MagicMock( + return_value="") + self.pb._domain = "somedomain" + + self.pb._should_proceed_cert = mock.MagicMock(return_value=False) + + self.pb._check_ca_fingerprint() + self.assertFalse(self.pb._provider_config. + get_ca_cert_fingerprint.called) + + def test_check_ca_cert_fingerprint_raises_bad_format(self): + self.pb._provider_config = mock.Mock() + self.pb._provider_config.get_ca_cert_fingerprint = mock.MagicMock( + return_value="wrongfprformat!!") + self.pb._domain = "somedomain" + + self.pb._should_proceed_cert = mock.MagicMock(return_value=True) + + with self.assertRaises(WrongFingerprint): + self.pb._check_ca_fingerprint() + + # This two hashes different in the last byte, but that's good enough + # for the tests + KNOWN_BAD_HASH = "SHA256: 0f17c033115f6b76ff67871872303ff65034efe" \ + "7dd1b910062ca323eb4da5c7f" + KNOWN_GOOD_HASH = "SHA256: 0f17c033115f6b76ff67871872303ff65034ef" \ + "e7dd1b910062ca323eb4da5c7e" + KNOWN_GOOD_CERT = """ +-----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----- +""" + + def _prepare_provider_config_with(self, cert_path, cert_hash): + """ + Mocks the provider config to give the cert_path and cert_hash + specified + + :param cert_path: path for the certificate + :type cert_path: str + :param cert_hash: hash for the certificate as it would appear + in the provider config json + :type cert_hash: str + """ + self.pb._provider_config = mock.Mock() + self.pb._provider_config.get_ca_cert_fingerprint = mock.MagicMock( + return_value=cert_hash) + self.pb._provider_config.get_ca_cert_path = mock.MagicMock( + return_value=cert_path) + self.pb._domain = "somedomain" + + def test_check_ca_fingerprint_checksout(self): + cert_path = os.path.join(tempfile.mkdtemp(), + "mynewcert.pem") + + with open(cert_path, "w") as c: + c.write(self.KNOWN_GOOD_CERT) + + self._prepare_provider_config_with(cert_path, self.KNOWN_GOOD_HASH) + + self.pb._should_proceed_cert = mock.MagicMock(return_value=True) + + self.pb._check_ca_fingerprint() + + os.unlink(cert_path) + + def test_check_ca_fingerprint_fails(self): + cert_path = os.path.join(tempfile.mkdtemp(), + "mynewcert.pem") + + with open(cert_path, "w") as c: + c.write(self.KNOWN_GOOD_CERT) + + self._prepare_provider_config_with(cert_path, self.KNOWN_BAD_HASH) + + self.pb._should_proceed_cert = mock.MagicMock(return_value=True) + + with self.assertRaises(WrongFingerprint): + self.pb._check_ca_fingerprint() + + os.unlink(cert_path) + + +############################################################################### +# Tests with a fake provider # +############################################################################### + +class ProviderBootstrapperActiveTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + factory = fake_provider.get_provider_factory() + http = reactor.listenTCP(8002, factory) + https = reactor.listenSSL( + 0, factory, + fake_provider.OpenSSLServerContextFactory()) + get_port = lambda p: p.getHost().port + cls.http_port = get_port(http) + cls.https_port = get_port(https) + + def setUp(self): + self.pb = ProviderBootstrapper() + + # At certain points we are going to be replacing these methods + # directly in ProviderConfig to be able to catch calls from + # new ProviderConfig objects inside the methods tested. We + # need to save the old implementation and restore it in + # tearDown so we are sure everything is as expected for each + # test. If we do it inside each specific test, a failure in + # the test will leave the implementation with the mock. + self.old_gpp = ProviderConfig.get_path_prefix + self.old_load = ProviderConfig.load + self.old_save = ProviderConfig.save + self.old_api_version = ProviderConfig.get_api_version + + def tearDown(self): + ProviderConfig.get_path_prefix = self.old_gpp + ProviderConfig.load = self.old_load + ProviderConfig.save = self.old_save + ProviderConfig.get_api_version = self.old_api_version + + def test_check_https_succeeds(self): + # XXX: Need a proper CA signed cert to test this + pass + + @deferred() + def test_check_https_fails(self): + self.pb._domain = "localhost:%s" % (self.https_port,) + + def check(*args): + with self.assertRaises(requests.exceptions.SSLError): + self.pb._check_https() + return threads.deferToThread(check) + + @deferred() + def test_second_check_https_fails(self): + self.pb._domain = "localhost:1234" + + def check(*args): + with self.assertRaises(Exception): + self.pb._check_https() + return threads.deferToThread(check) + + @deferred() + def test_check_https_succeeds_if_danger(self): + self.pb._domain = "localhost:%s" % (self.https_port,) + self.pb._bypass_checks = True + + def check(*args): + self.pb._check_https() + + return threads.deferToThread(check) + + def _setup_provider_config_with(self, api, path_prefix): + """ + Sets up the ProviderConfig with mocks for the path prefix, the + api returned and load/save methods. + It modifies ProviderConfig directly instead of an object + because the object used is created in the method itself and we + cannot control that. + + :param api: API to return + :type api: str + :param path_prefix: path prefix to be used when calculating + paths + :type path_prefix: str + """ + ProviderConfig.get_path_prefix = mock.MagicMock( + return_value=path_prefix) + ProviderConfig.get_api_version = mock.MagicMock( + return_value=api) + ProviderConfig.load = mock.MagicMock() + ProviderConfig.save = mock.MagicMock() + + def _setup_providerbootstrapper(self, ifneeded): + """ + Sets the provider bootstrapper's domain to + localhost:https_port, sets it to bypass https checks and sets + the download if needed based on the ifneeded value. + + :param ifneeded: Value for _download_if_needed + :type ifneeded: bool + """ + self.pb._domain = "localhost:%s" % (self.https_port,) + self.pb._bypass_checks = True + self.pb._download_if_needed = ifneeded + + def _produce_dummy_provider_json(self): + """ + Creates a dummy provider json on disk in order to test + behaviour around it (download if newer online, etc) + + :returns: the provider.json path used + :rtype: str + """ + provider_dir = os.path.join(ProviderConfig() + .get_path_prefix(), + "leap", + "providers", + self.pb._domain) + mkdir_p(provider_dir) + provider_path = os.path.join(provider_dir, + "provider.json") + + with open(provider_path, "w") as p: + p.write("A") + return provider_path + + def test_download_provider_info_new_provider(self): + self._setup_provider_config_with("1", tempfile.mkdtemp()) + self._setup_providerbootstrapper(True) + + self.pb._download_provider_info() + self.assertTrue(ProviderConfig.save.called) + + @mock.patch( + 'leap.bitmask.config.providerconfig.ProviderConfig.get_ca_cert_path', + lambda x: where('cacert.pem')) + def test_download_provider_info_not_modified(self): + self._setup_provider_config_with("1", tempfile.mkdtemp()) + self._setup_providerbootstrapper(True) + provider_path = self._produce_dummy_provider_json() + + # set mtime to something really new + os.utime(provider_path, (-1, time.time())) + + with mock.patch.object( + ProviderConfig, 'get_api_uri', + return_value="https://localhost:%s" % (self.https_port,)): + self.pb._download_provider_info() + # we check that it doesn't save the provider + # config, because it's new enough + self.assertFalse(ProviderConfig.save.called) + + @mock.patch( + 'leap.bitmask.config.providerconfig.ProviderConfig.get_domain', + lambda x: where('testdomain.com')) + def test_download_provider_info_not_modified_and_no_cacert(self): + self._setup_provider_config_with("1", tempfile.mkdtemp()) + self._setup_providerbootstrapper(True) + provider_path = self._produce_dummy_provider_json() + + # set mtime to something really new + os.utime(provider_path, (-1, time.time())) + + with mock.patch.object( + ProviderConfig, 'get_api_uri', + return_value="https://localhost:%s" % (self.https_port,)): + self.pb._download_provider_info() + # we check that it doesn't save the provider + # config, because it's new enough + self.assertFalse(ProviderConfig.save.called) + + @mock.patch( + 'leap.bitmask.config.providerconfig.ProviderConfig.get_ca_cert_path', + lambda x: where('cacert.pem')) + def test_download_provider_info_modified(self): + self._setup_provider_config_with("1", tempfile.mkdtemp()) + self._setup_providerbootstrapper(True) + provider_path = self._produce_dummy_provider_json() + + # set mtime to something really old + os.utime(provider_path, (-1, 100)) + + with mock.patch.object( + ProviderConfig, 'get_api_uri', + return_value="https://localhost:%s" % (self.https_port,)): + self.pb._download_provider_info() + self.assertTrue(ProviderConfig.load.called) + self.assertTrue(ProviderConfig.save.called) + + @mock.patch( + 'leap.bitmask.config.providerconfig.ProviderConfig.get_ca_cert_path', + lambda x: where('cacert.pem')) + def test_download_provider_info_unsupported_api_raises(self): + self._setup_provider_config_with("9999999", tempfile.mkdtemp()) + self._setup_providerbootstrapper(False) + self._produce_dummy_provider_json() + + with mock.patch.object( + ProviderConfig, 'get_api_uri', + return_value="https://localhost:%s" % (self.https_port,)): + with self.assertRaises(UnsupportedProviderAPI): + self.pb._download_provider_info() + + @mock.patch( + '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], + tempfile.mkdtemp()) + self._setup_providerbootstrapper(False) + self._produce_dummy_provider_json() + + with mock.patch.object( + ProviderConfig, 'get_api_uri', + return_value="https://localhost:%s" % (self.https_port,)): + self.pb._download_provider_info() + + @mock.patch( + 'leap.bitmask.config.providerconfig.ProviderConfig.get_api_uri', + lambda x: 'api.uri') + @mock.patch( + 'leap.bitmask.config.providerconfig.ProviderConfig.get_ca_cert_path', + lambda x: '/cert/path') + def test_check_api_certificate_skips(self): + self.pb._provider_config = ProviderConfig() + self.pb._session.get = mock.MagicMock(return_value=Response()) + + self.pb._should_proceed_cert = mock.MagicMock(return_value=False) + self.pb._check_api_certificate() + self.assertFalse(self.pb._session.get.called) + + @deferred() + def test_check_api_certificate_fails(self): + self.pb._provider_config = ProviderConfig() + self.pb._provider_config.get_api_uri = mock.MagicMock( + return_value="https://localhost:%s" % (self.https_port,)) + self.pb._provider_config.get_ca_cert_path = mock.MagicMock( + return_value=os.path.join( + os.path.split(__file__)[0], + "wrongcert.pem")) + self.pb._provider_config.get_api_version = mock.MagicMock( + return_value="1") + + self.pb._should_proceed_cert = mock.MagicMock(return_value=True) + + def check(*args): + with self.assertRaises(requests.exceptions.SSLError): + self.pb._check_api_certificate() + d = threads.deferToThread(check) + return d + + @deferred() + def test_check_api_certificate_succeeds(self): + self.pb._provider_config = ProviderConfig() + self.pb._provider_config.get_api_uri = mock.MagicMock( + return_value="https://localhost:%s" % (self.https_port,)) + self.pb._provider_config.get_ca_cert_path = mock.MagicMock( + return_value=where('cacert.pem')) + self.pb._provider_config.get_api_version = mock.MagicMock( + return_value="1") + + self.pb._should_proceed_cert = mock.MagicMock(return_value=True) + + def check(*args): + self.pb._check_api_certificate() + d = threads.deferToThread(check) + return d diff --git a/src/leap/bitmask/services/eip/providerbootstrapper.py b/src/leap/bitmask/services/eip/providerbootstrapper.py deleted file mode 100644 index 3b7c9899..00000000 --- a/src/leap/bitmask/services/eip/providerbootstrapper.py +++ /dev/null @@ -1,340 +0,0 @@ -# -*- coding: utf-8 -*- -# providerbootstrapper.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 . - -""" -Provider bootstrapping -""" -import logging -import socket -import os - -import requests - -from PySide import QtCore - -from leap.bitmask.config.providerconfig import ProviderConfig, MissingCACert -from leap.bitmask.util.request_helpers import get_content -from leap.bitmask.util import get_path_prefix -from leap.bitmask.util.constants import REQUEST_TIMEOUT -from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper -from leap.bitmask.provider.supportedapis import SupportedAPIs -from leap.common.certs import get_digest -from leap.common.files import check_and_fix_urw_only, get_mtime, mkdir_p -from leap.common.check import leap_assert, leap_assert_type, leap_check - - -logger = logging.getLogger(__name__) - - -class UnsupportedProviderAPI(Exception): - """ - Raised when attempting to use a provider with an incompatible API. - """ - pass - - -class WrongFingerprint(Exception): - """ - Raised when a fingerprint comparison does not match. - """ - pass - - -class ProviderBootstrapper(AbstractBootstrapper): - """ - Given a provider URL performs a series of checks and emits signals - after they are passed. - If a check fails, the subsequent checks are not executed - """ - - # All dicts returned are of the form - # {"passed": bool, "error": str} - name_resolution = QtCore.Signal(dict) - https_connection = QtCore.Signal(dict) - download_provider_info = QtCore.Signal(dict) - - download_ca_cert = QtCore.Signal(dict) - check_ca_fingerprint = QtCore.Signal(dict) - check_api_certificate = QtCore.Signal(dict) - - def __init__(self, bypass_checks=False): - """ - Constructor for provider bootstrapper object - - :param bypass_checks: Set to true if the app should bypass - first round of checks for CA certificates at bootstrap - :type bypass_checks: bool - """ - AbstractBootstrapper.__init__(self, bypass_checks) - - self._domain = None - self._provider_config = None - self._download_if_needed = False - - def _check_name_resolution(self): - """ - Checks that the name resolution for the provider name works - """ - leap_assert(self._domain, "Cannot check DNS without a domain") - - logger.debug("Checking name resolution for %s" % (self._domain)) - - # We don't skip this check, since it's basic for the whole - # system to work - socket.gethostbyname(self._domain) - - def _check_https(self, *args): - """ - Checks that https is working and that the provided certificate - checks out - """ - - leap_assert(self._domain, "Cannot check HTTPS without a domain") - - logger.debug("Checking https for %s" % (self._domain)) - - # We don't skip this check, since it's basic for the whole - # system to work - - try: - res = self._session.get("https://%s" % (self._domain,), - verify=not self._bypass_checks, - timeout=REQUEST_TIMEOUT) - res.raise_for_status() - except requests.exceptions.SSLError: - self._err_msg = self.tr("Provider certificate could " - "not be verified") - raise - except Exception: - self._err_msg = self.tr("Provider does not support HTTPS") - raise - - def _download_provider_info(self, *args): - """ - Downloads the provider.json defition - """ - leap_assert(self._domain, - "Cannot download provider info without a domain") - - logger.debug("Downloading provider info for %s" % (self._domain)) - - headers = {} - - provider_json = os.path.join(get_path_prefix(), "leap", "providers", - self._domain, "provider.json") - mtime = get_mtime(provider_json) - - if self._download_if_needed and mtime: - headers['if-modified-since'] = mtime - - uri = "https://%s/%s" % (self._domain, "provider.json") - verify = not self._bypass_checks - - if mtime: # the provider.json exists - provider_config = ProviderConfig() - provider_config.load(provider_json) - try: - verify = provider_config.get_ca_cert_path() - uri = provider_config.get_api_uri() + '/provider.json' - except MissingCACert: - # get_ca_cert_path fails if the certificate does not exists. - pass - - logger.debug("Requesting for provider.json... " - "uri: {0}, verify: {1}, headers: {2}".format( - uri, verify, headers)) - res = self._session.get(uri, verify=verify, - headers=headers, timeout=REQUEST_TIMEOUT) - res.raise_for_status() - logger.debug("Request status code: {0}".format(res.status_code)) - - # Not modified - if res.status_code == 304: - logger.debug("Provider definition has not been modified") - else: - provider_definition, mtime = get_content(res) - - provider_config = ProviderConfig() - provider_config.load(data=provider_definition, mtime=mtime) - provider_config.save(["leap", - "providers", - 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(SupportedAPIs.SUPPORTED_APIS) - 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. - - :param domain: domain to check - :type domain: str - - :param download_if_needed: if True, makes the checks do not - overwrite already downloaded data - :type download_if_needed: bool - """ - leap_assert(domain and len(domain) > 0, "We need a domain!") - - self._domain = ProviderConfig.sanitize_path_component(domain) - self._download_if_needed = download_if_needed - - cb_chain = [ - (self._check_name_resolution, self.name_resolution), - (self._check_https, self.https_connection), - (self._download_provider_info, self.download_provider_info) - ] - - return self.addCallbackChain(cb_chain) - - def _should_proceed_cert(self): - """ - Returns False if the certificate already exists for the given - provider. True otherwise - - :rtype: bool - """ - leap_assert(self._provider_config, "We need a provider config!") - - if not self._download_if_needed: - return True - - return not os.path.exists(self._provider_config - .get_ca_cert_path(about_to_download=True)) - - def _download_ca_cert(self, *args): - """ - Downloads the CA cert that is going to be used for the api URL - """ - - leap_assert(self._provider_config, "Cannot download the ca cert " - "without a provider config!") - - logger.debug("Downloading ca cert for %s at %s" % - (self._domain, self._provider_config.get_ca_cert_uri())) - - if not self._should_proceed_cert(): - check_and_fix_urw_only( - self._provider_config - .get_ca_cert_path(about_to_download=True)) - return - - res = self._session.get(self._provider_config.get_ca_cert_uri(), - verify=not self._bypass_checks, - timeout=REQUEST_TIMEOUT) - res.raise_for_status() - - cert_path = self._provider_config.get_ca_cert_path( - about_to_download=True) - cert_dir = os.path.dirname(cert_path) - mkdir_p(cert_dir) - with open(cert_path, "w") as f: - f.write(res.content) - - check_and_fix_urw_only(cert_path) - - def _check_ca_fingerprint(self, *args): - """ - Checks the CA cert fingerprint against the one provided in the - json definition - """ - leap_assert(self._provider_config, "Cannot check the ca cert " - "without a provider config!") - - logger.debug("Checking ca fingerprint for %s and cert %s" % - (self._domain, - self._provider_config.get_ca_cert_path())) - - if not self._should_proceed_cert(): - return - - parts = self._provider_config.get_ca_cert_fingerprint().split(":") - - error_msg = "Wrong fingerprint format" - leap_check(len(parts) == 2, error_msg, WrongFingerprint) - - method = parts[0].strip() - fingerprint = parts[1].strip() - cert_data = None - with open(self._provider_config.get_ca_cert_path()) as f: - cert_data = f.read() - - leap_assert(len(cert_data) > 0, "Could not read certificate data") - digest = get_digest(cert_data, method) - - error_msg = "Downloaded certificate has a different fingerprint!" - leap_check(digest == fingerprint, error_msg, WrongFingerprint) - - def _check_api_certificate(self, *args): - """ - Tries to make an API call with the downloaded cert and checks - if it validates against it - """ - leap_assert(self._provider_config, "Cannot check the ca cert " - "without a provider config!") - - logger.debug("Checking api certificate for %s and cert %s" % - (self._provider_config.get_api_uri(), - self._provider_config.get_ca_cert_path())) - - if not self._should_proceed_cert(): - return - - test_uri = "%s/%s/cert" % (self._provider_config.get_api_uri(), - self._provider_config.get_api_version()) - res = self._session.get(test_uri, - verify=self._provider_config - .get_ca_cert_path(), - timeout=REQUEST_TIMEOUT) - res.raise_for_status() - - def run_provider_setup_checks(self, - provider_config, - download_if_needed=False): - """ - Starts the checks needed for a new provider setup. - - :param provider_config: Provider configuration - :type provider_config: ProviderConfig - - :param download_if_needed: if True, makes the checks do not - overwrite already downloaded data. - :type download_if_needed: bool - """ - leap_assert(provider_config, "We need a provider config!") - leap_assert_type(provider_config, ProviderConfig) - - self._provider_config = provider_config - self._download_if_needed = download_if_needed - - cb_chain = [ - (self._download_ca_cert, self.download_ca_cert), - (self._check_ca_fingerprint, self.check_ca_fingerprint), - (self._check_api_certificate, self.check_api_certificate) - ] - - return self.addCallbackChain(cb_chain) diff --git a/src/leap/bitmask/services/eip/tests/test_providerbootstrapper.py b/src/leap/bitmask/services/eip/tests/test_providerbootstrapper.py deleted file mode 100644 index b0685676..00000000 --- a/src/leap/bitmask/services/eip/tests/test_providerbootstrapper.py +++ /dev/null @@ -1,560 +0,0 @@ -# -*- coding: utf-8 -*- -# test_providerbootstrapper.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 . - - -""" -Tests for the Provider Boostrapper checks - -These will be whitebox tests since we want to make sure the private -implementation is checking what we expect. -""" - -import os -import mock -import socket -import stat -import tempfile -import time -import requests -try: - import unittest2 as unittest -except ImportError: - import unittest - -from nose.twistedtools import deferred, reactor -from twisted.internet import threads -from requests.models import Response - -from leap.bitmask.services.eip.providerbootstrapper import ProviderBootstrapper -from leap.bitmask.services.eip.providerbootstrapper import \ - UnsupportedProviderAPI -from leap.bitmask.services.eip.providerbootstrapper import WrongFingerprint -from leap.bitmask.provider.supportedapis import SupportedAPIs -from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.crypto.tests import fake_provider -from leap.common.files import mkdir_p -from leap.common.testing.https_server import where -from leap.common.testing.basetest import BaseLeapTest - - -class ProviderBootstrapperTest(BaseLeapTest): - def setUp(self): - self.pb = ProviderBootstrapper() - - def tearDown(self): - pass - - def test_name_resolution_check(self): - # Something highly likely to success - self.pb._domain = "google.com" - self.pb._check_name_resolution() - # Something highly likely to fail - self.pb._domain = "uquhqweuihowquie.abc.def" - - # In python 2.7.4 raises socket.error - # In python 2.7.5 raises socket.gaierror - with self.assertRaises((socket.gaierror, socket.error)): - self.pb._check_name_resolution() - - @deferred() - def test_run_provider_select_checks(self): - self.pb._check_name_resolution = mock.MagicMock() - self.pb._check_https = mock.MagicMock() - self.pb._download_provider_info = mock.MagicMock() - - d = self.pb.run_provider_select_checks("somedomain") - - def check(*args): - self.pb._check_name_resolution.assert_called_once_with() - self.pb._check_https.assert_called_once_with(None) - self.pb._download_provider_info.assert_called_once_with(None) - d.addCallback(check) - return d - - @deferred() - def test_run_provider_setup_checks(self): - self.pb._download_ca_cert = mock.MagicMock() - self.pb._check_ca_fingerprint = mock.MagicMock() - self.pb._check_api_certificate = mock.MagicMock() - - d = self.pb.run_provider_setup_checks(ProviderConfig()) - - def check(*args): - self.pb._download_ca_cert.assert_called_once_with() - self.pb._check_ca_fingerprint.assert_called_once_with(None) - self.pb._check_api_certificate.assert_called_once_with(None) - d.addCallback(check) - return d - - def test_should_proceed_cert(self): - self.pb._provider_config = mock.Mock() - self.pb._provider_config.get_ca_cert_path = mock.MagicMock( - return_value=where("cacert.pem")) - - self.pb._download_if_needed = False - self.assertTrue(self.pb._should_proceed_cert()) - - self.pb._download_if_needed = True - self.assertFalse(self.pb._should_proceed_cert()) - - self.pb._provider_config.get_ca_cert_path = mock.MagicMock( - return_value=where("somefilethatdoesntexist.pem")) - self.assertTrue(self.pb._should_proceed_cert()) - - def _check_download_ca_cert(self, should_proceed): - """ - Helper to check different paths easily for the download ca - cert check - - :param should_proceed: sets the _should_proceed_cert in the - provider bootstrapper being tested - :type should_proceed: bool - - :returns: The contents of the certificate, the expected - content depending on should_proceed, and the mode of - the file to be checked by the caller - :rtype: tuple of str, str, int - """ - old_content = "NOT THE NEW CERT" - new_content = "NEW CERT" - new_cert_path = os.path.join(tempfile.mkdtemp(), - "mynewcert.pem") - - with open(new_cert_path, "w") as c: - c.write(old_content) - - self.pb._provider_config = mock.Mock() - self.pb._provider_config.get_ca_cert_path = mock.MagicMock( - return_value=new_cert_path) - self.pb._domain = "somedomain" - - self.pb._should_proceed_cert = mock.MagicMock( - return_value=should_proceed) - - read = None - content_to_check = None - mode = None - - with mock.patch('requests.models.Response.content', - new_callable=mock.PropertyMock) as \ - content: - content.return_value = new_content - response_obj = Response() - response_obj.raise_for_status = mock.MagicMock() - - self.pb._session.get = mock.MagicMock(return_value=response_obj) - self.pb._download_ca_cert() - with open(new_cert_path, "r") as nc: - read = nc.read() - if should_proceed: - content_to_check = new_content - else: - content_to_check = old_content - mode = stat.S_IMODE(os.stat(new_cert_path).st_mode) - - os.unlink(new_cert_path) - return read, content_to_check, mode - - def test_download_ca_cert_no_saving(self): - read, expected_read, mode = self._check_download_ca_cert(False) - self.assertEqual(read, expected_read) - self.assertEqual(mode, int("600", 8)) - - def test_download_ca_cert_saving(self): - read, expected_read, mode = self._check_download_ca_cert(True) - self.assertEqual(read, expected_read) - self.assertEqual(mode, int("600", 8)) - - def test_check_ca_fingerprint_skips(self): - self.pb._provider_config = mock.Mock() - self.pb._provider_config.get_ca_cert_fingerprint = mock.MagicMock( - return_value="") - self.pb._domain = "somedomain" - - self.pb._should_proceed_cert = mock.MagicMock(return_value=False) - - self.pb._check_ca_fingerprint() - self.assertFalse(self.pb._provider_config. - get_ca_cert_fingerprint.called) - - def test_check_ca_cert_fingerprint_raises_bad_format(self): - self.pb._provider_config = mock.Mock() - self.pb._provider_config.get_ca_cert_fingerprint = mock.MagicMock( - return_value="wrongfprformat!!") - self.pb._domain = "somedomain" - - self.pb._should_proceed_cert = mock.MagicMock(return_value=True) - - with self.assertRaises(WrongFingerprint): - self.pb._check_ca_fingerprint() - - # This two hashes different in the last byte, but that's good enough - # for the tests - KNOWN_BAD_HASH = "SHA256: 0f17c033115f6b76ff67871872303ff65034efe" \ - "7dd1b910062ca323eb4da5c7f" - KNOWN_GOOD_HASH = "SHA256: 0f17c033115f6b76ff67871872303ff65034ef" \ - "e7dd1b910062ca323eb4da5c7e" - KNOWN_GOOD_CERT = """ ------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----- -""" - - def _prepare_provider_config_with(self, cert_path, cert_hash): - """ - Mocks the provider config to give the cert_path and cert_hash - specified - - :param cert_path: path for the certificate - :type cert_path: str - :param cert_hash: hash for the certificate as it would appear - in the provider config json - :type cert_hash: str - """ - self.pb._provider_config = mock.Mock() - self.pb._provider_config.get_ca_cert_fingerprint = mock.MagicMock( - return_value=cert_hash) - self.pb._provider_config.get_ca_cert_path = mock.MagicMock( - return_value=cert_path) - self.pb._domain = "somedomain" - - def test_check_ca_fingerprint_checksout(self): - cert_path = os.path.join(tempfile.mkdtemp(), - "mynewcert.pem") - - with open(cert_path, "w") as c: - c.write(self.KNOWN_GOOD_CERT) - - self._prepare_provider_config_with(cert_path, self.KNOWN_GOOD_HASH) - - self.pb._should_proceed_cert = mock.MagicMock(return_value=True) - - self.pb._check_ca_fingerprint() - - os.unlink(cert_path) - - def test_check_ca_fingerprint_fails(self): - cert_path = os.path.join(tempfile.mkdtemp(), - "mynewcert.pem") - - with open(cert_path, "w") as c: - c.write(self.KNOWN_GOOD_CERT) - - self._prepare_provider_config_with(cert_path, self.KNOWN_BAD_HASH) - - self.pb._should_proceed_cert = mock.MagicMock(return_value=True) - - with self.assertRaises(WrongFingerprint): - self.pb._check_ca_fingerprint() - - os.unlink(cert_path) - - -############################################################################### -# Tests with a fake provider # -############################################################################### - -class ProviderBootstrapperActiveTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - factory = fake_provider.get_provider_factory() - http = reactor.listenTCP(8002, factory) - https = reactor.listenSSL( - 0, factory, - fake_provider.OpenSSLServerContextFactory()) - get_port = lambda p: p.getHost().port - cls.http_port = get_port(http) - cls.https_port = get_port(https) - - def setUp(self): - self.pb = ProviderBootstrapper() - - # At certain points we are going to be replacing these methods - # directly in ProviderConfig to be able to catch calls from - # new ProviderConfig objects inside the methods tested. We - # need to save the old implementation and restore it in - # tearDown so we are sure everything is as expected for each - # test. If we do it inside each specific test, a failure in - # the test will leave the implementation with the mock. - self.old_gpp = ProviderConfig.get_path_prefix - self.old_load = ProviderConfig.load - self.old_save = ProviderConfig.save - self.old_api_version = ProviderConfig.get_api_version - - def tearDown(self): - ProviderConfig.get_path_prefix = self.old_gpp - ProviderConfig.load = self.old_load - ProviderConfig.save = self.old_save - ProviderConfig.get_api_version = self.old_api_version - - def test_check_https_succeeds(self): - # XXX: Need a proper CA signed cert to test this - pass - - @deferred() - def test_check_https_fails(self): - self.pb._domain = "localhost:%s" % (self.https_port,) - - def check(*args): - with self.assertRaises(requests.exceptions.SSLError): - self.pb._check_https() - return threads.deferToThread(check) - - @deferred() - def test_second_check_https_fails(self): - self.pb._domain = "localhost:1234" - - def check(*args): - with self.assertRaises(Exception): - self.pb._check_https() - return threads.deferToThread(check) - - @deferred() - def test_check_https_succeeds_if_danger(self): - self.pb._domain = "localhost:%s" % (self.https_port,) - self.pb._bypass_checks = True - - def check(*args): - self.pb._check_https() - - return threads.deferToThread(check) - - def _setup_provider_config_with(self, api, path_prefix): - """ - Sets up the ProviderConfig with mocks for the path prefix, the - api returned and load/save methods. - It modifies ProviderConfig directly instead of an object - because the object used is created in the method itself and we - cannot control that. - - :param api: API to return - :type api: str - :param path_prefix: path prefix to be used when calculating - paths - :type path_prefix: str - """ - ProviderConfig.get_path_prefix = mock.MagicMock( - return_value=path_prefix) - ProviderConfig.get_api_version = mock.MagicMock( - return_value=api) - ProviderConfig.load = mock.MagicMock() - ProviderConfig.save = mock.MagicMock() - - def _setup_providerbootstrapper(self, ifneeded): - """ - Sets the provider bootstrapper's domain to - localhost:https_port, sets it to bypass https checks and sets - the download if needed based on the ifneeded value. - - :param ifneeded: Value for _download_if_needed - :type ifneeded: bool - """ - self.pb._domain = "localhost:%s" % (self.https_port,) - self.pb._bypass_checks = True - self.pb._download_if_needed = ifneeded - - def _produce_dummy_provider_json(self): - """ - Creates a dummy provider json on disk in order to test - behaviour around it (download if newer online, etc) - - :returns: the provider.json path used - :rtype: str - """ - provider_dir = os.path.join(ProviderConfig() - .get_path_prefix(), - "leap", - "providers", - self.pb._domain) - mkdir_p(provider_dir) - provider_path = os.path.join(provider_dir, - "provider.json") - - with open(provider_path, "w") as p: - p.write("A") - return provider_path - - def test_download_provider_info_new_provider(self): - self._setup_provider_config_with("1", tempfile.mkdtemp()) - self._setup_providerbootstrapper(True) - - self.pb._download_provider_info() - self.assertTrue(ProviderConfig.save.called) - - @mock.patch( - 'leap.bitmask.config.providerconfig.ProviderConfig.get_ca_cert_path', - lambda x: where('cacert.pem')) - def test_download_provider_info_not_modified(self): - self._setup_provider_config_with("1", tempfile.mkdtemp()) - self._setup_providerbootstrapper(True) - provider_path = self._produce_dummy_provider_json() - - # set mtime to something really new - os.utime(provider_path, (-1, time.time())) - - with mock.patch.object( - ProviderConfig, 'get_api_uri', - return_value="https://localhost:%s" % (self.https_port,)): - self.pb._download_provider_info() - # we check that it doesn't save the provider - # config, because it's new enough - self.assertFalse(ProviderConfig.save.called) - - @mock.patch( - 'leap.bitmask.config.providerconfig.ProviderConfig.get_domain', - lambda x: where('testdomain.com')) - def test_download_provider_info_not_modified_and_no_cacert(self): - self._setup_provider_config_with("1", tempfile.mkdtemp()) - self._setup_providerbootstrapper(True) - provider_path = self._produce_dummy_provider_json() - - # set mtime to something really new - os.utime(provider_path, (-1, time.time())) - - with mock.patch.object( - ProviderConfig, 'get_api_uri', - return_value="https://localhost:%s" % (self.https_port,)): - self.pb._download_provider_info() - # we check that it doesn't save the provider - # config, because it's new enough - self.assertFalse(ProviderConfig.save.called) - - @mock.patch( - 'leap.bitmask.config.providerconfig.ProviderConfig.get_ca_cert_path', - lambda x: where('cacert.pem')) - def test_download_provider_info_modified(self): - self._setup_provider_config_with("1", tempfile.mkdtemp()) - self._setup_providerbootstrapper(True) - provider_path = self._produce_dummy_provider_json() - - # set mtime to something really old - os.utime(provider_path, (-1, 100)) - - with mock.patch.object( - ProviderConfig, 'get_api_uri', - return_value="https://localhost:%s" % (self.https_port,)): - self.pb._download_provider_info() - self.assertTrue(ProviderConfig.load.called) - self.assertTrue(ProviderConfig.save.called) - - @mock.patch( - 'leap.bitmask.config.providerconfig.ProviderConfig.get_ca_cert_path', - lambda x: where('cacert.pem')) - def test_download_provider_info_unsupported_api_raises(self): - self._setup_provider_config_with("9999999", tempfile.mkdtemp()) - self._setup_providerbootstrapper(False) - self._produce_dummy_provider_json() - - with mock.patch.object( - ProviderConfig, 'get_api_uri', - return_value="https://localhost:%s" % (self.https_port,)): - with self.assertRaises(UnsupportedProviderAPI): - self.pb._download_provider_info() - - @mock.patch( - '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], - tempfile.mkdtemp()) - self._setup_providerbootstrapper(False) - self._produce_dummy_provider_json() - - with mock.patch.object( - ProviderConfig, 'get_api_uri', - return_value="https://localhost:%s" % (self.https_port,)): - self.pb._download_provider_info() - - @mock.patch( - 'leap.bitmask.config.providerconfig.ProviderConfig.get_api_uri', - lambda x: 'api.uri') - @mock.patch( - 'leap.bitmask.config.providerconfig.ProviderConfig.get_ca_cert_path', - lambda x: '/cert/path') - def test_check_api_certificate_skips(self): - self.pb._provider_config = ProviderConfig() - self.pb._session.get = mock.MagicMock(return_value=Response()) - - self.pb._should_proceed_cert = mock.MagicMock(return_value=False) - self.pb._check_api_certificate() - self.assertFalse(self.pb._session.get.called) - - @deferred() - def test_check_api_certificate_fails(self): - self.pb._provider_config = ProviderConfig() - self.pb._provider_config.get_api_uri = mock.MagicMock( - return_value="https://localhost:%s" % (self.https_port,)) - self.pb._provider_config.get_ca_cert_path = mock.MagicMock( - return_value=os.path.join( - os.path.split(__file__)[0], - "wrongcert.pem")) - self.pb._provider_config.get_api_version = mock.MagicMock( - return_value="1") - - self.pb._should_proceed_cert = mock.MagicMock(return_value=True) - - def check(*args): - with self.assertRaises(requests.exceptions.SSLError): - self.pb._check_api_certificate() - d = threads.deferToThread(check) - return d - - @deferred() - def test_check_api_certificate_succeeds(self): - self.pb._provider_config = ProviderConfig() - self.pb._provider_config.get_api_uri = mock.MagicMock( - return_value="https://localhost:%s" % (self.https_port,)) - self.pb._provider_config.get_ca_cert_path = mock.MagicMock( - return_value=where('cacert.pem')) - self.pb._provider_config.get_api_version = mock.MagicMock( - return_value="1") - - self.pb._should_proceed_cert = mock.MagicMock(return_value=True) - - def check(*args): - self.pb._check_api_certificate() - d = threads.deferToThread(check) - return d -- cgit v1.2.3 From 9c8c949f776d3a0377d5112e34a05499d2c97bd3 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sat, 28 Sep 2013 12:11:23 -0400 Subject: increase timeout, getting many timeouts for european providers --- src/leap/bitmask/util/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leap/bitmask/util/constants.py b/src/leap/bitmask/util/constants.py index 63f6b1f7..e6a6bdce 100644 --- a/src/leap/bitmask/util/constants.py +++ b/src/leap/bitmask/util/constants.py @@ -16,4 +16,4 @@ # along with this program. If not, see . SIGNUP_TIMEOUT = 5 -REQUEST_TIMEOUT = 10 +REQUEST_TIMEOUT = 15 -- cgit v1.2.3 From 37e27011b7bd8d5fe7825b1757f0b381fe8467e5 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sat, 28 Sep 2013 12:19:10 -0400 Subject: add changes On branch feature/provider_check_against_ca_bundle --- changes/feature_provider-check-against-ca-bundle | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changes/feature_provider-check-against-ca-bundle diff --git a/changes/feature_provider-check-against-ca-bundle b/changes/feature_provider-check-against-ca-bundle new file mode 100644 index 00000000..b3f9042f --- /dev/null +++ b/changes/feature_provider-check-against-ca-bundle @@ -0,0 +1,2 @@ + o Make the initial provider cert verifications against our modified + CA-bundle (includes ca-cert certificates, for now). Closes: #3850 -- cgit v1.2.3 From 05ec95fda3a10488ee29904668c1ef56102db0a9 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 26 Sep 2013 17:12:19 -0300 Subject: Split vpnaunchers by platform, refactor some code. --- src/leap/bitmask/services/eip/darwinvpnlauncher.py | 190 ++++++++++++++ src/leap/bitmask/services/eip/linuxvpnlauncher.py | 232 +++++++++++++++++ src/leap/bitmask/services/eip/vpnlauncher.py | 290 +++++++++++++++++++++ .../bitmask/services/eip/windowsvpnlauncher.py | 69 +++++ 4 files changed, 781 insertions(+) create mode 100644 src/leap/bitmask/services/eip/darwinvpnlauncher.py create mode 100644 src/leap/bitmask/services/eip/linuxvpnlauncher.py create mode 100644 src/leap/bitmask/services/eip/vpnlauncher.py create mode 100644 src/leap/bitmask/services/eip/windowsvpnlauncher.py diff --git a/src/leap/bitmask/services/eip/darwinvpnlauncher.py b/src/leap/bitmask/services/eip/darwinvpnlauncher.py new file mode 100644 index 00000000..f3b6bfc8 --- /dev/null +++ b/src/leap/bitmask/services/eip/darwinvpnlauncher.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- +# darwinvpnlauncher.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 . +""" +Darwin VPN launcher implementation. +""" +import commands +import getpass +import logging +import os + +from leap.bitmask.services.eip.vpnlauncher import VPNLauncher +from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException +from leap.bitmask.util import get_path_prefix + +logger = logging.getLogger(__name__) + + +class EIPNoTunKextLoaded(VPNLauncherException): + pass + + +class DarwinVPNLauncher(VPNLauncher): + """ + VPN launcher for the Darwin Platform + """ + COCOASUDO = "cocoasudo" + # XXX need the good old magic translate for these strings + # (look for magic in 0.2.0 release) + SUDO_MSG = ("Bitmask needs administrative privileges to run " + "Encrypted Internet.") + INSTALL_MSG = ("\"Bitmask needs administrative privileges to install " + "missing scripts and fix permissions.\"") + + INSTALL_PATH = os.path.realpath(os.getcwd() + "/../../") + INSTALL_PATH_ESCAPED = os.path.realpath(os.getcwd() + "/../../") + OPENVPN_BIN = 'openvpn.leap' + OPENVPN_PATH = "%s/Contents/Resources/openvpn" % (INSTALL_PATH,) + OPENVPN_PATH_ESCAPED = "%s/Contents/Resources/openvpn" % ( + INSTALL_PATH_ESCAPED,) + + UP_SCRIPT = "%s/client.up.sh" % (OPENVPN_PATH,) + DOWN_SCRIPT = "%s/client.down.sh" % (OPENVPN_PATH,) + OPENVPN_DOWN_PLUGIN = '%s/openvpn-down-root.so' % (OPENVPN_PATH,) + + UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT, OPENVPN_DOWN_PLUGIN) + OTHER_FILES = [] + + @classmethod + def cmd_for_missing_scripts(kls, frompath): + """ + Returns a command that can copy the missing scripts. + :rtype: str + """ + to = kls.OPENVPN_PATH_ESCAPED + + cmd = "#!/bin/sh\n" + cmd += "mkdir -p {0}\n".format(to) + cmd += "cp '{0}'/* {1}\n".format(frompath, to) + cmd += "chmod 744 {0}/*".format(to) + + return cmd + + @classmethod + def is_kext_loaded(kls): + """ + Checks if the needed kext is loaded before launching openvpn. + + :returns: True if kext is loaded, False otherwise. + :rtype: bool + """ + return bool(commands.getoutput('kextstat | grep "leap.tun"')) + + @classmethod + def _get_icon_path(kls): + """ + Returns the absolute path to the app icon. + + :rtype: str + """ + resources_path = os.path.abspath( + os.path.join(os.getcwd(), "../../Contents/Resources")) + + return os.path.join(resources_path, "leap-client.tiff") + + @classmethod + def get_cocoasudo_ovpn_cmd(kls): + """ + Returns a string with the cocoasudo command needed to run openvpn + as admin with a nice password prompt. The actual command needs to be + appended. + + :rtype: (str, list) + """ + # TODO add translation support for this + sudo_msg = ("Bitmask needs administrative privileges to run " + "Encrypted Internet.") + iconpath = kls._get_icon_path() + has_icon = os.path.isfile(iconpath) + args = ["--icon=%s" % iconpath] if has_icon else [] + args.append("--prompt=%s" % (sudo_msg,)) + + return kls.COCOASUDO, args + + @classmethod + def get_cocoasudo_installmissing_cmd(kls): + """ + Returns a string with the cocoasudo command needed to install missing + files as admin with a nice password prompt. The actual command needs to + be appended. + + :rtype: (str, list) + """ + # TODO add translation support for this + install_msg = ('"Bitmask needs administrative privileges to install ' + 'missing scripts and fix permissions."') + iconpath = kls._get_icon_path() + has_icon = os.path.isfile(iconpath) + args = ["--icon=%s" % iconpath] if has_icon else [] + args.append("--prompt=%s" % (install_msg,)) + + return kls.COCOASUDO, args + + @classmethod + def get_vpn_command(kls, eipconfig, providerconfig, socket_host, + socket_port="unix", openvpn_verb=1): + """ + Returns the OSX implementation for the vpn launching command. + + Might raise: + EIPNoTunKextLoaded, + OpenVPNNotFoundException, + VPNLauncherException. + + :param eipconfig: eip configuration object + :type eipconfig: EIPConfig + :param providerconfig: provider specific configuration + :type providerconfig: ProviderConfig + :param socket_host: either socket path (unix) or socket IP + :type socket_host: str + :param socket_port: either string "unix" if it's a unix socket, + or port otherwise + :type socket_port: str + :param openvpn_verb: the openvpn verbosity wanted + :type openvpn_verb: int + + :return: A VPN command ready to be launched. + :rtype: list + """ + if not kls.is_kext_loaded(): + raise EIPNoTunKextLoaded + + # we use `super` in order to send the class to use + command = super(DarwinVPNLauncher, kls).get_vpn_command( + eipconfig, providerconfig, socket_host, socket_port, openvpn_verb) + + cocoa, cargs = kls.get_cocoasudo_ovpn_cmd() + cargs.extend(command) + command = cargs + command.insert(0, cocoa) + + command.extend(['--setenv', "LEAPUSER", getpass.getuser()]) + + return command + + @classmethod + def get_vpn_env(kls): + """ + Returns a dictionary with the custom env for the platform. + This is mainly used for setting LD_LIBRARY_PATH to the correct + path when distributing a standalone client + + :rtype: dict + """ + return { + "DYLD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib") + } diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py new file mode 100644 index 00000000..c2c28627 --- /dev/null +++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py @@ -0,0 +1,232 @@ +# -*- coding: utf-8 -*- +# linuxvpnlauncher.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 . +""" +Linux VPN launcher implementation. +""" +import commands +import logging +import os +import subprocess +import time + +from leap.bitmask.config import flags +from leap.bitmask.util import privilege_policies +from leap.bitmask.util.privilege_policies import LinuxPolicyChecker +from leap.common.files import which +from leap.bitmask.services.eip.vpnlauncher import VPNLauncher +from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException +from leap.bitmask.util import get_path_prefix +from leap.common.check import leap_assert +from leap.bitmask.util import first + +logger = logging.getLogger(__name__) + + +class EIPNoPolkitAuthAgentAvailable(VPNLauncherException): + pass + + +class EIPNoPkexecAvailable(VPNLauncherException): + pass + + +def _is_pkexec_in_system(): + """ + Checks the existence of the pkexec binary in system. + """ + pkexec_path = which('pkexec') + if len(pkexec_path) == 0: + return False + return True + + +def _is_auth_agent_running(): + """ + Checks if a polkit daemon is running. + + :return: True if it's running, False if it's not. + :rtype: boolean + """ + ps = 'ps aux | grep polkit-%s-authentication-agent-1' + opts = (ps % case for case in ['[g]nome', '[k]de']) + is_running = map(lambda l: commands.getoutput(l), opts) + return any(is_running) + + +def _try_to_launch_agent(): + """ + Tries to launch a polkit daemon. + """ + env = None + if flags.STANDALONE is True: + env = {"PYTHONPATH": os.path.abspath('../../../../lib/')} + try: + # We need to quote the command because subprocess call + # will do "sh -c 'foo'", so if we do not quoute it we'll end + # up with a invocation to the python interpreter. And that + # is bad. + subprocess.call(["python -m leap.bitmask.util.polkit_agent"], + shell=True, env=env) + except Exception as exc: + logger.exception(exc) + + +class LinuxVPNLauncher(VPNLauncher): + PKEXEC_BIN = 'pkexec' + OPENVPN_BIN = 'openvpn' + OPENVPN_BIN_PATH = os.path.join( + get_path_prefix(), "..", "apps", "eip", OPENVPN_BIN) + + SYSTEM_CONFIG = "/etc/leap" + UP_DOWN_FILE = "resolv-update" + UP_DOWN_PATH = "%s/%s" % (SYSTEM_CONFIG, UP_DOWN_FILE) + + # We assume this is there by our openvpn dependency, and + # we will put it there on the bundle too. + # TODO adapt to the bundle path. + OPENVPN_DOWN_ROOT_BASE = "/usr/lib/openvpn/" + OPENVPN_DOWN_ROOT_FILE = "openvpn-plugin-down-root.so" + OPENVPN_DOWN_ROOT_PATH = "%s/%s" % ( + OPENVPN_DOWN_ROOT_BASE, + OPENVPN_DOWN_ROOT_FILE) + + UP_SCRIPT = DOWN_SCRIPT = UP_DOWN_PATH + UPDOWN_FILES = (UP_DOWN_PATH,) + POLKIT_PATH = LinuxPolicyChecker.get_polkit_path() + OTHER_FILES = (POLKIT_PATH, ) + + @classmethod + def maybe_pkexec(kls): + """ + Checks whether pkexec is available in the system, and + returns the path if found. + + Might raise: + EIPNoPkexecAvailable, + EIPNoPolkitAuthAgentAvailable. + + :returns: a list of the paths where pkexec is to be found + :rtype: list + """ + if _is_pkexec_in_system(): + if not _is_auth_agent_running(): + _try_to_launch_agent() + time.sleep(0.5) + if _is_auth_agent_running(): + pkexec_possibilities = which(kls.PKEXEC_BIN) + leap_assert(len(pkexec_possibilities) > 0, + "We couldn't find pkexec") + return pkexec_possibilities + else: + logger.warning("No polkit auth agent found. pkexec " + + "will use its own auth agent.") + raise EIPNoPolkitAuthAgentAvailable() + else: + logger.warning("System has no pkexec") + raise EIPNoPkexecAvailable() + + @classmethod + def missing_other_files(kls): + """ + 'Extend' the VPNLauncher's missing_other_files to check if the polkit + files is outdated. If the polkit file that is in OTHER_FILES exists but + is not up to date, it is added to the missing list. + + :returns: a list of missing files + :rtype: list of str + """ + # we use `super` in order to send the class to use + missing = super(LinuxVPNLauncher, kls).missing_other_files() + polkit_file = LinuxPolicyChecker.get_polkit_path() + if polkit_file not in missing: + if privilege_policies.is_policy_outdated(kls.OPENVPN_BIN_PATH): + missing.append(polkit_file) + + return missing + + @classmethod + def get_vpn_command(kls, eipconfig, providerconfig, socket_host, + socket_port="unix", openvpn_verb=1): + """ + Returns the Linux implementation for the vpn launching command. + + Might raise: + EIPNoPkexecAvailable, + EIPNoPolkitAuthAgentAvailable, + OpenVPNNotFoundException, + VPNLauncherException. + + :param eipconfig: eip configuration object + :type eipconfig: EIPConfig + :param providerconfig: provider specific configuration + :type providerconfig: ProviderConfig + :param socket_host: either socket path (unix) or socket IP + :type socket_host: str + :param socket_port: either string "unix" if it's a unix socket, + or port otherwise + :type socket_port: str + :param openvpn_verb: the openvpn verbosity wanted + :type openvpn_verb: int + + :return: A VPN command ready to be launched. + :rtype: list + """ + # we use `super` in order to send the class to use + command = super(LinuxVPNLauncher, kls).get_vpn_command( + eipconfig, providerconfig, socket_host, socket_port, openvpn_verb) + + pkexec = kls.maybe_pkexec() + if pkexec: + command.insert(0, first(pkexec)) + + return command + + @classmethod + def cmd_for_missing_scripts(kls, frompath, pol_file): + """ + Returns a sh script that can copy the missing files. + + :param frompath: The path where the up/down scripts live + :type frompath: str + :param pol_file: The path where the dynamically generated + policy file lives + :type pol_file: str + + :rtype: str + """ + to = kls.SYSTEM_CONFIG + + cmd = '#!/bin/sh\n' + cmd += 'mkdir -p "%s"\n' % (to, ) + cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.UP_DOWN_FILE, to) + cmd += 'cp "%s" "%s"\n' % (pol_file, kls.POLKIT_PATH) + cmd += 'chmod 644 "%s"\n' % (kls.POLKIT_PATH, ) + + return cmd + + @classmethod + def get_vpn_env(kls): + """ + Returns a dictionary with the custom env for the platform. + This is mainly used for setting LD_LIBRARY_PATH to the correct + path when distributing a standalone client + + :rtype: dict + """ + return { + "LD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib") + } diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py new file mode 100644 index 00000000..935d75f1 --- /dev/null +++ b/src/leap/bitmask/services/eip/vpnlauncher.py @@ -0,0 +1,290 @@ +# -*- coding: utf-8 -*- +# vpnlauncher.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 . +""" +Platform independant VPN launcher interface. +""" +import getpass +import logging +import os +import stat + +from abc import ABCMeta, abstractmethod +from functools import partial + +from leap.bitmask.config import flags +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 +from leap.bitmask.util import get_path_prefix +from leap.common.check import leap_assert, leap_assert_type +from leap.common.files import which + +logger = logging.getLogger(__name__) + + +class VPNLauncherException(Exception): + pass + + +class OpenVPNNotFoundException(VPNLauncherException): + pass + + +def _has_updown_scripts(path, warn=True): + """ + Checks the existence of the up/down scripts and its + exec bit if applicable. + + :param path: the path to be checked + :type path: str + + :param warn: whether we should log the absence + :type warn: bool + + :rtype: bool + """ + is_file = os.path.isfile(path) + if warn and not is_file: + logger.error("Could not find up/down script %s. " + "Might produce DNS leaks." % (path,)) + + # XXX check if applies in win + is_exe = False + try: + is_exe = (stat.S_IXUSR & os.stat(path)[stat.ST_MODE] != 0) + except OSError as e: + logger.warn("%s" % (e,)) + if warn and not is_exe: + logger.error("Up/down script %s is not executable. " + "Might produce DNS leaks." % (path,)) + return is_file and is_exe + + +def _has_other_files(path, warn=True): + """ + Checks the existence of other important files. + + :param path: the path to be checked + :type path: str + + :param warn: whether we should log the absence + :type warn: bool + + :rtype: bool + """ + is_file = os.path.isfile(path) + if warn and not is_file: + logger.warning("Could not find file during checks: %s. " % ( + path,)) + return is_file + + +class VPNLauncher(object): + """ + Abstract launcher class + """ + __metaclass__ = ABCMeta + + UPDOWN_FILES = None + OTHER_FILES = None + + @classmethod + @abstractmethod + def get_vpn_command(kls, eipconfig, providerconfig, + socket_host, socket_port, openvpn_verb=1): + """ + Returns the platform dependant vpn launching command + + Might raise: + OpenVPNNotFoundException, + VPNLauncherException. + + :param eipconfig: eip configuration object + :type eipconfig: EIPConfig + :param providerconfig: provider specific configuration + :type providerconfig: ProviderConfig + :param socket_host: either socket path (unix) or socket IP + :type socket_host: str + :param socket_port: either string "unix" if it's a unix socket, + or port otherwise + :type socket_port: str + :param openvpn_verb: the openvpn verbosity wanted + :type openvpn_verb: int + + :return: A VPN command ready to be launched. + :rtype: list + """ + leap_assert_type(eipconfig, EIPConfig) + leap_assert_type(providerconfig, ProviderConfig) + + kwargs = {} + if flags.STANDALONE: + kwargs['path_extension'] = os.path.join( + get_path_prefix(), "..", "apps", "eip") + + openvpn_possibilities = which(kls.OPENVPN_BIN, **kwargs) + if len(openvpn_possibilities) == 0: + raise OpenVPNNotFoundException() + + openvpn = first(openvpn_possibilities) + args = [] + + args += [ + '--setenv', "LEAPOPENVPN", "1" + ] + + if openvpn_verb is not None: + args += ['--verb', '%d' % (openvpn_verb,)] + + gateways = [] + leap_settings = LeapSettings() + domain = providerconfig.get_domain() + gateway_conf = leap_settings.get_selected_gateway(domain) + + 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!') + raise VPNLauncherException(kls.tr('No gateway was found!')) + + logger.debug("Using gateways ips: {0}".format(', '.join(gateways))) + + for gw in gateways: + args += ['--remote', gw, '1194', 'udp'] + + args += [ + '--client', + '--dev', 'tun', + ############################################################## + # persist-tun makes ping-restart fail because it leaves a + # broken routing table + ############################################################## + # '--persist-tun', + '--persist-key', + '--tls-client', + '--remote-cert-tls', + 'server' + ] + + openvpn_configuration = eipconfig.get_openvpn_configuration() + for key, value in openvpn_configuration.items(): + args += ['--%s' % (key,), value] + + user = getpass.getuser() + + ############################################################## + # The down-root plugin fails in some situations, so we don't + # drop privs for the time being + ############################################################## + # args += [ + # '--user', user, + # '--group', grp.getgrgid(os.getgroups()[-1]).gr_name + # ] + + if socket_port == "unix": # that's always the case for linux + args += [ + '--management-client-user', user + ] + + args += [ + '--management-signal', + '--management', socket_host, socket_port, + '--script-security', '2' + ] + + if _has_updown_scripts(kls.UP_SCRIPT): + args += [ + '--up', '\"%s\"' % (kls.UP_SCRIPT,), + ] + + if _has_updown_scripts(kls.DOWN_SCRIPT): + args += [ + '--down', '\"%s\"' % (kls.DOWN_SCRIPT,) + ] + + ########################################################### + # For the time being we are disabling the usage of the + # down-root plugin, because it doesn't quite work as + # expected (i.e. it doesn't run route -del as root + # when finishing, so it fails to properly + # restart/quit) + ########################################################### + # if _has_updown_scripts(kls.OPENVPN_DOWN_PLUGIN): + # args += [ + # '--plugin', kls.OPENVPN_DOWN_ROOT, + # '\'%s\'' % kls.DOWN_SCRIPT # for OSX + # '\'script_type=down %s\'' % kls.DOWN_SCRIPT # for Linux + # ] + + args += [ + '--cert', eipconfig.get_client_cert_path(providerconfig), + '--key', eipconfig.get_client_cert_path(providerconfig), + '--ca', providerconfig.get_ca_cert_path() + ] + + command_and_args = [openvpn] + args + logger.debug("Running VPN with command:") + logger.debug(" ".join(command_and_args)) + + return command_and_args + + @classmethod + def get_vpn_env(kls): + """ + Returns a dictionary with the custom env for the platform. + This is mainly used for setting LD_LIBRARY_PATH to the correct + path when distributing a standalone client + + :rtype: dict + """ + return {} + + @classmethod + def missing_updown_scripts(kls): + """ + Returns what updown scripts are missing. + + :rtype: list + """ + leap_assert(kls.UPDOWN_FILES is not None, + "Need to define UPDOWN_FILES for this particular " + "launcher before calling this method") + file_exist = partial(_has_updown_scripts, warn=False) + zipped = zip(kls.UPDOWN_FILES, map(file_exist, kls.UPDOWN_FILES)) + missing = filter(lambda (path, exists): exists is False, zipped) + return [path for path, exists in missing] + + @classmethod + def missing_other_files(kls): + """ + Returns what other important files are missing during startup. + Same as missing_updown_scripts but does not check for exec bit. + + :rtype: list + """ + leap_assert(kls.OTHER_FILES is not None, + "Need to define OTHER_FILES for this particular " + "auncher before calling this method") + file_exist = partial(_has_other_files, warn=False) + zipped = zip(kls.OTHER_FILES, map(file_exist, kls.OTHER_FILES)) + missing = filter(lambda (path, exists): exists is False, zipped) + return [path for path, exists in missing] diff --git a/src/leap/bitmask/services/eip/windowsvpnlauncher.py b/src/leap/bitmask/services/eip/windowsvpnlauncher.py new file mode 100644 index 00000000..3f1ed43b --- /dev/null +++ b/src/leap/bitmask/services/eip/windowsvpnlauncher.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# windowsvpnlauncher.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 . +""" +Windows VPN launcher implementation. +""" +import logging + +from leap.bitmask.services.eip.vpnlauncher import VPNLauncher +from leap.common.check import leap_assert + +logger = logging.getLogger(__name__) + + +class WindowsVPNLauncher(VPNLauncher): + """ + VPN launcher for the Windows platform + """ + + OPENVPN_BIN = 'openvpn_leap.exe' + + # XXX UPDOWN_FILES ... we do not have updown files defined yet! + # (and maybe we won't) + @classmethod + def get_vpn_command(kls, eipconfig, providerconfig, socket_host, + socket_port="9876", openvpn_verb=1): + """ + Returns the Windows implementation for the vpn launching command. + + Might raise: + OpenVPNNotFoundException, + VPNLauncherException. + + :param eipconfig: eip configuration object + :type eipconfig: EIPConfig + :param providerconfig: provider specific configuration + :type providerconfig: ProviderConfig + :param socket_host: either socket path (unix) or socket IP + :type socket_host: str + :param socket_port: either string "unix" if it's a unix socket, + or port otherwise + :type socket_port: str + :param openvpn_verb: the openvpn verbosity wanted + :type openvpn_verb: int + + :return: A VPN command ready to be launched. + :rtype: list + """ + leap_assert(socket_port != "unix", + "We cannot use unix sockets in windows!") + + # we use `super` in order to send the class to use + command = super(WindowsVPNLauncher, kls).get_vpn_command( + eipconfig, providerconfig, socket_host, socket_port, openvpn_verb) + + return command -- cgit v1.2.3 From 310b49e1ce5b8adb45f86be82a9f886b2ae4715e Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 26 Sep 2013 17:57:37 -0300 Subject: Replace launcher with new implementation. --- src/leap/bitmask/gui/mainwindow.py | 10 +- src/leap/bitmask/platform_init/initializers.py | 10 +- src/leap/bitmask/services/eip/__init__.py | 27 +- src/leap/bitmask/services/eip/vpnlaunchers.py | 965 ------------------------- src/leap/bitmask/services/eip/vpnprocess.py | 4 +- 5 files changed, 39 insertions(+), 977 deletions(-) delete mode 100644 src/leap/bitmask/services/eip/vpnlaunchers.py diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 200d68aa..6022210c 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -53,12 +53,12 @@ from leap.bitmask.services.eip.vpnprocess import VPN from leap.bitmask.services.eip.vpnprocess import OpenVPNAlreadyRunning from leap.bitmask.services.eip.vpnprocess import AlienOpenVPNAlreadyRunning -from leap.bitmask.services.eip.vpnlaunchers import VPNLauncherException -from leap.bitmask.services.eip.vpnlaunchers import OpenVPNNotFoundException -from leap.bitmask.services.eip.vpnlaunchers import EIPNoPkexecAvailable -from leap.bitmask.services.eip.vpnlaunchers import \ +from leap.bitmask.services.eip.vpnlauncher import VPNLauncherException +from leap.bitmask.services.eip.vpnlauncher import OpenVPNNotFoundException +from leap.bitmask.services.eip.linuxvpnlauncher import EIPNoPkexecAvailable +from leap.bitmask.services.eip.linuxvpnlauncher import \ EIPNoPolkitAuthAgentAvailable -from leap.bitmask.services.eip.vpnlaunchers import EIPNoTunKextLoaded +from leap.bitmask.services.eip.darwinvpnlauncher import EIPNoTunKextLoaded from leap.bitmask.util.keyring_helpers import has_keyring from leap.bitmask.util.leap_log_handler import LeapLogHandler diff --git a/src/leap/bitmask/platform_init/initializers.py b/src/leap/bitmask/platform_init/initializers.py index 831c6a1c..d93efbc6 100644 --- a/src/leap/bitmask/platform_init/initializers.py +++ b/src/leap/bitmask/platform_init/initializers.py @@ -29,7 +29,9 @@ import tempfile from PySide import QtGui from leap.bitmask.config.leapsettings import LeapSettings -from leap.bitmask.services.eip import vpnlaunchers +from leap.bitmask.services.eip import get_vpn_launcher +from leap.bitmask.services.eip.linuxvpnlauncher import LinuxVPNLauncher +from leap.bitmask.services.eip.darwinvpnlauncher import DarwinVPNLauncher from leap.bitmask.util import first from leap.bitmask.util import privilege_policies @@ -106,7 +108,7 @@ def check_missing(): config = LeapSettings() alert_missing = config.get_alert_missing_scripts() - launcher = vpnlaunchers.get_platform_launcher() + launcher = get_vpn_launcher() missing_scripts = launcher.missing_updown_scripts missing_other = launcher.missing_other_files @@ -251,7 +253,7 @@ def _darwin_install_missing_scripts(badexec, notfound): "..", "Resources", "openvpn") - launcher = vpnlaunchers.DarwinVPNLauncher + launcher = DarwinVPNLauncher if os.path.isdir(installer_path): fd, tempscript = tempfile.mkstemp(prefix="leap_installer-") @@ -356,7 +358,7 @@ def _linux_install_missing_scripts(badexec, notfound): """ success = False installer_path = os.path.join(os.getcwd(), "apps", "eip", "files") - launcher = vpnlaunchers.LinuxVPNLauncher + launcher = LinuxVPNLauncher # XXX refactor with darwin, same block. diff --git a/src/leap/bitmask/services/eip/__init__.py b/src/leap/bitmask/services/eip/__init__.py index dd010027..6030cac3 100644 --- a/src/leap/bitmask/services/eip/__init__.py +++ b/src/leap/bitmask/services/eip/__init__.py @@ -20,7 +20,11 @@ leap.bitmask.services.eip module initialization import os import tempfile -from leap.bitmask.platform_init import IS_WIN +from leap.bitmask.services.eip.darwinvpnlauncher import DarwinVPNLauncher +from leap.bitmask.services.eip.linuxvpnlauncher import LinuxVPNLauncher +from leap.bitmask.services.eip.windowsvpnlauncher import WindowsVPNLauncher +from leap.bitmask.platform_init import IS_LINUX, IS_MAC, IS_WIN +from leap.common.check import leap_assert def get_openvpn_management(): @@ -40,3 +44,24 @@ def get_openvpn_management(): port = "unix" return host, port + + +def get_vpn_launcher(): + """ + Return the VPN launcher for the current platform. + """ + if not (IS_LINUX or IS_MAC or IS_WIN): + error_msg = "VPN Launcher not implemented for this platform." + raise NotImplementedError(error_msg) + + launcher = None + if IS_LINUX: + launcher = LinuxVPNLauncher + elif IS_MAC: + launcher = DarwinVPNLauncher + elif IS_WIN: + launcher = WindowsVPNLauncher + + leap_assert(launcher is not None) + + return launcher() diff --git a/src/leap/bitmask/services/eip/vpnlaunchers.py b/src/leap/bitmask/services/eip/vpnlaunchers.py deleted file mode 100644 index e27a48d9..00000000 --- a/src/leap/bitmask/services/eip/vpnlaunchers.py +++ /dev/null @@ -1,965 +0,0 @@ -# -*- coding: utf-8 -*- -# vpnlaunchers.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 . - -""" -Platform dependant VPN launchers -""" -import commands -import logging -import getpass -import os -import platform -import stat -import subprocess -try: - import grp -except ImportError: - pass # ignore, probably windows - -from abc import ABCMeta, abstractmethod -from functools import partial -from time import sleep - -from leap.bitmask.config import flags -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 -from leap.bitmask.util import get_path_prefix -from leap.bitmask.util.privilege_policies import LinuxPolicyChecker -from leap.bitmask.util import privilege_policies -from leap.common.check import leap_assert, leap_assert_type -from leap.common.files import which - - -logger = logging.getLogger(__name__) - - -class VPNLauncherException(Exception): - pass - - -class OpenVPNNotFoundException(VPNLauncherException): - pass - - -class EIPNoPolkitAuthAgentAvailable(VPNLauncherException): - pass - - -class EIPNoPkexecAvailable(VPNLauncherException): - pass - - -class EIPNoTunKextLoaded(VPNLauncherException): - pass - - -class VPNLauncher(object): - """ - Abstract launcher class - """ - __metaclass__ = ABCMeta - - UPDOWN_FILES = None - OTHER_FILES = None - - @abstractmethod - def get_vpn_command(self, eipconfig=None, providerconfig=None, - socket_host=None, socket_port=None): - """ - Returns the platform dependant vpn launching command - - :param eipconfig: eip configuration object - :type eipconfig: EIPConfig - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - :param socket_host: either socket path (unix) or socket IP - :type socket_host: str - :param socket_port: either string "unix" if it's a unix - socket, or port otherwise - :type socket_port: str - - :return: A VPN command ready to be launched - :rtype: list - """ - return [] - - @abstractmethod - def get_vpn_env(self): - """ - Returns a dictionary with the custom env for the platform. - This is mainly used for setting LD_LIBRARY_PATH to the correct - path when distributing a standalone client - - :rtype: dict - """ - return {} - - @classmethod - def missing_updown_scripts(kls): - """ - Returns what updown scripts are missing. - :rtype: list - """ - leap_assert(kls.UPDOWN_FILES is not None, - "Need to define UPDOWN_FILES for this particular " - "auncher before calling this method") - file_exist = partial(_has_updown_scripts, warn=False) - zipped = zip(kls.UPDOWN_FILES, map(file_exist, kls.UPDOWN_FILES)) - missing = filter(lambda (path, exists): exists is False, zipped) - return [path for path, exists in missing] - - @classmethod - def missing_other_files(kls): - """ - Returns what other important files are missing during startup. - Same as missing_updown_scripts but does not check for exec bit. - :rtype: list - """ - leap_assert(kls.OTHER_FILES is not None, - "Need to define OTHER_FILES for this particular " - "auncher before calling this method") - file_exist = partial(_has_other_files, warn=False) - zipped = zip(kls.OTHER_FILES, map(file_exist, kls.OTHER_FILES)) - missing = filter(lambda (path, exists): exists is False, zipped) - return [path for path, exists in missing] - - -def get_platform_launcher(): - launcher = globals()[platform.system() + "VPNLauncher"] - leap_assert(launcher, "Unimplemented platform launcher: %s" % - (platform.system(),)) - return launcher() - - -def _is_pkexec_in_system(): - """ - Checks the existence of the pkexec binary in system. - """ - pkexec_path = which('pkexec') - if len(pkexec_path) == 0: - return False - return True - - -def _has_updown_scripts(path, warn=True): - """ - Checks the existence of the up/down scripts and its - exec bit if applicable. - - :param path: the path to be checked - :type path: str - - :param warn: whether we should log the absence - :type warn: bool - - :rtype: bool - """ - is_file = os.path.isfile(path) - if warn and not is_file: - logger.error("Could not find up/down script %s. " - "Might produce DNS leaks." % (path,)) - - # XXX check if applies in win - is_exe = False - try: - is_exe = (stat.S_IXUSR & os.stat(path)[stat.ST_MODE] != 0) - except OSError as e: - logger.warn("%s" % (e,)) - if warn and not is_exe: - logger.error("Up/down script %s is not executable. " - "Might produce DNS leaks." % (path,)) - return is_file and is_exe - - -def _has_other_files(path, warn=True): - """ - Checks the existence of other important files. - - :param path: the path to be checked - :type path: str - - :param warn: whether we should log the absence - :type warn: bool - - :rtype: bool - """ - is_file = os.path.isfile(path) - if warn and not is_file: - logger.warning("Could not find file during checks: %s. " % ( - path,)) - return is_file - - -def _is_auth_agent_running(): - """ - Checks if a polkit daemon is running. - - :return: True if it's running, False if it's not. - :rtype: boolean - """ - ps = 'ps aux | grep polkit-%s-authentication-agent-1' - opts = (ps % case for case in ['[g]nome', '[k]de']) - is_running = map(lambda l: commands.getoutput(l), opts) - return any(is_running) - - -def _try_to_launch_agent(): - """ - Tries to launch a polkit daemon. - """ - env = None - if flags.STANDALONE is True: - env = {"PYTHONPATH": os.path.abspath('../../../../lib/')} - try: - # We need to quote the command because subprocess call - # will do "sh -c 'foo'", so if we do not quoute it we'll end - # up with a invocation to the python interpreter. And that - # is bad. - subprocess.call(["python -m leap.bitmask.util.polkit_agent"], - shell=True, env=env) - except Exception as exc: - logger.exception(exc) - - -class LinuxVPNLauncher(VPNLauncher): - """ - VPN launcher for the Linux platform - """ - - PKEXEC_BIN = 'pkexec' - OPENVPN_BIN = 'openvpn' - OPENVPN_BIN_PATH = os.path.join( - get_path_prefix(), "..", "apps", "eip", OPENVPN_BIN) - - SYSTEM_CONFIG = "/etc/leap" - UP_DOWN_FILE = "resolv-update" - UP_DOWN_PATH = "%s/%s" % (SYSTEM_CONFIG, UP_DOWN_FILE) - - # We assume this is there by our openvpn dependency, and - # we will put it there on the bundle too. - # TODO adapt to the bundle path. - OPENVPN_DOWN_ROOT_BASE = "/usr/lib/openvpn/" - OPENVPN_DOWN_ROOT_FILE = "openvpn-plugin-down-root.so" - OPENVPN_DOWN_ROOT_PATH = "%s/%s" % ( - OPENVPN_DOWN_ROOT_BASE, - OPENVPN_DOWN_ROOT_FILE) - - UP_SCRIPT = DOWN_SCRIPT = UP_DOWN_PATH - UPDOWN_FILES = (UP_DOWN_PATH,) - POLKIT_PATH = LinuxPolicyChecker.get_polkit_path() - OTHER_FILES = (POLKIT_PATH, ) - - def missing_other_files(self): - """ - 'Extend' the VPNLauncher's missing_other_files to check if the polkit - files is outdated. If the polkit file that is in OTHER_FILES exists but - is not up to date, it is added to the missing list. - - :returns: a list of missing files - :rtype: list of str - """ - missing = VPNLauncher.missing_other_files.im_func(self) - polkit_file = LinuxPolicyChecker.get_polkit_path() - if polkit_file not in missing: - if privilege_policies.is_policy_outdated(self.OPENVPN_BIN_PATH): - missing.append(polkit_file) - - return missing - - @classmethod - def cmd_for_missing_scripts(kls, frompath, pol_file): - """ - Returns a sh script that can copy the missing files. - - :param frompath: The path where the up/down scripts live - :type frompath: str - :param pol_file: The path where the dynamically generated - policy file lives - :type pol_file: str - - :rtype: str - """ - to = kls.SYSTEM_CONFIG - - cmd = '#!/bin/sh\n' - cmd += 'mkdir -p "%s"\n' % (to, ) - cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.UP_DOWN_FILE, to) - cmd += 'cp "%s" "%s"\n' % (pol_file, kls.POLKIT_PATH) - cmd += 'chmod 644 "%s"\n' % (kls.POLKIT_PATH, ) - - return cmd - - @classmethod - def maybe_pkexec(kls): - """ - Checks whether pkexec is available in the system, and - returns the path if found. - - Might raise EIPNoPkexecAvailable or EIPNoPolkitAuthAgentAvailable - - :returns: a list of the paths where pkexec is to be found - :rtype: list - """ - if _is_pkexec_in_system(): - if not _is_auth_agent_running(): - _try_to_launch_agent() - sleep(0.5) - if _is_auth_agent_running(): - pkexec_possibilities = which(kls.PKEXEC_BIN) - leap_assert(len(pkexec_possibilities) > 0, - "We couldn't find pkexec") - return pkexec_possibilities - else: - logger.warning("No polkit auth agent found. pkexec " + - "will use its own auth agent.") - raise EIPNoPolkitAuthAgentAvailable() - else: - logger.warning("System has no pkexec") - raise EIPNoPkexecAvailable() - - @classmethod - def maybe_down_plugin(kls): - """ - Returns the path of the openvpn down-root-plugin, searching first - in the relative path for the standalone bundle, and then in the system - path where the debian package puts it. - - :returns: the path where the plugin was found, or None - :rtype: str or None - """ - cwd = os.getcwd() - rel_path_in_bundle = os.path.join( - 'apps', 'eip', 'files', kls.OPENVPN_DOWN_ROOT_FILE) - abs_path_in_bundle = os.path.join(cwd, rel_path_in_bundle) - if os.path.isfile(abs_path_in_bundle): - return abs_path_in_bundle - abs_path_in_system = kls.OPENVPN_DOWN_ROOT_PATH - if os.path.isfile(abs_path_in_system): - return abs_path_in_system - - logger.warning("We could not find the down-root-plugin, so no updown " - "scripts will be run. DNS leaks are likely!") - return None - - def get_vpn_command(self, eipconfig, providerconfig, socket_host, - socket_port="unix", openvpn_verb=1): - """ - Returns the platform dependant vpn launching command. It will - look for openvpn in the regular paths and algo in - path_prefix/apps/eip/ (in case that standalone is set) - - Might raise: - EIPNoTunKextLoaded, - OpenVPNNotFoundException, - VPNLauncherException. - - :param eipconfig: eip configuration object - :type eipconfig: EIPConfig - - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - - :param socket_host: either socket path (unix) or socket IP - :type socket_host: str - - :param socket_port: either string "unix" if it's a unix - socket, or port otherwise - :type socket_port: str - - :param openvpn_verb: openvpn verbosity wanted - :type openvpn_verb: int - - :return: A VPN command ready to be launched - :rtype: list - """ - leap_assert_type(eipconfig, EIPConfig) - leap_assert_type(providerconfig, ProviderConfig) - - kwargs = {} - if flags.STANDALONE: - kwargs['path_extension'] = os.path.join( - get_path_prefix(), "..", "apps", "eip") - - openvpn_possibilities = which(self.OPENVPN_BIN, **kwargs) - if len(openvpn_possibilities) == 0: - raise OpenVPNNotFoundException() - - openvpn = first(openvpn_possibilities) - args = [] - - args += [ - '--setenv', "LEAPOPENVPN", "1" - ] - - if openvpn_verb is not None: - args += ['--verb', '%d' % (openvpn_verb,)] - - gateways = [] - leap_settings = LeapSettings() - domain = providerconfig.get_domain() - gateway_conf = leap_settings.get_selected_gateway(domain) - - 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!') - raise VPNLauncherException(self.tr('No gateway was found!')) - - logger.debug("Using gateways ips: {0}".format(', '.join(gateways))) - - for gw in gateways: - args += ['--remote', gw, '1194', 'udp'] - - args += [ - '--client', - '--dev', 'tun', - ############################################################## - # persist-tun makes ping-restart fail because it leaves a - # broken routing table - ############################################################## - # '--persist-tun', - '--persist-key', - '--tls-client', - '--remote-cert-tls', - 'server' - ] - - openvpn_configuration = eipconfig.get_openvpn_configuration() - for key, value in openvpn_configuration.items(): - args += ['--%s' % (key,), value] - - user = getpass.getuser() - - ############################################################## - # The down-root plugin fails in some situations, so we don't - # drop privs for the time being - ############################################################## - # args += [ - # '--user', user, - # '--group', grp.getgrgid(os.getgroups()[-1]).gr_name - # ] - - if socket_port == "unix": # that's always the case for linux - args += [ - '--management-client-user', user - ] - - args += [ - '--management-signal', - '--management', socket_host, socket_port, - '--script-security', '2' - ] - - if _has_updown_scripts(self.UP_SCRIPT): - args += [ - '--up', '\"%s\"' % (self.UP_SCRIPT,), - ] - - if _has_updown_scripts(self.DOWN_SCRIPT): - args += [ - '--down', '\"%s\"' % (self.DOWN_SCRIPT,) - ] - - ########################################################### - # For the time being we are disabling the usage of the - # down-root plugin, because it doesn't quite work as - # expected (i.e. it doesn't run route -del as root - # when finishing, so it fails to properly - # restart/quit) - ########################################################### - # if _has_updown_scripts(self.OPENVPN_DOWN_PLUGIN): - # args += [ - # '--plugin', self.OPENVPN_DOWN_ROOT, - # '\'%s\'' % self.DOWN_SCRIPT # for OSX - # '\'script_type=down %s\'' % self.DOWN_SCRIPT # for Linux - # ] - - args += [ - '--cert', eipconfig.get_client_cert_path(providerconfig), - '--key', eipconfig.get_client_cert_path(providerconfig), - '--ca', providerconfig.get_ca_cert_path() - ] - - command = [openvpn] - pkexec = self.maybe_pkexec() - if pkexec: - command.insert(0, first(pkexec)) - - command_and_args = command + args - logger.debug("Running VPN with command:") - logger.debug(" ".join(command_and_args)) - - return command_and_args - - def get_vpn_env(self): - """ - Returns a dictionary with the custom env for the platform. - This is mainly used for setting LD_LIBRARY_PATH to the correct - path when distributing a standalone client - - :rtype: dict - """ - return { - "LD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib") - } - - -class DarwinVPNLauncher(VPNLauncher): - """ - VPN launcher for the Darwin Platform - """ - - COCOASUDO = "cocoasudo" - # XXX need the good old magic translate for these strings - # (look for magic in 0.2.0 release) - SUDO_MSG = ("Bitmask needs administrative privileges to run " - "Encrypted Internet.") - INSTALL_MSG = ("\"Bitmask needs administrative privileges to install " - "missing scripts and fix permissions.\"") - - INSTALL_PATH = os.path.realpath(os.getcwd() + "/../../") - INSTALL_PATH_ESCAPED = os.path.realpath(os.getcwd() + "/../../") - OPENVPN_BIN = 'openvpn.leap' - OPENVPN_PATH = "%s/Contents/Resources/openvpn" % (INSTALL_PATH,) - OPENVPN_PATH_ESCAPED = "%s/Contents/Resources/openvpn" % ( - INSTALL_PATH_ESCAPED,) - - UP_SCRIPT = "%s/client.up.sh" % (OPENVPN_PATH,) - DOWN_SCRIPT = "%s/client.down.sh" % (OPENVPN_PATH,) - OPENVPN_DOWN_PLUGIN = '%s/openvpn-down-root.so' % (OPENVPN_PATH,) - - UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT, OPENVPN_DOWN_PLUGIN) - OTHER_FILES = [] - - @classmethod - def cmd_for_missing_scripts(kls, frompath): - """ - Returns a command that can copy the missing scripts. - :rtype: str - """ - to = kls.OPENVPN_PATH_ESCAPED - cmd = "#!/bin/sh\nmkdir -p %s\ncp \"%s/\"* %s\nchmod 744 %s/*" % ( - to, frompath, to, to) - return cmd - - @classmethod - def maybe_kextloaded(kls): - """ - Checks if the needed kext is loaded before launching openvpn. - """ - return bool(commands.getoutput('kextstat | grep "leap.tun"')) - - def _get_resource_path(self): - """ - Returns the absolute path to the app resources directory - - :rtype: str - """ - return os.path.abspath( - os.path.join( - os.getcwd(), - "../../Contents/Resources")) - - def _get_icon_path(self): - """ - Returns the absolute path to the app icon - - :rtype: str - """ - return os.path.join(self._get_resource_path(), - "leap-client.tiff") - - def get_cocoasudo_ovpn_cmd(self): - """ - Returns a string with the cocoasudo command needed to run openvpn - as admin with a nice password prompt. The actual command needs to be - appended. - - :rtype: (str, list) - """ - iconpath = self._get_icon_path() - has_icon = os.path.isfile(iconpath) - args = ["--icon=%s" % iconpath] if has_icon else [] - args.append("--prompt=%s" % (self.SUDO_MSG,)) - - return self.COCOASUDO, args - - def get_cocoasudo_installmissing_cmd(self): - """ - Returns a string with the cocoasudo command needed to install missing - files as admin with a nice password prompt. The actual command needs to - be appended. - - :rtype: (str, list) - """ - iconpath = self._get_icon_path() - has_icon = os.path.isfile(iconpath) - args = ["--icon=%s" % iconpath] if has_icon else [] - args.append("--prompt=%s" % (self.INSTALL_MSG,)) - - return self.COCOASUDO, args - - def get_vpn_command(self, eipconfig=None, providerconfig=None, - socket_host=None, socket_port="unix", openvpn_verb=1): - """ - Returns the platform dependant vpn launching command - - Might raise VPNException. - - :param eipconfig: eip configuration object - :type eipconfig: EIPConfig - - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - - :param socket_host: either socket path (unix) or socket IP - :type socket_host: str - - :param socket_port: either string "unix" if it's a unix - socket, or port otherwise - :type socket_port: str - - :param openvpn_verb: openvpn verbosity wanted - :type openvpn_verb: int - - :return: A VPN command ready to be launched - :rtype: list - """ - leap_assert(eipconfig, "We need an eip config") - leap_assert_type(eipconfig, EIPConfig) - leap_assert(providerconfig, "We need a provider config") - leap_assert_type(providerconfig, ProviderConfig) - leap_assert(socket_host, "We need a socket host!") - leap_assert(socket_port, "We need a socket port!") - - if not self.maybe_kextloaded(): - raise EIPNoTunKextLoaded - - kwargs = {} - if flags.STANDALONE: - kwargs['path_extension'] = os.path.join( - get_path_prefix(), "..", "apps", "eip") - - openvpn_possibilities = which( - self.OPENVPN_BIN, - **kwargs) - if len(openvpn_possibilities) == 0: - raise OpenVPNNotFoundException() - - openvpn = first(openvpn_possibilities) - args = [openvpn] - - args += [ - '--setenv', "LEAPOPENVPN", "1" - ] - - if openvpn_verb is not None: - args += ['--verb', '%d' % (openvpn_verb,)] - - gateways = [] - leap_settings = LeapSettings() - domain = providerconfig.get_domain() - gateway_conf = leap_settings.get_selected_gateway(domain) - - 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!') - raise VPNLauncherException(self.tr('No gateway was found!')) - - logger.debug("Using gateways ips: {0}".format(', '.join(gateways))) - - for gw in gateways: - args += ['--remote', gw, '1194', 'udp'] - - args += [ - '--client', - '--dev', 'tun', - ############################################################## - # persist-tun makes ping-restart fail because it leaves a - # broken routing table - ############################################################## - # '--persist-tun', - '--persist-key', - '--tls-client', - '--remote-cert-tls', - 'server' - ] - - openvpn_configuration = eipconfig.get_openvpn_configuration() - for key, value in openvpn_configuration.items(): - args += ['--%s' % (key,), value] - - user = getpass.getuser() - - ############################################################## - # The down-root plugin fails in some situations, so we don't - # drop privs for the time being - ############################################################## - # args += [ - # '--user', user, - # '--group', grp.getgrgid(os.getgroups()[-1]).gr_name - # ] - - if socket_port == "unix": - args += [ - '--management-client-user', user - ] - - args += [ - '--management-signal', - '--management', socket_host, socket_port, - '--script-security', '2' - ] - - if _has_updown_scripts(self.UP_SCRIPT): - args += [ - '--up', '\"%s\"' % (self.UP_SCRIPT,), - ] - - if _has_updown_scripts(self.DOWN_SCRIPT): - args += [ - '--down', '\"%s\"' % (self.DOWN_SCRIPT,) - ] - - # should have the down script too - if _has_updown_scripts(self.OPENVPN_DOWN_PLUGIN): - args += [ - ########################################################### - # For the time being we are disabling the usage of the - # down-root plugin, because it doesn't quite work as - # expected (i.e. it doesn't run route -del as root - # when finishing, so it fails to properly - # restart/quit) - ########################################################### - # '--plugin', self.OPENVPN_DOWN_PLUGIN, - # '\'%s\'' % self.DOWN_SCRIPT - ] - - # we set user to be passed to the up/down scripts - args += [ - '--setenv', "LEAPUSER", "%s" % (user,)] - - args += [ - '--cert', eipconfig.get_client_cert_path(providerconfig), - '--key', eipconfig.get_client_cert_path(providerconfig), - '--ca', providerconfig.get_ca_cert_path() - ] - - command, cargs = self.get_cocoasudo_ovpn_cmd() - cmd_args = cargs + args - - logger.debug("Running VPN with command:") - logger.debug("%s %s" % (command, " ".join(cmd_args))) - - return [command] + cmd_args - - def get_vpn_env(self): - """ - Returns a dictionary with the custom env for the platform. - This is mainly used for setting LD_LIBRARY_PATH to the correct - path when distributing a standalone client - - :rtype: dict - """ - return { - "DYLD_LIBRARY_PATH": os.path.join(get_path_prefix(), "..", "lib") - } - - -class WindowsVPNLauncher(VPNLauncher): - """ - VPN launcher for the Windows platform - """ - - OPENVPN_BIN = 'openvpn_leap.exe' - - # XXX UPDOWN_FILES ... we do not have updown files defined yet! - # (and maybe we won't) - - def get_vpn_command(self, eipconfig=None, providerconfig=None, - socket_host=None, socket_port="9876", openvpn_verb=1): - """ - Returns the platform dependant vpn launching command. It will - look for openvpn in the regular paths and algo in - path_prefix/apps/eip/ (in case standalone is set) - - Might raise VPNException. - - :param eipconfig: eip configuration object - :type eipconfig: EIPConfig - - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - - :param socket_host: either socket path (unix) or socket IP - :type socket_host: str - - :param socket_port: either string "unix" if it's a unix - socket, or port otherwise - :type socket_port: str - - :param openvpn_verb: the openvpn verbosity wanted - :type openvpn_verb: int - - :return: A VPN command ready to be launched - :rtype: list - """ - leap_assert(eipconfig, "We need an eip config") - leap_assert_type(eipconfig, EIPConfig) - leap_assert(providerconfig, "We need a provider config") - leap_assert_type(providerconfig, ProviderConfig) - leap_assert(socket_host, "We need a socket host!") - leap_assert(socket_port, "We need a socket port!") - leap_assert(socket_port != "unix", - "We cannot use unix sockets in windows!") - - openvpn_possibilities = which( - self.OPENVPN_BIN, - path_extension=os.path.join(get_path_prefix(), - "..", "apps", "eip")) - - if len(openvpn_possibilities) == 0: - raise OpenVPNNotFoundException() - - openvpn = first(openvpn_possibilities) - args = [] - - args += [ - '--setenv', "LEAPOPENVPN", "1" - ] - - if openvpn_verb is not None: - args += ['--verb', '%d' % (openvpn_verb,)] - - gateways = [] - leap_settings = LeapSettings() - domain = providerconfig.get_domain() - gateway_conf = leap_settings.get_selected_gateway(domain) - - 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!') - raise VPNLauncherException(self.tr('No gateway was found!')) - - logger.debug("Using gateways ips: {0}".format(', '.join(gateways))) - - for gw in gateways: - args += ['--remote', gw, '1194', 'udp'] - - args += [ - '--client', - '--dev', 'tun', - ############################################################## - # persist-tun makes ping-restart fail because it leaves a - # broken routing table - ############################################################## - # '--persist-tun', - '--persist-key', - '--tls-client', - # We make it log to a file because we cannot attach to the - # openvpn process' stdout since it's a process with more - # privileges than we are - '--log-append', 'eip.log', - '--remote-cert-tls', - 'server' - ] - - openvpn_configuration = eipconfig.get_openvpn_configuration() - for key, value in openvpn_configuration.items(): - args += ['--%s' % (key,), value] - - ############################################################## - # The down-root plugin fails in some situations, so we don't - # drop privs for the time being - ############################################################## - # args += [ - # '--user', getpass.getuser(), - # #'--group', grp.getgrgid(os.getgroups()[-1]).gr_name - # ] - - args += [ - '--management-signal', - '--management', socket_host, socket_port, - '--script-security', '2' - ] - - args += [ - '--cert', eipconfig.get_client_cert_path(providerconfig), - '--key', eipconfig.get_client_cert_path(providerconfig), - '--ca', providerconfig.get_ca_cert_path() - ] - - logger.debug("Running VPN with command:") - logger.debug("%s %s" % (openvpn, " ".join(args))) - - return [openvpn] + args - - def get_vpn_env(self): - """ - Returns a dictionary with the custom env for the platform. - This is mainly used for setting LD_LIBRARY_PATH to the correct - path when distributing a standalone client - - :rtype: dict - """ - return {} - - -if __name__ == "__main__": - logger = logging.getLogger(name='leap') - logger.setLevel(logging.DEBUG) - console = logging.StreamHandler() - console.setLevel(logging.DEBUG) - formatter = logging.Formatter( - '%(asctime)s ' - '- %(name)s - %(levelname)s - %(message)s') - console.setFormatter(formatter) - logger.addHandler(console) - - try: - abs_launcher = VPNLauncher() - except Exception as e: - assert isinstance(e, TypeError), "Something went wrong" - print "Abstract Prefixer class is working as expected" - - vpnlauncher = get_platform_launcher() - - eipconfig = EIPConfig() - eipconfig.set_api_version('1') - if eipconfig.load("leap/providers/bitmask.net/eip-service.json"): - provider = ProviderConfig() - if provider.load("leap/providers/bitmask.net/provider.json"): - vpnlauncher.get_vpn_command(eipconfig=eipconfig, - providerconfig=provider, - socket_host="/blah") diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py index 15ac812b..707967e0 100644 --- a/src/leap/bitmask/services/eip/vpnprocess.py +++ b/src/leap/bitmask/services/eip/vpnprocess.py @@ -27,7 +27,7 @@ import socket from PySide import QtCore from leap.bitmask.config.providerconfig import ProviderConfig -from leap.bitmask.services.eip.vpnlaunchers import get_platform_launcher +from leap.bitmask.services.eip import get_vpn_launcher from leap.bitmask.services.eip.eipconfig import EIPConfig from leap.bitmask.services.eip.udstelnet import UDSTelnet from leap.bitmask.util import first @@ -697,7 +697,7 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager): self._socket_host = socket_host self._socket_port = socket_port - self._launcher = get_platform_launcher() + self._launcher = get_vpn_launcher() self._last_state = None self._last_status = None -- cgit v1.2.3 From d4df128d1e5b452eb3f8de88174991fafd70913a Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Fri, 27 Sep 2013 15:32:32 -0300 Subject: Add changelog. Closes #2858. --- changes/feature-2858_refactor-vpnlaunchers | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changes/feature-2858_refactor-vpnlaunchers diff --git a/changes/feature-2858_refactor-vpnlaunchers b/changes/feature-2858_refactor-vpnlaunchers new file mode 100644 index 00000000..27106f7a --- /dev/null +++ b/changes/feature-2858_refactor-vpnlaunchers @@ -0,0 +1,2 @@ + o Refactor vpn launchers, reuse code, improve implementations, update + documentation. Closes #2858. -- cgit v1.2.3 From 42a262e685dd616e2bdc9a6f1e092cf25c3dc4e7 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Sat, 28 Sep 2013 17:19:57 -0400 Subject: add connection_aborted signal to state machine --- changes/bug_3926_connection-aborted | 2 ++ src/leap/bitmask/gui/mainwindow.py | 19 +++++++++---------- src/leap/bitmask/gui/statemachines.py | 8 +++++++- src/leap/bitmask/services/connections.py | 1 + src/leap/bitmask/services/eip/connection.py | 1 + 5 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 changes/bug_3926_connection-aborted diff --git a/changes/bug_3926_connection-aborted b/changes/bug_3926_connection-aborted new file mode 100644 index 00000000..58e6fe11 --- /dev/null +++ b/changes/bug_3926_connection-aborted @@ -0,0 +1,2 @@ + o Fix a bug in which failing to authenticate properly left connection + in an unconsistent state. Closes: #3926 diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 200d68aa..1e437999 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1531,7 +1531,9 @@ class MainWindow(QtGui.QMainWindow): # TODO we should have a way of parsing the latest lines in the vpn # log buffer so we can have a more precise idea of which type # of error did we have (server side, local problem, etc) - abnormal = True + + qtsigs = self._eip_connection.qtsigs + signal = qtsigs.disconnected_signal # XXX check if these exitCodes are pkexec/cocoasudo specific if exitCode in (126, 127): @@ -1540,28 +1542,25 @@ class MainWindow(QtGui.QMainWindow): "because you did not authenticate properly."), error=True) self._vpn.killit() + signal = qtsigs.connection_aborted_signal + elif exitCode != 0 or not self.user_stopped_eip: self._status_panel.set_global_status( self.tr("Encrypted Internet finished in an " "unexpected manner!"), error=True) - else: - abnormal = False + signal = qtsigs.connection_died_signal + if exitCode == 0 and IS_MAC: # XXX remove this warning after I fix cocoasudo. logger.warning("The above exit code MIGHT BE WRONG.") - # We emit signals to trigger transitions in the state machine: - qtsigs = self._eip_connection.qtsigs - if abnormal: - signal = qtsigs.connection_died_signal - else: - signal = qtsigs.disconnected_signal - # XXX verify that the logic kees the same w/o the abnormal flag # after the refactor to EIPConnection has been completed # (eipconductor taking the most of the logic under transitions # that right now are handled under status_panel) #self._stop_eip(abnormal) + + # We emit signals to trigger transitions in the state machine: signal.emit() def _on_raise_window_event(self, req): diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py index c3dd5ed3..c02bf9bc 100644 --- a/src/leap/bitmask/gui/statemachines.py +++ b/src/leap/bitmask/gui/statemachines.py @@ -128,11 +128,17 @@ class ConnectionMachineBuilder(object): states[_OFF]) # * If we receive the connection_died, we transition - # to the off state + # from on directly to the off state states[_ON].addTransition( conn.qtsigs.connection_died_signal, states[_OFF]) + # * If we receive the connection_aborted, we transition + # from connecting to the off state + states[_CON].addTransition( + conn.qtsigs.connection_aborted_signal, + states[_OFF]) + # adding states to the machine for state in states.itervalues(): machine.addState(state) diff --git a/src/leap/bitmask/services/connections.py b/src/leap/bitmask/services/connections.py index f3ab9e8e..8aeb4e0c 100644 --- a/src/leap/bitmask/services/connections.py +++ b/src/leap/bitmask/services/connections.py @@ -103,6 +103,7 @@ class AbstractLEAPConnection(object): # Bypass stages connection_died_signal = None + connection_aborted_signal = None class Disconnected(State): """Disconnected state""" diff --git a/src/leap/bitmask/services/eip/connection.py b/src/leap/bitmask/services/eip/connection.py index 5f05ba07..08b29070 100644 --- a/src/leap/bitmask/services/eip/connection.py +++ b/src/leap/bitmask/services/eip/connection.py @@ -40,6 +40,7 @@ class EIPConnectionSignals(QtCore.QObject): disconnected_signal = QtCore.Signal() connection_died_signal = QtCore.Signal() + connection_aborted_signal = QtCore.Signal() class EIPConnection(AbstractLEAPConnection): -- cgit v1.2.3 From 24c91beb6f7102158a37330e914e19570bb85ecf Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Mon, 30 Sep 2013 13:32:51 -0400 Subject: add connection_died transition to connecting->off too --- src/leap/bitmask/gui/statemachines.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py index c02bf9bc..94726720 100644 --- a/src/leap/bitmask/gui/statemachines.py +++ b/src/leap/bitmask/gui/statemachines.py @@ -138,6 +138,14 @@ class ConnectionMachineBuilder(object): states[_CON].addTransition( conn.qtsigs.connection_aborted_signal, states[_OFF]) + # * Connection died can in some cases also be + # triggered while we are in CONNECTING + # state. I should be avoided, since connection_aborted + # is clearer (and reserve connection_died + # for transitions from on->off + states[_CON].addTransition( + conn.qtsigs.connection_died_signal, + states[_OFF]) # adding states to the machine for state in states.itervalues(): -- cgit v1.2.3 From f2c68ba96498f8b88e415aaf27ced735c790510e Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 27 Sep 2013 16:07:29 -0400 Subject: Improve error handling during soledad boostrapping in the client. The aim is to have better logs for debugging the different problems behind issues like #3619 and #3867. As I'm finding a good quantity of SSL handshake timeouts, I'm also adding a litte retry subroutine to the load_and_sync. Also, initialization and sync calls are separeted to be able to correlate logs with server-side soledad. --- .../services/soledad/soledadbootstrapper.py | 205 ++++++++++++++++----- src/leap/bitmask/util/__init__.py | 14 ++ 2 files changed, 170 insertions(+), 49 deletions(-) diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index cac91440..afd7d623 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -14,15 +14,15 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - """ Soledad bootstrapping """ - import logging import os import socket +from ssl import SSLError + from PySide import QtCore from u1db import errors as u1db_errors @@ -32,9 +32,10 @@ from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.services.soledad.soledadconfig import SoledadConfig from leap.bitmask.util.request_helpers import get_content +from leap.bitmask.util import is_file, is_empty_file from leap.bitmask.util import get_path_prefix -from leap.common.check import leap_assert, leap_assert_type from leap.common.files import get_mtime +from leap.common.check import leap_assert, leap_assert_type, leap_check from leap.keymanager import KeyManager, openpgp from leap.keymanager.errors import KeyNotFound from leap.soledad.client import Soledad @@ -42,6 +43,17 @@ from leap.soledad.client import Soledad logger = logging.getLogger(__name__) +# TODO these exceptions could be moved to soledad itself +# after settling this down. + +class SoledadSyncError(Exception): + message = "Error while syncing Soledad" + + +class SoledadInitError(Exception): + message = "Error while initializing Soledad" + + class SoledadBootstrapper(AbstractBootstrapper): """ Soledad init procedure @@ -109,68 +121,159 @@ class SoledadBootstrapper(AbstractBootstrapper): """ self._soledad_retries += 1 + def _get_db_paths(self, uuid): + """ + Returns the secrets and local db paths needed for soledad + initialization + + :param uuid: uuid for user + :type uuid: str + + :return: a tuple with secrets, local_db paths + :rtype: tuple + """ + prefix = os.path.join(get_path_prefix(), "leap", "soledad") + secrets = "%s/%s.secret" % (prefix, uuid) + local_db = "%s/%s.db" % (prefix, uuid) + + # We remove an empty file if found to avoid complains + # about the db not being properly initialized + if is_file(local_db) and is_empty_file(local_db): + try: + os.remove(local_db) + except OSError: + logger.warning("Could not remove empty file %s" + % local_db) + return secrets, local_db + # initialization def load_and_sync_soledad(self): """ Once everthing is in the right place, we instantiate and sync Soledad - - :param srp_auth: SRPAuth object used - :type srp_auth: SRPAuth """ - srp_auth = self.srpauth - uuid = srp_auth.get_uid() + uuid = self.srpauth.get_uid() + token = self.srpauth.get_token() - prefix = os.path.join(get_path_prefix(), "leap", "soledad") - secrets_path = "%s/%s.secret" % (prefix, uuid) - local_db_path = "%s/%s.db" % (prefix, uuid) + secrets_path, local_db_path = self._get_db_paths(uuid) # TODO: Select server based on timezone (issue #3308) server_dict = self._soledad_config.get_hosts() - if server_dict.keys(): - selected_server = server_dict[server_dict.keys()[0]] - server_url = "https://%s:%s/user-%s" % ( - selected_server["hostname"], - selected_server["port"], - uuid) + if not server_dict.keys(): + # XXX raise more specific exception + raise Exception("No soledad server found") + + selected_server = server_dict[server_dict.keys()[0]] + server_url = "https://%s:%s/user-%s" % ( + selected_server["hostname"], + selected_server["port"], + uuid) + + logger.debug("Using soledad server url: %s" % (server_url,)) - logger.debug("Using soledad server url: %s" % (server_url,)) + cert_file = self._provider_config.get_ca_cert_path() - cert_file = self._provider_config.get_ca_cert_path() + logger.debug('local_db:%s' % (local_db_path,)) + logger.debug('secrets_path:%s' % (secrets_path,)) - # TODO: If selected server fails, retry with another host - # (issue #3309) + try: + self._try_soledad_init( + uuid, secrets_path, local_db_path, + server_url, cert_file, token) + except: + # re-raise the exceptions from try_init, + # we're currently handling the retries from the + # soledad-launcher in the gui. + raise + + leap_check(self._soledad is not None, + "Null soledad, error while initializing") + + # and now, let's sync + sync_tries = 10 + while sync_tries > 0: try: - self._soledad = Soledad( - uuid, - self._password.encode("utf-8"), - secrets_path=secrets_path, - local_db_path=local_db_path, - server_url=server_url, - cert_file=cert_file, - auth_token=srp_auth.get_token()) - self._soledad.sync() - - # XXX All these errors should be handled by soledad itself, - # and return a subclass of SoledadInitializationFailed - except socket.timeout: - logger.debug("SOLEDAD TIMED OUT...") - self.soledad_timeout.emit() - except socket.error as exc: - logger.error("Socket error while initializing soledad") - self.soledad_failed.emit() - except u1db_errors.Unauthorized: - logger.error("Error while initializing soledad " - "(unauthorized).") - self.soledad_failed.emit() - except Exception as exc: - logger.error("Unhandled error while initializating " + self._try_soledad_sync() + logger.debug("Soledad has been synced.") + # so long, and thanks for all the fish + return + except SoledadSyncError: + # maybe it's my connection, but I'm getting + # ssl handshake timeouts and read errors quite often. + # A particularly big sync is a disaster. + # This deserves further investigation, maybe the + # retry strategy can be pushed to u1db, or at least + # it's something worthy to talk about with the + # ubuntu folks. + sync_tries -= 1 + continue + + # reached bottom, failed to sync + # and there's nothing we can do... + self.soledad_failed.emit() + raise SoledadSyncError() + + def _try_soledad_init(self, uuid, secrets_path, local_db_path, + server_url, cert_file, auth_token): + """ + Tries to initialize soledad. + + :param uuid: user identifier + :param secrets_path: path to secrets file + :param local_db_path: path to local db file + :param server_url: soledad server uri + :cert_file: + :param auth token: auth token + :type auth_token: str + """ + + # TODO: If selected server fails, retry with another host + # (issue #3309) + try: + self._soledad = Soledad( + uuid, + self._password.encode("utf-8"), + secrets_path=secrets_path, + local_db_path=local_db_path, + server_url=server_url, + cert_file=cert_file, + auth_token=auth_token) + + # XXX All these errors should be handled by soledad itself, + # and return a subclass of SoledadInitializationFailed + except socket.timeout: + logger.debug("SOLEDAD initialization TIMED OUT...") + self.soledad_timeout.emit() + except socket.error as exc: + logger.error("Socket error while initializing soledad") + self.soledad_failed.emit() + except u1db_errors.Unauthorized: + logger.error("Error while initializing soledad " + "(unauthorized).") + self.soledad_failed.emit() + except Exception as exc: + logger.exception("Unhandled error while initializating " "soledad: %r" % (exc,)) - raise - else: - raise Exception("No soledad server found") + self.soledad_failed.emit() + + def _try_soledad_sync(self): + """ + Tries to sync soledad. + Raises SoledadSyncError if not successful. + """ + try: + logger.error("trying to sync soledad....") + self._soledad.sync() + except SSLError as exc: + logger.error("%r" % (exc,)) + raise SoledadSyncError("Failed to sync soledad") + except Exception as exc: + logger.exception("Unhandled error while syncing" + "soledad: %r" % (exc,)) + self.soledad_failed.emit() + raise SoledadSyncError("Failed to sync soledad") def _download_config(self): """ @@ -185,6 +288,8 @@ class SoledadBootstrapper(AbstractBootstrapper): self._soledad_config = SoledadConfig() + # TODO factor out with eip/provider configs. + headers = {} mtime = get_mtime( os.path.join(get_path_prefix(), "leap", "providers", @@ -242,8 +347,10 @@ class SoledadBootstrapper(AbstractBootstrapper): Generates the key pair if needed, uploads it to the webapp and nickserver """ - leap_assert(self._provider_config, + leap_assert(self._provider_config is not None, "We need a provider configuration!") + leap_assert(self._soledad is not None, + "We need a non-null soledad to generate keys") address = "%s@%s" % (self._user, self._provider_config.get_domain()) diff --git a/src/leap/bitmask/util/__init__.py b/src/leap/bitmask/util/__init__.py index f762a350..b58e6e3b 100644 --- a/src/leap/bitmask/util/__init__.py +++ b/src/leap/bitmask/util/__init__.py @@ -62,3 +62,17 @@ def update_modification_ts(path): """ os.utime(path, None) return get_modification_ts(path) + + +def is_file(path): + """ + Returns True if the path exists and is a file. + """ + return os.path.isfile(path) + + +def is_empty_file(path): + """ + Returns True if the file at path is empty. + """ + return os.stat(path).st_size is 0 -- cgit v1.2.3 From e55af608c81284f50133f53df6f49d69da0756b7 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 27 Sep 2013 17:29:10 -0400 Subject: add comments --- src/leap/bitmask/gui/mainwindow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 200d68aa..33af37e3 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -1007,6 +1007,7 @@ class MainWindow(QtGui.QMainWindow): """ Retries soledad connection. """ + # XXX should move logic to soledad boostrapper itself logger.debug("Retrying soledad connection.") if self._soledad_bootstrapper.should_retry_initialization(): self._soledad_bootstrapper.increment_retries_count() @@ -1031,8 +1032,9 @@ class MainWindow(QtGui.QMainWindow): """ passed = data[self._soledad_bootstrapper.PASSED_KEY] if not passed: + # TODO should actually *display* on the panel. logger.debug("ERROR on soledad bootstrapping:") - logger.error(data[self._soledad_bootstrapper.ERROR_KEY]) + logger.error("%r" % data[self._soledad_bootstrapper.ERROR_KEY]) return else: logger.debug("Done bootstrapping Soledad") -- cgit v1.2.3 From aea7b3dde14074d7462b5a0854dfad9d5d60e330 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 27 Sep 2013 17:29:58 -0400 Subject: remove unused import --- src/leap/bitmask/services/eip/eipbootstrapper.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/leap/bitmask/services/eip/eipbootstrapper.py b/src/leap/bitmask/services/eip/eipbootstrapper.py index 885c4420..5a238a1c 100644 --- a/src/leap/bitmask/services/eip/eipbootstrapper.py +++ b/src/leap/bitmask/services/eip/eipbootstrapper.py @@ -28,7 +28,6 @@ from leap.bitmask.services import download_service_config from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.services.eip.eipconfig import EIPConfig from leap.common import certs as leap_certs -from leap.bitmask.util import get_path_prefix from leap.common.check import leap_assert, leap_assert_type from leap.common.files import check_and_fix_urw_only -- cgit v1.2.3 From 7eb0052c95585cd71f61a5477a963f6357b34400 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 27 Sep 2013 17:32:40 -0400 Subject: refactor to use generic download_service_config --- .../services/soledad/soledadbootstrapper.py | 146 +++++++++------------ 1 file changed, 62 insertions(+), 84 deletions(-) diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index afd7d623..835e4cd9 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -29,13 +29,13 @@ from u1db import errors as u1db_errors from leap.bitmask.config import flags from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpauth import SRPAuth +from leap.bitmask.services import download_service_config from leap.bitmask.services.abstractbootstrapper import AbstractBootstrapper from leap.bitmask.services.soledad.soledadconfig import SoledadConfig -from leap.bitmask.util.request_helpers import get_content from leap.bitmask.util import is_file, is_empty_file from leap.bitmask.util import get_path_prefix -from leap.common.files import get_mtime from leap.common.check import leap_assert, leap_assert_type, leap_check +from leap.common.files import which from leap.keymanager import KeyManager, openpgp from leap.keymanager.errors import KeyNotFound from leap.soledad.client import Soledad @@ -58,13 +58,13 @@ class SoledadBootstrapper(AbstractBootstrapper): """ Soledad init procedure """ - SOLEDAD_KEY = "soledad" KEYMANAGER_KEY = "keymanager" PUBKEY_KEY = "user[public_key]" MAX_INIT_RETRIES = 10 + MAX_SYNC_RETRIES = 10 # All dicts returned are of the form # {"passed": bool, "error": str} @@ -80,6 +80,7 @@ class SoledadBootstrapper(AbstractBootstrapper): self._soledad_config = None self._keymanager = None self._download_if_needed = False + self._user = "" self._password = "" self._srpauth = None @@ -153,6 +154,7 @@ class SoledadBootstrapper(AbstractBootstrapper): Once everthing is in the right place, we instantiate and sync Soledad """ + # TODO this method is still too large uuid = self.srpauth.get_uid() token = self.srpauth.get_token() @@ -162,7 +164,7 @@ class SoledadBootstrapper(AbstractBootstrapper): server_dict = self._soledad_config.get_hosts() if not server_dict.keys(): - # XXX raise more specific exception + # XXX raise more specific exception, and catch it properly! raise Exception("No soledad server found") selected_server = server_dict[server_dict.keys()[0]] @@ -170,7 +172,6 @@ class SoledadBootstrapper(AbstractBootstrapper): selected_server["hostname"], selected_server["port"], uuid) - logger.debug("Using soledad server url: %s" % (server_url,)) cert_file = self._provider_config.get_ca_cert_path() @@ -192,10 +193,14 @@ class SoledadBootstrapper(AbstractBootstrapper): "Null soledad, error while initializing") # and now, let's sync - sync_tries = 10 + sync_tries = self.MAX_SYNC_RETRIES while sync_tries > 0: try: self._try_soledad_sync() + + # at this point, sometimes the client + # gets stuck and does not progress to + # the _gen_key step. XXX investigate. logger.debug("Soledad has been synced.") # so long, and thanks for all the fish return @@ -224,11 +229,13 @@ class SoledadBootstrapper(AbstractBootstrapper): :param secrets_path: path to secrets file :param local_db_path: path to local db file :param server_url: soledad server uri - :cert_file: + :param cert_file: path to the certificate of the ca used + to validate the SSL certificate used by the remote + soledad server. + :type cert_file: str :param auth token: auth token :type auth_token: str """ - # TODO: If selected server fails, retry with another host # (issue #3309) try: @@ -282,90 +289,46 @@ class SoledadBootstrapper(AbstractBootstrapper): leap_assert(self._provider_config, "We need a provider configuration!") - logger.debug("Downloading Soledad config for %s" % (self._provider_config.get_domain(),)) self._soledad_config = SoledadConfig() - - # TODO factor out with eip/provider configs. - - headers = {} - mtime = get_mtime( - os.path.join(get_path_prefix(), "leap", "providers", - self._provider_config.get_domain(), - "soledad-service.json")) - - if self._download_if_needed and mtime: - headers['if-modified-since'] = mtime - - api_version = self._provider_config.get_api_version() - - # there is some confusion with this uri, - config_uri = "%s/%s/config/soledad-service.json" % ( - self._provider_config.get_api_uri(), - api_version) - logger.debug('Downloading soledad config from: %s' % config_uri) - - # TODO factor out this srpauth protected get (make decorator) - srp_auth = self.srpauth - session_id = srp_auth.get_session_id() - cookies = None - if session_id: - cookies = {"_session_id": session_id} - - res = self._session.get(config_uri, - verify=self._provider_config - .get_ca_cert_path(), - headers=headers, - cookies=cookies) - res.raise_for_status() - - self._soledad_config.set_api_version(api_version) - - # Not modified - if res.status_code == 304: - logger.debug("Soledad definition has not been modified") - self._soledad_config.load( - os.path.join( - "leap", "providers", - self._provider_config.get_domain(), - "soledad-service.json")) - else: - soledad_definition, mtime = get_content(res) - - self._soledad_config.load(data=soledad_definition, mtime=mtime) - self._soledad_config.save(["leap", - "providers", - self._provider_config.get_domain(), - "soledad-service.json"]) - + download_service_config( + self._provider_config, + self._soledad_config, + self._session, + self._download_if_needed) + + # soledad config is ok, let's proceed to load and sync soledad + # XXX but honestly, this is a pretty strange entry point for that. + # it feels like it should be the other way around: + # load_and_sync, and from there, if needed, call download_config self.load_and_sync_soledad() - def _gen_key(self, _): + def _get_gpg_bin_path(self): """ - Generates the key pair if needed, uploads it to the webapp and - nickserver + Returns the path to gpg binary. + :returns: the gpg binary path + :rtype: str """ - leap_assert(self._provider_config is not None, - "We need a provider configuration!") - leap_assert(self._soledad is not None, - "We need a non-null soledad to generate keys") - - address = "%s@%s" % (self._user, self._provider_config.get_domain()) - - logger.debug("Retrieving key for %s" % (address,)) - - srp_auth = self.srpauth - - # TODO: use which implementation with known paths # TODO: Fix for Windows - gpgbin = "/usr/bin/gpg" - + gpgbin = None if flags.STANDALONE: - gpgbin = os.path.join(get_path_prefix(), - "..", "apps", "mail", "gpg") + gpgbin = os.path.join( + get_path_prefix(), "..", "apps", "mail", "gpg") + else: + gpgbin = which("gpg") + leap_check(gpgbin is not None, "Could not find gpg binary") + return gpgbin + def _init_keymanager(self, address): + """ + Initializes the keymanager. + :param address: the address to initialize the keymanager with. + :type address: str + """ + srp_auth = self.srpauth + logger.debug('initializing keymanager...') self._keymanager = KeyManager( address, "https://nicknym.%s:6425" % (self._provider_config.get_domain(),), @@ -376,10 +339,25 @@ class SoledadBootstrapper(AbstractBootstrapper): api_uri=self._provider_config.get_api_uri(), api_version=self._provider_config.get_api_version(), uid=srp_auth.get_uid(), - gpgbinary=gpgbin) + gpgbinary=self._get_gpg_bin_path()) + + def _gen_key(self, _): + """ + Generates the key pair if needed, uploads it to the webapp and + nickserver + """ + leap_assert(self._provider_config is not None, + "We need a provider configuration!") + leap_assert(self._soledad is not None, + "We need a non-null soledad to generate keys") + + address = "%s@%s" % (self._user, self._provider_config.get_domain()) + self._init_keymanager(address) + logger.debug("Retrieving key for %s" % (address,)) + try: - self._keymanager.get_key(address, openpgp.OpenPGPKey, - private=True, fetch_remote=False) + self._keymanager.get_key( + address, openpgp.OpenPGPKey, private=True, fetch_remote=False) except KeyNotFound: logger.debug("Key not found. Generating key for %s" % (address,)) self._keymanager.gen_key(openpgp.OpenPGPKey) -- cgit v1.2.3 From 2f13fb345ef0d400b099956654958025c7ef7392 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 27 Sep 2013 17:46:08 -0400 Subject: make socket errors during initialization recoverable --- src/leap/bitmask/gui/mainwindow.py | 2 ++ src/leap/bitmask/services/soledad/soledadbootstrapper.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 33af37e3..947ce58c 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -234,6 +234,8 @@ class MainWindow(QtGui.QMainWindow): self._soledad_bootstrapped_stage) self._soledad_bootstrapper.soledad_timeout.connect( self._retry_soledad_connection) + # XXX missing connect to soledad_failed (signal unrecoverable to user) + # TODO wait until chiiph ui refactor. self._smtp_bootstrapper = SMTPBootstrapper() self._smtp_bootstrapper.download_config.connect( diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 835e4cd9..d348661d 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -250,12 +250,16 @@ class SoledadBootstrapper(AbstractBootstrapper): # XXX All these errors should be handled by soledad itself, # and return a subclass of SoledadInitializationFailed + + # recoverable, will guarantee retries except socket.timeout: logger.debug("SOLEDAD initialization TIMED OUT...") self.soledad_timeout.emit() except socket.error as exc: logger.error("Socket error while initializing soledad") - self.soledad_failed.emit() + self.soledad_timeout.emit() + + # unrecoverable except u1db_errors.Unauthorized: logger.error("Error while initializing soledad " "(unauthorized).") -- cgit v1.2.3 From decae946e725ce5275f8035e33a9d6adb69b3342 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Fri, 27 Sep 2013 17:57:07 -0400 Subject: add changes file --- changes/feature_3965_soledad-bootstrap-error-handling | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changes/feature_3965_soledad-bootstrap-error-handling diff --git a/changes/feature_3965_soledad-bootstrap-error-handling b/changes/feature_3965_soledad-bootstrap-error-handling new file mode 100644 index 00000000..d9f16378 --- /dev/null +++ b/changes/feature_3965_soledad-bootstrap-error-handling @@ -0,0 +1,2 @@ + o Improve error handling during soledad bootstrap. Closes: #3965. + Affects: #3619, #3867, #3966 -- cgit v1.2.3 From f59ddb7788509de1e84fcee90be7a1df700dd7b0 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 30 Sep 2013 17:28:42 -0300 Subject: Force cleanlooks only from bundle. --- changes/feature-3981_cleanlooks-for-bundle-only | 2 ++ src/leap/bitmask/app.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 changes/feature-3981_cleanlooks-for-bundle-only diff --git a/changes/feature-3981_cleanlooks-for-bundle-only b/changes/feature-3981_cleanlooks-for-bundle-only new file mode 100644 index 00000000..c762b8f3 --- /dev/null +++ b/changes/feature-3981_cleanlooks-for-bundle-only @@ -0,0 +1,2 @@ + o Force cleanlooks style for kde only if the app is running from bundle. + Closes #3981. diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index d5132731..c1859478 100644 --- a/src/leap/bitmask/app.py +++ b/src/leap/bitmask/app.py @@ -211,7 +211,7 @@ def main(): # We force the style if on KDE so that it doesn't load all the kde # libs, which causes a compatibility issue in some systems. # For more info, see issue #3194 - if os.environ.get("KDE_SESSION_UID") is not None: + if flags.STANDALONE and os.environ.get("KDE_SESSION_UID") is not None: sys.argv.append("-style") sys.argv.append("Cleanlooks") -- cgit v1.2.3 From 10d59e81c9ae5275a92d041e36c61a0628b726f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 30 Sep 2013 12:14:23 -0300 Subject: Implement new UI Also: - Remove status_panel - Add new icons - Refactor components a bit (mostly divide functionality) --- Makefile | 2 +- data/bitmask.pro | 10 +- data/images/black/32/arrow-down.png | Bin 0 -> 493 bytes data/images/black/32/arrow-up.png | Bin 0 -> 468 bytes data/images/black/32/cloud.png | Bin 0 -> 468 bytes data/images/black/32/contract.png | Bin 0 -> 417 bytes data/images/black/32/earth-square.png | Bin 0 -> 1068 bytes data/images/black/32/earth.png | Bin 0 -> 1145 bytes data/images/black/32/email-square.png | Bin 0 -> 787 bytes data/images/black/32/email.png | Bin 0 -> 754 bytes data/images/black/32/expand.png | Bin 0 -> 451 bytes data/images/black/32/gear.png | Bin 0 -> 644 bytes data/images/black/32/off.png | Bin 0 -> 695 bytes data/images/black/32/on.png | Bin 0 -> 667 bytes data/images/black/32/refresh.png | Bin 0 -> 624 bytes data/images/black/32/user.png | Bin 0 -> 534 bytes data/images/black/32/wait.png | Bin 0 -> 836 bytes data/images/white/32/arrow-down.png | Bin 0 -> 486 bytes data/images/white/32/arrow-up.png | Bin 0 -> 463 bytes data/images/white/32/cloud.png | Bin 0 -> 504 bytes data/images/white/32/contract.png | Bin 0 -> 417 bytes data/images/white/32/earth-square.png | Bin 0 -> 1068 bytes data/images/white/32/earth.png | Bin 0 -> 896 bytes data/images/white/32/email-square.png | Bin 0 -> 787 bytes data/images/white/32/email.png | Bin 0 -> 622 bytes data/images/white/32/expand.png | Bin 0 -> 451 bytes data/images/white/32/gear.png | Bin 0 -> 656 bytes data/images/white/32/off.png | Bin 0 -> 703 bytes data/images/white/32/on.png | Bin 0 -> 677 bytes data/images/white/32/refresh.png | Bin 0 -> 654 bytes data/images/white/32/user.png | Bin 0 -> 556 bytes data/images/white/32/wait.png | Bin 0 -> 855 bytes data/resources/mainwindow.qrc | 18 + src/leap/bitmask/config/providerconfig.py | 19 + src/leap/bitmask/gui/clickablelabel.py | 28 + src/leap/bitmask/gui/eip_preferenceswindow.py | 177 +++++++ src/leap/bitmask/gui/eip_status.py | 427 ++++++++++++++++ src/leap/bitmask/gui/login.py | 126 ++++- src/leap/bitmask/gui/mail_status.py | 399 +++++++++++++++ src/leap/bitmask/gui/mainwindow.py | 208 +++----- src/leap/bitmask/gui/preferenceswindow.py | 117 ----- src/leap/bitmask/gui/statuspanel.py | 710 -------------------------- src/leap/bitmask/gui/ui/eip_status.ui | 287 +++++++++++ src/leap/bitmask/gui/ui/eippreferences.ui | 94 ++++ src/leap/bitmask/gui/ui/login.ui | 302 ++++++++--- src/leap/bitmask/gui/ui/mail_status.ui | 98 ++++ src/leap/bitmask/gui/ui/mainwindow.ui | 437 +++++++++------- src/leap/bitmask/gui/ui/preferences.ui | 175 ++----- src/leap/bitmask/gui/ui/statuspanel.ui | 393 -------------- 49 files changed, 2292 insertions(+), 1735 deletions(-) create mode 100644 data/images/black/32/arrow-down.png create mode 100644 data/images/black/32/arrow-up.png create mode 100644 data/images/black/32/cloud.png create mode 100644 data/images/black/32/contract.png create mode 100644 data/images/black/32/earth-square.png create mode 100644 data/images/black/32/earth.png create mode 100644 data/images/black/32/email-square.png create mode 100644 data/images/black/32/email.png create mode 100644 data/images/black/32/expand.png create mode 100644 data/images/black/32/gear.png create mode 100644 data/images/black/32/off.png create mode 100644 data/images/black/32/on.png create mode 100644 data/images/black/32/refresh.png create mode 100644 data/images/black/32/user.png create mode 100644 data/images/black/32/wait.png create mode 100644 data/images/white/32/arrow-down.png create mode 100644 data/images/white/32/arrow-up.png create mode 100644 data/images/white/32/cloud.png create mode 100644 data/images/white/32/contract.png create mode 100644 data/images/white/32/earth-square.png create mode 100644 data/images/white/32/earth.png create mode 100644 data/images/white/32/email-square.png create mode 100644 data/images/white/32/email.png create mode 100644 data/images/white/32/expand.png create mode 100644 data/images/white/32/gear.png create mode 100644 data/images/white/32/off.png create mode 100644 data/images/white/32/on.png create mode 100644 data/images/white/32/refresh.png create mode 100644 data/images/white/32/user.png create mode 100644 data/images/white/32/wait.png create mode 100644 src/leap/bitmask/gui/clickablelabel.py create mode 100644 src/leap/bitmask/gui/eip_preferenceswindow.py create mode 100644 src/leap/bitmask/gui/eip_status.py create mode 100644 src/leap/bitmask/gui/mail_status.py delete mode 100644 src/leap/bitmask/gui/statuspanel.py create mode 100644 src/leap/bitmask/gui/ui/eip_status.ui create mode 100644 src/leap/bitmask/gui/ui/eippreferences.ui create mode 100644 src/leap/bitmask/gui/ui/mail_status.ui delete mode 100644 src/leap/bitmask/gui/ui/statuspanel.ui diff --git a/Makefile b/Makefile index 4f4d77c4..766544f4 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ TRANSLAT_DIR = data/translations PROJFILE = data/bitmask.pro #UI files to compile -UI_FILES = loggerwindow.ui mainwindow.ui wizard.ui login.ui statuspanel.ui preferences.ui +UI_FILES = loggerwindow.ui mainwindow.ui wizard.ui login.ui preferences.ui eip_status.ui mail_status.ui eippreferences.ui #Qt resource files to compile RESOURCES = locale.qrc loggerwindow.qrc mainwindow.qrc icons.qrc diff --git a/data/bitmask.pro b/data/bitmask.pro index e117668b..0f9aaf90 100644 --- a/data/bitmask.pro +++ b/data/bitmask.pro @@ -14,10 +14,13 @@ SOURCES += ../src/leap/bitmask/app.py \ ../src/leap/bitmask/gui/loggerwindow.py \ ../src/leap/bitmask/gui/login.py \ ../src/leap/bitmask/gui/mainwindow.py \ - ../src/leap/bitmask/gui/statuspanel.py \ ../src/leap/bitmask/gui/twisted_main.py \ ../src/leap/bitmask/gui/wizardpage.py \ ../src/leap/bitmask/gui/wizard.py \ + ../src/leap/bitmask/gui/eip_status.py \ + ../src/leap/bitmask/gui/mail_status.py \ + ../src/leap/bitmask/gui/eippreferences.py \ + ../src/leap/bitmask/gui/preferences.py \ ../src/leap/bitmask/platform_init/initializers.py \ ../src/leap/bitmask/platform_init/locks.py \ ../src/leap/bitmask/provider/supportedapis.py \ @@ -45,8 +48,11 @@ SOURCES += ../src/leap/bitmask/app.py \ FORMS += ../src/leap/bitmask/gui/ui/loggerwindow.ui \ ../src/leap/bitmask/gui/ui/login.ui \ ../src/leap/bitmask/gui/ui/mainwindow.ui \ - ../src/leap/bitmask/gui/ui/statuspanel.ui \ ../src/leap/bitmask/gui/ui/wizard.ui \ + ../src/leap/bitmask/gui/ui/eip_status.ui \ + ../src/leap/bitmask/gui/ui/mail_status.ui \ + ../src/leap/bitmask/gui/ui/eippreferences.ui \ + ../src/leap/bitmask/gui/ui/preferences.ui \ # where to generate ts files -- tx will pick from here diff --git a/data/images/black/32/arrow-down.png b/data/images/black/32/arrow-down.png new file mode 100644 index 00000000..328eb08d Binary files /dev/null and b/data/images/black/32/arrow-down.png differ diff --git a/data/images/black/32/arrow-up.png b/data/images/black/32/arrow-up.png new file mode 100644 index 00000000..5eea5108 Binary files /dev/null and b/data/images/black/32/arrow-up.png differ diff --git a/data/images/black/32/cloud.png b/data/images/black/32/cloud.png new file mode 100644 index 00000000..10cbd62d Binary files /dev/null and b/data/images/black/32/cloud.png differ diff --git a/data/images/black/32/contract.png b/data/images/black/32/contract.png new file mode 100644 index 00000000..9f0ee605 Binary files /dev/null and b/data/images/black/32/contract.png differ diff --git a/data/images/black/32/earth-square.png b/data/images/black/32/earth-square.png new file mode 100644 index 00000000..da7bf2cf Binary files /dev/null and b/data/images/black/32/earth-square.png differ diff --git a/data/images/black/32/earth.png b/data/images/black/32/earth.png new file mode 100644 index 00000000..64919edc Binary files /dev/null and b/data/images/black/32/earth.png differ diff --git a/data/images/black/32/email-square.png b/data/images/black/32/email-square.png new file mode 100644 index 00000000..499d7a8f Binary files /dev/null and b/data/images/black/32/email-square.png differ diff --git a/data/images/black/32/email.png b/data/images/black/32/email.png new file mode 100644 index 00000000..a6f40297 Binary files /dev/null and b/data/images/black/32/email.png differ diff --git a/data/images/black/32/expand.png b/data/images/black/32/expand.png new file mode 100644 index 00000000..c581503d Binary files /dev/null and b/data/images/black/32/expand.png differ diff --git a/data/images/black/32/gear.png b/data/images/black/32/gear.png new file mode 100644 index 00000000..93c8742d Binary files /dev/null and b/data/images/black/32/gear.png differ diff --git a/data/images/black/32/off.png b/data/images/black/32/off.png new file mode 100644 index 00000000..6ddde746 Binary files /dev/null and b/data/images/black/32/off.png differ diff --git a/data/images/black/32/on.png b/data/images/black/32/on.png new file mode 100644 index 00000000..bbd28bad Binary files /dev/null and b/data/images/black/32/on.png differ diff --git a/data/images/black/32/refresh.png b/data/images/black/32/refresh.png new file mode 100644 index 00000000..ad67f563 Binary files /dev/null and b/data/images/black/32/refresh.png differ diff --git a/data/images/black/32/user.png b/data/images/black/32/user.png new file mode 100644 index 00000000..7ea0f43a Binary files /dev/null and b/data/images/black/32/user.png differ diff --git a/data/images/black/32/wait.png b/data/images/black/32/wait.png new file mode 100644 index 00000000..a01ce923 Binary files /dev/null and b/data/images/black/32/wait.png differ diff --git a/data/images/white/32/arrow-down.png b/data/images/white/32/arrow-down.png new file mode 100644 index 00000000..02ccc540 Binary files /dev/null and b/data/images/white/32/arrow-down.png differ diff --git a/data/images/white/32/arrow-up.png b/data/images/white/32/arrow-up.png new file mode 100644 index 00000000..fbe1f816 Binary files /dev/null and b/data/images/white/32/arrow-up.png differ diff --git a/data/images/white/32/cloud.png b/data/images/white/32/cloud.png new file mode 100644 index 00000000..39d589ef Binary files /dev/null and b/data/images/white/32/cloud.png differ diff --git a/data/images/white/32/contract.png b/data/images/white/32/contract.png new file mode 100644 index 00000000..262ff3c0 Binary files /dev/null and b/data/images/white/32/contract.png differ diff --git a/data/images/white/32/earth-square.png b/data/images/white/32/earth-square.png new file mode 100644 index 00000000..da7bf2cf Binary files /dev/null and b/data/images/white/32/earth-square.png differ diff --git a/data/images/white/32/earth.png b/data/images/white/32/earth.png new file mode 100644 index 00000000..efca7ccb Binary files /dev/null and b/data/images/white/32/earth.png differ diff --git a/data/images/white/32/email-square.png b/data/images/white/32/email-square.png new file mode 100644 index 00000000..499d7a8f Binary files /dev/null and b/data/images/white/32/email-square.png differ diff --git a/data/images/white/32/email.png b/data/images/white/32/email.png new file mode 100644 index 00000000..abb36035 Binary files /dev/null and b/data/images/white/32/email.png differ diff --git a/data/images/white/32/expand.png b/data/images/white/32/expand.png new file mode 100644 index 00000000..0ec28dcc Binary files /dev/null and b/data/images/white/32/expand.png differ diff --git a/data/images/white/32/gear.png b/data/images/white/32/gear.png new file mode 100644 index 00000000..83f8d5ff Binary files /dev/null and b/data/images/white/32/gear.png differ diff --git a/data/images/white/32/off.png b/data/images/white/32/off.png new file mode 100644 index 00000000..22621594 Binary files /dev/null and b/data/images/white/32/off.png differ diff --git a/data/images/white/32/on.png b/data/images/white/32/on.png new file mode 100644 index 00000000..8946f763 Binary files /dev/null and b/data/images/white/32/on.png differ diff --git a/data/images/white/32/refresh.png b/data/images/white/32/refresh.png new file mode 100644 index 00000000..36a89da9 Binary files /dev/null and b/data/images/white/32/refresh.png differ diff --git a/data/images/white/32/user.png b/data/images/white/32/user.png new file mode 100644 index 00000000..ff8edd00 Binary files /dev/null and b/data/images/white/32/user.png differ diff --git a/data/images/white/32/wait.png b/data/images/white/32/wait.png new file mode 100644 index 00000000..8562d636 Binary files /dev/null and b/data/images/white/32/wait.png differ diff --git a/data/resources/mainwindow.qrc b/data/resources/mainwindow.qrc index 1e4159b8..0a917d5a 100644 --- a/data/resources/mainwindow.qrc +++ b/data/resources/mainwindow.qrc @@ -1,5 +1,23 @@ + ../images/white/32/off.png + ../images/white/32/on.png + ../images/white/32/wait.png + ../images/black/32/arrow-down.png + ../images/black/32/arrow-up.png + ../images/black/32/cloud.png + ../images/black/32/contract.png + ../images/black/32/earth-square.png + ../images/black/32/earth.png + ../images/black/32/email-square.png + ../images/black/32/email.png + ../images/black/32/expand.png + ../images/black/32/gear.png + ../images/black/32/off.png + ../images/black/32/on.png + ../images/black/32/refresh.png + ../images/black/32/user.png + ../images/black/32/wait.png ../images/mask-launcher.png ../images/mask-icon.png ../images/leap-gray-big.png diff --git a/src/leap/bitmask/config/providerconfig.py b/src/leap/bitmask/config/providerconfig.py index c8c8a59e..44698d83 100644 --- a/src/leap/bitmask/config/providerconfig.py +++ b/src/leap/bitmask/config/providerconfig.py @@ -44,6 +44,25 @@ class ProviderConfig(BaseConfig): def __init__(self): BaseConfig.__init__(self) + @classmethod + 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 _get_schema(self): """ Returns the schema corresponding to the version given. diff --git a/src/leap/bitmask/gui/clickablelabel.py b/src/leap/bitmask/gui/clickablelabel.py new file mode 100644 index 00000000..2808a601 --- /dev/null +++ b/src/leap/bitmask/gui/clickablelabel.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# clickablelabel.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 . + +""" +Clickable label +""" +from PySide import QtCore, QtGui + + +class ClickableLabel(QtGui.QLabel): + clicked = QtCore.Signal() + + def mousePressEvent(self, event): + self.clicked.emit() diff --git a/src/leap/bitmask/gui/eip_preferenceswindow.py b/src/leap/bitmask/gui/eip_preferenceswindow.py new file mode 100644 index 00000000..0e6e8dda --- /dev/null +++ b/src/leap/bitmask/gui/eip_preferenceswindow.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +# eip_preferenceswindow.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 . + +""" +EIP Preferences window +""" +import os +import logging + +from functools import partial +from PySide import QtGui + +from leap.bitmask.config.leapsettings import LeapSettings +from leap.bitmask.config.providerconfig import ProviderConfig +from leap.bitmask.gui.ui_eippreferences import Ui_EIPPreferences +from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector + +logger = logging.getLogger(__name__) + + +class EIPPreferencesWindow(QtGui.QDialog): + """ + Window that displays the EIP preferences. + """ + def __init__(self, parent): + """ + :param parent: parent object of the EIPPreferencesWindow. + :parent type: QWidget + """ + QtGui.QDialog.__init__(self, parent) + self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic") + + self._settings = LeapSettings() + + # Load UI + self.ui = Ui_EIPPreferences() + self.ui.setupUi(self) + self.ui.lblProvidersGatewayStatus.setVisible(False) + + # Connections + self.ui.cbProvidersGateway.currentIndexChanged[unicode].connect( + self._populate_gateways) + + self.ui.cbGateways.currentIndexChanged[unicode].connect( + lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False)) + + self._add_configured_providers() + + 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 = "%s" % (status,) + elif error: + status = "%s" % (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. + """ + self.ui.cbProvidersGateway.clear() + for provider in self._settings.get_configured_providers(): + self.ui.cbProvidersGateway.addItem(provider) + + 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) + 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 previously 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 = ProviderConfig.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/eip_status.py b/src/leap/bitmask/gui/eip_status.py new file mode 100644 index 00000000..f7408b13 --- /dev/null +++ b/src/leap/bitmask/gui/eip_status.py @@ -0,0 +1,427 @@ +# -*- coding: utf-8 -*- +# eip_status.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 . +""" +EIP Status Panel widget implementation +""" +import logging + +from datetime import datetime +from functools import partial + +from PySide import QtCore, QtGui + +from leap.bitmask.services.eip.connection import EIPConnection +from leap.bitmask.services.eip.vpnprocess import VPNManager +from leap.bitmask.platform_init import IS_LINUX +from leap.bitmask.util.averages import RateMovingAverage +from leap.common.check import leap_assert_type + +from ui_eip_status import Ui_EIPStatus + +logger = logging.getLogger(__name__) + + +class EIPStatusWidget(QtGui.QWidget): + """ + EIP Status widget that displays the current state of the EIP service + """ + DISPLAY_TRAFFIC_RATES = True + RATE_STR = "%14.2f KB/s" + TOTAL_STR = "%14.2f Kb" + + eip_connection_connected = QtCore.Signal() + + def __init__(self, parent=None): + QtGui.QWidget.__init__(self, parent) + + self._systray = None + self._eip_status_menu = None + + self.ui = Ui_EIPStatus() + self.ui.setupUi(self) + + self.eipconnection = EIPConnection() + + # set systray tooltip status + self._eip_status = "" + + self.ui.eip_bandwidth.hide() + + # Set the EIP status icons + self.CONNECTING_ICON = None + self.CONNECTED_ICON = None + self.ERROR_ICON = None + self.CONNECTING_ICON_TRAY = None + self.CONNECTED_ICON_TRAY = None + self.ERROR_ICON_TRAY = None + self._set_eip_icons() + + self._set_traffic_rates() + self._make_status_clickable() + + self._provider = "" + + def _make_status_clickable(self): + """ + Makes upload and download figures clickable. + """ + onclicked = self._on_VPN_status_clicked + self.ui.btnUpload.clicked.connect(onclicked) + self.ui.btnDownload.clicked.connect(onclicked) + + def _on_VPN_status_clicked(self): + """ + SLOT + TRIGGER: self.ui.btnUpload.clicked + self.ui.btnDownload.clicked + + Toggles between rate and total throughput display for vpn + status figures. + """ + self.DISPLAY_TRAFFIC_RATES = not self.DISPLAY_TRAFFIC_RATES + self.update_vpn_status(None) # refresh + + def _set_traffic_rates(self): + """ + Initializes up and download rates. + """ + self._up_rate = RateMovingAverage() + self._down_rate = RateMovingAverage() + + self.ui.btnUpload.setText(self.RATE_STR % (0,)) + self.ui.btnDownload.setText(self.RATE_STR % (0,)) + + def _reset_traffic_rates(self): + """ + Resets up and download rates, and cleans up the labels. + """ + self._up_rate.reset() + self._down_rate.reset() + self.update_vpn_status(None) + + def _update_traffic_rates(self, up, down): + """ + Updates up and download rates. + + :param up: upload total. + :type up: int + :param down: download total. + :type down: int + """ + ts = datetime.now() + self._up_rate.append((ts, up)) + self._down_rate.append((ts, down)) + + def _get_traffic_rates(self): + """ + Gets the traffic rates (in KB/s). + + :returns: a tuple with the (up, down) rates + :rtype: tuple + """ + up = self._up_rate + down = self._down_rate + + return (up.get_average(), down.get_average()) + + def _get_traffic_totals(self): + """ + Gets the traffic total throughput (in Kb). + + :returns: a tuple with the (up, down) totals + :rtype: tuple + """ + up = self._up_rate + down = self._down_rate + + return (up.get_total(), down.get_total()) + + def _set_eip_icons(self): + """ + Sets the EIP status icons for the main window and for the tray + + MAC : dark icons + LINUX : dark icons in window, light icons in tray + WIN : light icons + """ + EIP_ICONS = EIP_ICONS_TRAY = ( + ":/images/black/32/wait.png", + ":/images/black/32/on.png", + ":/images/black/32/off.png") + + if IS_LINUX: + EIP_ICONS_TRAY = ( + ":/images/white/32/wait.png", + ":/images/white/32/on.png", + ":/images/white/32/off.png") + + self.CONNECTING_ICON = QtGui.QPixmap(EIP_ICONS[0]) + self.CONNECTED_ICON = QtGui.QPixmap(EIP_ICONS[1]) + self.ERROR_ICON = QtGui.QPixmap(EIP_ICONS[2]) + + self.CONNECTING_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[0]) + self.CONNECTED_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[1]) + self.ERROR_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[2]) + + # Systray and actions + + def set_systray(self, systray): + """ + Sets the systray object to use. + + :param systray: Systray object + :type systray: QtGui.QSystemTrayIcon + """ + leap_assert_type(systray, QtGui.QSystemTrayIcon) + self._systray = systray + self._systray.setToolTip(self.tr("All services are OFF")) + + def _update_systray_tooltip(self): + """ + Updates the system tray icon tooltip using the eip and mx status. + """ + status = self.tr("Encrypted Internet: {0}").format(self._eip_status) + self._systray.setToolTip(status) + + def set_action_eip_startstop(self, action_eip_startstop): + """ + Sets the action_eip_startstop to use. + + :param action_eip_startstop: action_eip_status to be used + :type action_eip_startstop: QtGui.QAction + """ + self._action_eip_startstop = action_eip_startstop + + def set_eip_status_menu(self, eip_status_menu): + """ + Sets the eip_status_menu to use. + + :param eip_status_menu: eip_status_menu to be used + :type eip_status_menu: QtGui.QMenu + """ + leap_assert_type(eip_status_menu, QtGui.QMenu) + self._eip_status_menu = eip_status_menu + + def set_eip_status(self, status, error=False): + """ + Sets the global status label. + + :param status: status message + :type status: str or unicode + :param error: if the status is an erroneous one, then set this + to True + :type error: bool + """ + leap_assert_type(error, bool) + if error: + status = "%s" % (status,) + self.ui.lblEIPStatus.setText(status) + self.ui.lblEIPStatus.show() + + # EIP status --- + + @property + def eip_button(self): + return self.ui.btnEipStartStop + + @property + def eip_label(self): + return self.ui.lblEIPStatus + + def eip_pre_up(self): + """ + Triggered when the app activates eip. + Hides the status box and disables the start/stop button. + """ + self.set_startstop_enabled(False) + + # XXX disable (later) -------------------------- + def set_eip_status(self, status, error=False): + """ + Sets the status label at the VPN stage to status + + :param status: status message + :type status: str or unicode + :param error: if the status is an erroneous one, then set this + to True + :type error: bool + """ + leap_assert_type(error, bool) + + self._eip_status = status + + if error: + status = "%s" % (status,) + self.ui.lblEIPStatus.setText(status) + self.ui.lblEIPStatus.show() + self._update_systray_tooltip() + + # XXX disable --------------------------------- + def set_startstop_enabled(self, value): + """ + Enable or disable btnEipStartStop and _action_eip_startstop + based on value + + :param value: True for enabled, False otherwise + :type value: bool + """ + leap_assert_type(value, bool) + self.ui.btnEipStartStop.setEnabled(value) + self._action_eip_startstop.setEnabled(value) + + # XXX disable ----------------------------- + def eip_started(self): + """ + Sets the state of the widget to how it should look after EIP + has started + """ + self.ui.btnEipStartStop.setText(self.tr("Turn OFF")) + self.ui.btnEipStartStop.disconnect(self) + self.ui.btnEipStartStop.clicked.connect( + self.eipconnection.qtsigs.do_connect_signal) + + # XXX disable ----------------------------- + def eip_stopped(self): + """ + Sets the state of the widget to how it should look after EIP + has stopped + """ + # XXX should connect this to EIPConnection.disconnected_signal + self._reset_traffic_rates() + # XXX disable ----------------------------- + self.ui.btnEipStartStop.setText(self.tr("Turn ON")) + self.ui.btnEipStartStop.disconnect(self) + self.ui.btnEipStartStop.clicked.connect( + self.eipconnection.qtsigs.do_disconnect_signal) + + self.ui.eip_bandwidth.hide() + self.ui.lblEIPMessage.setText( + self.tr("Traffic is being routed in the clear")) + self.ui.lblEIPStatus.show() + + def update_vpn_status(self, data): + """ + SLOT + TRIGGER: VPN.status_changed + + Updates the download/upload labels based on the data provided + by the VPN thread. + + :param data: a dictionary with the tcp/udp write and read totals. + If data is None, we just will refresh the display based + on the previous data. + :type data: dict + """ + if data: + upload = float(data[VPNManager.TCPUDP_WRITE_KEY] or "0") + download = float(data[VPNManager.TCPUDP_READ_KEY] or "0") + self._update_traffic_rates(upload, download) + + if self.DISPLAY_TRAFFIC_RATES: + uprate, downrate = self._get_traffic_rates() + upload_str = self.RATE_STR % (uprate,) + download_str = self.RATE_STR % (downrate,) + + else: # display total throughput + uptotal, downtotal = self._get_traffic_totals() + upload_str = self.TOTAL_STR % (uptotal,) + download_str = self.TOTAL_STR % (downtotal,) + + self.ui.btnUpload.setText(upload_str) + self.ui.btnDownload.setText(download_str) + + def update_vpn_state(self, data): + """ + SLOT + TRIGGER: VPN.state_changed + + Updates the displayed VPN state based on the data provided by + the VPN thread. + + Emits: + If the status is connected, we emit EIPConnection.qtsigs. + connected_signal + """ + status = data[VPNManager.STATUS_STEP_KEY] + self.set_eip_status_icon(status) + if status == "CONNECTED": + self.ui.eip_bandwidth.show() + self.ui.lblEIPStatus.hide() + + # XXX should be handled by the state machine too. + self.eip_connection_connected.emit() + + # XXX should lookup status map in EIPConnection + elif status == "AUTH": + self.set_eip_status(self.tr("Authenticating...")) + elif status == "GET_CONFIG": + self.set_eip_status(self.tr("Retrieving configuration...")) + elif status == "WAIT": + self.set_eip_status(self.tr("Waiting to start...")) + elif status == "ASSIGN_IP": + self.set_eip_status(self.tr("Assigning IP")) + elif status == "RECONNECTING": + self.set_eip_status(self.tr("Reconnecting...")) + elif status == "ALREADYRUNNING": + # Put the following calls in Qt's event queue, otherwise + # the UI won't update properly + QtCore.QTimer.singleShot( + 0, self.eipconnection.qtsigs.do_disconnect_signal) + QtCore.QTimer.singleShot(0, partial(self.set_eip_status, + self.tr("Unable to start VPN, " + "it's already " + "running."))) + else: + self.set_eip_status(status) + + def set_eip_icon(self, icon): + """ + Sets the icon to display for EIP + + :param icon: icon to display + :type icon: QPixmap + """ + self.ui.lblVPNStatusIcon.setPixmap(icon) + + def set_eip_status_icon(self, status): + """ + Given a status step from the VPN thread, set the icon properly + + :param status: status step + :type status: str + """ + selected_pixmap = self.ERROR_ICON + selected_pixmap_tray = self.ERROR_ICON_TRAY + tray_message = self.tr("Encrypted Internet: OFF") + if status in ("WAIT", "AUTH", "GET_CONFIG", + "RECONNECTING", "ASSIGN_IP"): + selected_pixmap = self.CONNECTING_ICON + selected_pixmap_tray = self.CONNECTING_ICON_TRAY + tray_message = self.tr("Encrypted Internet: Starting...") + elif status in ("CONNECTED"): + tray_message = self.tr("Encrypted Internet: ON") + selected_pixmap = self.CONNECTED_ICON + selected_pixmap_tray = self.CONNECTED_ICON_TRAY + + self.set_eip_icon(selected_pixmap) + self._systray.setIcon(QtGui.QIcon(selected_pixmap_tray)) + self._eip_status_menu.setTitle(tray_message) + + def set_provider(self, provider): + self._provider = provider + self.ui.lblEIPMessage.setText( + self.tr("Route traffic through: {0}").format(self._provider)) diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index db7b8e2a..9a369f6d 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -20,10 +20,13 @@ Login widget implementation """ import logging +import keyring + from PySide import QtCore, QtGui from ui_login import Ui_LoginWidget from leap.bitmask.util.keyring_helpers import has_keyring +from leap.common.check import leap_assert_type logger = logging.getLogger(__name__) @@ -37,6 +40,7 @@ class LoginWidget(QtGui.QWidget): # Emitted when the login button is clicked login = QtCore.Signal() cancel_login = QtCore.Signal() + logout = QtCore.Signal() # Emitted when the user selects "Other..." in the provider # combobox or click "Create Account" @@ -76,13 +80,21 @@ class LoginWidget(QtGui.QWidget): self.ui.cmbProviders.currentIndexChanged.connect( self._current_provider_changed) - self.ui.btnCreateAccount.clicked.connect( - self.show_wizard) + + self.ui.btnLogout.clicked.connect( + self.logout) username_re = QtCore.QRegExp(self.BARE_USERNAME_REGEX) self.ui.lnUser.setValidator( QtGui.QRegExpValidator(username_re, self)) + self.logged_out() + + self.ui.btnLogout.clicked.connect(self.start_logout) + + self.ui.clblErrorMsg.hide() + self.ui.clblErrorMsg.clicked.connect(self.ui.clblErrorMsg.hide) + def _remember_state_changed(self, state): """ Saves the remember state in the LeapSettings @@ -185,8 +197,10 @@ class LoginWidget(QtGui.QWidget): if len(status) > self.MAX_STATUS_WIDTH: status = status[:self.MAX_STATUS_WIDTH] + "..." if error: - status = "%s" % (status,) - self.ui.lblStatus.setText(status) + self.ui.clblErrorMsg.show() + self.ui.clblErrorMsg.setText(status) + else: + self.ui.lblStatus.setText(status) def set_enabled(self, enabled=False): """ @@ -211,6 +225,7 @@ class LoginWidget(QtGui.QWidget): """ text = self.tr("Cancel") login_or_cancel = self.cancel_login + hide_remember = enabled if not enabled: text = self.tr("Log In") @@ -220,6 +235,8 @@ class LoginWidget(QtGui.QWidget): self.ui.btnLogin.clicked.disconnect() self.ui.btnLogin.clicked.connect(login_or_cancel) + self.ui.chkRemember.setVisible(not hide_remember) + self.ui.lblStatus.setVisible(hide_remember) def _focus_password(self): """ @@ -243,3 +260,104 @@ class LoginWidget(QtGui.QWidget): self.ui.cmbProviders.blockSignals(False) else: self._selected_provider_index = param + + def start_login(self): + """ + Setups the login widgets for actually performing the login and + performs some basic checks. + + :returns: True if everything's good to go, False otherwise + :rtype: bool + """ + username = self.get_user() + password = self.get_password() + provider = self.get_selected_provider() + + self._enabled_services = self._settings.get_enabled_services( + self.get_selected_provider()) + + if len(provider) == 0: + self.set_status( + self.tr("Please select a valid provider")) + return False + + if len(username) == 0: + self.set_status( + self.tr("Please provide a valid username")) + return False + + if len(password) == 0: + self.set_status( + self.tr("Please provide a valid password")) + return False + + self.set_status(self.tr("Logging in..."), error=False) + self.set_enabled(False) + + if self.get_remember() and has_keyring(): + # 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_domain, + password.encode("utf8")) + # Only save the username if it was saved correctly in + # the keyring + self._settings.set_user(username_domain) + except Exception as e: + logger.exception("Problem saving data to keyring. %r" + % (e,)) + return True + + def logged_in(self): + """ + Sets the widgets to the logged in state + """ + self.ui.login_widget.hide() + self.ui.logged_widget.show() + self.ui.lblUser.setText("%s@%s" % (self.get_user(), + self.get_selected_provider())) + self.set_login_status("") + + def logged_out(self): + """ + Sets the widgets to the logged out state + """ + self.ui.login_widget.show() + self.ui.logged_widget.hide() + + self.set_password("") + self.set_enabled(True) + self.set_status("") + + def set_login_status(self, msg, error=False): + """ + Sets the status label for the logged in state. + + :param msg: status message + :type msg: str or unicode + :param error: if the status is an erroneous one, then set this + to True + :type error: bool + """ + leap_assert_type(error, bool) + if error: + msg = "%s" % (msg,) + self.ui.lblLoginStatus.setText(msg) + self.ui.lblLoginStatus.show() + + def start_logout(self): + """ + Sets the widgets to the logging out state + """ + self.ui.btnLogout.setText(self.tr("Loggin out...")) + self.ui.btnLogout.setEnabled(False) + + def done_logout(self): + """ + Sets the widgets to the logged out state + """ + self.ui.btnLogout.setText(self.tr("Logout")) + self.ui.btnLogout.setEnabled(True) + self.ui.clblErrorMsg.hide() diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py new file mode 100644 index 00000000..770d991f --- /dev/null +++ b/src/leap/bitmask/gui/mail_status.py @@ -0,0 +1,399 @@ +# -*- coding: utf-8 -*- +# mail_status.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 . +""" +Mail Status Panel widget implementation +""" +import logging + +from PySide import QtCore, QtGui + +from leap.bitmask.platform_init import IS_LINUX +from leap.common.check import leap_assert, leap_assert_type +from leap.common.events import register +from leap.common.events import events_pb2 as proto + +from ui_mail_status import Ui_MailStatusWidget + +logger = logging.getLogger(__name__) + + +class MailStatusWidget(QtGui.QWidget): + """ + Status widget that displays the state of the LEAP Mail service + """ + eip_connection_connected = QtCore.Signal() + _soledad_event = QtCore.Signal(object) + _smtp_event = QtCore.Signal(object) + _imap_event = QtCore.Signal(object) + _keymanager_event = QtCore.Signal(object) + + def __init__(self, parent=None): + """ + Constructor for MailStatusWidget + + :param parent: parent widget for this one. + :type parent: QtGui.QWidget + """ + QtGui.QWidget.__init__(self, parent) + + self._systray = None + + self.ui = Ui_MailStatusWidget() + self.ui.setupUi(self) + + # set systray tooltip status + self._mx_status = "" + + # Set the Mail status icons + self.CONNECTING_ICON = None + self.CONNECTED_ICON = None + self.ERROR_ICON = None + self.CONNECTING_ICON_TRAY = None + self.CONNECTED_ICON_TRAY = None + self.ERROR_ICON_TRAY = None + self._set_mail_icons() + + register(signal=proto.KEYMANAGER_LOOKING_FOR_KEY, + callback=self._mail_handle_keymanager_events, + reqcbk=lambda req, resp: None) + + register(signal=proto.KEYMANAGER_KEY_FOUND, + callback=self._mail_handle_keymanager_events, + reqcbk=lambda req, resp: None) + + # register(signal=proto.KEYMANAGER_KEY_NOT_FOUND, + # callback=self._mail_handle_keymanager_events, + # reqcbk=lambda req, resp: None) + + register(signal=proto.KEYMANAGER_STARTED_KEY_GENERATION, + callback=self._mail_handle_keymanager_events, + reqcbk=lambda req, resp: None) + + register(signal=proto.KEYMANAGER_FINISHED_KEY_GENERATION, + callback=self._mail_handle_keymanager_events, + reqcbk=lambda req, resp: None) + + register(signal=proto.KEYMANAGER_DONE_UPLOADING_KEYS, + callback=self._mail_handle_keymanager_events, + reqcbk=lambda req, resp: None) + + register(signal=proto.SOLEDAD_DONE_DOWNLOADING_KEYS, + callback=self._mail_handle_soledad_events, + reqcbk=lambda req, resp: None) + + register(signal=proto.SOLEDAD_DONE_UPLOADING_KEYS, + callback=self._mail_handle_soledad_events, + reqcbk=lambda req, resp: None) + + register(signal=proto.SMTP_SERVICE_STARTED, + callback=self._mail_handle_smtp_events, + reqcbk=lambda req, resp: None) + + register(signal=proto.SMTP_SERVICE_FAILED_TO_START, + callback=self._mail_handle_smtp_events, + reqcbk=lambda req, resp: None) + + register(signal=proto.IMAP_SERVICE_STARTED, + callback=self._mail_handle_imap_events, + reqcbk=lambda req, resp: None) + + register(signal=proto.IMAP_SERVICE_FAILED_TO_START, + callback=self._mail_handle_imap_events, + reqcbk=lambda req, resp: None) + + register(signal=proto.IMAP_UNREAD_MAIL, + callback=self._mail_handle_imap_events, + reqcbk=lambda req, resp: None) + + self._smtp_started = False + self._imap_started = False + + self._soledad_event.connect( + self._mail_handle_soledad_events_slot) + self._imap_event.connect( + self._mail_handle_imap_events_slot) + self._smtp_event.connect( + self._mail_handle_smtp_events_slot) + self._keymanager_event.connect( + self._mail_handle_keymanager_events_slot) + + def _set_mail_icons(self): + """ + Sets the Mail status icons for the main window and for the tray + + MAC : dark icons + LINUX : dark icons in window, light icons in tray + WIN : light icons + """ + EIP_ICONS = EIP_ICONS_TRAY = ( + ":/images/black/32/wait.png", + ":/images/black/32/on.png", + ":/images/black/32/off.png") + + if IS_LINUX: + EIP_ICONS_TRAY = ( + ":/images/white/32/wait.png", + ":/images/white/32/on.png", + ":/images/white/32/off.png") + + self.CONNECTING_ICON = QtGui.QPixmap(EIP_ICONS[0]) + self.CONNECTED_ICON = QtGui.QPixmap(EIP_ICONS[1]) + self.ERROR_ICON = QtGui.QPixmap(EIP_ICONS[2]) + + self.CONNECTING_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[0]) + self.CONNECTED_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[1]) + self.ERROR_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[2]) + + # Systray and actions + + def set_systray(self, systray): + """ + Sets the systray object to use. + + :param systray: Systray object + :type systray: QtGui.QSystemTrayIcon + """ + leap_assert_type(systray, QtGui.QSystemTrayIcon) + self._systray = systray + self._systray.setToolTip(self.tr("All services are OFF")) + + def _update_systray_tooltip(self): + """ + Updates the system tray icon tooltip using the eip and mx status. + """ + # TODO: Figure out how to handle this with the two status in different + # classes + # status = self.tr("Encrypted Internet: {0}").format(self._eip_status) + # status += '\n' + # status += self.tr("Mail is {0}").format(self._mx_status) + # self._systray.setToolTip(status) + pass + + def set_action_mail_status(self, action_mail_status): + """ + Sets the action_mail_status to use. + + :param action_mail_status: action_mail_status to be used + :type action_mail_status: QtGui.QAction + """ + leap_assert_type(action_mail_status, QtGui.QAction) + self._action_mail_status = action_mail_status + + def _set_mail_status(self, status, ready=0): + """ + Sets the Mail status in the label and in the tray icon. + + :param status: the status text to display + :type status: unicode + :param ready: 2 or >2 if mx is ready, 0 if stopped, 1 if it's starting. + :type ready: int + """ + self.ui.lblMailStatus.setText(status) + + self._mx_status = self.tr('OFF') + tray_status = self.tr('Mail is OFF') + + icon = self.ERROR_ICON + if ready == 0: + self.ui.lblMailStatus.setText( + self.tr("You must be logged in to use encrypted email.")) + elif ready == 1: + icon = self.CONNECTING_ICON + self._mx_status = self.tr('Starting..') + tray_status = self.tr('Mail is starting') + elif ready >= 2: + icon = self.CONNECTED_ICON + self._mx_status = self.tr('ON') + tray_status = self.tr('Mail is ON') + + self.ui.lblMailStatusIcon.setPixmap(icon) + self._action_mail_status.setText(tray_status) + self._update_systray_tooltip() + + def _mail_handle_soledad_events(self, req): + """ + Callback for handling events that are emitted from Soledad + + :param req: Request type + :type req: leap.common.events.events_pb2.SignalRequest + """ + self._soledad_event.emit(req) + + def _mail_handle_soledad_events_slot(self, req): + """ + SLOT + TRIGGER: _mail_handle_soledad_events + + Reacts to an Soledad event + + :param req: Request type + :type req: leap.common.events.events_pb2.SignalRequest + """ + self._set_mail_status(self.tr("Starting..."), ready=1) + + ext_status = "" + + if req.event == proto.SOLEDAD_DONE_UPLOADING_KEYS: + ext_status = self.tr("Soledad has started...") + elif req.event == proto.SOLEDAD_DONE_DOWNLOADING_KEYS: + ext_status = self.tr("Soledad is starting, please wait...") + else: + leap_assert(False, + "Don't know how to handle this state: %s" + % (req.event)) + + self._set_mail_status(ext_status, ready=1) + + def _mail_handle_keymanager_events(self, req): + """ + Callback for the KeyManager events + + :param req: Request type + :type req: leap.common.events.events_pb2.SignalRequest + """ + self._keymanager_event.emit(req) + + def _mail_handle_keymanager_events_slot(self, req): + """ + SLOT + TRIGGER: _mail_handle_keymanager_events + + Reacts to an KeyManager event + + :param req: Request type + :type req: leap.common.events.events_pb2.SignalRequest + """ + # We want to ignore this kind of events once everything has + # started + if self._smtp_started and self._imap_started: + return + + self._set_mail_status(self.tr("Starting..."), ready=1) + + ext_status = "" + + if req.event == proto.KEYMANAGER_LOOKING_FOR_KEY: + ext_status = self.tr("Looking for key for this user") + elif req.event == proto.KEYMANAGER_KEY_FOUND: + ext_status = self.tr("Found key! Starting mail...") + # elif req.event == proto.KEYMANAGER_KEY_NOT_FOUND: + # ext_status = self.tr("Key not found!") + elif req.event == proto.KEYMANAGER_STARTED_KEY_GENERATION: + ext_status = self.tr("Generating new key, please wait...") + elif req.event == proto.KEYMANAGER_FINISHED_KEY_GENERATION: + ext_status = self.tr("Finished generating key!") + elif req.event == proto.KEYMANAGER_DONE_UPLOADING_KEYS: + ext_status = self.tr("Starting mail...") + else: + leap_assert(False, + "Don't know how to handle this state: %s" + % (req.event)) + + self._set_mail_status(ext_status, ready=1) + + def _mail_handle_smtp_events(self, req): + """ + Callback for the SMTP events + + :param req: Request type + :type req: leap.common.events.events_pb2.SignalRequest + """ + self._smtp_event.emit(req) + + def _mail_handle_smtp_events_slot(self, req): + """ + SLOT + TRIGGER: _mail_handle_smtp_events + + Reacts to an SMTP event + + :param req: Request type + :type req: leap.common.events.events_pb2.SignalRequest + """ + ext_status = "" + + if req.event == proto.SMTP_SERVICE_STARTED: + ext_status = self.tr("SMTP has started...") + self._smtp_started = True + if self._smtp_started and self._imap_started: + self._set_mail_status(self.tr("ON"), ready=2) + ext_status = "" + elif req.event == proto.SMTP_SERVICE_FAILED_TO_START: + ext_status = self.tr("SMTP failed to start, check the logs.") + self._set_mail_status(self.tr("Failed")) + else: + leap_assert(False, + "Don't know how to handle this state: %s" + % (req.event)) + + self._set_mail_status(ext_status, ready=2) + + def _mail_handle_imap_events(self, req): + """ + Callback for the IMAP events + + :param req: Request type + :type req: leap.common.events.events_pb2.SignalRequest + """ + self._imap_event.emit(req) + + def _mail_handle_imap_events_slot(self, req): + """ + SLOT + TRIGGER: _mail_handle_imap_events + + Reacts to an IMAP event + + :param req: Request type + :type req: leap.common.events.events_pb2.SignalRequest + """ + ext_status = None + + if req.event == proto.IMAP_SERVICE_STARTED: + ext_status = self.tr("IMAP has started...") + self._imap_started = True + if self._smtp_started and self._imap_started: + self._set_mail_status(self.tr("ON"), ready=2) + ext_status = "" + elif req.event == proto.IMAP_SERVICE_FAILED_TO_START: + ext_status = self.tr("IMAP failed to start, check the logs.") + self._set_mail_status(self.tr("Failed")) + elif req.event == proto.IMAP_UNREAD_MAIL: + if self._smtp_started and self._imap_started: + self._set_mail_status(self.tr("%s Unread Emails") % + (req.content), ready=2) + else: + leap_assert(False, # XXX ??? + "Don't know how to handle this state: %s" + % (req.event)) + + if ext_status is not None: + self._set_mail_status(ext_status, ready=1) + + def about_to_start(self): + """ + Displays the correct UI for the point where mail components + haven't really started, but they are about to in a second. + """ + self._set_mail_status(self.tr("About to start, please wait..."), + ready=1) + + def stopped_mail(self): + """ + Displayes the correct UI for the stopped state. + """ + self._set_mail_status(self.tr("OFF")) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 92d6906e..58ed3eb3 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -32,8 +32,10 @@ from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.gui.loggerwindow import LoggerWindow from leap.bitmask.gui.login import LoginWidget from leap.bitmask.gui.preferenceswindow import PreferencesWindow +from leap.bitmask.gui.eip_preferenceswindow import EIPPreferencesWindow from leap.bitmask.gui import statemachines -from leap.bitmask.gui.statuspanel import StatusPanelWidget +from leap.bitmask.gui.eip_status import EIPStatusWidget +from leap.bitmask.gui.mail_status import MailStatusWidget from leap.bitmask.gui.wizard import Wizard from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper @@ -147,7 +149,7 @@ class MainWindow(QtGui.QMainWindow): self._login_widget = LoginWidget( self._settings, - self.ui.stackedWidget.widget(self.LOGIN_INDEX)) + self) self.ui.loginLayout.addWidget(self._login_widget) # Qt Signal Connections ##################################### @@ -155,17 +157,14 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.login.connect(self._login) self._login_widget.cancel_login.connect(self._cancel_login) - self._login_widget.show_wizard.connect( - self._launch_wizard) - - self.ui.btnShowLog.clicked.connect(self._show_logger_window) - self.ui.btnPreferences.clicked.connect(self._show_preferences) + self._login_widget.show_wizard.connect(self._launch_wizard) + self._login_widget.logout.connect(self._logout) - self._status_panel = StatusPanelWidget( - self.ui.stackedWidget.widget(self.EIP_STATUS_INDEX)) - self.ui.statusLayout.addWidget(self._status_panel) + self._eip_status = EIPStatusWidget(self) + self.ui.eipLayout.addWidget(self._eip_status) - self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) + self._mail_status = MailStatusWidget(self) + self.ui.mailLayout.addWidget(self._mail_status) self._eip_connection = EIPConnection() @@ -173,7 +172,7 @@ class MainWindow(QtGui.QMainWindow): self._start_eip) self._eip_connection.qtsigs.disconnecting_signal.connect( self._stop_eip) - self._status_panel.eip_connection_connected.connect( + self._eip_status.eip_connection_connected.connect( self._on_eip_connected) # This is loaded only once, there's a bug when doing that more @@ -221,9 +220,9 @@ class MainWindow(QtGui.QMainWindow): self._finish_eip_bootstrap) self._vpn = VPN(openvpn_verb=openvpn_verb) self._vpn.qtsigs.state_changed.connect( - self._status_panel.update_vpn_state) + self._eip_status.update_vpn_state) self._vpn.qtsigs.status_changed.connect( - self._status_panel.update_vpn_status) + self._eip_status.update_vpn_status) self._vpn.qtsigs.process_finished.connect( self._eip_finished) @@ -241,12 +240,16 @@ class MainWindow(QtGui.QMainWindow): self._smtp_bootstrapper.download_config.connect( self._smtp_bootstrapped_stage) - self.ui.action_log_out.setEnabled(False) - self.ui.action_log_out.triggered.connect(self._logout) self.ui.action_about_leap.triggered.connect(self._about) self.ui.action_quit.triggered.connect(self.quit) self.ui.action_wizard.triggered.connect(self._launch_wizard) self.ui.action_show_logs.triggered.connect(self._show_logger_window) + self.ui.action_create_new_account.triggered.connect( + self._launch_wizard) + + if IS_MAC: + self.ui.menuFile.menuAction().setText(self.tr("Util")) + self.raise_window.connect(self._do_raise_mainwindow) # Used to differentiate between real quits and close to tray @@ -256,17 +259,17 @@ class MainWindow(QtGui.QMainWindow): self._action_mail_status = QtGui.QAction(self.tr("Mail is OFF"), self) self._action_mail_status.setEnabled(False) - self._status_panel.set_action_mail_status(self._action_mail_status) + self._mail_status.set_action_mail_status(self._action_mail_status) self._action_eip_startstop = QtGui.QAction("", self) - self._status_panel.set_action_eip_startstop(self._action_eip_startstop) - - self._action_preferences = QtGui.QAction(self.tr("Preferences"), self) - self._action_preferences.triggered.connect(self._show_preferences) + self._eip_status.set_action_eip_startstop(self._action_eip_startstop) self._action_visible = QtGui.QAction(self.tr("Hide Main Window"), self) self._action_visible.triggered.connect(self._toggle_visible) + self.ui.btnPreferences.clicked.connect(self._show_preferences) + self.ui.btnEIPPreferences.clicked.connect(self._show_eip_preferences) + self._enabled_services = [] self._center_window() @@ -282,6 +285,7 @@ class MainWindow(QtGui.QMainWindow): self.mail_client_logged_in.connect(self._fetch_incoming_mail) self.logout.connect(self._stop_imap_service) self.logout.connect(self._stop_smtp_service) + self.logout.connect(self._mail_status.stopped_mail) ################################# end Qt Signals connection ######## @@ -393,7 +397,6 @@ class MainWindow(QtGui.QMainWindow): SLOT TRIGGERS: self.ui.action_show_logs.triggered - self.ui.btnShowLog.clicked Displays the window with the history of messages logged until now and displays the new ones on arrival. @@ -407,18 +410,13 @@ class MainWindow(QtGui.QMainWindow): self._logger_window = LoggerWindow(handler=leap_log_handler) self._logger_window.setVisible( not self._logger_window.isVisible()) - self.ui.btnShowLog.setChecked(self._logger_window.isVisible()) else: self._logger_window.setVisible(not self._logger_window.isVisible()) - self.ui.btnShowLog.setChecked(self._logger_window.isVisible()) - - self._logger_window.finished.connect(self._uncheck_logger_button) def _show_preferences(self): """ SLOT TRIGGERS: - self.ui.action_show_preferences.triggered self.ui.btnPreferences.clicked Displays the preferences window. @@ -433,22 +431,25 @@ class MainWindow(QtGui.QMainWindow): preferences_window.show() - def _set_soledad_ready(self): + def _show_eip_preferences(self): """ SLOT TRIGGERS: - self.soledad_ready + self.ui.btnEIPPreferences.clicked - It sets the soledad object as ready to use. + Displays the EIP preferences window. """ - self._soledad_ready = True + EIPPreferencesWindow(self).show() - def _uncheck_logger_button(self): + def _set_soledad_ready(self): """ SLOT - Sets the checked state of the loggerwindow button to false. + TRIGGERS: + self.soledad_ready + + It sets the soledad object as ready to use. """ - self.ui.btnShowLog.setChecked(False) + self._soledad_ready = True def _new_updates_available(self, req): """ @@ -623,24 +624,20 @@ class MainWindow(QtGui.QMainWindow): systrayMenu.addAction(self._action_visible) systrayMenu.addSeparator() - eip_menu = systrayMenu.addMenu(self.tr("Encrypted Internet is OFF")) + eip_menu = systrayMenu.addMenu(self.tr("Encrypted Internet: OFF")) eip_menu.addAction(self._action_eip_startstop) - self._status_panel.set_eip_status_menu(eip_menu) + self._eip_status.set_eip_status_menu(eip_menu) systrayMenu.addAction(self._action_mail_status) systrayMenu.addSeparator() - systrayMenu.addAction(self._action_preferences) - systrayMenu.addAction(help_action) - systrayMenu.addSeparator() - systrayMenu.addAction(self.ui.action_log_out) systrayMenu.addAction(self.ui.action_quit) self._systray = QtGui.QSystemTrayIcon(self) self._systray.setContextMenu(systrayMenu) - self._systray.setIcon(self._status_panel.ERROR_ICON_TRAY) + self._systray.setIcon(self._eip_status.ERROR_ICON_TRAY) self._systray.setVisible(True) self._systray.activated.connect(self._tray_activated) - self._status_panel.set_systray(self._systray) + self._eip_status.set_systray(self._systray) def _tray_activated(self, reason=None): """ @@ -846,47 +843,8 @@ class MainWindow(QtGui.QMainWindow): """ leap_assert(self._provider_config, "We need a provider config") - username = self._login_widget.get_user() - password = self._login_widget.get_password() - provider = self._login_widget.get_selected_provider() - - self._enabled_services = self._settings.get_enabled_services( - self._login_widget.get_selected_provider()) - - if len(provider) == 0: - self._login_widget.set_status( - self.tr("Please select a valid provider")) - return - - if len(username) == 0: - self._login_widget.set_status( - self.tr("Please provide a valid username")) - return - - if len(password) == 0: - self._login_widget.set_status( - self.tr("Please provide a valid Password")) - return - - self._login_widget.set_status(self.tr("Logging in..."), error=False) - self._login_widget.set_enabled(False) - - if self._login_widget.get_remember() and has_keyring(): - # 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_domain, - password.encode("utf8")) - # Only save the username if it was saved correctly in - # the keyring - self._settings.set_user(username_domain) - except Exception as e: - logger.error("Problem saving data to keyring. %r" - % (e,)) - - self._download_provider_config() + if self._login_widget.start_login(): + self._download_provider_config() def _cancel_login(self): """ @@ -954,7 +912,6 @@ class MainWindow(QtGui.QMainWindow): if ok: self._logged_user = self._login_widget.get_user() - self.ui.action_log_out.setEnabled(True) # We leave a bit of room for the user to see the # "Succeeded" message and then we switch to the EIP status # panel @@ -969,15 +926,15 @@ class MainWindow(QtGui.QMainWindow): Changes the stackedWidget index to the EIP status one and triggers the eip bootstrapping """ - if not self._already_started_eip: - self._status_panel.set_provider( - "%s@%s" % (self._login_widget.get_user(), - self._get_best_provider_config().get_domain())) - self.ui.stackedWidget.setCurrentIndex(self.EIP_STATUS_INDEX) + self._login_widget.logged_in() # TODO separate UI from logic. # TODO soledad should check if we want to run only over EIP. + if self._provider_config.provides_mx() and \ + self._enabled_services.count(self.MX_SERVICE) > 0: + self._mail_status.about_to_start() + self._soledad_bootstrapper.run_soledad_setup_checks( self._provider_config, self._login_widget.get_user(), @@ -1199,9 +1156,9 @@ class MainWindow(QtGui.QMainWindow): """ Initializes and starts the EIP state machine """ - button = self._status_panel.eip_button + button = self._eip_status.eip_button action = self._action_eip_startstop - label = self._status_panel.eip_label + label = self._eip_status.eip_label builder = statemachines.ConnectionMachineBuilder(self._eip_connection) eip_machine = builder.make_machine(button=button, action=action, @@ -1214,7 +1171,7 @@ class MainWindow(QtGui.QMainWindow): """ SLOT TRIGGERS: - self._status_panel.eip_connection_connected + self._eip_status.eip_connection_connected Emits the EIPConnection.qtsigs.connected_signal This is a little workaround for connecting the vpn-connected @@ -1229,7 +1186,7 @@ class MainWindow(QtGui.QMainWindow): """ SLOT TRIGGERS: - self._status_panel.start_eip + self._eip_status.start_eip self._action_eip_startstop.triggered or called from _finish_eip_bootstrap @@ -1237,7 +1194,7 @@ class MainWindow(QtGui.QMainWindow): """ provider_config = self._get_best_provider_config() provider = provider_config.get_domain() - self._status_panel.eip_pre_up() + self._eip_status.eip_pre_up() self.user_stopped_eip = False try: @@ -1248,16 +1205,14 @@ class MainWindow(QtGui.QMainWindow): socket_host=host, socket_port=port) self._settings.set_defaultprovider(provider) - if self._logged_user is not None: - provider = "%s@%s" % (self._logged_user, provider) # XXX move to the state machine too - self._status_panel.set_provider(provider) + self._eip_status.set_provider(provider) # TODO refactor exceptions so they provide translatable # usef-facing messages. except EIPNoPolkitAuthAgentAvailable: - self._status_panel.set_global_status( + self._eip_status.set_eip_status( # XXX this should change to polkit-kde where # applicable. self.tr("We could not find any " @@ -1270,30 +1225,30 @@ class MainWindow(QtGui.QMainWindow): error=True) self._set_eipstatus_off() except EIPNoTunKextLoaded: - self._status_panel.set_global_status( + self._eip_status.set_eip_status( self.tr("Encrypted Internet cannot be started because " "the tuntap extension is not installed properly " "in your system.")) self._set_eipstatus_off() except EIPNoPkexecAvailable: - self._status_panel.set_global_status( + self._eip_status.set_eip_status( self.tr("We could not find pkexec " "in your system."), error=True) self._set_eipstatus_off() except OpenVPNNotFoundException: - self._status_panel.set_global_status( + self._eip_status.set_eip_status( self.tr("We could not find openvpn binary."), error=True) self._set_eipstatus_off() except OpenVPNAlreadyRunning as e: - self._status_panel.set_global_status( + self._eip_status.set_eip_status( self.tr("Another openvpn instance is already running, and " "could not be stopped."), error=True) self._set_eipstatus_off() except AlienOpenVPNAlreadyRunning as e: - self._status_panel.set_global_status( + self._eip_status.set_eip_status( self.tr("Another openvpn instance is already running, and " "could not be stopped because it was not launched by " "Bitmask. Please stop it and try again."), @@ -1302,7 +1257,7 @@ class MainWindow(QtGui.QMainWindow): except VPNLauncherException as e: # XXX We should implement again translatable exceptions so # we can pass a translatable string to the panel (usermessage attr) - self._status_panel.set_global_status("%s" % (e,), error=True) + self._eip_status.set_eip_status("%s" % (e,), error=True) self._set_eipstatus_off() else: self._already_started_eip = True @@ -1312,7 +1267,7 @@ class MainWindow(QtGui.QMainWindow): """ SLOT TRIGGERS: - self._status_panel.stop_eip + self._eip_status.stop_eip self._action_eip_startstop.triggered or called from _eip_finished @@ -1328,23 +1283,25 @@ class MainWindow(QtGui.QMainWindow): self.user_stopped_eip = True self._vpn.terminate() - self._set_eipstatus_off() + self._set_eipstatus_off(False) self._already_started_eip = False # XXX do via signal self._settings.set_defaultprovider(None) if self._logged_user: - self._status_panel.set_provider( + self._eip_status.set_provider( "%s@%s" % (self._logged_user, self._get_best_provider_config().get_domain())) + self._eip_status.eip_stopped() - def _set_eipstatus_off(self): + def _set_eipstatus_off(self, error=True): """ Sets eip status to off """ - self._status_panel.set_eip_status(self.tr("OFF"), error=True) - self._status_panel.set_eip_status_icon("error") + self._eip_status.set_eip_status(self.tr("EIP has stopped"), + error=error) + self._eip_status.set_eip_status_icon("error") def _download_eip_config(self): """ @@ -1359,7 +1316,7 @@ class MainWindow(QtGui.QMainWindow): not self._already_started_eip: # XXX this should be handled by the state machine. - self._status_panel.set_eip_status( + self._eip_status.set_eip_status( self.tr("Starting...")) self._eip_bootstrapper.run_eip_setup_checks( provider_config, @@ -1367,11 +1324,11 @@ class MainWindow(QtGui.QMainWindow): self._already_started_eip = True elif not self._already_started_eip: if self._enabled_services.count(self.OPENVPN_SERVICE) > 0: - self._status_panel.set_eip_status( + self._eip_status.set_eip_status( self.tr("Not supported"), error=True) else: - self._status_panel.set_eip_status(self.tr("Disabled")) + self._eip_status.set_eip_status(self.tr("Disabled")) def _finish_eip_bootstrap(self, data): """ @@ -1386,7 +1343,7 @@ class MainWindow(QtGui.QMainWindow): if not passed: error_msg = self.tr("There was a problem with the provider") - self._status_panel.set_eip_status(error_msg, error=True) + self._eip_status.set_eip_status(error_msg, error=True) logger.error(data[self._eip_bootstrapper.ERROR_KEY]) self._already_started_eip = False return @@ -1406,7 +1363,7 @@ class MainWindow(QtGui.QMainWindow): # DO START EIP Connection! self._eip_connection.qtsigs.do_connect_signal.emit() else: - self._status_panel.set_eip_status( + self._eip_status.set_eip_status( self.tr("Could not load Encrypted Internet " "Configuration."), error=True) @@ -1441,7 +1398,7 @@ class MainWindow(QtGui.QMainWindow): def _logout(self): """ SLOT - TRIGGER: self.ui.action_log_out.triggered + TRIGGER: self._login_widget.logout Starts the logout sequence """ @@ -1461,16 +1418,17 @@ class MainWindow(QtGui.QMainWindow): Switches the stackedWidget back to the login stage after logging out """ + self._login_widget.done_logout() + if ok: self._logged_user = None - self.ui.action_log_out.setEnabled(False) - self.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) - self._login_widget.set_password("") - self._login_widget.set_enabled(True) - self._login_widget.set_status("") + + self._login_widget.logged_out() + else: - status_text = self.tr("Something went wrong with the logout.") - self._status_panel.set_global_status(status_text, error=True) + self._login_widget.set_login_status( + self.tr("Something went wrong with the logout."), + error=True) def _intermediate_stage(self, data): """ @@ -1541,7 +1499,7 @@ class MainWindow(QtGui.QMainWindow): # XXX check if these exitCodes are pkexec/cocoasudo specific if exitCode in (126, 127): - self._status_panel.set_global_status( + self._eip_status.set_eip_status( self.tr("Encrypted Internet could not be launched " "because you did not authenticate properly."), error=True) @@ -1549,7 +1507,7 @@ class MainWindow(QtGui.QMainWindow): signal = qtsigs.connection_aborted_signal elif exitCode != 0 or not self.user_stopped_eip: - self._status_panel.set_global_status( + self._eip_status.set_eip_status( self.tr("Encrypted Internet finished in an " "unexpected manner!"), error=True) signal = qtsigs.connection_died_signal diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 2d17f6c2..7e281b44 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -60,7 +60,6 @@ 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() @@ -68,11 +67,6 @@ class PreferencesWindow(QtGui.QDialog): 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) @@ -217,37 +211,13 @@ 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 = "%s" % (status,) - elif error: - status = "%s" % (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. """ self.ui.cbProvidersServices.clear() - self.ui.cbProvidersGateway.clear() for provider in self._settings.get_configured_providers(): self.ui.cbProvidersServices.addItem(provider) - self.ui.cbProvidersGateway.addItem(provider) def _service_selection_changed(self, service, state): """ @@ -366,90 +336,3 @@ class PreferencesWindow(QtGui.QDialog): 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/statuspanel.py b/src/leap/bitmask/gui/statuspanel.py deleted file mode 100644 index 679f00b1..00000000 --- a/src/leap/bitmask/gui/statuspanel.py +++ /dev/null @@ -1,710 +0,0 @@ -# -*- coding: utf-8 -*- -# statuspanel.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 . -""" -Status Panel widget implementation -""" -import logging - -from datetime import datetime -from functools import partial - -from PySide import QtCore, QtGui - -from leap.bitmask.services.eip.connection import EIPConnection -from leap.bitmask.services.eip.vpnprocess import VPNManager -from leap.bitmask.platform_init import IS_WIN, IS_LINUX -from leap.bitmask.util.averages import RateMovingAverage -from leap.common.check import leap_assert, leap_assert_type -from leap.common.events import register -from leap.common.events import events_pb2 as proto - -from ui_statuspanel import Ui_StatusPanel - -logger = logging.getLogger(__name__) - - -class StatusPanelWidget(QtGui.QWidget): - """ - Status widget that displays the current state of the LEAP services - """ - DISPLAY_TRAFFIC_RATES = True - RATE_STR = "%14.2f KB/s" - TOTAL_STR = "%14.2f Kb" - - MAIL_OFF_ICON = ":/images/mail-unlocked.png" - MAIL_ON_ICON = ":/images/mail-locked.png" - - eip_connection_connected = QtCore.Signal() - _soledad_event = QtCore.Signal(object) - _smtp_event = QtCore.Signal(object) - _imap_event = QtCore.Signal(object) - _keymanager_event = QtCore.Signal(object) - - def __init__(self, parent=None): - QtGui.QWidget.__init__(self, parent) - - self._systray = None - self._eip_status_menu = None - - self.ui = Ui_StatusPanel() - self.ui.setupUi(self) - - self.eipconnection = EIPConnection() - - self.hide_status_box() - - # set systray tooltip statuses - self._eip_status = self._mx_status = "" - - # Set the EIP status icons - self.CONNECTING_ICON = None - self.CONNECTED_ICON = None - self.ERROR_ICON = None - self.CONNECTING_ICON_TRAY = None - self.CONNECTED_ICON_TRAY = None - self.ERROR_ICON_TRAY = None - self._set_eip_icons() - - self._set_traffic_rates() - self._make_status_clickable() - - register(signal=proto.KEYMANAGER_LOOKING_FOR_KEY, - callback=self._mail_handle_keymanager_events, - reqcbk=lambda req, resp: None) - - register(signal=proto.KEYMANAGER_KEY_FOUND, - callback=self._mail_handle_keymanager_events, - reqcbk=lambda req, resp: None) - - # register(signal=proto.KEYMANAGER_KEY_NOT_FOUND, - # callback=self._mail_handle_keymanager_events, - # reqcbk=lambda req, resp: None) - - register(signal=proto.KEYMANAGER_STARTED_KEY_GENERATION, - callback=self._mail_handle_keymanager_events, - reqcbk=lambda req, resp: None) - - register(signal=proto.KEYMANAGER_FINISHED_KEY_GENERATION, - callback=self._mail_handle_keymanager_events, - reqcbk=lambda req, resp: None) - - register(signal=proto.KEYMANAGER_DONE_UPLOADING_KEYS, - callback=self._mail_handle_keymanager_events, - reqcbk=lambda req, resp: None) - - register(signal=proto.SOLEDAD_DONE_DOWNLOADING_KEYS, - callback=self._mail_handle_soledad_events, - reqcbk=lambda req, resp: None) - - register(signal=proto.SOLEDAD_DONE_UPLOADING_KEYS, - callback=self._mail_handle_soledad_events, - reqcbk=lambda req, resp: None) - - register(signal=proto.SMTP_SERVICE_STARTED, - callback=self._mail_handle_smtp_events, - reqcbk=lambda req, resp: None) - - register(signal=proto.SMTP_SERVICE_FAILED_TO_START, - callback=self._mail_handle_smtp_events, - reqcbk=lambda req, resp: None) - - register(signal=proto.IMAP_SERVICE_STARTED, - callback=self._mail_handle_imap_events, - reqcbk=lambda req, resp: None) - - register(signal=proto.IMAP_SERVICE_FAILED_TO_START, - callback=self._mail_handle_imap_events, - reqcbk=lambda req, resp: None) - - register(signal=proto.IMAP_UNREAD_MAIL, - callback=self._mail_handle_imap_events, - reqcbk=lambda req, resp: None) - - self._set_long_mail_status("") - self.ui.lblUnread.setVisible(False) - - self._smtp_started = False - self._imap_started = False - - self._soledad_event.connect( - self._mail_handle_soledad_events_slot) - self._imap_event.connect( - self._mail_handle_imap_events_slot) - self._smtp_event.connect( - self._mail_handle_smtp_events_slot) - self._keymanager_event.connect( - self._mail_handle_keymanager_events_slot) - - def _make_status_clickable(self): - """ - Makes upload and download figures clickable. - """ - onclicked = self._on_VPN_status_clicked - self.ui.btnUpload.clicked.connect(onclicked) - self.ui.btnDownload.clicked.connect(onclicked) - - def _on_VPN_status_clicked(self): - """ - SLOT - TRIGGER: self.ui.btnUpload.clicked - self.ui.btnDownload.clicked - - Toggles between rate and total throughput display for vpn - status figures. - """ - self.DISPLAY_TRAFFIC_RATES = not self.DISPLAY_TRAFFIC_RATES - self.update_vpn_status(None) # refresh - - def _set_traffic_rates(self): - """ - Initializes up and download rates. - """ - self._up_rate = RateMovingAverage() - self._down_rate = RateMovingAverage() - - self.ui.btnUpload.setText(self.RATE_STR % (0,)) - self.ui.btnDownload.setText(self.RATE_STR % (0,)) - - def _reset_traffic_rates(self): - """ - Resets up and download rates, and cleans up the labels. - """ - self._up_rate.reset() - self._down_rate.reset() - self.update_vpn_status(None) - - def _update_traffic_rates(self, up, down): - """ - Updates up and download rates. - - :param up: upload total. - :type up: int - :param down: download total. - :type down: int - """ - ts = datetime.now() - self._up_rate.append((ts, up)) - self._down_rate.append((ts, down)) - - def _get_traffic_rates(self): - """ - Gets the traffic rates (in KB/s). - - :returns: a tuple with the (up, down) rates - :rtype: tuple - """ - up = self._up_rate - down = self._down_rate - - return (up.get_average(), down.get_average()) - - def _get_traffic_totals(self): - """ - Gets the traffic total throughput (in Kb). - - :returns: a tuple with the (up, down) totals - :rtype: tuple - """ - up = self._up_rate - down = self._down_rate - - return (up.get_total(), down.get_total()) - - def _set_eip_icons(self): - """ - Sets the EIP status icons for the main window and for the tray - - MAC : dark icons - LINUX : dark icons in window, light icons in tray - WIN : light icons - """ - EIP_ICONS = EIP_ICONS_TRAY = ( - ":/images/conn_connecting-light.png", - ":/images/conn_connected-light.png", - ":/images/conn_error-light.png") - - if IS_LINUX: - EIP_ICONS_TRAY = ( - ":/images/conn_connecting.png", - ":/images/conn_connected.png", - ":/images/conn_error.png") - elif IS_WIN: - EIP_ICONS = EIP_ICONS_TRAY = ( - ":/images/conn_connecting.png", - ":/images/conn_connected.png", - ":/images/conn_error.png") - - self.CONNECTING_ICON = QtGui.QPixmap(EIP_ICONS[0]) - self.CONNECTED_ICON = QtGui.QPixmap(EIP_ICONS[1]) - self.ERROR_ICON = QtGui.QPixmap(EIP_ICONS[2]) - - self.CONNECTING_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[0]) - self.CONNECTED_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[1]) - self.ERROR_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[2]) - - # Systray and actions - - def set_systray(self, systray): - """ - Sets the systray object to use. - - :param systray: Systray object - :type systray: QtGui.QSystemTrayIcon - """ - leap_assert_type(systray, QtGui.QSystemTrayIcon) - self._systray = systray - self._systray.setToolTip(self.tr("All services are OFF")) - - def _update_systray_tooltip(self): - """ - Updates the system tray icon tooltip using the eip and mx statuses. - """ - status = self.tr("Encrypted Internet is {0}").format(self._eip_status) - status += '\n' - status += self.tr("Mail is {0}").format(self._mx_status) - self._systray.setToolTip(status) - - def set_action_eip_startstop(self, action_eip_startstop): - """ - Sets the action_eip_startstop to use. - - :param action_eip_startstop: action_eip_status to be used - :type action_eip_startstop: QtGui.QAction - """ - self._action_eip_startstop = action_eip_startstop - - def set_eip_status_menu(self, eip_status_menu): - """ - Sets the eip_status_menu to use. - - :param eip_status_menu: eip_status_menu to be used - :type eip_status_menu: QtGui.QMenu - """ - leap_assert_type(eip_status_menu, QtGui.QMenu) - self._eip_status_menu = eip_status_menu - - def set_action_mail_status(self, action_mail_status): - """ - Sets the action_mail_status to use. - - :param action_mail_status: action_mail_status to be used - :type action_mail_status: QtGui.QAction - """ - leap_assert_type(action_mail_status, QtGui.QAction) - self._action_mail_status = action_mail_status - - def set_global_status(self, status, error=False): - """ - Sets the global status label. - - :param status: status message - :type status: str or unicode - :param error: if the status is an erroneous one, then set this - to True - :type error: bool - """ - leap_assert_type(error, bool) - if error: - status = "%s" % (status,) - self.ui.lblGlobalStatus.setText(status) - self.ui.globalStatusBox.show() - - def hide_status_box(self): - """ - Hide global status box. - """ - self.ui.globalStatusBox.hide() - - # EIP status --- - - @property - def eip_button(self): - return self.ui.btnEipStartStop - - @property - def eip_label(self): - return self.ui.lblEIPStatus - - def eip_pre_up(self): - """ - Triggered when the app activates eip. - Hides the status box and disables the start/stop button. - """ - self.hide_status_box() - self.set_startstop_enabled(False) - - # XXX disable (later) -------------------------- - def set_eip_status(self, status, error=False): - """ - Sets the status label at the VPN stage to status - - :param status: status message - :type status: str or unicode - :param error: if the status is an erroneous one, then set this - to True - :type error: bool - """ - leap_assert_type(error, bool) - - self._eip_status = status - - if error: - status = "%s" % (status,) - self.ui.lblEIPStatus.setText(status) - self._update_systray_tooltip() - - # XXX disable --------------------------------- - def set_startstop_enabled(self, value): - """ - Enable or disable btnEipStartStop and _action_eip_startstop - based on value - - :param value: True for enabled, False otherwise - :type value: bool - """ - leap_assert_type(value, bool) - self.ui.btnEipStartStop.setEnabled(value) - self._action_eip_startstop.setEnabled(value) - - # XXX disable ----------------------------- - def eip_started(self): - """ - Sets the state of the widget to how it should look after EIP - has started - """ - self.ui.btnEipStartStop.setText(self.tr("Turn OFF")) - self.ui.btnEipStartStop.disconnect(self) - self.ui.btnEipStartStop.clicked.connect( - self.eipconnection.qtsigs.do_connect_signal) - - # XXX disable ----------------------------- - def eip_stopped(self): - """ - Sets the state of the widget to how it should look after EIP - has stopped - """ - # XXX should connect this to EIPConnection.disconnected_signal - self._reset_traffic_rates() - # XXX disable ----------------------------- - self.ui.btnEipStartStop.setText(self.tr("Turn ON")) - self.ui.btnEipStartStop.disconnect(self) - self.ui.btnEipStartStop.clicked.connect( - self.eipconnection.qtsigs.do_disconnect_signal) - - def update_vpn_status(self, data): - """ - SLOT - TRIGGER: VPN.status_changed - - Updates the download/upload labels based on the data provided - by the VPN thread. - - :param data: a dictionary with the tcp/udp write and read totals. - If data is None, we just will refresh the display based - on the previous data. - :type data: dict - """ - if data: - upload = float(data[VPNManager.TCPUDP_WRITE_KEY] or "0") - download = float(data[VPNManager.TCPUDP_READ_KEY] or "0") - self._update_traffic_rates(upload, download) - - if self.DISPLAY_TRAFFIC_RATES: - uprate, downrate = self._get_traffic_rates() - upload_str = self.RATE_STR % (uprate,) - download_str = self.RATE_STR % (downrate,) - - else: # display total throughput - uptotal, downtotal = self._get_traffic_totals() - upload_str = self.TOTAL_STR % (uptotal,) - download_str = self.TOTAL_STR % (downtotal,) - - self.ui.btnUpload.setText(upload_str) - self.ui.btnDownload.setText(download_str) - - def update_vpn_state(self, data): - """ - SLOT - TRIGGER: VPN.state_changed - - Updates the displayed VPN state based on the data provided by - the VPN thread. - - Emits: - If the status is connected, we emit EIPConnection.qtsigs. - connected_signal - """ - status = data[VPNManager.STATUS_STEP_KEY] - self.set_eip_status_icon(status) - if status == "CONNECTED": - # XXX should be handled by the state machine too. - self.set_eip_status(self.tr("ON")) - logger.debug("STATUS IS CONNECTED --- emitting signal") - self.eip_connection_connected.emit() - - # XXX should lookup status map in EIPConnection - elif status == "AUTH": - self.set_eip_status(self.tr("Authenticating...")) - elif status == "GET_CONFIG": - self.set_eip_status(self.tr("Retrieving configuration...")) - elif status == "WAIT": - self.set_eip_status(self.tr("Waiting to start...")) - elif status == "ASSIGN_IP": - self.set_eip_status(self.tr("Assigning IP")) - elif status == "RECONNECTING": - self.set_eip_status(self.tr("Reconnecting...")) - elif status == "ALREADYRUNNING": - # Put the following calls in Qt's event queue, otherwise - # the UI won't update properly - QtCore.QTimer.singleShot( - 0, self.eipconnection.qtsigs.do_disconnect_signal) - QtCore.QTimer.singleShot(0, partial(self.set_global_status, - self.tr("Unable to start VPN, " - "it's already " - "running."))) - else: - self.set_eip_status(status) - - def set_eip_icon(self, icon): - """ - Sets the icon to display for EIP - - :param icon: icon to display - :type icon: QPixmap - """ - self.ui.lblVPNStatusIcon.setPixmap(icon) - - def set_eip_status_icon(self, status): - """ - Given a status step from the VPN thread, set the icon properly - - :param status: status step - :type status: str - """ - selected_pixmap = self.ERROR_ICON - selected_pixmap_tray = self.ERROR_ICON_TRAY - tray_message = self.tr("Encrypted Internet is OFF") - if status in ("WAIT", "AUTH", "GET_CONFIG", - "RECONNECTING", "ASSIGN_IP"): - selected_pixmap = self.CONNECTING_ICON - selected_pixmap_tray = self.CONNECTING_ICON_TRAY - tray_message = self.tr("Encrypted Internet is STARTING") - elif status in ("CONNECTED"): - tray_message = self.tr("Encrypted Internet is ON") - selected_pixmap = self.CONNECTED_ICON - selected_pixmap_tray = self.CONNECTED_ICON_TRAY - - self.set_eip_icon(selected_pixmap) - self._systray.setIcon(QtGui.QIcon(selected_pixmap_tray)) - self._eip_status_menu.setTitle(tray_message) - - def set_provider(self, provider): - self.ui.lblProvider.setText(provider) - - # - # mail methods - # - - def _set_mail_status(self, status, ready=False): - """ - Sets the Mail status in the label and in the tray icon. - - :param status: the status text to display - :type status: unicode - :param ready: if mx is ready or not. - :type ready: bool - """ - self.ui.lblMailStatus.setText(status) - - self._mx_status = self.tr('OFF') - tray_status = self.tr('Mail is OFF') - - icon = QtGui.QPixmap(self.MAIL_OFF_ICON) - if ready: - icon = QtGui.QPixmap(self.MAIL_ON_ICON) - self._mx_status = self.tr('ON') - tray_status = self.tr('Mail is ON') - - self.ui.lblMailIcon.setPixmap(icon) - self._action_mail_status.setText(tray_status) - self._update_systray_tooltip() - - def _mail_handle_soledad_events(self, req): - """ - Callback for ... - - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest - """ - self._soledad_event.emit(req) - - def _mail_handle_soledad_events_slot(self, req): - """ - SLOT - TRIGGER: _mail_handle_soledad_events - - Reacts to an Soledad event - - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest - """ - self._set_mail_status(self.tr("Starting...")) - - ext_status = "" - - if req.event == proto.SOLEDAD_DONE_UPLOADING_KEYS: - ext_status = self.tr("Soledad has started...") - elif req.event == proto.SOLEDAD_DONE_DOWNLOADING_KEYS: - ext_status = self.tr("Soledad is starting, please wait...") - else: - leap_assert(False, - "Don't know how to handle this state: %s" - % (req.event)) - - self._set_long_mail_status(ext_status) - - def _mail_handle_keymanager_events(self, req): - """ - Callback for the KeyManager events - - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest - """ - self._keymanager_event.emit(req) - - def _mail_handle_keymanager_events_slot(self, req): - """ - SLOT - TRIGGER: _mail_handle_keymanager_events - - Reacts to an KeyManager event - - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest - """ - # We want to ignore this kind of events once everything has - # started - if self._smtp_started and self._imap_started: - return - - self._set_mail_status(self.tr("Starting...")) - - ext_status = "" - - if req.event == proto.KEYMANAGER_LOOKING_FOR_KEY: - ext_status = self.tr("Looking for key for this user") - elif req.event == proto.KEYMANAGER_KEY_FOUND: - ext_status = self.tr("Found key! Starting mail...") - # elif req.event == proto.KEYMANAGER_KEY_NOT_FOUND: - # ext_status = self.tr("Key not found!") - elif req.event == proto.KEYMANAGER_STARTED_KEY_GENERATION: - ext_status = self.tr("Generating new key, please wait...") - elif req.event == proto.KEYMANAGER_FINISHED_KEY_GENERATION: - ext_status = self.tr("Finished generating key!") - elif req.event == proto.KEYMANAGER_DONE_UPLOADING_KEYS: - ext_status = self.tr("Starting mail...") - else: - leap_assert(False, - "Don't know how to handle this state: %s" - % (req.event)) - - self._set_long_mail_status(ext_status) - - def _mail_handle_smtp_events(self, req): - """ - Callback for the SMTP events - - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest - """ - self._smtp_event.emit(req) - - def _mail_handle_smtp_events_slot(self, req): - """ - SLOT - TRIGGER: _mail_handle_smtp_events - - Reacts to an SMTP event - - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest - """ - ext_status = "" - - if req.event == proto.SMTP_SERVICE_STARTED: - ext_status = self.tr("SMTP has started...") - self._smtp_started = True - if self._smtp_started and self._imap_started: - self._set_mail_status(self.tr("ON"), ready=True) - ext_status = "" - elif req.event == proto.SMTP_SERVICE_FAILED_TO_START: - ext_status = self.tr("SMTP failed to start, check the logs.") - self._set_mail_status(self.tr("Failed")) - else: - leap_assert(False, - "Don't know how to handle this state: %s" - % (req.event)) - - self._set_long_mail_status(ext_status) - - def _mail_handle_imap_events(self, req): - """ - Callback for the IMAP events - - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest - """ - self._imap_event.emit(req) - - def _mail_handle_imap_events_slot(self, req): - """ - SLOT - TRIGGER: _mail_handle_imap_events - - Reacts to an IMAP event - - :param req: Request type - :type req: leap.common.events.events_pb2.SignalRequest - """ - ext_status = None - - if req.event == proto.IMAP_SERVICE_STARTED: - ext_status = self.tr("IMAP has started...") - self._imap_started = True - if self._smtp_started and self._imap_started: - self._set_mail_status(self.tr("ON"), ready=True) - ext_status = "" - elif req.event == proto.IMAP_SERVICE_FAILED_TO_START: - ext_status = self.tr("IMAP failed to start, check the logs.") - self._set_mail_status(self.tr("Failed")) - elif req.event == proto.IMAP_UNREAD_MAIL: - if self._smtp_started and self._imap_started: - self.ui.lblUnread.setText( - self.tr("%s Unread Emails") % (req.content)) - self.ui.lblUnread.setVisible(req.content != "0") - self._set_mail_status(self.tr("ON"), ready=True) - else: - leap_assert(False, # XXX ??? - "Don't know how to handle this state: %s" - % (req.event)) - - if ext_status is not None: - self._set_long_mail_status(ext_status) - - def _set_long_mail_status(self, ext_status): - self.ui.lblLongMailStatus.setText(ext_status) - self.ui.grpMailStatus.setVisible(len(ext_status) > 0) diff --git a/src/leap/bitmask/gui/ui/eip_status.ui b/src/leap/bitmask/gui/ui/eip_status.ui new file mode 100644 index 00000000..27df3f31 --- /dev/null +++ b/src/leap/bitmask/gui/ui/eip_status.ui @@ -0,0 +1,287 @@ + + + EIPStatus + + + + 0 + 0 + 470 + 157 + + + + + 0 + 0 + + + + Form + + + + 24 + + + + + + + Turn On + + + + + + + + 32 + 32 + + + + + + + :/images/black/32/earth.png + + + + + + + + 16777215 + 32 + + + + color: rgb(80, 80, 80); + + + ... + + + true + + + + + + + + 0 + 0 + + + + + 14 + 75 + true + + + + Traffic is being routed in the clear + + + true + + + + + + + + 16 + 16 + + + + + + + :/images/black/32/off.png + + + true + + + + + + + Qt::Horizontal + + + + 40 + 0 + + + + + + + + + 16777215 + 32 + + + + + 0 + + + 0 + + + + + 4 + + + QLayout::SetDefaultConstraint + + + + + + + + :/images/light/16/down-arrow.png + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 120 + 16777215 + + + + + 11 + 75 + true + + + + PointingHandCursor + + + 0.0 KB/s + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 10 + 20 + + + + + + + + + + + :/images/light/16/up-arrow.png + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 120 + 16777215 + + + + + 11 + 75 + true + + + + PointingHandCursor + + + 0.0 KB/s + + + true + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + + + + + + + + diff --git a/src/leap/bitmask/gui/ui/eippreferences.ui b/src/leap/bitmask/gui/ui/eippreferences.ui new file mode 100644 index 00000000..e9bc203d --- /dev/null +++ b/src/leap/bitmask/gui/ui/eippreferences.ui @@ -0,0 +1,94 @@ + + + EIPPreferences + + + + 0 + 0 + 400 + 170 + + + + EIP Preferences + + + + :/images/mask-icon.png:/images/mask-icon.png + + + + + + true + + + Select gateway for provider + + + false + + + + + + + <Select provider> + + + + + + + + &Select provider: + + + cbProvidersGateway + + + + + + + Select gateway: + + + + + + + + Automatic + + + + + + + + Save this provider settings + + + + + + + < Providers Gateway Status > + + + Qt::AlignCenter + + + + + + + + + + + + + diff --git a/src/leap/bitmask/gui/ui/login.ui b/src/leap/bitmask/gui/ui/login.ui index 42a6897a..a1842608 100644 --- a/src/leap/bitmask/gui/ui/login.ui +++ b/src/leap/bitmask/gui/ui/login.ui @@ -6,127 +6,273 @@ 0 0 - 356 - 223 + 468 + 350 + + + 0 + 0 + + + + + 0 + 0 + + Form + + + - - - - Qt::Horizontal + + 0 + + + + + + 0 + 0 + - + - 40 - 20 + 0 + 0 - - - - + + + 24 + + + + + + 15 + 75 + true + + + + ... + + + + + + + Logout + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + color: rgb(132, 132, 132); +font: 75 12pt "Lucida Grande"; + + + + + + + + - - - - Qt::Horizontal + + + + + 0 + 0 + - + - 40 - 20 + 16777215 + 800 - - - - - Create a new account + - - - - - - <b>Provider:</b> + + :/images/black/32/user.png - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + false - - - - - - + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - - Remember username and password + + 0 - - - - <b>Username:</b> + + + + + 0 + 0 + - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0 + 0 + + + + 24 + + + + + <b>Provider:</b> + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Log In + + + + + + + + + Remember username and password + + + + + + + + + + Qt::AlignCenter + + + true + + + + + + + + + + + + <b>Username:</b> + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + <b>Password:</b> + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + - - - - <b>Password:</b> + + + + Qt::Horizontal - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + QSizePolicy::Maximum - - - - - - Log In + + + 12 + 0 + - + - - + + + + + 0 + 50 + + + + background-color: rgb(255, 127, 114); + Qt::AlignCenter - - true - + + + ClickableLabel + QLabel +
clickablelabel.h
+
+
- cmbProviders - lnUser - lnPassword chkRemember - btnLogin - btnCreateAccount - + + + diff --git a/src/leap/bitmask/gui/ui/mail_status.ui b/src/leap/bitmask/gui/ui/mail_status.ui new file mode 100644 index 00000000..1327f9e7 --- /dev/null +++ b/src/leap/bitmask/gui/ui/mail_status.ui @@ -0,0 +1,98 @@ + + + MailStatusWidget + + + + 0 + 0 + 400 + 72 + + + + + 0 + 0 + + + + Form + + + + + + + + color: rgb(80, 80, 80); + + + You must login to use encrypted email. + + + + + + + + 0 + 0 + + + + Email + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 16 + 16 + + + + + + + :/images/black/32/off.png + + + true + + + + + + + + + + + + :/images/black/32/email.png + + + + + + + + + + diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index 17837642..920160b8 100644 --- a/src/leap/bitmask/gui/ui/mainwindow.ui +++ b/src/leap/bitmask/gui/ui/mainwindow.ui @@ -6,10 +6,28 @@ 0 0 - 429 - 579 + 524 + 722 + + + 0 + 0 + + + + + 524 + 0 + + + + + 524 + 16777215 + + Bitmask @@ -28,9 +46,222 @@ - + + 0 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 524 + 635 + + + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 24 + + + 24 + + + + + + 16 + 75 + true + + + + Encrypted Internet + + + + + + + + 48 + 20 + + + + + + + + :/images/black/32/gear.png:/images/black/32/gear.png + + + false + + + false + + + false + + + + + + + + + + 12 + + + 0 + + + 12 + + + 0 + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 24 + + + 24 + + + + + + 16 + 75 + true + + + + Login + + + + + + + + 48 + 20 + + + + + + + + :/images/black/32/gear.png:/images/black/32/gear.png + + + + + + + + + + + + + Qt::Horizontal + + + + + + + 12 + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + - + + + + There are new updates available, please restart. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + Qt::Horizontal @@ -43,7 +274,7 @@ - + Qt::Horizontal @@ -56,17 +287,7 @@ - - - - There are new updates available, please restart. - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - + @@ -82,7 +303,7 @@ - + Qt::Horizontal @@ -97,164 +318,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - 1 - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - - - - false - - - - - - :/images/mask-launcher.png - - - Qt::AlignCenter - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - true - - - Preferences - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Show Log - - - true - - - false - - - true - - - - - @@ -262,15 +325,15 @@ 0 0 - 429 - 21 + 524 + 22 - + - &Session + &Bitmask - + @@ -279,16 +342,17 @@ Help + - + - + - Log &out + Preferences... @@ -313,7 +377,12 @@ - Show &logs + Show &Log + + + + + Create a new account... diff --git a/src/leap/bitmask/gui/ui/preferences.ui b/src/leap/bitmask/gui/ui/preferences.ui index e66a2d68..e187c016 100644 --- a/src/leap/bitmask/gui/ui/preferences.ui +++ b/src/leap/bitmask/gui/ui/preferences.ui @@ -7,7 +7,7 @@ 0 0 503 - 529 + 401 @@ -18,7 +18,7 @@ :/images/mask-icon.png:/images/mask-icon.png - + Qt::Vertical @@ -31,64 +31,80 @@ - - + + - true + false - Select gateway for provider - - - false + Password Change - - - - - - <Select provider> - - - - + + + QFormLayout::ExpandingFieldsGrow + - + - &Select provider: + &Current password: - cbProvidersGateway + leCurrentPassword + + + + + + + QLineEdit::Password - + - Select gateway: + &New password: + + + leNewPassword - - - - - Automatic - - + + + + QLineEdit::Password + - - + + - Save this provider settings + &Re-enter new password: + + + leNewPassword2 - - + + + + QLineEdit::Password + + + + + - < Providers Gateway Status > + Change + + + + + + + <Password change status> Qt::AlignCenter @@ -98,7 +114,7 @@ - + Enabled services @@ -155,89 +171,6 @@ - - - - false - - - Password Change - - - - QFormLayout::ExpandingFieldsGrow - - - - - &Current password: - - - leCurrentPassword - - - - - - - QLineEdit::Password - - - - - - - &New password: - - - leNewPassword - - - - - - - QLineEdit::Password - - - - - - - &Re-enter new password: - - - leNewPassword2 - - - - - - - QLineEdit::Password - - - - - - - Change - - - - - - - <Password change status> - - - Qt::AlignCenter - - - - - - diff --git a/src/leap/bitmask/gui/ui/statuspanel.ui b/src/leap/bitmask/gui/ui/statuspanel.ui deleted file mode 100644 index d77af1da..00000000 --- a/src/leap/bitmask/gui/ui/statuspanel.ui +++ /dev/null @@ -1,393 +0,0 @@ - - - StatusPanel - - - - 0 - 0 - 470 - 477 - - - - Form - - - - - - font: bold; - - - user@domain.org - - - true - - - - - - - true - - - - - - - - - - - 0 Unread Emails - - - - - - - font: bold; - - - Disabled - - - - - - - - 0 - 0 - - - - Encrypted Mail: - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 1 - - - - - - - - - - 4 - - - QLayout::SetDefaultConstraint - - - - - - - - :/images/light/16/down-arrow.png - - - - - - - - 0 - 0 - - - - - 100 - 0 - - - - - 120 - 16777215 - - - - PointingHandCursor - - - 0.0 KB/s - - - true - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 20 - 20 - - - - - - - - - - - :/images/light/16/up-arrow.png - - - - - - - - 0 - 0 - - - - - 100 - 0 - - - - - 120 - 16777215 - - - - PointingHandCursor - - - 0.0 KB/s - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Preferred - - - - 0 - 11 - - - - - - - - - 64 - 64 - - - - - - - :/images/light/64/network-eip-down.png - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 64 - 64 - - - - - 64 - 999 - - - - - - - :/images/mail-unlocked.png - - - - - - - - - - - - - ... - - - - - - - - - - false - - - - - - false - - - ... - - - true - - - - - - - - - - - - Encrypted Internet: - - - - - - - font: bold; - - - Off - - - Qt::AutoText - - - Qt::AlignCenter - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Turn On - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - -- cgit v1.2.3 From fa3dcc727f3690439c57981ffa639453b395a618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Mon, 30 Sep 2013 12:23:10 -0300 Subject: Add changes file --- changes/feature_new_ui | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/feature_new_ui diff --git a/changes/feature_new_ui b/changes/feature_new_ui new file mode 100644 index 00000000..b84fd39e --- /dev/null +++ b/changes/feature_new_ui @@ -0,0 +1 @@ + o Implement new UI design. Closes #3973. \ No newline at end of file -- cgit v1.2.3 From 98c6cfdce1e05e4e04f5683704df2dcffe24c05d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 1 Oct 2013 13:25:50 -0300 Subject: Hide error message label when starting a new login --- src/leap/bitmask/gui/login.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index 9a369f6d..324280b9 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -293,6 +293,7 @@ class LoginWidget(QtGui.QWidget): self.set_status(self.tr("Logging in..."), error=False) self.set_enabled(False) + self.ui.clblErrorMsg.hide() if self.get_remember() and has_keyring(): # in the keyring and in the settings -- cgit v1.2.3 From 010c2119c787b61301dbc9b9ec4e654edd33086e Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 1 Oct 2013 12:36:20 -0400 Subject: Add more verbose error handling To help diagnose this problem. I think we might prefer to leave this on hold until we merge the new gnupg module. --- .../bitmask/services/soledad/soledadbootstrapper.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index d348661d..6731cc84 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -362,11 +362,27 @@ class SoledadBootstrapper(AbstractBootstrapper): try: self._keymanager.get_key( address, openpgp.OpenPGPKey, private=True, fetch_remote=False) + return except KeyNotFound: logger.debug("Key not found. Generating key for %s" % (address,)) + + # generate key + try: self._keymanager.gen_key(openpgp.OpenPGPKey) + except Exception as exc: + logger.error("error while generating key!") + logger.exception(exc) + raise + + # send key + try: self._keymanager.send_key(openpgp.OpenPGPKey) - logger.debug("Key generated successfully.") + except Exception as exc: + logger.error("error while sending key!") + logger.exception(exc) + raise + + logger.debug("Key generated successfully.") def run_soledad_setup_checks(self, provider_config, -- cgit v1.2.3 From 009fcbf7870b95910f50fe9e618fcaed51930ada Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 1 Oct 2013 12:54:10 -0400 Subject: partial changes file --- changes/bug_3985-soledad-boostrap-problem | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changes/bug_3985-soledad-boostrap-problem diff --git a/changes/bug_3985-soledad-boostrap-problem b/changes/bug_3985-soledad-boostrap-problem new file mode 100644 index 00000000..629481de --- /dev/null +++ b/changes/bug_3985-soledad-boostrap-problem @@ -0,0 +1,2 @@ + o Add more verbose error handling during key generation and syncing. + Helps diagnose: #3985; Addresses in part: #3965 -- cgit v1.2.3 From d74a4c3840c95e5879c89ec9d1f6d48ab54b0f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 1 Oct 2013 14:46:53 -0300 Subject: Use the same exception for all the auth user facing errors --- changes/login_error_undistinguishable | 2 ++ src/leap/bitmask/crypto/srpauth.py | 20 ++++++-------------- src/leap/bitmask/crypto/tests/test_srpauth.py | 6 +++--- src/leap/bitmask/gui/preferenceswindow.py | 5 ++--- 4 files changed, 13 insertions(+), 20 deletions(-) create mode 100644 changes/login_error_undistinguishable diff --git a/changes/login_error_undistinguishable b/changes/login_error_undistinguishable new file mode 100644 index 00000000..5391f3fc --- /dev/null +++ b/changes/login_error_undistinguishable @@ -0,0 +1,2 @@ + o Do not distinguish between different possible authentication + errors. Fixes #3859. \ No newline at end of file diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index bf85f75c..9c08d353 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -52,13 +52,6 @@ class SRPAuthConnectionError(SRPAuthenticationError): pass -class SRPAuthUnknownUser(SRPAuthenticationError): - """ - Exception raised when trying to authenticate an unknown user - """ - pass - - class SRPAuthBadStatusCode(SRPAuthenticationError): """ Exception raised when we received an unknown bad status code @@ -97,7 +90,7 @@ class SRPAuthJSONDecodeError(SRPAuthenticationError): pass -class SRPAuthBadPassword(SRPAuthenticationError): +class SRPAuthBadUserOrPassword(SRPAuthenticationError): """ Exception raised when the user provided a bad password to auth. """ @@ -219,7 +212,6 @@ class SRPAuth(QtCore.QObject): Might raise all SRPAuthenticationError based: SRPAuthenticationError SRPAuthConnectionError - SRPAuthUnknownUser SRPAuthBadStatusCode SRPAuthNoSalt SRPAuthNoB @@ -266,7 +258,7 @@ class SRPAuth(QtCore.QObject): "Status code = %r. Content: %r" % (init_session.status_code, content)) if init_session.status_code == 422: - raise SRPAuthUnknownUser(self._WRONG_USER_PASS) + raise SRPAuthBadUserOrPassword(self._WRONG_USER_PASS) raise SRPAuthBadStatusCode(self.tr("There was a problem with" " authentication")) @@ -296,7 +288,7 @@ class SRPAuth(QtCore.QObject): SRPAuthBadDataFromServer SRPAuthConnectionError SRPAuthJSONDecodeError - SRPAuthBadPassword + SRPAuthBadUserOrPassword :param salt_B: salt and B parameters for the username :type salt_B: tuple @@ -355,7 +347,7 @@ class SRPAuth(QtCore.QObject): "received: %s", (content,)) logger.error("[%s] Wrong password (HAMK): [%s]" % (auth_result.status_code, error)) - raise SRPAuthBadPassword(self._WRONG_USER_PASS) + raise SRPAuthBadUserOrPassword(self._WRONG_USER_PASS) if auth_result.status_code not in (200,): logger.error("No valid response (HAMK): " @@ -452,7 +444,7 @@ class SRPAuth(QtCore.QObject): It requires to be authenticated. Might raise: - SRPAuthBadPassword + SRPAuthBadUserOrPassword requests.exceptions.HTTPError :param current_password: the current password for the logged user. @@ -463,7 +455,7 @@ class SRPAuth(QtCore.QObject): leap_assert(self.get_uid() is not None) if current_password != self._password: - raise SRPAuthBadPassword + raise SRPAuthBadUserOrPassword url = "%s/%s/users/%s.json" % ( self._provider_config.get_api_uri(), diff --git a/src/leap/bitmask/crypto/tests/test_srpauth.py b/src/leap/bitmask/crypto/tests/test_srpauth.py index 6fb2b739..0cb8e79a 100644 --- a/src/leap/bitmask/crypto/tests/test_srpauth.py +++ b/src/leap/bitmask/crypto/tests/test_srpauth.py @@ -246,7 +246,7 @@ class SRPAuthTestCase(unittest.TestCase): d = self._prepare_auth_test(422) def wrapper(_): - with self.assertRaises(srpauth.SRPAuthUnknownUser): + with self.assertRaises(srpauth.SRPAuthBadUserOrPassword): with mock.patch( 'leap.bitmask.util.request_helpers.get_content', new=mock.create_autospec(get_content)) as content: @@ -425,7 +425,7 @@ class SRPAuthTestCase(unittest.TestCase): new=mock.create_autospec(get_content)) as \ content: content.return_value = ("", 0) - with self.assertRaises(srpauth.SRPAuthBadPassword): + with self.assertRaises(srpauth.SRPAuthBadUserOrPassword): self.auth_backend._process_challenge( salt_B, username=self.TEST_USER) @@ -449,7 +449,7 @@ class SRPAuthTestCase(unittest.TestCase): new=mock.create_autospec(get_content)) as \ content: content.return_value = ("[]", 0) - with self.assertRaises(srpauth.SRPAuthBadPassword): + with self.assertRaises(srpauth.SRPAuthBadUserOrPassword): self.auth_backend._process_challenge( salt_B, username=self.TEST_USER) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 7e281b44..58cb05ba 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -27,11 +27,10 @@ from PySide import QtCore, QtGui from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.gui.ui_preferences import Ui_Preferences from leap.soledad.client import NoStorageSecret -from leap.bitmask.crypto.srpauth import SRPAuthBadPassword +from leap.bitmask.crypto.srpauth import SRPAuthBadUserOrPassword 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__) @@ -179,7 +178,7 @@ class PreferencesWindow(QtGui.QDialog): logger.error("Error changing password: %s", (failure, )) problem = self.tr("There was a problem changing the password.") - if failure.check(SRPAuthBadPassword): + if failure.check(SRPAuthBadUserOrPassword): problem = self.tr("You did not enter a correct current password.") self._set_password_change_status(problem, error=True) -- cgit v1.2.3 From 04bf753b0e120a92a4ffdbbdb0128d4f6c19db79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 2 Oct 2013 11:06:33 -0300 Subject: Hide logout error message label --- src/leap/bitmask/gui/login.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index 324280b9..c635081c 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -330,7 +330,7 @@ class LoginWidget(QtGui.QWidget): self.set_password("") self.set_enabled(True) - self.set_status("") + self.set_status("", error=False) def set_login_status(self, msg, error=False): """ -- cgit v1.2.3 From 85103cd2977bee78006dac15cfd33a549f6a39de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Tue, 1 Oct 2013 15:45:06 -0300 Subject: Fix failing tests/code --- src/leap/bitmask/crypto/tests/test_srpauth.py | 5 +-- src/leap/bitmask/provider/providerbootstrapper.py | 4 +- .../provider/tests/test_providerbootstrapper.py | 50 +++++++++------------- src/leap/bitmask/services/__init__.py | 4 +- .../services/eip/tests/test_eipbootstrapper.py | 4 +- 5 files changed, 30 insertions(+), 37 deletions(-) diff --git a/src/leap/bitmask/crypto/tests/test_srpauth.py b/src/leap/bitmask/crypto/tests/test_srpauth.py index 6fb2b739..5f2b44ee 100644 --- a/src/leap/bitmask/crypto/tests/test_srpauth.py +++ b/src/leap/bitmask/crypto/tests/test_srpauth.py @@ -680,10 +680,7 @@ class SRPAuthTestCase(unittest.TestCase): self.auth_backend._session.delete, side_effect=Exception()) - def wrapper(*args): - self.auth_backend.logout() - - d = threads.deferToThread(wrapper) + d = threads.deferToThread(self.auth.logout) return d @deferred() diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index 751da828..a10973f0 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -156,7 +156,9 @@ class ProviderBootstrapper(AbstractBootstrapper): # Watch out! We're handling the verify paramenter differently here. headers = {} - provider_json = os.path.join(get_path_prefix(), "leap", "providers", + provider_json = os.path.join(get_path_prefix(), + "leap", + "providers", self._domain, "provider.json") mtime = get_mtime(provider_json) diff --git a/src/leap/bitmask/provider/tests/test_providerbootstrapper.py b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py index 9b47d60e..fe5b52bd 100644 --- a/src/leap/bitmask/provider/tests/test_providerbootstrapper.py +++ b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py @@ -42,6 +42,8 @@ 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 import util +from leap.bitmask.util import get_path_prefix from leap.common.files import mkdir_p from leap.common.testing.https_server import where from leap.common.testing.basetest import BaseLeapTest @@ -315,16 +317,19 @@ class ProviderBootstrapperActiveTest(unittest.TestCase): # tearDown so we are sure everything is as expected for each # test. If we do it inside each specific test, a failure in # the test will leave the implementation with the mock. - self.old_gpp = ProviderConfig.get_path_prefix + self.old_gpp = util.get_path_prefix + self.old_load = ProviderConfig.load self.old_save = ProviderConfig.save self.old_api_version = ProviderConfig.get_api_version + self.old_api_uri = ProviderConfig.get_api_uri def tearDown(self): - ProviderConfig.get_path_prefix = self.old_gpp + util.get_path_prefix = self.old_gpp ProviderConfig.load = self.old_load ProviderConfig.save = self.old_save ProviderConfig.get_api_version = self.old_api_version + ProviderConfig.get_api_uri = self.old_api_uri def test_check_https_succeeds(self): # XXX: Need a proper CA signed cert to test this @@ -372,10 +377,11 @@ class ProviderBootstrapperActiveTest(unittest.TestCase): paths :type path_prefix: str """ - ProviderConfig.get_path_prefix = mock.MagicMock( - return_value=path_prefix) + util.get_path_prefix = mock.MagicMock(return_value=path_prefix) ProviderConfig.get_api_version = mock.MagicMock( return_value=api) + ProviderConfig.get_api_uri = mock.MagicMock( + return_value="https://localhost:%s" % (self.https_port,)) ProviderConfig.load = mock.MagicMock() ProviderConfig.save = mock.MagicMock() @@ -400,10 +406,8 @@ class ProviderBootstrapperActiveTest(unittest.TestCase): :returns: the provider.json path used :rtype: str """ - provider_dir = os.path.join(ProviderConfig() - .get_path_prefix(), - "leap", - "providers", + provider_dir = os.path.join(get_path_prefix(), + "leap", "providers", self.pb._domain) mkdir_p(provider_dir) provider_path = os.path.join(provider_dir, @@ -413,6 +417,9 @@ class ProviderBootstrapperActiveTest(unittest.TestCase): p.write("A") return provider_path + @mock.patch( + 'leap.bitmask.config.providerconfig.ProviderConfig.get_domain', + lambda x: where('testdomain.com')) def test_download_provider_info_new_provider(self): self._setup_provider_config_with("1", tempfile.mkdtemp()) self._setup_providerbootstrapper(True) @@ -431,10 +438,7 @@ class ProviderBootstrapperActiveTest(unittest.TestCase): # set mtime to something really new os.utime(provider_path, (-1, time.time())) - with mock.patch.object( - ProviderConfig, 'get_api_uri', - return_value="https://localhost:%s" % (self.https_port,)): - self.pb._download_provider_info() + self.pb._download_provider_info() # we check that it doesn't save the provider # config, because it's new enough self.assertFalse(ProviderConfig.save.called) @@ -450,10 +454,7 @@ class ProviderBootstrapperActiveTest(unittest.TestCase): # set mtime to something really new os.utime(provider_path, (-1, time.time())) - with mock.patch.object( - ProviderConfig, 'get_api_uri', - return_value="https://localhost:%s" % (self.https_port,)): - self.pb._download_provider_info() + self.pb._download_provider_info() # we check that it doesn't save the provider # config, because it's new enough self.assertFalse(ProviderConfig.save.called) @@ -469,10 +470,7 @@ class ProviderBootstrapperActiveTest(unittest.TestCase): # set mtime to something really old os.utime(provider_path, (-1, 100)) - with mock.patch.object( - ProviderConfig, 'get_api_uri', - return_value="https://localhost:%s" % (self.https_port,)): - self.pb._download_provider_info() + self.pb._download_provider_info() self.assertTrue(ProviderConfig.load.called) self.assertTrue(ProviderConfig.save.called) @@ -484,11 +482,8 @@ class ProviderBootstrapperActiveTest(unittest.TestCase): self._setup_providerbootstrapper(False) self._produce_dummy_provider_json() - with mock.patch.object( - ProviderConfig, 'get_api_uri', - return_value="https://localhost:%s" % (self.https_port,)): - with self.assertRaises(UnsupportedProviderAPI): - self.pb._download_provider_info() + with self.assertRaises(UnsupportedProviderAPI): + self.pb._download_provider_info() @mock.patch( 'leap.bitmask.config.providerconfig.ProviderConfig.get_ca_cert_path', @@ -499,10 +494,7 @@ class ProviderBootstrapperActiveTest(unittest.TestCase): self._setup_providerbootstrapper(False) self._produce_dummy_provider_json() - with mock.patch.object( - ProviderConfig, 'get_api_uri', - return_value="https://localhost:%s" % (self.https_port,)): - self.pb._download_provider_info() + self.pb._download_provider_info() @mock.patch( 'leap.bitmask.config.providerconfig.ProviderConfig.get_api_uri', diff --git a/src/leap/bitmask/services/__init__.py b/src/leap/bitmask/services/__init__.py index afce72f6..0d74e0e2 100644 --- a/src/leap/bitmask/services/__init__.py +++ b/src/leap/bitmask/services/__init__.py @@ -105,10 +105,12 @@ def download_service_config(provider_config, service_config, service_name = service_config.name service_json = "{0}-service.json".format(service_name) headers = {} - mtime = get_mtime(os.path.join(get_path_prefix(), + mtime = get_mtime(os.path.join(service_config + .get_path_prefix(), "leap", "providers", provider_config.get_domain(), service_json)) + if download_if_needed and mtime: headers['if-modified-since'] = mtime diff --git a/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py b/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py index d0d78eed..fed4a783 100644 --- a/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py +++ b/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py @@ -150,10 +150,10 @@ class EIPBootstrapperActiveTest(BaseLeapTest): def check(*args): self.eb._eip_config.save.assert_called_once_with( - ["leap", + ("leap", "providers", self.eb._provider_config.get_domain(), - "eip-service.json"]) + "eip-service.json")) d.addCallback(check) return d -- cgit v1.2.3 From c9742f3549d07c23f5caf8a8e48317a4fb5e75a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 2 Oct 2013 12:46:14 -0300 Subject: Fix some more tests --- src/leap/bitmask/provider/providerbootstrapper.py | 4 ++-- .../bitmask/provider/tests/test_providerbootstrapper.py | 3 +-- src/leap/bitmask/services/__init__.py | 5 ++--- src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py | 13 +++++++------ 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/leap/bitmask/provider/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index a10973f0..1b5947e1 100644 --- a/src/leap/bitmask/provider/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -27,7 +27,7 @@ from PySide import QtCore from leap.bitmask.config.providerconfig import ProviderConfig, MissingCACert from leap.bitmask.util.request_helpers import get_content -from leap.bitmask.util import get_path_prefix +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 @@ -156,7 +156,7 @@ class ProviderBootstrapper(AbstractBootstrapper): # Watch out! We're handling the verify paramenter differently here. headers = {} - provider_json = os.path.join(get_path_prefix(), + provider_json = os.path.join(util.get_path_prefix(), "leap", "providers", self._domain, "provider.json") diff --git a/src/leap/bitmask/provider/tests/test_providerbootstrapper.py b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py index fe5b52bd..88a4ff0b 100644 --- a/src/leap/bitmask/provider/tests/test_providerbootstrapper.py +++ b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py @@ -43,7 +43,6 @@ from leap.bitmask.provider.providerbootstrapper import UnsupportedProviderAPI from leap.bitmask.provider.providerbootstrapper import WrongFingerprint from leap.bitmask.provider.supportedapis import SupportedAPIs from leap.bitmask import util -from leap.bitmask.util import get_path_prefix from leap.common.files import mkdir_p from leap.common.testing.https_server import where from leap.common.testing.basetest import BaseLeapTest @@ -406,7 +405,7 @@ class ProviderBootstrapperActiveTest(unittest.TestCase): :returns: the provider.json path used :rtype: str """ - provider_dir = os.path.join(get_path_prefix(), + provider_dir = os.path.join(util.get_path_prefix(), "leap", "providers", self.pb._domain) mkdir_p(provider_dir) diff --git a/src/leap/bitmask/services/__init__.py b/src/leap/bitmask/services/__init__.py index 0d74e0e2..9b32c5ad 100644 --- a/src/leap/bitmask/services/__init__.py +++ b/src/leap/bitmask/services/__init__.py @@ -27,7 +27,7 @@ from leap.bitmask.crypto.srpauth import SRPAuth from leap.bitmask.util.constants import REQUEST_TIMEOUT from leap.bitmask.util.privilege_policies import is_missing_policy_permissions from leap.bitmask.util.request_helpers import get_content -from leap.bitmask.util import get_path_prefix +from leap.bitmask import util from leap.common.check import leap_assert from leap.common.config.baseconfig import BaseConfig @@ -105,8 +105,7 @@ def download_service_config(provider_config, service_config, service_name = service_config.name service_json = "{0}-service.json".format(service_name) headers = {} - mtime = get_mtime(os.path.join(service_config - .get_path_prefix(), + mtime = get_mtime(os.path.join(util.get_path_prefix(), "leap", "providers", provider_config.get_domain(), service_json)) diff --git a/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py b/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py index fed4a783..6640a860 100644 --- a/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py +++ b/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py @@ -41,6 +41,7 @@ from leap.bitmask.services.eip.eipconfig import EIPConfig from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.tests import fake_provider from leap.bitmask.crypto.srpauth import SRPAuth +from leap.bitmask import util from leap.common.testing.basetest import BaseLeapTest from leap.common.files import mkdir_p @@ -60,13 +61,13 @@ class EIPBootstrapperActiveTest(BaseLeapTest): def setUp(self): self.eb = EIPBootstrapper() - self.old_pp = EIPConfig.get_path_prefix + self.old_pp = util.get_path_prefix self.old_save = EIPConfig.save self.old_load = EIPConfig.load self.old_si = SRPAuth.get_session_id def tearDown(self): - EIPConfig.get_path_prefix = self.old_pp + util.get_path_prefix = self.old_pp EIPConfig.save = self.old_save EIPConfig.load = self.old_load SRPAuth.get_session_id = self.old_si @@ -97,13 +98,13 @@ class EIPBootstrapperActiveTest(BaseLeapTest): pc.get_ca_cert_path = mock.MagicMock(return_value=False) path_prefix = tempfile.mkdtemp() - EIPConfig.get_path_prefix = mock.MagicMock(return_value=path_prefix) + util.get_path_prefix = mock.MagicMock(return_value=path_prefix) EIPConfig.save = mock.MagicMock() EIPConfig.load = mock.MagicMock() self.eb._download_if_needed = ifneeded - provider_dir = os.path.join(EIPConfig.get_path_prefix(), + provider_dir = os.path.join(util.get_path_prefix(), "leap", "providers", pc.get_domain()) @@ -184,13 +185,13 @@ class EIPBootstrapperActiveTest(BaseLeapTest): pc.get_ca_cert_path = mock.MagicMock(return_value=False) path_prefix = tempfile.mkdtemp() - EIPConfig.get_path_prefix = mock.MagicMock(return_value=path_prefix) + util.get_path_prefix = mock.MagicMock(return_value=path_prefix) EIPConfig.save = mock.MagicMock() EIPConfig.load = mock.MagicMock() self.eb._download_if_needed = ifneeded - provider_dir = os.path.join(EIPConfig.get_path_prefix(), + provider_dir = os.path.join(util.get_path_prefix(), "leap", "providers", "somedomain") -- cgit v1.2.3 From 5fc48bf44bb127d54c001ab2e668dd5bed3c8f2d Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 30 Sep 2013 12:26:46 -0300 Subject: Move echo-mode-setting to the ui file. --- src/leap/bitmask/gui/ui/wizard.ui | 12 ++++++++++-- src/leap/bitmask/gui/wizard.py | 3 --- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui index 2a412784..420c74ae 100644 --- a/src/leap/bitmask/gui/ui/wizard.ui +++ b/src/leap/bitmask/gui/ui/wizard.ui @@ -626,10 +626,18 @@ - + + + QLineEdit::Password + + - + + + QLineEdit::Password + + diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index bb38b136..5c00f631 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -110,9 +110,6 @@ class Wizard(QtGui.QWizard): self.currentIdChanged.connect(self._current_id_changed) - self.ui.lblPassword.setEchoMode(QtGui.QLineEdit.Password) - self.ui.lblPassword2.setEchoMode(QtGui.QLineEdit.Password) - self.ui.lnProvider.textChanged.connect( self._enable_check) -- cgit v1.2.3 From 057f42dcc2c23b93b872301d01a944fde5025439 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Mon, 30 Sep 2013 18:28:00 -0300 Subject: Add providers combobox with configured providers. --- src/leap/bitmask/gui/ui/wizard.ui | 171 ++++++++++++++++++++++++++++++++------ src/leap/bitmask/gui/wizard.py | 6 ++ 2 files changed, 151 insertions(+), 26 deletions(-) diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui index 420c74ae..b796b795 100644 --- a/src/leap/bitmask/gui/ui/wizard.ui +++ b/src/leap/bitmask/gui/ui/wizard.ui @@ -134,17 +134,7 @@ - - - - - - - Check - - - - + Qt::Vertical @@ -157,17 +147,7 @@ - - - - https:// - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - + Checking for a valid provider @@ -276,13 +256,73 @@ - + + + + + Configure or select a provider + + + + + + https:// + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + false + + + + + + + Check + + + + + + + Configure new provider + + + true + + + + + + + Use existing one + + + + + + + https:// + + + + + + @@ -764,12 +804,91 @@ btnRegister rdoRegister rdoLogin - lnProvider - btnCheck - + + + rbExistingProvider + toggled(bool) + cbProviders + setFocus() + + + 167 + 192 + + + 265 + 191 + + + + + rbNewProvider + toggled(bool) + lnProvider + setFocus() + + + 171 + 164 + + + 246 + 164 + + + + + rbExistingProvider + toggled(bool) + lnProvider + setDisabled(bool) + + + 169 + 196 + + + 327 + 163 + + + + + rbNewProvider + toggled(bool) + cbProviders + setDisabled(bool) + + + 169 + 162 + + + 269 + 193 + + + + + rbExistingProvider + toggled(bool) + btnCheck + setDisabled(bool) + + + 154 + 193 + + + 498 + 170 + + + + diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 5c00f631..f71ce06b 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -26,6 +26,7 @@ from functools import partial from PySide import QtCore, QtGui from twisted.internet import threads +from leap.bitmask.config.leapsettings import LeapSettings from leap.bitmask.config.providerconfig import ProviderConfig from leap.bitmask.crypto.srpregister import SRPRegister from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper @@ -141,6 +142,11 @@ class Wizard(QtGui.QWizard): self.ui.label_12.setVisible(False) self.ui.lblProviderPolicy.setVisible(False) + # Load configured providers into wizard + ls = LeapSettings() + providers = ls.get_configured_providers() + self.ui.cbProviders.addItems(providers) + def get_domain(self): return self._domain -- cgit v1.2.3 From 5b2220bc0177f12c81a3dbb1ebffd3cdae8b350d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 2 Oct 2013 11:57:57 -0300 Subject: Use token header also for authenticated requests --- changes/feature_use_token | 1 + src/leap/bitmask/crypto/srpauth.py | 8 +++++++- src/leap/bitmask/services/__init__.py | 7 ++++++- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 changes/feature_use_token diff --git a/changes/feature_use_token b/changes/feature_use_token new file mode 100644 index 00000000..b412cc2d --- /dev/null +++ b/changes/feature_use_token @@ -0,0 +1 @@ + o Use token header for authenticated requests. Closes #3910. \ No newline at end of file diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 9c08d353..90d9ea0a 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -129,6 +129,7 @@ class SRPAuth(QtCore.QObject): SESSION_ID_KEY = "_session_id" USER_VERIFIER_KEY = 'user[password_verifier]' USER_SALT_KEY = 'user[password_salt]' + AUTHORIZATION_KEY = "Authorization" def __init__(self, provider_config): """ @@ -466,6 +467,10 @@ class SRPAuth(QtCore.QObject): self._username, new_password, self._hashfun, self._ng) cookies = {self.SESSION_ID_KEY: self.get_session_id()} + headers = { + self.AUTHORIZATION_KEY: + "Token token={0}".format(self.get_token()) + } user_data = { self.USER_VERIFIER_KEY: binascii.hexlify(verifier), self.USER_SALT_KEY: binascii.hexlify(salt) @@ -475,7 +480,8 @@ class SRPAuth(QtCore.QObject): url, data=user_data, verify=self._provider_config.get_ca_cert_path(), cookies=cookies, - timeout=REQUEST_TIMEOUT) + timeout=REQUEST_TIMEOUT, + headers=headers) # In case of non 2xx it raises HTTPError change_password.raise_for_status() diff --git a/src/leap/bitmask/services/__init__.py b/src/leap/bitmask/services/__init__.py index 0d74e0e2..e19b82b9 100644 --- a/src/leap/bitmask/services/__init__.py +++ b/src/leap/bitmask/services/__init__.py @@ -127,10 +127,15 @@ def download_service_config(provider_config, service_config, # XXX make and use @with_srp_auth decorator srp_auth = SRPAuth(provider_config) session_id = srp_auth.get_session_id() + token = srp_auth.get_token() cookies = None - if session_id: + if session_id is not None: cookies = {"_session_id": session_id} + # API v2 will only support token auth, but in v1 we can send both + if token is not None: + headers["Authorization"] = 'Token token="{0}"'.format(token) + res = session.get(config_uri, verify=provider_config.get_ca_cert_path(), headers=headers, -- cgit v1.2.3 From ff32e6bda529fffa8aaf5ebb9abf9028e7f0917f Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Wed, 2 Oct 2013 12:16:22 -0400 Subject: update deps to reflect keymanager changes --- changes/VERSION_COMPAT | 1 + pkg/requirements.pip | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/changes/VERSION_COMPAT b/changes/VERSION_COMPAT index 425478c8..ac2d2e73 100644 --- a/changes/VERSION_COMPAT +++ b/changes/VERSION_COMPAT @@ -9,3 +9,4 @@ # BEGIN DEPENDENCY LIST ------------------------- # leap.foo.bar>=x.y.z leap.common >= 0.3.4 # because the ca_bundle +leap.keymanager >= 0.3.3 # because the gnupg dep diff --git a/pkg/requirements.pip b/pkg/requirements.pip index 154e51b4..269a4646 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -14,7 +14,6 @@ psutil ipaddr twisted qt4reactor -python-gnupg python-daemon # this should not be needed for Windows. keyring -- cgit v1.2.3 From 201e9917a957b68e02a540ee395b32521f699eda Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Tue, 1 Oct 2013 12:33:17 -0300 Subject: Skip checks for an existing provider. --- src/leap/bitmask/gui/wizard.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index f71ce06b..7cff742e 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -82,6 +82,8 @@ class Wizard(QtGui.QWizard): self._show_register = False + 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) @@ -123,6 +125,8 @@ class Wizard(QtGui.QWizard): self.ui.btnRegister.clicked.connect( self._register) + self.ui.rbExistingProvider.toggled.connect(self._skip_provider_checks) + usernameRe = QtCore.QRegExp(self.BARE_USERNAME_REGEX) self.ui.lblUser.setValidator( QtGui.QRegExpValidator(usernameRe, self)) @@ -318,6 +322,25 @@ class Wizard(QtGui.QWizard): self._provider_select_defer = self._provider_bootstrapper.\ run_provider_select_checks(self._domain) + def _skip_provider_checks(self, skip): + """ + SLOT + Triggered: + self.ui.rbExistingProvider.toggled + + Allows the user to move to the next page without make any checks, + used when we are selecting an already configured provider. + + :param skip: if we should skip checks or not + :type skip: bool + """ + if skip: + self._reset_provider_check() + + self.page(self.SELECT_PROVIDER_PAGE).set_completed(skip) + self.button(QtGui.QWizard.NextButton).setEnabled(skip) + self._use_existing_provider = skip + def _complete_task(self, data, label, complete=False, complete_page=-1): """ Checks a task and completes a page if specified @@ -564,4 +587,14 @@ class Wizard(QtGui.QWizard): else: return self.SERVICES_PAGE + if self.currentPage() == self.page(self.SELECT_PROVIDER_PAGE): + if self._use_existing_provider: + self._domain = self.ui.cbProviders.currentText() + self._provider_config = ProviderConfig.get_provider_config( + self._domain) + if self._show_register: + return self.REGISTER_USER_PAGE + else: + return self.SERVICES_PAGE + return QtGui.QWizard.nextId(self) -- cgit v1.2.3 From 956b5d75afffd67ac0ea0efd20c7e5dd158fe767 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 2 Oct 2013 14:36:32 -0300 Subject: Add changes file for #3995. --- changes/feature-3995_add-drop-down-for-known-providers | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/feature-3995_add-drop-down-for-known-providers diff --git a/changes/feature-3995_add-drop-down-for-known-providers b/changes/feature-3995_add-drop-down-for-known-providers new file mode 100644 index 00000000..f05c3b58 --- /dev/null +++ b/changes/feature-3995_add-drop-down-for-known-providers @@ -0,0 +1 @@ + o Add a dropdown for known providers in the wizard. Closes #3995. -- cgit v1.2.3 From e9c84a3d2d9fd3a0457f256a908cb5bce9351682 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 1 Oct 2013 15:02:09 -0400 Subject: remove duplicated method definition --- src/leap/bitmask/gui/eip_status.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index f7408b13..6b1235a6 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -216,22 +216,6 @@ class EIPStatusWidget(QtGui.QWidget): leap_assert_type(eip_status_menu, QtGui.QMenu) self._eip_status_menu = eip_status_menu - def set_eip_status(self, status, error=False): - """ - Sets the global status label. - - :param status: status message - :type status: str or unicode - :param error: if the status is an erroneous one, then set this - to True - :type error: bool - """ - leap_assert_type(error, bool) - if error: - status = "%s" % (status,) - self.ui.lblEIPStatus.setText(status) - self.ui.lblEIPStatus.show() - # EIP status --- @property @@ -261,9 +245,7 @@ class EIPStatusWidget(QtGui.QWidget): :type error: bool """ leap_assert_type(error, bool) - self._eip_status = status - if error: status = "%s" % (status,) self.ui.lblEIPStatus.setText(status) -- cgit v1.2.3 From 40161f730310d18756123e53ea29f724eea59730 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 2 Oct 2013 16:41:23 -0300 Subject: Separate pre-seeded providers from user added ones --- src/leap/bitmask/config/leapsettings.py | 17 +++++++++++++++++ src/leap/bitmask/gui/wizard.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index 338fa475..7ab1ace3 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -67,6 +67,7 @@ class LeapSettings(object): DEFAULTPROVIDER_KEY = "DefaultProvider" ALERTMISSING_KEY = "AlertMissingScripts" GATEWAY_KEY = "Gateway" + PINNED_KEY = "Pinned" # values GATEWAY_AUTOMATIC = "Automatic" @@ -134,6 +135,22 @@ class LeapSettings(object): return providers + def is_pinned_provider(self, domain): + """ + Returns True if the domain 'domain' is pinned with the application. + False otherwise. + + :param provider: provider domain + :type provider: str + + :rtype: bool + """ + leap_assert(len(domain) > 0, "We need a nonempty domain.") + pinned_key = "{0}/{1}".format(domain, self.PINNED_KEY) + result = to_bool(self._settings.value(pinned_key, False)) + + return result + def get_selected_gateway(self, provider): """ Returns the configured gateway for the given provider. diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 7cff742e..219270c7 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -20,6 +20,7 @@ First run wizard import os import logging import json +import random from functools import partial @@ -146,10 +147,33 @@ class Wizard(QtGui.QWizard): self.ui.label_12.setVisible(False) self.ui.lblProviderPolicy.setVisible(False) - # Load configured providers into wizard + self._load_configured_providers() + + def _load_configured_providers(self): + """ + Loads the configured providers into the wizard providers combo box. + """ ls = LeapSettings() providers = ls.get_configured_providers() - self.ui.cbProviders.addItems(providers) + pinned = [] + user_added = [] + + # separate pinned providers from user added ones + for p in providers: + if ls.is_pinned_provider(p): + pinned.append(p) + else: + user_added.append(p) + + if user_added: + self.ui.cbProviders.addItems(user_added) + + if user_added and pinned: + self.ui.cbProviders.addItem('---') + + if pinned: + random.shuffle(pinned) # don't prioritize alphabetically + self.ui.cbProviders.addItems(pinned) def get_domain(self): return self._domain -- cgit v1.2.3 From 99625ee3ab32791c0ddac946685379940b9f291e Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 2 Oct 2013 16:43:09 -0300 Subject: Add changes file for #3996. --- changes/feature-3996_separate-providers-in-wizard | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/feature-3996_separate-providers-in-wizard diff --git a/changes/feature-3996_separate-providers-in-wizard b/changes/feature-3996_separate-providers-in-wizard new file mode 100644 index 00000000..2466d140 --- /dev/null +++ b/changes/feature-3996_separate-providers-in-wizard @@ -0,0 +1 @@ + o Separate pinned providers from user configures ones. Closes #3996. -- cgit v1.2.3 From b9b7b244984111ec799675dc90073396481e6173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Wed, 2 Oct 2013 21:36:37 -0300 Subject: Only pick one gnupg bin path which is not a symlink --- changes/choose_one_gnupg | 1 + src/leap/bitmask/services/soledad/soledadbootstrapper.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 changes/choose_one_gnupg diff --git a/changes/choose_one_gnupg b/changes/choose_one_gnupg new file mode 100644 index 00000000..d759616b --- /dev/null +++ b/changes/choose_one_gnupg @@ -0,0 +1 @@ + o Choose one gnupg binary path that is also not a symlink. Closes #3999. \ No newline at end of file diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 6731cc84..7968dd6a 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -321,7 +321,18 @@ class SoledadBootstrapper(AbstractBootstrapper): gpgbin = os.path.join( get_path_prefix(), "..", "apps", "mail", "gpg") else: - gpgbin = which("gpg") + try: + gpgbin_options = which("gpg") + # gnupg checks that the path to the binary is not a + # symlink, so we need to filter those and come up with + # just one option. + for opt in gpgbin_options: + if not os.path.islink(opt): + gpgbin = opt + break + except IndexError as e: + logger.debug("Couldn't find the gpg binary!") + logger.exception(e) leap_check(gpgbin is not None, "Could not find gpg binary") return gpgbin -- cgit v1.2.3 From 6f932294e7bf58e66ca117fe46ebe346e10aef0f Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 3 Oct 2013 14:00:14 -0300 Subject: Reorder providers combo, disable if no providers. - Move radio buttons to get more space for the labels. - If there are no configured providers then disable the combo. --- src/leap/bitmask/gui/ui/wizard.ui | 53 +++++++++++++++++++++------------------ src/leap/bitmask/gui/wizard.py | 6 +++++ 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui index b796b795..0f6eef6e 100644 --- a/src/leap/bitmask/gui/ui/wizard.ui +++ b/src/leap/bitmask/gui/ui/wizard.ui @@ -269,7 +269,24 @@ Configure or select a provider - + + + + Configure new provider: + + + true + + + + + + + Use existing one: + + + + https:// @@ -279,44 +296,30 @@ - + - - - false - - - - Check - - + + - Configure new provider - - - true + https:// - - - - - - Use existing one + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - https:// + + + + false diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 219270c7..e3f5904e 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -155,6 +155,12 @@ class Wizard(QtGui.QWizard): """ ls = LeapSettings() providers = ls.get_configured_providers() + if not providers: + self.ui.rbExistingProvider.setEnabled(False) + self.ui.label_8.setEnabled(False) # 'https://' label + self.ui.cbProviders.setEnabled(False) + return + pinned = [] user_added = [] -- cgit v1.2.3 From 689c716b831047c8fe18945956e809b74e4250a3 Mon Sep 17 00:00:00 2001 From: Kali Kaneko Date: Tue, 1 Oct 2013 17:34:24 -0400 Subject: Disable EIP on/off button and action when login required. Also adds an explicit should_autostart flag in config. --- changes/bug_3927_fix-eip-turn-on-button-action | 2 + src/leap/bitmask/config/__init__.py | 0 src/leap/bitmask/config/leapsettings.py | 19 ++++ src/leap/bitmask/gui/eip_status.py | 26 ++++++ src/leap/bitmask/gui/login.py | 4 +- src/leap/bitmask/gui/mainwindow.py | 118 ++++++++++++++++++------- src/leap/bitmask/provider/__init__.py | 34 +++++++ src/leap/bitmask/services/eip/eipconfig.py | 40 ++++++++- 8 files changed, 206 insertions(+), 37 deletions(-) create mode 100644 changes/bug_3927_fix-eip-turn-on-button-action delete mode 100644 src/leap/bitmask/config/__init__.py diff --git a/changes/bug_3927_fix-eip-turn-on-button-action b/changes/bug_3927_fix-eip-turn-on-button-action new file mode 100644 index 00000000..53e9b133 --- /dev/null +++ b/changes/bug_3927_fix-eip-turn-on-button-action @@ -0,0 +1,2 @@ + o Avoids errors due to the EIP switch button and action being enabled + when we do not have a configured provider. Closes: #3927 diff --git a/src/leap/bitmask/config/__init__.py b/src/leap/bitmask/config/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index 338fa475..dc1af899 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -65,6 +65,7 @@ class LeapSettings(object): PROPERPROVIDER_KEY = "ProperProvider" REMEMBER_KEY = "RememberUserAndPass" DEFAULTPROVIDER_KEY = "DefaultProvider" + AUTOSTARTEIP_KEY = "AutoStartEIP" ALERTMISSING_KEY = "AlertMissingScripts" GATEWAY_KEY = "Gateway" @@ -285,6 +286,24 @@ class LeapSettings(object): else: self._settings.setValue(self.DEFAULTPROVIDER_KEY, provider) + def get_autostart_eip(self): + """ + Gets whether the app should autostart EIP. + + :rtype: bool + """ + return to_bool(self._settings.value(self.AUTOSTARTEIP_KEY, False)) + + def set_autostart_eip(self, autostart): + """ + Sets whether the app should autostart EIP. + + :param autostart: True if we should try to autostart EIP. + :type autostart: bool + """ + leap_assert_type(autostart, bool) + self._settings.setValue(self.AUTOSTARTEIP_KEY, autostart) + def get_alert_missing_scripts(self): """ Returns the setting for alerting of missing up/down scripts. diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py index 6b1235a6..946eaa4e 100644 --- a/src/leap/bitmask/gui/eip_status.py +++ b/src/leap/bitmask/gui/eip_status.py @@ -233,6 +233,30 @@ class EIPStatusWidget(QtGui.QWidget): """ self.set_startstop_enabled(False) + @QtCore.Slot() + def disable_eip_start(self): + """ + Triggered when a default provider_config has not been found. + Disables the start button and adds instructions to the user. + """ + logger.debug('Hiding EIP start button') + # you might be tempted to change this for a .setEnabled(False). + # it won't work. it's under the claws of the state machine. + # probably the best thing would be to make a transitional + # transition there, but that's more involved. + self.eip_button.hide() + msg = self.tr("You must login to use Encrypted Internet") + self.eip_label.setText(msg) + + @QtCore.Slot() + def enable_eip_start(self): + """ + Triggered after a successful login. + Enables the start button. + """ + logger.debug('Showing EIP start button') + self.eip_button.show() + # XXX disable (later) -------------------------- def set_eip_status(self, status, error=False): """ @@ -261,6 +285,8 @@ class EIPStatusWidget(QtGui.QWidget): :param value: True for enabled, False otherwise :type value: bool """ + # TODO use disable_eip_start instead + # this should be handled by the state machine leap_assert_type(value, bool) self.ui.btnEipStartStop.setEnabled(value) self._action_eip_startstop.setEnabled(value) diff --git a/src/leap/bitmask/gui/login.py b/src/leap/bitmask/gui/login.py index c635081c..582f26be 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -14,7 +14,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - """ Login widget implementation """ @@ -36,9 +35,9 @@ class LoginWidget(QtGui.QWidget): Login widget that emits signals to display the wizard or to perform login. """ - # Emitted when the login button is clicked login = QtCore.Signal() + logged_in_signal = QtCore.Signal() cancel_login = QtCore.Signal() logout = QtCore.Signal() @@ -320,6 +319,7 @@ class LoginWidget(QtGui.QWidget): self.ui.lblUser.setText("%s@%s" % (self.get_user(), self.get_selected_provider())) self.set_login_status("") + self.logged_in_signal.emit() def logged_out(self): """ diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 58ed3eb3..7129b670 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -38,9 +38,10 @@ from leap.bitmask.gui.eip_status import EIPStatusWidget from leap.bitmask.gui.mail_status import MailStatusWidget from leap.bitmask.gui.wizard import Wizard +from leap.bitmask import provider from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper -from leap.bitmask.services.eip.eipconfig import EIPConfig +from leap.bitmask.services.eip import eipconfig # XXX: Soledad might not work out of the box in Windows, issue #2932 from leap.bitmask.services.soledad.soledadbootstrapper import \ SoledadBootstrapper @@ -100,6 +101,7 @@ class MainWindow(QtGui.QMainWindow): MX_SERVICE = "mx" # Signals + eip_needs_login = QtCore.Signal([]) new_updates = QtCore.Signal(object) raise_window = QtCore.Signal([]) soledad_ready = QtCore.Signal([]) @@ -162,6 +164,10 @@ class MainWindow(QtGui.QMainWindow): self._eip_status = EIPStatusWidget(self) self.ui.eipLayout.addWidget(self._eip_status) + self._login_widget.logged_in_signal.connect( + self._eip_status.enable_eip_start) + self._login_widget.logged_in_signal.connect( + self._enable_eip_start_action) self._mail_status = MailStatusWidget(self) self.ui.mailLayout.addWidget(self._mail_status) @@ -174,13 +180,17 @@ class MainWindow(QtGui.QMainWindow): self._stop_eip) self._eip_status.eip_connection_connected.connect( self._on_eip_connected) + self.eip_needs_login.connect( + self._eip_status.disable_eip_start) + self.eip_needs_login.connect( + self._disable_eip_start_action) # This is loaded only once, there's a bug when doing that more # than once self._provider_config = ProviderConfig() # Used for automatic start of EIP self._provisional_provider_config = ProviderConfig() - self._eip_config = EIPConfig() + self._eip_config = eipconfig.EIPConfig() self._already_started_eip = False @@ -309,27 +319,29 @@ class MainWindow(QtGui.QMainWindow): self._smtp_config = SMTPConfig() - if self._first_run(): - self._wizard_firstrun = True - self._wizard = Wizard(bypass_checks=bypass_checks) - # Give this window time to finish init and then show the wizard - QtCore.QTimer.singleShot(1, self._launch_wizard) - self._wizard.accepted.connect(self._finish_init) - self._wizard.rejected.connect(self._rejected_wizard) - else: - self._finish_init() - # Eip machine is a public attribute where the state machine for # the eip connection will be available to the different components. # Remember that this will not live in the +1600LOC mainwindow for # all the eternity, so at some point we will be moving this to # the EIPConductor or some other clever component that we will # instantiate from here. - self.eip_machine = None + self.eip_machine = None # start event machines self.start_eip_machine() + if self._first_run(): + self._wizard_firstrun = True + self._wizard = Wizard(bypass_checks=bypass_checks) + # Give this window time to finish init and then show the wizard + QtCore.QTimer.singleShot(1, self._launch_wizard) + self._wizard.accepted.connect(self._finish_init) + self._wizard.rejected.connect(self._rejected_wizard) + else: + # during finish_init, we disable the eip start button + # so this has to be done after eip_machine is started + self._finish_init() + def _rejected_wizard(self): """ SLOT @@ -582,21 +594,29 @@ class MainWindow(QtGui.QMainWindow): """ Tries to autostart EIP """ - default_provider = self._settings.get_defaultprovider() + settings = self._settings + + should_autostart = settings.get_autostart_eip() + if not should_autostart: + logger.debug('Will not autostart EIP since it is setup ' + 'to not to do it') + self.eip_needs_login.emit() + return + + default_provider = settings.get_defaultprovider() if default_provider is None: logger.info("Cannot autostart Encrypted Internet because there is " "no default provider configured") + self.eip_needs_login.emit() return - self._enabled_services = self._settings.get_enabled_services( + self._enabled_services = settings.get_enabled_services( default_provider) - if self._provisional_provider_config.load( - os.path.join("leap", - "providers", - default_provider, - "provider.json")): + loaded = self._provisional_provider_config.load( + provider.get_provider_path(default_provider)) + if loaded: # XXX I think we should not try to re-download config every time, # it adds some delay. # Maybe if it's the first run in a session, @@ -604,6 +624,7 @@ class MainWindow(QtGui.QMainWindow): self._download_eip_config() else: # XXX: Display a proper message to the user + self.eip_needs_login.emit() logger.error("Unable to load %s config, cannot autostart." % (default_provider,)) @@ -1165,6 +1186,21 @@ class MainWindow(QtGui.QMainWindow): label=label) self.eip_machine = eip_machine self.eip_machine.start() + logger.debug('eip machine started') + + @QtCore.Slot() + def _disable_eip_start_action(self): + """ + Disables the EIP start action in the systray menu. + """ + self._action_eip_startstop.setEnabled(False) + + @QtCore.Slot() + def _enable_eip_start_action(self): + """ + Enables the EIP start action in the systray menu. + """ + self._action_eip_startstop.setEnabled(True) @QtCore.Slot() def _on_eip_connected(self): @@ -1197,6 +1233,27 @@ class MainWindow(QtGui.QMainWindow): self._eip_status.eip_pre_up() self.user_stopped_eip = False + # until we set an option in the preferences window, + # we'll assume that by default we try to autostart. + # If we switch it off manually, it won't try the next + # time. + self._settings.set_autostart_eip(True) + + loaded = eipconfig.load_eipconfig_if_needed( + provider_config, self._eip_config, provider) + + if not loaded: + self._eip_status.set_eip_status( + self.tr("Could not load Encrypted Internet " + "Configuration."), + error=True) + # signal connection aborted to state machine + qtsigs = self._eip_connection.qtsigs + qtsigs.connection_aborted_signal.emit() + logger.error("Tried to start EIP but cannot find any " + "available provider!") + return + try: # XXX move this to EIPConductor host, port = get_openvpn_management() @@ -1263,7 +1320,7 @@ class MainWindow(QtGui.QMainWindow): self._already_started_eip = True @QtCore.Slot() - def _stop_eip(self, abnormal=False): + def _stop_eip(self): """ SLOT TRIGGERS: @@ -1277,18 +1334,15 @@ class MainWindow(QtGui.QMainWindow): :param abnormal: whether this was an abnormal termination. :type abnormal: bool """ - if abnormal: - logger.warning("Abnormal EIP termination.") - self.user_stopped_eip = True self._vpn.terminate() self._set_eipstatus_off(False) - self._already_started_eip = False - # XXX do via signal - self._settings.set_defaultprovider(None) + logger.debug('Setting autostart to: False') + self._settings.set_autostart_eip(False) + if self._logged_user: self._eip_status.set_provider( "%s@%s" % (self._logged_user, @@ -1351,13 +1405,9 @@ class MainWindow(QtGui.QMainWindow): provider_config = self._get_best_provider_config() domain = provider_config.get_domain() - loaded = self._eip_config.loaded() - if not loaded: - eip_config_path = os.path.join("leap", "providers", - domain, "eip-service.json") - api_version = provider_config.get_api_version() - self._eip_config.set_api_version(api_version) - loaded = self._eip_config.load(eip_config_path) + # XXX move check to _start_eip ? + loaded = eipconfig.load_eipconfig_if_needed( + provider_config, self._eip_config, domain) if loaded: # DO START EIP Connection! diff --git a/src/leap/bitmask/provider/__init__.py b/src/leap/bitmask/provider/__init__.py index e69de29b..53587d65 100644 --- a/src/leap/bitmask/provider/__init__.py +++ b/src/leap/bitmask/provider/__init__.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# __init.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 . +""" +Module initialization for leap.bitmask.provider +""" +import os +from leap.common.check import leap_assert + + +def get_provider_path(domain): + """ + Returns relative path for provider config. + + :param domain: the domain to which this providerconfig belongs to. + :type domain: str + :returns: the path + :rtype: str + """ + leap_assert(domain is not None, "get_provider_path: We need a domain") + return os.path.join("leap", "providers", domain, "provider.json") diff --git a/src/leap/bitmask/services/eip/eipconfig.py b/src/leap/bitmask/services/eip/eipconfig.py index 7d8995b4..16ed4cc0 100644 --- a/src/leap/bitmask/services/eip/eipconfig.py +++ b/src/leap/bitmask/services/eip/eipconfig.py @@ -33,6 +33,45 @@ from leap.common.check import leap_assert, leap_assert_type logger = logging.getLogger(__name__) +def get_eipconfig_path(domain): + """ + Returns relative path for EIP config. + + :param domain: the domain to which this eipconfig belongs to. + :type domain: str + :returns: the path + :rtype: str + """ + leap_assert(domain is not None, "get_eipconfig_path: We need a domain") + return os.path.join("leap", "providers", domain, "eip-service.json") + + +def load_eipconfig_if_needed(provider_config, eip_config, domain): + """ + Utility function to prime a eip_config object from a loaded + provider_config and the chosen provider domain. + + :param provider_config: a loaded instance of ProviderConfig + :type provider_config: ProviderConfig + + :param eip_config: the eipconfig object to be primed. + :type eip_config: EIPConfig + + :param domain: the chosen provider domain + :type domain: str + + :returns: Whether the eip_config object has been succesfully loaded + :rtype: bool + """ + loaded = eip_config.loaded() + if not loaded: + eip_config_path = get_eipconfig_path(domain) + api_version = provider_config.get_api_version() + eip_config.set_api_version(api_version) + loaded = eip_config.load(eip_config_path) + return loaded + + class VPNGatewaySelector(object): """ VPN Gateway selector. @@ -59,7 +98,6 @@ class VPNGatewaySelector(object): tz_offset = self.equivalent_timezones[tz_offset] self._local_offset = tz_offset - self._eipconfig = eipconfig def get_gateways_list(self): -- cgit v1.2.3 From 5e418935bdc5c64bc1cef8d5f440dc79cc6e2892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 3 Oct 2013 13:45:11 -0300 Subject: Update provider_config in SRPAuth initialization --- changes/better_support_login_multiple_providers | 2 ++ src/leap/bitmask/crypto/srpauth.py | 7 +++++++ 2 files changed, 9 insertions(+) create mode 100644 changes/better_support_login_multiple_providers diff --git a/changes/better_support_login_multiple_providers b/changes/better_support_login_multiple_providers new file mode 100644 index 00000000..c31d5931 --- /dev/null +++ b/changes/better_support_login_multiple_providers @@ -0,0 +1,2 @@ + o Fixes a bug where you cannot login to a different provider once + you logged in to another one. \ No newline at end of file diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 90d9ea0a..42262610 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -603,6 +603,13 @@ class SRPAuth(QtCore.QObject): # Store instance reference as the only member in the handle self.__dict__['_SRPAuth__instance'] = SRPAuth.__instance + # Generally, we initialize this with a provider_config once, + # and after that initialize it without one and use the one + # that was assigned before. But we need to update it if we + # want to be able to logout and login into another provider. + if provider_config is not None: + SRPAuth.__instance._provider_config = provider_config + def authenticate(self, username, password): """ Executes the whole authentication process for a user -- cgit v1.2.3 From b6d7ffdb354ad4727f6a4dd158d439a2e768d68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 3 Oct 2013 14:32:47 -0300 Subject: Reset the session on every login attempt --- changes/better_support_login_multiple_providers | 3 ++- src/leap/bitmask/crypto/srpauth.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/changes/better_support_login_multiple_providers b/changes/better_support_login_multiple_providers index c31d5931..149471d9 100644 --- a/changes/better_support_login_multiple_providers +++ b/changes/better_support_login_multiple_providers @@ -1,2 +1,3 @@ o Fixes a bug where you cannot login to a different provider once - you logged in to another one. \ No newline at end of file + you logged in to another one. Fixes #3695. + o Also resets the session for every login attempt. Related to #3695. \ No newline at end of file diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 42262610..cbff4b49 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -508,6 +508,8 @@ class SRPAuth(QtCore.QObject): self._username = username self._password = password + self._session = self._fetcher.session() + d = threads.deferToThread(self._authentication_preprocessing, username=username, password=password) -- cgit v1.2.3 From e05d074d5458bc3cce5519227c9971d4ffa09280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 3 Oct 2013 15:36:05 -0300 Subject: Start Soledad only if Mail is enabled for the current provider Also update enabled_services in mainwindow right after login to have an updated list of services to launch from that point on. --- changes/properly_enable_services | 1 + src/leap/bitmask/gui/mail_status.py | 11 ++++++++++- src/leap/bitmask/gui/mainwindow.py | 15 ++++++++++----- 3 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 changes/properly_enable_services diff --git a/changes/properly_enable_services b/changes/properly_enable_services new file mode 100644 index 00000000..1b08fa3c --- /dev/null +++ b/changes/properly_enable_services @@ -0,0 +1 @@ + o Do not start Soledad if Mail is not enabled. Fixes #3989. \ No newline at end of file diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 770d991f..ab9052d7 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -199,7 +199,8 @@ class MailStatusWidget(QtGui.QWidget): :param status: the status text to display :type status: unicode - :param ready: 2 or >2 if mx is ready, 0 if stopped, 1 if it's starting. + :param ready: 2 or >2 if mx is ready, 0 if stopped, 1 if it's + starting, < 0 if disabled. :type ready: int """ self.ui.lblMailStatus.setText(status) @@ -219,6 +220,8 @@ class MailStatusWidget(QtGui.QWidget): icon = self.CONNECTED_ICON self._mx_status = self.tr('ON') tray_status = self.tr('Mail is ON') + elif ready < 0: + tray_status = self.tr("Mail is disabled") self.ui.lblMailStatusIcon.setPixmap(icon) self._action_mail_status.setText(tray_status) @@ -392,6 +395,12 @@ class MailStatusWidget(QtGui.QWidget): self._set_mail_status(self.tr("About to start, please wait..."), ready=1) + def set_disabled(self): + """ + Displays the correct UI for disabled mail. + """ + self._set_mail_status(self.tr("Disabled"), -1) + def stopped_mail(self): """ Displayes the correct UI for the stopped state. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 7129b670..dd4a341e 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -950,17 +950,22 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.logged_in() + self._enabled_services = self._settings.get_enabled_services( + self._provider_config.get_domain()) + # TODO separate UI from logic. # TODO soledad should check if we want to run only over EIP. if self._provider_config.provides_mx() and \ self._enabled_services.count(self.MX_SERVICE) > 0: self._mail_status.about_to_start() - self._soledad_bootstrapper.run_soledad_setup_checks( - self._provider_config, - self._login_widget.get_user(), - self._login_widget.get_password(), - download_if_needed=True) + self._soledad_bootstrapper.run_soledad_setup_checks( + self._provider_config, + self._login_widget.get_user(), + self._login_widget.get_password(), + download_if_needed=True) + else: + self._mail_status.set_disabled() self._download_eip_config() -- cgit v1.2.3 From 1f36c21ca532e39ef80b4a27f79bcad66ed335b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 3 Oct 2013 18:44:21 -0300 Subject: Allow window minization in OSX --- changes/properly_minimize_osx | 1 + src/leap/bitmask/gui/mainwindow.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 changes/properly_minimize_osx diff --git a/changes/properly_minimize_osx b/changes/properly_minimize_osx new file mode 100644 index 00000000..c3e5a3ef --- /dev/null +++ b/changes/properly_minimize_osx @@ -0,0 +1 @@ + o Allow window minization on OSX. Fixes #3932. \ No newline at end of file diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index dd4a341e..79ff68c4 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -766,9 +766,10 @@ class MainWindow(QtGui.QMainWindow): """ Reimplements the changeEvent method to minimize to tray """ - if QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \ - e.type() == QtCore.QEvent.WindowStateChange and \ - self.isMinimized(): + if not IS_MAC and \ + QtGui.QSystemTrayIcon.isSystemTrayAvailable() and \ + e.type() == QtCore.QEvent.WindowStateChange and \ + self.isMinimized(): self._toggle_visible() e.accept() return -- cgit v1.2.3 From effec111c39110f5057f49224f07bdd0730e7862 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 3 Oct 2013 17:19:50 -0300 Subject: Add preference to set autostart EIP option. --- changes/feature-3631_autostart-eip-optional | 2 + src/leap/bitmask/gui/eip_preferenceswindow.py | 43 ++++++++- src/leap/bitmask/gui/ui/eippreferences.ui | 125 ++++++++++++++++++++++---- 3 files changed, 151 insertions(+), 19 deletions(-) create mode 100644 changes/feature-3631_autostart-eip-optional diff --git a/changes/feature-3631_autostart-eip-optional b/changes/feature-3631_autostart-eip-optional new file mode 100644 index 00000000..83288548 --- /dev/null +++ b/changes/feature-3631_autostart-eip-optional @@ -0,0 +1,2 @@ + o Add preferences option to enable/disable the automatic start of EIP, and + selection of the EIP provider to auto start. Closes #3631. diff --git a/src/leap/bitmask/gui/eip_preferenceswindow.py b/src/leap/bitmask/gui/eip_preferenceswindow.py index 0e6e8dda..9f8c4ff4 100644 --- a/src/leap/bitmask/gui/eip_preferenceswindow.py +++ b/src/leap/bitmask/gui/eip_preferenceswindow.py @@ -50,6 +50,7 @@ class EIPPreferencesWindow(QtGui.QDialog): self.ui = Ui_EIPPreferences() self.ui.setupUi(self) self.ui.lblProvidersGatewayStatus.setVisible(False) + self.ui.lblAutoStartEIPStatus.setVisible(False) # Connections self.ui.cbProvidersGateway.currentIndexChanged[unicode].connect( @@ -58,8 +59,40 @@ class EIPPreferencesWindow(QtGui.QDialog): self.ui.cbGateways.currentIndexChanged[unicode].connect( lambda x: self.ui.lblProvidersGatewayStatus.setVisible(False)) + self.ui.cbProvidersEIP.currentIndexChanged[unicode].connect( + lambda x: self.ui.lblAutoStartEIPStatus.setVisible(False)) + + self.ui.cbAutoStartEIP.toggled.connect( + lambda x: self.ui.lblAutoStartEIPStatus.setVisible(False)) + + self.ui.pbSaveAutoStartEIP.clicked.connect(self._save_auto_start_eip) + self._add_configured_providers() + # Load auto start EIP settings + self.ui.cbAutoStartEIP.setChecked(self._settings.get_autostart_eip()) + default_provider = self._settings.get_defaultprovider() + idx = self.ui.cbProvidersEIP.findText(default_provider) + self.ui.cbProvidersEIP.setCurrentIndex(idx) + + def _save_auto_start_eip(self): + """ + SLOT + TRIGGER: + self.ui.cbAutoStartEIP.toggled + + Saves the automatic start of EIP user preference. + """ + default_provider = self.ui.cbProvidersEIP.currentText() + enabled = self.ui.cbAutoStartEIP.isChecked() + + self._settings.set_autostart_eip(enabled) + self._settings.set_defaultprovider(default_provider) + + self.ui.lblAutoStartEIPStatus.show() + logger.debug('Auto start EIP saved: {0} {1}.'.format( + default_provider, enabled)) + def _set_providers_gateway_status(self, status, success=False, error=False): """ @@ -87,8 +120,16 @@ class EIPPreferencesWindow(QtGui.QDialog): Add the client's configured providers to the providers combo boxes. """ self.ui.cbProvidersGateway.clear() - for provider in self._settings.get_configured_providers(): + self.ui.cbProvidersEIP.clear() + providers = self._settings.get_configured_providers() + if not providers: + self.ui.gbAutomaticEIP.setEnabled(False) + self.ui.gbGatewaySelector.setEnabled(False) + return + + for provider in providers: self.ui.cbProvidersGateway.addItem(provider) + self.ui.cbProvidersEIP.addItem(provider) def _save_selected_gateway(self, provider): """ diff --git a/src/leap/bitmask/gui/ui/eippreferences.ui b/src/leap/bitmask/gui/ui/eippreferences.ui index e9bc203d..9493d330 100644 --- a/src/leap/bitmask/gui/ui/eippreferences.ui +++ b/src/leap/bitmask/gui/ui/eippreferences.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 170 + 398 + 262 @@ -17,8 +17,8 @@ :/images/mask-icon.png:/images/mask-icon.png - - + + true @@ -30,6 +30,16 @@ false + + + + &Select provider: + + + cbProvidersGateway + + + @@ -39,13 +49,20 @@ - - + + - &Select provider: + Save this provider settings - - cbProvidersGateway + + + + + + < Providers Gateway Status > + + + Qt::AlignCenter @@ -65,30 +82,102 @@ - - + + + + + + + Automatic EIP start + + + + + + Qt::LeftToRight + + + + + + QFrame::NoFrame + + + QFrame::Plain + - Save this provider settings + <font color='green'><b>Automatic EIP start saved!</b></font> + + + Qt::AlignCenter - - + + - < Providers Gateway Status > + Save auto start setting - - Qt::AlignCenter + + + + + + Qt::LeftToRight + + + Enable Automatic start of EIP + + + true + + + + + <Select provider> + + + + + cbAutoStartEIP + lblAutoStartEIPStatus + pbSaveAutoStartEIP + cbProvidersEIP + + cbAutoStartEIP + cbProvidersEIP + pbSaveAutoStartEIP + cbProvidersGateway + cbGateways + pbSaveGateway + - + + + cbAutoStartEIP + toggled(bool) + cbProvidersEIP + setEnabled(bool) + + + 180 + 53 + + + 238 + 53 + + + + -- cgit v1.2.3 From 90a731e8a5f7e8b44b5ad76262ff01fb98f8e18c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 3 Oct 2013 20:41:31 -0300 Subject: Properly stop the smtp daemon --- changes/properly_stop_smtp | 1 + src/leap/bitmask/gui/mainwindow.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 changes/properly_stop_smtp diff --git a/changes/properly_stop_smtp b/changes/properly_stop_smtp new file mode 100644 index 00000000..e556ce29 --- /dev/null +++ b/changes/properly_stop_smtp @@ -0,0 +1 @@ + o Properly stop the smtp daemon. Fixes #3873. \ No newline at end of file diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 79ff68c4..84f09fd9 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -312,6 +312,7 @@ class MainWindow(QtGui.QMainWindow): self._soledad_ready = False self._keymanager = None self._smtp_service = None + self._smtp_port = None self._imap_service = None self._login_defer = None @@ -1104,7 +1105,7 @@ class MainWindow(QtGui.QMainWindow): # the specific default. from leap.mail.smtp import setup_smtp_relay - self._smtp_service = setup_smtp_relay( + self._smtp_service, self._smtp_port = setup_smtp_relay( port=2013, keymanager=self._keymanager, smtp_host=host, @@ -1124,6 +1125,7 @@ class MainWindow(QtGui.QMainWindow): # but in the imap case we are just stopping the fetcher. if self._smtp_service is not None: logger.debug('Stopping smtp service.') + self._smtp_port.stopListening() self._smtp_service.doStop() ################################################################### -- cgit v1.2.3 From 74f858aa290aa8e5da5a75cb80fe70e30559d39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 4 Oct 2013 12:01:25 -0300 Subject: Update release notes and dependencies --- changes/VERSION_COMPAT | 2 -- pkg/requirements.pip | 4 +-- relnotes.txt | 94 +++++++++++++++++++++++++------------------------- 3 files changed, 49 insertions(+), 51 deletions(-) diff --git a/changes/VERSION_COMPAT b/changes/VERSION_COMPAT index ac2d2e73..cc00ecf7 100644 --- a/changes/VERSION_COMPAT +++ b/changes/VERSION_COMPAT @@ -8,5 +8,3 @@ # # BEGIN DEPENDENCY LIST ------------------------- # leap.foo.bar>=x.y.z -leap.common >= 0.3.4 # because the ca_bundle -leap.keymanager >= 0.3.3 # because the gnupg dep diff --git a/pkg/requirements.pip b/pkg/requirements.pip index 269a4646..bb1deb1b 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -17,9 +17,9 @@ qt4reactor python-daemon # this should not be needed for Windows. keyring -leap.common>=0.3.2 +leap.common>=0.3.4 leap.soledad.client>=0.4.0 -leap.keymanager>=0.2.0 +leap.keymanager>=0.3.3 leap.mail>=0.3.2 # Remove this when u1db fixes its dependency on oauth diff --git a/relnotes.txt b/relnotes.txt index 30f26f9c..bea48e98 100644 --- a/relnotes.txt +++ b/relnotes.txt @@ -1,101 +1,101 @@ -ANNOUNCING Bitmask, the internet encryption toolkit, release 0.3.3 +ANNOUNCING Bitmask, the internet encryption toolkit, release 0.3.4 -The LEAP team is pleased to announce the immediate availability of -version 0.3.3 of Bitmask, the Internet Encryption Toolkit, codename -"the calm after the tempest". +The LEAP team is pleased to announce the immediate availability of +version 0.3.4 of Bitmask, the Internet Encryption Toolkit, codename +"look at my new makeup". https://downloads.leap.se/client/ -LEAP (LEAP Encryption Access Project) develops a plan to secure +LEAP (LEAP Encryption Access Project) develops a plan to secure everyday communication, breaking down into discrete services. -Bitmask is the desktop client to connect to the services offered by the -LEAP Platform. In the current phase the supported services are +Bitmask is the desktop client to connect to the services offered by +the LEAP Platform. In the current phase the supported services are Encrypted Internet Proxy and Encrypted Mail. -The Encrypted Internet Proxy provides circumvention, location -anonymization, and traffic encryption in a hassle-free, automatically +The Encrypted Internet Proxy provides circumvention, location +anonymization, and traffic encryption in a hassle-free, automatically self-configuring fashion. -Encrypted Mail offers automatic encryption and decryption for both -outgoing and incoming email, adding public key cryptography to your -mail without you ever having to worry about key distribution or +Encrypted Mail offers automatic encryption and decryption for both +outgoing and incoming email, adding public key cryptography to your +mail without you ever having to worry about key distribution or signature verification. -You can read about this and many other cool things in the user manual +You can read about this and many other cool things in the user manual and the developer notes, which can be found online at: http://bitmask.rtfd.org/ -WARNING: This is still part of a beta release of our software, a lot of -testing and auditing is still needed, so indeed use it, and feed us -back, fork it and contribute to its development, but by any means DO +WARNING: This is still part of a beta release of our software, a lot +of testing and auditing is still needed, so indeed use it, and feed us +back, fork it and contribute to its development, but by any means DO NOT trust your life to it (yet!). WHAT CAN THIS VERSION OF BITMASK DO FOR ME? -Bitmask 0.3.3 is mostly a bugfix release, with some minor improvements. -On this release, we have fixed many UI bugs, and have undergone internal -reorganizations in the code. This release also bumps the requirement for -Soledad, the encrypted data syncronization engine behind Bitmask, which -has experienced a backward-incompatible change. You can refer to the +Bitmask 0.3.4 is a bugfix release but it also has a shiny new UI +design, it's still a work in progress since it will change after +testing in this release, but it's progressing nicely. We have better +mail support, and we are using the new gnupg instead of python-gnupg +for its security and better flexibility. You can refer to the CHANGELOG for the meat. -As always, you can connect to the Encrypted Internet Proxy service -offered by a provider of your choice, and enjoy a encrypted internet +As always, you can connect to the Encrypted Internet Proxy service +offered by a provider of your choice, and enjoy a encrypted internet connection that the spying eyes can only track back to your provider. -The Encrypted Mail services will run local SMTP and IMAP proxies that, -once you configure the mail client of your choice, will automatically +The Encrypted Mail services will run local SMTP and IMAP proxies that, +once you configure the mail client of your choice, will automatically encrypt and decrypt your email using GPG encryption under the hood. -If it is the first time you run Bitmask, the first run wizard will help -you registering an user with your selected provider, downloading all -the config files needed to connect to the various LEAP services. +If it is the first time you run Bitmask, the first run wizard will +help you registering an user with your selected provider, downloading +all the config files needed to connect to the various LEAP services. LICENSE -You may use Bitmask under the GNU General Public License, version 3 or, -at your option, any later version. See the file "LICENSE" for the terms -of the GNU General Public License, version 3. +You may use Bitmask under the GNU General Public License, version 3 +or, at your option, any later version. See the file "LICENSE" for the +terms of the GNU General Public License, version 3. -In addition, as a special exception, the copyright holders give -permission to link the code of portions of this program with the -OpenSSL library under certain conditions as described in each -individual source file, and distribute linked combinations including +In addition, as a special exception, the copyright holders give +permission to link the code of portions of this program with the +OpenSSL library under certain conditions as described in each +individual source file, and distribute linked combinations including the two. INSTALLATION -We distribute the current version of Bitmask as standalone bundles for -GNU/Linux and OSX, but it is likely that you are able to run it under -other systems, specially if you are skillful and patience is one of your -virtues. +We distribute the current version of Bitmask as standalone bundles for +GNU/Linux and OSX, but it is likely that you are able to run it under +other systems, specially if you are skillful and patience is one of +your virtues. Have a look at "docs/user/install.rst". -Packages will be soon provided for debian and ubuntu, and the release of -windows bundles will be resumed shortly. +Packages will be soon provided for debian and ubuntu, and the release +of windows bundles will be resumed shortly. -We will love to hear if you are interested in help making packages +We will love to hear if you are interested in help making packages available for any other system. BUGS -You can send the bugs our way by pointing your telnet session to port -443 on https://leap.se/code. We will do our best to make them follow +You can send the bugs our way by pointing your telnet session to port +443 on https://leap.se/code. We will do our best to make them follow our intensive bug-reeducation program. HACKING You can find us in the #leap-dev channel on the freenode network. -If you are lucky enough, you can also spot us drinking mate, sleepless -in night trains, rooftops, rainforests, lonely islands and, always, +If you are lucky enough, you can also spot us drinking mate, sleepless +in night trains, rooftops, rainforests, lonely islands and, always, beyond any border. The LEAP team, -Sep 20, 2013 +Oct 4, 2013 Somewhere in the middle of the intertubes. EOF -- cgit v1.2.3 From 759d73ae0728f074e0fb0740269249d0e5066574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Fri, 4 Oct 2013 12:07:23 -0300 Subject: Fold in changes --- CHANGELOG | 33 ++++++++++++++++++++++ changes/better_support_login_multiple_providers | 3 -- changes/bug-3914_unhandled-error-on-version-flag | 1 - changes/bug_3926_connection-aborted | 2 -- changes/bug_3927_fix-eip-turn-on-button-action | 2 -- changes/bug_3985-soledad-boostrap-problem | 2 -- changes/choose_one_gnupg | 1 - changes/feature-2858_refactor-vpnlaunchers | 2 -- changes/feature-3631_autostart-eip-optional | 2 -- changes/feature-3981_cleanlooks-for-bundle-only | 2 -- .../feature-3995_add-drop-down-for-known-providers | 1 - changes/feature-3996_separate-providers-in-wizard | 1 - .../feature_3965_soledad-bootstrap-error-handling | 2 -- changes/feature_new_ui | 1 - changes/feature_provider-check-against-ca-bundle | 2 -- changes/feature_use_token | 1 - changes/login_error_undistinguishable | 2 -- changes/properly_enable_services | 1 - changes/properly_minimize_osx | 1 - changes/properly_stop_smtp | 1 - 20 files changed, 33 insertions(+), 30 deletions(-) delete mode 100644 changes/better_support_login_multiple_providers delete mode 100644 changes/bug-3914_unhandled-error-on-version-flag delete mode 100644 changes/bug_3926_connection-aborted delete mode 100644 changes/bug_3927_fix-eip-turn-on-button-action delete mode 100644 changes/bug_3985-soledad-boostrap-problem delete mode 100644 changes/choose_one_gnupg delete mode 100644 changes/feature-2858_refactor-vpnlaunchers delete mode 100644 changes/feature-3631_autostart-eip-optional delete mode 100644 changes/feature-3981_cleanlooks-for-bundle-only delete mode 100644 changes/feature-3995_add-drop-down-for-known-providers delete mode 100644 changes/feature-3996_separate-providers-in-wizard delete mode 100644 changes/feature_3965_soledad-bootstrap-error-handling delete mode 100644 changes/feature_new_ui delete mode 100644 changes/feature_provider-check-against-ca-bundle delete mode 100644 changes/feature_use_token delete mode 100644 changes/login_error_undistinguishable delete mode 100644 changes/properly_enable_services delete mode 100644 changes/properly_minimize_osx delete mode 100644 changes/properly_stop_smtp diff --git a/CHANGELOG b/CHANGELOG index ac1aceeb..4791f18b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,36 @@ +0.3.4 Oct 4 -- the "look at my new makeup" release: + o Fixes a bug where you cannot login to a different provider once + you logged in to another one. Fixes #3695. + o Resets the session for every login attempt. Related to #3695. + o Avoid error message if --version flag is used. Closes #3914. + o Fix a bug in which failing to authenticate properly left + connection in an unconsistent state. Closes: #3926 + o Avoids errors due to the EIP switch button and action being + enabled when we do not have a configured provider. Closes: #3927 + o Add more verbose error handling during key generation and syncing. + Helps diagnose: #3985; Addresses in part: #3965 + o Choose one gnupg binary path that is also not a symlink. Closes + #3999. + o Refactor vpn launchers, reuse code, improve implementations, + update documentation. Closes #2858. + o Add preferences option to enable/disable the automatic start of + EIP and selection of the EIP provider to auto start. Closes #3631. + o Force cleanlooks style for kde only if the app is running from + bundle. Closes #3981. + o Add a dropdown for known providers in the wizard. Closes #3995. + o Separate pinned providers from user configures ones. Closes #3996. + o Improve error handling during soledad bootstrap. Closes: #3965. + Affects: #3619, #3867, #3966 + o Implement new UI design. Closes #3973. + o Make the initial provider cert verifications against our modified + CA-bundle (includes ca-cert certificates, for now). Closes: #3850 + o Use token header for authenticated requests. Closes #3910. + o Do not distinguish between different possible authentication + errors. Fixes #3859. + o Do not start Soledad if Mail is not enabled. Fixes #3989. + o Allow window minization on OSX. Fixes #3932. + o Properly stop the smtp daemon. Fixes #3873. + 0.3.3 Sep 20 -- "the calm after the tempest" release: o Remove execution bits in text files in bundle. Closes #3617. o Use generic bad username/password message instead of specific ones when diff --git a/changes/better_support_login_multiple_providers b/changes/better_support_login_multiple_providers deleted file mode 100644 index 149471d9..00000000 --- a/changes/better_support_login_multiple_providers +++ /dev/null @@ -1,3 +0,0 @@ - o Fixes a bug where you cannot login to a different provider once - you logged in to another one. Fixes #3695. - o Also resets the session for every login attempt. Related to #3695. \ No newline at end of file diff --git a/changes/bug-3914_unhandled-error-on-version-flag b/changes/bug-3914_unhandled-error-on-version-flag deleted file mode 100644 index 41e023a0..00000000 --- a/changes/bug-3914_unhandled-error-on-version-flag +++ /dev/null @@ -1 +0,0 @@ - o Avoid error message if --version flag is used. Closes #3914. diff --git a/changes/bug_3926_connection-aborted b/changes/bug_3926_connection-aborted deleted file mode 100644 index 58e6fe11..00000000 --- a/changes/bug_3926_connection-aborted +++ /dev/null @@ -1,2 +0,0 @@ - o Fix a bug in which failing to authenticate properly left connection - in an unconsistent state. Closes: #3926 diff --git a/changes/bug_3927_fix-eip-turn-on-button-action b/changes/bug_3927_fix-eip-turn-on-button-action deleted file mode 100644 index 53e9b133..00000000 --- a/changes/bug_3927_fix-eip-turn-on-button-action +++ /dev/null @@ -1,2 +0,0 @@ - o Avoids errors due to the EIP switch button and action being enabled - when we do not have a configured provider. Closes: #3927 diff --git a/changes/bug_3985-soledad-boostrap-problem b/changes/bug_3985-soledad-boostrap-problem deleted file mode 100644 index 629481de..00000000 --- a/changes/bug_3985-soledad-boostrap-problem +++ /dev/null @@ -1,2 +0,0 @@ - o Add more verbose error handling during key generation and syncing. - Helps diagnose: #3985; Addresses in part: #3965 diff --git a/changes/choose_one_gnupg b/changes/choose_one_gnupg deleted file mode 100644 index d759616b..00000000 --- a/changes/choose_one_gnupg +++ /dev/null @@ -1 +0,0 @@ - o Choose one gnupg binary path that is also not a symlink. Closes #3999. \ No newline at end of file diff --git a/changes/feature-2858_refactor-vpnlaunchers b/changes/feature-2858_refactor-vpnlaunchers deleted file mode 100644 index 27106f7a..00000000 --- a/changes/feature-2858_refactor-vpnlaunchers +++ /dev/null @@ -1,2 +0,0 @@ - o Refactor vpn launchers, reuse code, improve implementations, update - documentation. Closes #2858. diff --git a/changes/feature-3631_autostart-eip-optional b/changes/feature-3631_autostart-eip-optional deleted file mode 100644 index 83288548..00000000 --- a/changes/feature-3631_autostart-eip-optional +++ /dev/null @@ -1,2 +0,0 @@ - o Add preferences option to enable/disable the automatic start of EIP, and - selection of the EIP provider to auto start. Closes #3631. diff --git a/changes/feature-3981_cleanlooks-for-bundle-only b/changes/feature-3981_cleanlooks-for-bundle-only deleted file mode 100644 index c762b8f3..00000000 --- a/changes/feature-3981_cleanlooks-for-bundle-only +++ /dev/null @@ -1,2 +0,0 @@ - o Force cleanlooks style for kde only if the app is running from bundle. - Closes #3981. diff --git a/changes/feature-3995_add-drop-down-for-known-providers b/changes/feature-3995_add-drop-down-for-known-providers deleted file mode 100644 index f05c3b58..00000000 --- a/changes/feature-3995_add-drop-down-for-known-providers +++ /dev/null @@ -1 +0,0 @@ - o Add a dropdown for known providers in the wizard. Closes #3995. diff --git a/changes/feature-3996_separate-providers-in-wizard b/changes/feature-3996_separate-providers-in-wizard deleted file mode 100644 index 2466d140..00000000 --- a/changes/feature-3996_separate-providers-in-wizard +++ /dev/null @@ -1 +0,0 @@ - o Separate pinned providers from user configures ones. Closes #3996. diff --git a/changes/feature_3965_soledad-bootstrap-error-handling b/changes/feature_3965_soledad-bootstrap-error-handling deleted file mode 100644 index d9f16378..00000000 --- a/changes/feature_3965_soledad-bootstrap-error-handling +++ /dev/null @@ -1,2 +0,0 @@ - o Improve error handling during soledad bootstrap. Closes: #3965. - Affects: #3619, #3867, #3966 diff --git a/changes/feature_new_ui b/changes/feature_new_ui deleted file mode 100644 index b84fd39e..00000000 --- a/changes/feature_new_ui +++ /dev/null @@ -1 +0,0 @@ - o Implement new UI design. Closes #3973. \ No newline at end of file diff --git a/changes/feature_provider-check-against-ca-bundle b/changes/feature_provider-check-against-ca-bundle deleted file mode 100644 index b3f9042f..00000000 --- a/changes/feature_provider-check-against-ca-bundle +++ /dev/null @@ -1,2 +0,0 @@ - o Make the initial provider cert verifications against our modified - CA-bundle (includes ca-cert certificates, for now). Closes: #3850 diff --git a/changes/feature_use_token b/changes/feature_use_token deleted file mode 100644 index b412cc2d..00000000 --- a/changes/feature_use_token +++ /dev/null @@ -1 +0,0 @@ - o Use token header for authenticated requests. Closes #3910. \ No newline at end of file diff --git a/changes/login_error_undistinguishable b/changes/login_error_undistinguishable deleted file mode 100644 index 5391f3fc..00000000 --- a/changes/login_error_undistinguishable +++ /dev/null @@ -1,2 +0,0 @@ - o Do not distinguish between different possible authentication - errors. Fixes #3859. \ No newline at end of file diff --git a/changes/properly_enable_services b/changes/properly_enable_services deleted file mode 100644 index 1b08fa3c..00000000 --- a/changes/properly_enable_services +++ /dev/null @@ -1 +0,0 @@ - o Do not start Soledad if Mail is not enabled. Fixes #3989. \ No newline at end of file diff --git a/changes/properly_minimize_osx b/changes/properly_minimize_osx deleted file mode 100644 index c3e5a3ef..00000000 --- a/changes/properly_minimize_osx +++ /dev/null @@ -1 +0,0 @@ - o Allow window minization on OSX. Fixes #3932. \ No newline at end of file diff --git a/changes/properly_stop_smtp b/changes/properly_stop_smtp deleted file mode 100644 index e556ce29..00000000 --- a/changes/properly_stop_smtp +++ /dev/null @@ -1 +0,0 @@ - o Properly stop the smtp daemon. Fixes #3873. \ No newline at end of file -- cgit v1.2.3