diff options
author | Tomás Touceda <chiiph@leap.se> | 2013-10-04 12:09:58 -0300 |
---|---|---|
committer | Tomás Touceda <chiiph@leap.se> | 2013-10-04 12:09:58 -0300 |
commit | 584866689560bd2ebea01ecc5e6ae5e79ce7fc81 (patch) | |
tree | 4fe300c3c105e17721f79eeece9bbd3745b43d78 | |
parent | 1f0f8efc4cb985c082b3b8fe7b3dc45aed047a47 (diff) | |
parent | 759d73ae0728f074e0fb0740269249d0e5066574 (diff) |
Merge branch 'release-0.3.4'0.3.4
96 files changed, 4818 insertions, 3437 deletions
@@ -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 @@ -27,6 +60,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 @@ -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/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. 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 Binary files differnew file mode 100644 index 00000000..328eb08d --- /dev/null +++ b/data/images/black/32/arrow-down.png diff --git a/data/images/black/32/arrow-up.png b/data/images/black/32/arrow-up.png Binary files differnew file mode 100644 index 00000000..5eea5108 --- /dev/null +++ b/data/images/black/32/arrow-up.png diff --git a/data/images/black/32/cloud.png b/data/images/black/32/cloud.png Binary files differnew file mode 100644 index 00000000..10cbd62d --- /dev/null +++ b/data/images/black/32/cloud.png diff --git a/data/images/black/32/contract.png b/data/images/black/32/contract.png Binary files differnew file mode 100644 index 00000000..9f0ee605 --- /dev/null +++ b/data/images/black/32/contract.png diff --git a/data/images/black/32/earth-square.png b/data/images/black/32/earth-square.png Binary files differnew file mode 100644 index 00000000..da7bf2cf --- /dev/null +++ b/data/images/black/32/earth-square.png diff --git a/data/images/black/32/earth.png b/data/images/black/32/earth.png Binary files differnew file mode 100644 index 00000000..64919edc --- /dev/null +++ b/data/images/black/32/earth.png diff --git a/data/images/black/32/email-square.png b/data/images/black/32/email-square.png Binary files differnew file mode 100644 index 00000000..499d7a8f --- /dev/null +++ b/data/images/black/32/email-square.png diff --git a/data/images/black/32/email.png b/data/images/black/32/email.png Binary files differnew file mode 100644 index 00000000..a6f40297 --- /dev/null +++ b/data/images/black/32/email.png diff --git a/data/images/black/32/expand.png b/data/images/black/32/expand.png Binary files differnew file mode 100644 index 00000000..c581503d --- /dev/null +++ b/data/images/black/32/expand.png diff --git a/data/images/black/32/gear.png b/data/images/black/32/gear.png Binary files differnew file mode 100644 index 00000000..93c8742d --- /dev/null +++ b/data/images/black/32/gear.png diff --git a/data/images/black/32/off.png b/data/images/black/32/off.png Binary files differnew file mode 100644 index 00000000..6ddde746 --- /dev/null +++ b/data/images/black/32/off.png diff --git a/data/images/black/32/on.png b/data/images/black/32/on.png Binary files differnew file mode 100644 index 00000000..bbd28bad --- /dev/null +++ b/data/images/black/32/on.png diff --git a/data/images/black/32/refresh.png b/data/images/black/32/refresh.png Binary files differnew file mode 100644 index 00000000..ad67f563 --- /dev/null +++ b/data/images/black/32/refresh.png diff --git a/data/images/black/32/user.png b/data/images/black/32/user.png Binary files differnew file mode 100644 index 00000000..7ea0f43a --- /dev/null +++ b/data/images/black/32/user.png diff --git a/data/images/black/32/wait.png b/data/images/black/32/wait.png Binary files differnew file mode 100644 index 00000000..a01ce923 --- /dev/null +++ b/data/images/black/32/wait.png diff --git a/data/images/white/32/arrow-down.png b/data/images/white/32/arrow-down.png Binary files differnew file mode 100644 index 00000000..02ccc540 --- /dev/null +++ b/data/images/white/32/arrow-down.png diff --git a/data/images/white/32/arrow-up.png b/data/images/white/32/arrow-up.png Binary files differnew file mode 100644 index 00000000..fbe1f816 --- /dev/null +++ b/data/images/white/32/arrow-up.png diff --git a/data/images/white/32/cloud.png b/data/images/white/32/cloud.png Binary files differnew file mode 100644 index 00000000..39d589ef --- /dev/null +++ b/data/images/white/32/cloud.png diff --git a/data/images/white/32/contract.png b/data/images/white/32/contract.png Binary files differnew file mode 100644 index 00000000..262ff3c0 --- /dev/null +++ b/data/images/white/32/contract.png diff --git a/data/images/white/32/earth-square.png b/data/images/white/32/earth-square.png Binary files differnew file mode 100644 index 00000000..da7bf2cf --- /dev/null +++ b/data/images/white/32/earth-square.png diff --git a/data/images/white/32/earth.png b/data/images/white/32/earth.png Binary files differnew file mode 100644 index 00000000..efca7ccb --- /dev/null +++ b/data/images/white/32/earth.png diff --git a/data/images/white/32/email-square.png b/data/images/white/32/email-square.png Binary files differnew file mode 100644 index 00000000..499d7a8f --- /dev/null +++ b/data/images/white/32/email-square.png diff --git a/data/images/white/32/email.png b/data/images/white/32/email.png Binary files differnew file mode 100644 index 00000000..abb36035 --- /dev/null +++ b/data/images/white/32/email.png diff --git a/data/images/white/32/expand.png b/data/images/white/32/expand.png Binary files differnew file mode 100644 index 00000000..0ec28dcc --- /dev/null +++ b/data/images/white/32/expand.png diff --git a/data/images/white/32/gear.png b/data/images/white/32/gear.png Binary files differnew file mode 100644 index 00000000..83f8d5ff --- /dev/null +++ b/data/images/white/32/gear.png diff --git a/data/images/white/32/off.png b/data/images/white/32/off.png Binary files differnew file mode 100644 index 00000000..22621594 --- /dev/null +++ b/data/images/white/32/off.png diff --git a/data/images/white/32/on.png b/data/images/white/32/on.png Binary files differnew file mode 100644 index 00000000..8946f763 --- /dev/null +++ b/data/images/white/32/on.png diff --git a/data/images/white/32/refresh.png b/data/images/white/32/refresh.png Binary files differnew file mode 100644 index 00000000..36a89da9 --- /dev/null +++ b/data/images/white/32/refresh.png diff --git a/data/images/white/32/user.png b/data/images/white/32/user.png Binary files differnew file mode 100644 index 00000000..ff8edd00 --- /dev/null +++ b/data/images/white/32/user.png diff --git a/data/images/white/32/wait.png b/data/images/white/32/wait.png Binary files differnew file mode 100644 index 00000000..8562d636 --- /dev/null +++ b/data/images/white/32/wait.png 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 @@ <RCC> <qresource prefix="/"> + <file>../images/white/32/off.png</file> + <file>../images/white/32/on.png</file> + <file>../images/white/32/wait.png</file> + <file>../images/black/32/arrow-down.png</file> + <file>../images/black/32/arrow-up.png</file> + <file>../images/black/32/cloud.png</file> + <file>../images/black/32/contract.png</file> + <file>../images/black/32/earth-square.png</file> + <file>../images/black/32/earth.png</file> + <file>../images/black/32/email-square.png</file> + <file>../images/black/32/email.png</file> + <file>../images/black/32/expand.png</file> + <file>../images/black/32/gear.png</file> + <file>../images/black/32/off.png</file> + <file>../images/black/32/on.png</file> + <file>../images/black/32/refresh.png</file> + <file>../images/black/32/user.png</file> + <file>../images/black/32/wait.png</file> <file>../images/mask-launcher.png</file> <file>../images/mask-icon.png</file> <file>../images/leap-gray-big.png</file> 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 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS><TS version="1.1"> <context> - <name>EIPBootstrapper</name> + <name>DarwinVPNLauncher</name> <message> - <location filename="../src/leap/services/eip/eipbootstrapper.py" line="151"/> - <source>The downloaded certificate is not a valid PEM file</source> + <location filename="../src/leap/bitmask/services/eip/vpnlaunchers.py" line="691"/> + <source>No gateway was found!</source> <translation type="unfinished"></translation> </message> </context> <context> + <name>EIPBootstrapper</name> +</context> +<context> <name>LinuxVPNLauncher</name> <message> - <location filename="../src/leap/services/eip/vpnlaunchers.py" line="380"/> + <location filename="../src/leap/bitmask/services/eip/vpnlaunchers.py" line="435"/> <source>No gateway was found!</source> <translation type="unfinished"></translation> </message> @@ -19,292 +22,337 @@ <context> <name>LoggerWindow</name> <message> - <location filename="../src/leap/gui/ui/loggerwindow.ui" line="14"/> + <location filename="../src/leap/bitmask/gui/ui/loggerwindow.ui" line="14"/> <source>Logs</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/loggerwindow.ui" line="29"/> + <location filename="../src/leap/bitmask/gui/ui/loggerwindow.ui" line="49"/> <source>Debug</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/loggerwindow.ui" line="49"/> + <location filename="../src/leap/bitmask/gui/ui/loggerwindow.ui" line="69"/> <source>Info</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/loggerwindow.ui" line="69"/> + <location filename="../src/leap/bitmask/gui/ui/loggerwindow.ui" line="89"/> <source>Warning</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/loggerwindow.ui" line="89"/> + <location filename="../src/leap/bitmask/gui/ui/loggerwindow.ui" line="109"/> <source>Error</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/loggerwindow.ui" line="109"/> + <location filename="../src/leap/bitmask/gui/ui/loggerwindow.ui" line="129"/> <source>Critical</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/loggerwindow.ui" line="129"/> + <location filename="../src/leap/bitmask/gui/ui/loggerwindow.ui" line="149"/> <source>Save to file</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/loggerwindow.py" line="113"/> + <location filename="../src/leap/bitmask/gui/loggerwindow.py" line="148"/> <source>Save As</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/loggerwindow.ui" line="24"/> + <source>Filter by:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/loggerwindow.ui" line="34"/> + <source>Case Insensitive</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>LoginWidget</name> <message> - <location filename="../src/leap/gui/ui/login.ui" line="14"/> + <location filename="../src/leap/bitmask/gui/ui/login.ui" line="14"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/login.ui" line="49"/> + <location filename="../src/leap/bitmask/gui/ui/login.ui" line="49"/> <source>Create a new account</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/login.ui" line="56"/> + <location filename="../src/leap/bitmask/gui/ui/login.ui" line="56"/> <source><b>Provider:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/login.ui" line="76"/> + <location filename="../src/leap/bitmask/gui/ui/login.ui" line="76"/> <source>Remember username and password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/login.ui" line="83"/> + <location filename="../src/leap/bitmask/gui/ui/login.ui" line="83"/> <source><b>Username:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/login.ui" line="93"/> + <location filename="../src/leap/bitmask/gui/ui/login.ui" line="93"/> <source><b>Password:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/login.ui" line="103"/> + <location filename="../src/leap/bitmask/gui/login.py" line="217"/> <source>Log In</source> <translation type="unfinished"></translation> </message> -</context> -<context> - <name>MainWindow</name> <message> - <location filename="../src/leap/gui/ui/mainwindow.ui" line="14"/> - <source>LEAP</source> + <location filename="../src/leap/bitmask/gui/login.py" line="107"/> + <source>Other...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/login.py" line="213"/> + <source>Cancel</source> <translation type="unfinished"></translation> </message> +</context> +<context> + <name>MainWindow</name> <message> - <location filename="../src/leap/gui/ui/mainwindow.ui" line="62"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="62"/> <source>There are new updates available, please restart.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/mainwindow.ui" line="78"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="78"/> <source>More...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/mainwindow.ui" line="233"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="243"/> <source>Show Log</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/mainwindow.ui" line="261"/> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="271"/> <source>&Session</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="521"/> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="617"/> <source>Help</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/mainwindow.ui" line="281"/> - <source>&Sign out</source> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="296"/> + <source>&Quit</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/mainwindow.ui" line="286"/> - <source>&Quit</source> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="306"/> + <source>&Help</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/mainwindow.ui" line="291"/> - <source>About &LEAP</source> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="311"/> + <source>&Wizard</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/mainwindow.ui" line="296"/> - <source>&Help</source> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="316"/> + <source>Show &logs</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/mainwindow.ui" line="301"/> - <source>&Wizard</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="667"/> + <source>Hide Main Window</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/mainwindow.ui" line="306"/> - <source>Show &logs</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="493"/> + <source> The following components will be updated: +%s</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="230"/> - <source>No default provider</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="496"/> + <source>Updates available</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="233"/> - <source>Encrypted internet is OFF</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="262"/> + <source>Preferences</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="1051"/> - <source>Turn ON</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="666"/> + <source>Show Main Window</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="553"/> - <source>Hide Main Window</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="856"/> + <source>Please select a valid provider</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="391"/> - <source>The LEAPClient app is ready to update, please restart the application.</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="861"/> + <source>Please provide a valid username</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="400"/> - <source> The following components will be updated: -%s</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="866"/> + <source>Please provide a valid Password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="403"/> - <source>Updates available</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="869"/> + <source>Logging in...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="519"/> - <source>Preferences</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1265"/> + <source>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.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="552"/> - <source>Show Main Window</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1277"/> + <source>We could not find <b>pkexec</b> in your system.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="606"/> - <source>About LEAP - %s</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1282"/> + <source>We could not find openvpn binary.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="614"/> - <source>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></source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1342"/> + <source>OFF</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="716"/> - <source>Could not load provider configuration.</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1359"/> + <source>Starting...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="746"/> - <source>Please select a valid provider</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1367"/> + <source>Not supported</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="751"/> - <source>Please provide a valid username</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1370"/> + <source>Disabled</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="756"/> - <source>Please provide a valid Password</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1407"/> + <source>Could not load Encrypted Internet Configuration.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="759"/> - <source>Logging in...</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1540"/> + <source>Encrypted Internet could not be launched because you did not authenticate properly.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="995"/> - <source>Turn OFF</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1546"/> + <source>Encrypted Internet finished in an unexpected manner!</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="1009"/> - <source>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.</source> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="14"/> + <source>Bitmask</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="1015"/> - <source>We could not find <b>pkexec</b> in your system.</source> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="291"/> + <source>Log &out</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="1020"/> - <source>We could not find openvpn binary.</source> + <location filename="../src/leap/bitmask/gui/ui/mainwindow.ui" line="301"/> + <source>About &Bitmask</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="1048"/> - <source>OFF</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="255"/> + <source>Mail is OFF</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="1100"/> - <source>Starting...</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="484"/> + <source>The Bitmask app is ready to update, please restart the application.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="1108"/> - <source>Not supported</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="624"/> + <source>Encrypted Internet is OFF</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="1111"/> - <source>Disabled</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="729"/> + <source>About Bitmask - %s</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="1139"/> - <source>Could not load Encrypted Internet Configuration.</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="743"/> + <source>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></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="1239"/> - <source>Encrypted Internet could not be launched because you did not authenticate properly.</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="830"/> + <source>Unable to login: Problem with provider</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/mainwindow.py" line="1245"/> - <source>Encrypted Internet finished in an unexpected manner!</source> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="907"/> + <source>Log in cancelled by the user.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1272"/> + <source>Encrypted Internet cannot be started because the tuntap extension is not installed properly in your system.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1288"/> + <source>Another openvpn instance is already running, and could not be stopped.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1295"/> + <source>Another openvpn instance is already running, and could not be stopped because it was not launched by Bitmask. Please stop it and try again.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1385"/> + <source>There was a problem with the provider</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1469"/> + <source>Something went wrong with the logout.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/mainwindow.py" line="1504"/> + <source>Unable to connect: Problem with provider</source> <translation type="unfinished"></translation> </message> </context> <context> <name>ProviderBootstrapper</name> <message> - <location filename="../src/leap/services/eip/providerbootstrapper.py" line="113"/> + <location filename="../src/leap/bitmask/services/eip/providerbootstrapper.py" line="121"/> <source>Provider certificate could not be verified</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/services/eip/providerbootstrapper.py" line="116"/> + <location filename="../src/leap/bitmask/services/eip/providerbootstrapper.py" line="124"/> <source>Provider does not support HTTPS</source> <translation type="unfinished"></translation> </message> @@ -312,7 +360,7 @@ <context> <name>SRPAuth</name> <message> - <location filename="../src/leap/crypto/srpauth.py" line="598"/> + <location filename="../src/leap/bitmask/crypto/srpauth.py" line="690"/> <source>Succeeded</source> <translation type="unfinished"></translation> </message> @@ -320,516 +368,604 @@ <context> <name>StatusPanel</name> <message> - <location filename="../src/leap/gui/ui/statuspanel.ui" line="14"/> + <location filename="../src/leap/bitmask/gui/ui/statuspanel.ui" line="14"/> <source>Form</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/statuspanel.ui" line="23"/> + <location filename="../src/leap/bitmask/gui/ui/statuspanel.ui" line="23"/> <source>user@domain.org</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/statuspanel.ui" line="44"/> + <location filename="../src/leap/bitmask/gui/ui/statuspanel.ui" line="326"/> <source>Encrypted Internet: </source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/statuspanel.ui" line="54"/> + <location filename="../src/leap/bitmask/gui/ui/statuspanel.ui" line="336"/> <source>Off</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/statuspanel.ui" line="83"/> + <location filename="../src/leap/bitmask/gui/ui/statuspanel.ui" line="365"/> <source>Turn On</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/statuspanel.ui" line="178"/> - <source>0.0 Kb</source> + <location filename="../src/leap/bitmask/gui/ui/statuspanel.ui" line="311"/> + <source>...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/statuspanel.ui" line="209"/> - <source>...</source> + <location filename="../src/leap/bitmask/gui/ui/statuspanel.ui" line="44"/> + <source>0 Unread Emails</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/statuspanel.ui" line="54"/> + <source>Disabled</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/statuspanel.ui" line="67"/> + <source>Encrypted Mail:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/statuspanel.ui" line="198"/> + <source>0.0 KB/s</source> <translation type="unfinished"></translation> </message> </context> <context> <name>StatusPanelWidget</name> <message> - <location filename="../src/leap/gui/statuspanel.py" line="180"/> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="389"/> <source>Turn OFF</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/statuspanel.py" line="190"/> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="403"/> <source>Turn ON</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/statuspanel.py" line="232"/> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="699"/> <source>ON</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/statuspanel.py" line="236"/> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="461"/> <source>Authenticating...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/statuspanel.py" line="238"/> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="463"/> <source>Retrieving configuration...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/statuspanel.py" line="240"/> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="465"/> <source>Waiting to start...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/statuspanel.py" line="242"/> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="467"/> <source>Assigning IP</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/statuspanel.py" line="250"/> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="478"/> <source>Unable to start VPN, it's already running.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/statuspanel.py" line="264"/> - <source>Encryption is OFF</source> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="270"/> + <source>All services are OFF</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/statuspanel.py" line="269"/> - <source>Turning ON</source> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="276"/> + <source>Encrypted Internet is {0}</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/statuspanel.py" line="271"/> - <source>Encryption is ON</source> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="278"/> + <source>Mail is {0}</source> <translation type="unfinished"></translation> </message> -</context> -<context> - <name>Wizard</name> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="14"/> - <source>LEAP First run</source> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="469"/> + <source>Reconnecting...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="31"/> - <source>Welcome</source> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="501"/> + <source>Encrypted Internet is OFF</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="506"/> + <source>Encrypted Internet is STARTING</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="508"/> + <source>Encrypted Internet is ON</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="534"/> + <source>OFF</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="536"/> + <source>Mail is OFF</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="542"/> + <source>Mail is ON</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="34"/> - <source>This is the LEAP Client first run wizard</source> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="604"/> + <source>Starting...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="571"/> + <source>Soledad has started...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="573"/> + <source>Soledad is starting, please wait...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="610"/> + <source>Looking for key for this user</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="614"/> + <source>Found key! Starting mail...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="616"/> + <source>Generating new key, please wait...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="618"/> + <source>Finished generating key!</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="620"/> + <source>Starting mail...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="650"/> + <source>SMTP has started...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="656"/> + <source>SMTP failed to start, check the logs.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="693"/> + <source>Failed</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="687"/> + <source>IMAP has started...</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="693"/> + <source>IMAP failed to start, check the logs.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/statuspanel.py" line="697"/> + <source>%s Unread Emails</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>WindowsVPNLauncher</name> + <message> + <location filename="../src/leap/bitmask/services/eip/vpnlaunchers.py" line="871"/> + <source>No gateway was found!</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>Wizard</name> + <message> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="43"/> + <source>Welcome</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="43"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="55"/> <source>Log In with my credentials</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="50"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="62"/> <source><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></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="63"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="75"/> <source>Sign up for a new account</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="100"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="112"/> <source>Provider selection</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="103"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="115"/> <source>Please enter the domain of the provider you want to use for your connection</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="131"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="143"/> <source>Check</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="151"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="163"/> <source>https://</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="161"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="173"/> <source>Checking for a valid provider</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="167"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="179"/> <source>Getting provider information</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="174"/> - <source>Can we stablish a secure connection?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="247"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="259"/> <source>Can we reach this provider?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="278"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="290"/> <source>Provider Information</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="281"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="293"/> <source>Description of services offered by this provider</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="290"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="302"/> <source>Name</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="322"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="334"/> <source>Desc</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="332"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="344"/> <source><b>Services offered:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="342"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="354"/> <source>services</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="362"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="374"/> <source><b>Enrollment policy:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="372"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="384"/> <source>policy</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="392"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="404"/> <source><b>URL:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="402"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="414"/> <source>URL</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="409"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="421"/> <source><b>Description:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="420"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="432"/> <source>Provider setup</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="423"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="435"/> <source>Gathering configuration options for this provider</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="445"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="457"/> <source>We are downloading some bits that we need to establish a secure connection with the provider for the first time.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="468"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="480"/> <source>Setting up provider</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="518"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="530"/> <source>Getting info from the Certificate Authority</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="525"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="537"/> <source>Do we trust this Certificate Authority?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="532"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="544"/> <source>Establishing a trust relationship with this provider</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="591"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="603"/> <source>Register new user</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="594"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="606"/> <source>Register a new user with provider</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="609"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="621"/> <source><b>Password:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="628"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="640"/> <source><b>Re-enter password:</b></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="638"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="650"/> <source>Register</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="671"/> - <source><b>User:</b></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="684"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="696"/> <source>Remember my username and password</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="708"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="720"/> <source>Service selection</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="711"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="723"/> <source>Please select the services you would like to have</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="733"/> - <source>Congratulations!</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/gui/ui/wizard.ui" line="736"/> - <source>You have successfully configured the LEAP Client.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/gui/wizard.py" line="86"/> - <source>Encrypted Internet</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/gui/wizard.py" line="88"/> - <source>Encrypted Mail</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/gui/wizard.py" line="92"/> - <source>(will need admin password to start)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/gui/wizard.py" line="155"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="141"/> <source>&Next ></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/wizard.py" line="157"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="143"/> <source>Connect</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/wizard.py" line="214"/> - <source>Passwords don't match</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/gui/wizard.py" line="217"/> - <source>Password too short</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/gui/wizard.py" line="220"/> - <source>Password too easy</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/gui/wizard.py" line="223"/> - <source>Password equal to username</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/gui/wizard.py" line="252"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="204"/> <source>Starting registration...</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/wizard.py" line="281"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="235"/> <source>User %s successfully registered.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/wizard.py" line="301"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="255"/> <source>Unknown error</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/wizard.py" line="407"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="361"/> <source><font color='red'><b>Non-existent provider</b></font></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/wizard.py" line="425"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="379"/> <source><font color='red'><b>%s</b></font></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/wizard.py" line="453"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="407"/> <source>Unable to load provider configuration</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/wizard.py" line="459"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="413"/> <source><font color='red'><b>Not a valid provider</b></font></source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/wizard.py" line="525"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="479"/> <source>Services by %s</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/wizard.py" line="544"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="499"/> <source>Something went wrong while trying to load service %s</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/wizard.py" line="561"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="516"/> <source>Gathering configuration options for %s</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/wizard.py" line="570"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="525"/> <source>Description of services offered by %s</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/gui/wizard.py" line="591"/> + <location filename="../src/leap/bitmask/gui/wizard.py" line="546"/> <source>Register a new user with %s</source> <translation type="unfinished"></translation> </message> -</context> -<context> - <name>__impl</name> <message> - <location filename="../src/leap/crypto/srpauth.py" line="259"/> - <source>Unknown user</source> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="26"/> + <source>Bitmask first run</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="46"/> + <source>This is the Bitmask first run wizard</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="186"/> + <source>Can we establish a secure connection?</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/crypto/srpauth.py" line="271"/> + <location filename="../src/leap/bitmask/gui/ui/wizard.ui" line="683"/> + <source><b>Username:</b></source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>__impl</name> + <message> + <location filename="../src/leap/bitmask/crypto/srpauth.py" line="281"/> <source>The server did not send the salt parameter</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/crypto/srpauth.py" line="275"/> + <location filename="../src/leap/bitmask/crypto/srpauth.py" line="285"/> <source>The server did not send the B parameter</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/crypto/srpauth.py" line="307"/> + <location filename="../src/leap/bitmask/crypto/srpauth.py" line="317"/> <source>The data sent from the server had errors</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/crypto/srpauth.py" line="329"/> + <location filename="../src/leap/bitmask/crypto/srpauth.py" line="339"/> <source>Could not connect to the server</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/crypto/srpauth.py" line="348"/> - <source>Wrong password</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/crypto/srpauth.py" line="355"/> + <location filename="../src/leap/bitmask/crypto/srpauth.py" line="365"/> <source>Unknown error (%s)</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/crypto/srpauth.py" line="386"/> + <location filename="../src/leap/bitmask/crypto/srpauth.py" line="396"/> <source>Problem getting data from server</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/crypto/srpauth.py" line="410"/> + <location filename="../src/leap/bitmask/crypto/srpauth.py" line="422"/> <source>Bad data from server</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/crypto/srpauth.py" line="417"/> + <location filename="../src/leap/bitmask/crypto/srpauth.py" line="429"/> <source>Auth verification failed</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/crypto/srpauth.py" line="425"/> + <location filename="../src/leap/bitmask/crypto/srpauth.py" line="437"/> <source>Session cookie verification failed</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/crypto/srpauth.py" line="262"/> + <location filename="../src/leap/bitmask/crypto/srpauth.py" line="272"/> <source>There was a problem with authentication</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../src/leap/bitmask/crypto/srpauth.py" line="178"/> + <source>Invalid username or password.</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>msg</name> <message> - <location filename="../src/leap/platform_init/initializers.py" line="89"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="91"/> <source>Missing up/down scripts</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/platform_init/initializers.py" line="175"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="182"/> <source>TAP Driver</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/platform_init/initializers.py" line="299"/> - <source>LEAPClient needs to install the necessary drivers for Encrypted Internet to work. Would you like to proceed?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="../src/leap/platform_init/initializers.py" line="182"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="189"/> <source>Encrypted Internet uses VPN, which needs a TAP device installed and none has been found. This will ask for administrative privileges.</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/platform_init/initializers.py" line="296"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="308"/> <source>TUN Driver</source> <translation type="unfinished"></translation> </message> <message> - <location filename="../src/leap/platform_init/initializers.py" line="304"/> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="316"/> <source>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.</source> <translation type="unfinished"></translation> </message> + <message> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="130"/> + <source>Problem installing files</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="131"/> + <source>Some of the files could not be copied.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="../src/leap/bitmask/platform_init/initializers.py" line="311"/> + <source>Bitmask needs to install the necessary drivers for Encrypted Internet to work. Would you like to proceed?</source> + <translation type="unfinished"></translation> + </message> </context> </TS> 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 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 <http://nvie.com/posts/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 <https://freenode.net>`_. -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 <your branch> - # 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 <your remote> +<your branch> 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. diff --git a/docs/release_checklist.wiki b/docs/release_checklist.wiki index 19a19289..fc99fdf0 100644 --- a/docs/release_checklist.wiki +++ b/docs/release_checklist.wiki @@ -12,23 +12,27 @@ * [ ] 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 <package> 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 <package> 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/<os>/ to build the the bundles. * [ ] Sign them with gpg -a --sign --detach-sign <path/to/bundle> - * [ ] Upload bundle and signature to web-uploads@salmon.leap.se:~/public/client/<os>/Bitmask-<os>-<ver>.(tar.bz2,dmg,zip) + * [ ] Upload bundle and signature to downloads.leap.se/client/<os>/Bitmask-<os>-<ver>.(tar.bz2,dmg,zip) * [ ] Update symbolic link for latest upload and signature: * [ ] ~/public/client/Bitmask-<os>-latest * [ ] ~/public/client/Bitmask-<os>-latest.asc 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 <reporting_bugs>`, and +#. :ref:`Quickly fetching latest development code <fetchinglatest>`. 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 + <http://www.chiark.greenend.org.uk/~sgtatham/bugs.html>`_. + +Where to report bugs +^^^^^^^^^^^^^^^^^^^^ + +We use the `Bitmask Bug Tracker <https://leap.se/code/projects/eip-client>`_, +although you can also use `Github issues +<https://github.com/leapcode/bitmask_client/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 +<https://freenode.net>`_. If you do not have a IRC client at hand, you can +`enter the channel via web +<http://webchat.freenode.net/?nick=leaper....&channels=%23leap-dev&uio=d4>`_. + + .. _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 <environment>` section, only that in a more compact way suitable (ahem) also for non developers. +Normally, testing the latest :ref:`client bundles <standalone-bundle>` 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 <environment>` 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 <standalone-bundle>` with the ability to self-update. + At some point in the near future, we will be using :ref:`standalone bundles + <standalone-bundle>` 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 <http://www.chiark.greenend.org.uk/~sgtatham/bugs.html>`_. - -.. TODO add a line with ref. to running Bitmask in debug mode... - -We use the `Bitmask Bug Tracker <https://leap.se/code/projects/eip-client>`_, although you can also use `Github issues <https://github.com/leapcode/bitmask/issues>`_. 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 <http://www.pip-installer.org/>`_ 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. 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 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/pkg/requirements.pip b/pkg/requirements.pip index 154e51b4..bb1deb1b 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -14,13 +14,12 @@ psutil ipaddr twisted qt4reactor -python-gnupg 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 19cfb879..bea48e98 100644 --- a/relnotes.txt +++ b/relnotes.txt @@ -1,104 +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 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. -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. +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 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 +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. - -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. +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. 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 +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 +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, +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. +Oct 4, 2013 +Somewhere in the middle of the intertubes. +EOF 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 diff --git a/src/leap/bitmask/app.py b/src/leap/bitmask/app.py index 02b1693d..c1859478 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. @@ -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 " @@ -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") diff --git a/src/leap/bitmask/config/leapsettings.py b/src/leap/bitmask/config/leapsettings.py index 338fa475..4660535a 100644 --- a/src/leap/bitmask/config/leapsettings.py +++ b/src/leap/bitmask/config/leapsettings.py @@ -65,8 +65,10 @@ class LeapSettings(object): PROPERPROVIDER_KEY = "ProperProvider" REMEMBER_KEY = "RememberUserAndPass" DEFAULTPROVIDER_KEY = "DefaultProvider" + AUTOSTARTEIP_KEY = "AutoStartEIP" ALERTMISSING_KEY = "AlertMissingScripts" GATEWAY_KEY = "Gateway" + PINNED_KEY = "Pinned" # values GATEWAY_AUTOMATIC = "Automatic" @@ -134,6 +136,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. @@ -285,6 +303,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/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/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() 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/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index bf85f75c..cbff4b49 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. """ @@ -136,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): """ @@ -219,7 +213,6 @@ class SRPAuth(QtCore.QObject): Might raise all SRPAuthenticationError based: SRPAuthenticationError SRPAuthConnectionError - SRPAuthUnknownUser SRPAuthBadStatusCode SRPAuthNoSalt SRPAuthNoB @@ -266,7 +259,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 +289,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 +348,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 +445,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 +456,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(), @@ -474,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) @@ -483,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() @@ -510,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) @@ -605,6 +605,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 diff --git a/src/leap/bitmask/crypto/tests/test_srpauth.py b/src/leap/bitmask/crypto/tests/test_srpauth.py index 6fb2b739..e63c1385 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) @@ -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/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 <http://www.gnu.org/licenses/>. + +""" +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..9f8c4ff4 --- /dev/null +++ b/src/leap/bitmask/gui/eip_preferenceswindow.py @@ -0,0 +1,218 @@ +# -*- 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 <http://www.gnu.org/licenses/>. + +""" +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) + self.ui.lblAutoStartEIPStatus.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.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): + """ + Sets the status label for the gateway change. + + :param status: status message to display, can be HTML + :type status: str + :param success: is set to True if we should display the + message as green + :type success: bool + :param error: is set to True if we should display the + message as red + :type error: bool + """ + if success: + status = "<font color='green'><b>%s</b></font>" % (status,) + elif error: + status = "<font color='red'><b>%s</b></font>" % (status,) + + self.ui.lblProvidersGatewayStatus.setVisible(True) + self.ui.lblProvidersGatewayStatus.setText(status) + + def _add_configured_providers(self): + """ + Add the client's configured providers to the providers combo boxes. + """ + self.ui.cbProvidersGateway.clear() + 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): + """ + 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..946eaa4e --- /dev/null +++ b/src/leap/bitmask/gui/eip_status.py @@ -0,0 +1,435 @@ +# -*- 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 <http://www.gnu.org/licenses/>. +""" +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 + + # 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) + + @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): + """ + 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 = "<font color='red'>%s</font>" % (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 + """ + # 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) + + # 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..582f26be 100644 --- a/src/leap/bitmask/gui/login.py +++ b/src/leap/bitmask/gui/login.py @@ -14,16 +14,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ 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__) @@ -33,10 +35,11 @@ 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() # Emitted when the user selects "Other..." in the provider # combobox or click "Create Account" @@ -76,13 +79,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 +196,10 @@ class LoginWidget(QtGui.QWidget): if len(status) > self.MAX_STATUS_WIDTH: status = status[:self.MAX_STATUS_WIDTH] + "..." if error: - status = "<font color='red'><b>%s</b></font>" % (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 +224,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 +234,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 +259,106 @@ 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) + self.ui.clblErrorMsg.hide() + + 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("") + self.logged_in_signal.emit() + + 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("", error=False) + + 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 = "<font color='red'><b>%s</b></font>" % (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..ab9052d7 --- /dev/null +++ b/src/leap/bitmask/gui/mail_status.py @@ -0,0 +1,408 @@ +# -*- 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 <http://www.gnu.org/licenses/>. +""" +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, < 0 if disabled. + :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') + elif ready < 0: + tray_status = self.tr("Mail is disabled") + + 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 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. + """ + self._set_mail_status(self.tr("OFF")) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 200d68aa..84f09fd9 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -32,13 +32,16 @@ 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 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.providerbootstrapper import ProviderBootstrapper +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 @@ -53,12 +56,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 @@ -98,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([]) @@ -147,7 +151,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 +159,18 @@ 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._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.ui.stackedWidget.setCurrentIndex(self.LOGIN_INDEX) + self._mail_status = MailStatusWidget(self) + self.ui.mailLayout.addWidget(self._mail_status) self._eip_connection = EIPConnection() @@ -173,15 +178,19 @@ 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) + 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 @@ -221,9 +230,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) @@ -234,17 +243,23 @@ 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( 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 @@ -254,17 +269,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() @@ -280,6 +295,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 ######## @@ -296,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 @@ -303,27 +320,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 @@ -391,7 +410,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. @@ -405,18 +423,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. @@ -431,22 +444,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): """ @@ -579,21 +595,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, @@ -601,6 +625,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,)) @@ -621,24 +646,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): """ @@ -746,9 +767,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 @@ -844,47 +866,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): """ @@ -952,7 +935,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 @@ -967,20 +949,25 @@ 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() + + 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. - self._soledad_bootstrapper.run_soledad_setup_checks( - self._provider_config, - self._login_widget.get_user(), - self._login_widget.get_password(), - download_if_needed=True) + 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) + else: + self._mail_status.set_disabled() self._download_eip_config() @@ -1007,6 +994,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 +1019,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") @@ -1116,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, @@ -1136,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() ################################################################### @@ -1195,22 +1185,37 @@ 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, 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): """ 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 @@ -1225,7 +1230,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 @@ -1233,9 +1238,30 @@ 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 + # 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() @@ -1244,16 +1270,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 " @@ -1266,30 +1290,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 <b>pkexec</b> " "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."), @@ -1298,17 +1322,17 @@ 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 @QtCore.Slot() - def _stop_eip(self, abnormal=False): + def _stop_eip(self): """ SLOT TRIGGERS: - self._status_panel.stop_eip + self._eip_status.stop_eip self._action_eip_startstop.triggered or called from _eip_finished @@ -1318,29 +1342,28 @@ 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() - + 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._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): """ @@ -1355,7 +1378,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, @@ -1363,11 +1386,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): """ @@ -1382,7 +1405,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 @@ -1390,19 +1413,15 @@ 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! 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) @@ -1437,7 +1456,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 """ @@ -1457,16 +1476,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): """ @@ -1531,37 +1551,36 @@ 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): - 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) self._vpn.killit() + 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) - 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/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 2d17f6c2..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__) @@ -60,7 +59,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 +66,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) @@ -185,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) @@ -217,37 +210,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 = "<font color='green'><b>%s</b></font>" % (status,) - elif error: - status = "<font color='red'><b>%s</b></font>" % (status,) - - self.ui.lblProvidersGatewayStatus.setVisible(True) - self.ui.lblProvidersGatewayStatus.setText(status) - def _add_configured_providers(self): """ Add the client's configured providers to the providers combo boxes. """ 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 +335,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/statemachines.py b/src/leap/bitmask/gui/statemachines.py index c3dd5ed3..94726720 100644 --- a/src/leap/bitmask/gui/statemachines.py +++ b/src/leap/bitmask/gui/statemachines.py @@ -128,11 +128,25 @@ 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]) + # * 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(): machine.addState(state) 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 <http://www.gnu.org/licenses/>. -""" -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 = "<font color='red'><b>%s</b></font>" % (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 = "<font color='red'>%s</font>" % (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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>EIPStatus</class> + <widget class="QWidget" name="EIPStatus"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>470</width> + <height>157</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="bottomMargin"> + <number>24</number> + </property> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="2"> + <widget class="QPushButton" name="btnEipStartStop"> + <property name="text"> + <string>Turn On</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="maximumSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/black/32/earth.png</pixmap> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="lblEIPStatus"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>32</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">color: rgb(80, 80, 80);</string> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="lblEIPMessage"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <pointsize>14</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Traffic is being routed in the clear</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="lblVPNStatusIcon"> + <property name="maximumSize"> + <size> + <width>16</width> + <height>16</height> + </size> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/black/32/off.png</pixmap> + </property> + <property name="scaledContents"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="1" colspan="3"> + <widget class="QWidget" name="eip_bandwidth" native="true"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>32</height> + </size> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="eip_bandwidth_layout"> + <property name="spacing"> + <number>4</number> + </property> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/down-arrow.png</pixmap> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnDownload"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>100</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>120</width> + <height>16777215</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>11</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="cursor"> + <cursorShape>PointingHandCursor</cursorShape> + </property> + <property name="text"> + <string>0.0 KB/s</string> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>10</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/up-arrow.png</pixmap> + </property> + </widget> + </item> + <item alignment="Qt::AlignLeft"> + <widget class="QPushButton" name="btnUpload"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>100</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>120</width> + <height>16777215</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>11</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="cursor"> + <cursorShape>PointingHandCursor</cursorShape> + </property> + <property name="text"> + <string>0.0 KB/s</string> + </property> + <property name="flat"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources> + <include location="../../../../../data/resources/mainwindow.qrc"/> + <include location="../../../../../data/resources/icons.qrc"/> + </resources> + <connections/> +</ui> diff --git a/src/leap/bitmask/gui/ui/eippreferences.ui b/src/leap/bitmask/gui/ui/eippreferences.ui new file mode 100644 index 00000000..9493d330 --- /dev/null +++ b/src/leap/bitmask/gui/ui/eippreferences.ui @@ -0,0 +1,183 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>EIPPreferences</class> + <widget class="QDialog" name="EIPPreferences"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>398</width> + <height>262</height> + </rect> + </property> + <property name="windowTitle"> + <string>EIP Preferences</string> + </property> + <property name="windowIcon"> + <iconset resource="../../../../../data/resources/mainwindow.qrc"> + <normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="0"> + <widget class="QGroupBox" name="gbGatewaySelector"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="title"> + <string>Select gateway for provider</string> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="lblSelectProvider"> + <property name="text"> + <string>&Select provider:</string> + </property> + <property name="buddy"> + <cstring>cbProvidersGateway</cstring> + </property> + </widget> + </item> + <item row="0" column="1" colspan="2"> + <widget class="QComboBox" name="cbProvidersGateway"> + <item> + <property name="text"> + <string><Select provider></string> + </property> + </item> + </widget> + </item> + <item row="7" column="2"> + <widget class="QPushButton" name="pbSaveGateway"> + <property name="text"> + <string>Save this provider settings</string> + </property> + </widget> + </item> + <item row="4" column="0" colspan="3"> + <widget class="QLabel" name="lblProvidersGatewayStatus"> + <property name="text"> + <string>< Providers Gateway Status ></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Select gateway:</string> + </property> + </widget> + </item> + <item row="1" column="1" colspan="2"> + <widget class="QComboBox" name="cbGateways"> + <item> + <property name="text"> + <string>Automatic</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="0"> + <widget class="QGroupBox" name="gbAutomaticEIP"> + <property name="title"> + <string>Automatic EIP start</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="3" column="0"> + <widget class="QLabel" name="lblAutoStartEIPStatus"> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <property name="text"> + <string><font color='green'><b>Automatic EIP start saved!</b></font></string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QPushButton" name="pbSaveAutoStartEIP"> + <property name="text"> + <string>Save auto start setting</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QCheckBox" name="cbAutoStartEIP"> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="text"> + <string>Enable Automatic start of EIP</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="cbProvidersEIP"> + <item> + <property name="text"> + <string><Select provider></string> + </property> + </item> + </widget> + </item> + </layout> + <zorder>cbAutoStartEIP</zorder> + <zorder>lblAutoStartEIPStatus</zorder> + <zorder>pbSaveAutoStartEIP</zorder> + <zorder>cbProvidersEIP</zorder> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>cbAutoStartEIP</tabstop> + <tabstop>cbProvidersEIP</tabstop> + <tabstop>pbSaveAutoStartEIP</tabstop> + <tabstop>cbProvidersGateway</tabstop> + <tabstop>cbGateways</tabstop> + <tabstop>pbSaveGateway</tabstop> + </tabstops> + <resources> + <include location="../../../../../data/resources/mainwindow.qrc"/> + </resources> + <connections> + <connection> + <sender>cbAutoStartEIP</sender> + <signal>toggled(bool)</signal> + <receiver>cbProvidersEIP</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>180</x> + <y>53</y> + </hint> + <hint type="destinationlabel"> + <x>238</x> + <y>53</y> + </hint> + </hints> + </connection> + </connections> +</ui> 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 @@ <rect> <x>0</x> <y>0</y> - <width>356</width> - <height>223</height> + <width>468</width> + <height>350</height> </rect> </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> <property name="windowTitle"> <string>Form</string> </property> + <property name="styleSheet"> + <string notr="true"/> + </property> <layout class="QGridLayout" name="gridLayout"> - <item row="5" column="2"> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> + <property name="margin"> + <number>0</number> + </property> + <item row="2" column="2" colspan="2"> + <widget class="QWidget" name="logged_widget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> - <property name="sizeHint" stdset="0"> + <property name="minimumSize"> <size> - <width>40</width> - <height>20</height> + <width>0</width> + <height>0</height> </size> </property> - </spacer> - </item> - <item row="1" column="1" colspan="2"> - <widget class="QComboBox" name="cmbProviders"/> + <layout class="QGridLayout" name="gridLayout_5"> + <property name="bottomMargin"> + <number>24</number> + </property> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="lblUser"> + <property name="font"> + <font> + <pointsize>15</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QPushButton" name="btnLogout"> + <property name="text"> + <string>Logout</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QLabel" name="lblLoginStatus"> + <property name="styleSheet"> + <string notr="true">color: rgb(132, 132, 132); +font: 75 12pt "Lucida Grande";</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> </item> - <item row="5" column="0"> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> + <item row="1" column="1" rowspan="2"> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> - <property name="sizeHint" stdset="0"> + <property name="maximumSize"> <size> - <width>40</width> - <height>20</height> + <width>16777215</width> + <height>800</height> </size> </property> - </spacer> - </item> - <item row="6" column="1"> - <widget class="QPushButton" name="btnCreateAccount"> <property name="text"> - <string>Create a new account</string> + <string/> </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string><b>Provider:</b></string> + <property name="pixmap"> + <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/black/32/user.png</pixmap> </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + <property name="scaledContents"> + <bool>false</bool> </property> - </widget> - </item> - <item row="3" column="1" colspan="2"> - <widget class="QLineEdit" name="lnPassword"> - <property name="inputMask"> - <string/> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> </property> - </widget> - </item> - <item row="2" column="1" colspan="2"> - <widget class="QLineEdit" name="lnUser"/> - </item> - <item row="4" column="1" colspan="2"> - <widget class="QCheckBox" name="chkRemember"> - <property name="text"> - <string>Remember username and password</string> + <property name="margin"> + <number>0</number> </property> </widget> </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string><b>Username:</b></string> + <item row="1" column="2" colspan="2"> + <widget class="QWidget" name="login_widget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> </property> + <layout class="QGridLayout" name="gridLayout_4"> + <property name="rightMargin"> + <number>24</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string><b>Provider:</b></string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QPushButton" name="btnLogin"> + <property name="text"> + <string>Log In</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QCheckBox" name="chkRemember"> + <property name="text"> + <string>Remember username and password</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="lblStatus"> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="cmbProviders"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string><b>Username:</b></string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="lnUser"/> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string><b>Password:</b></string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="lnPassword"> + <property name="inputMask"> + <string/> + </property> + </widget> + </item> + </layout> </widget> </item> - <item row="3" column="0"> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string><b>Password:</b></string> + <item row="1" column="0"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + <property name="sizeType"> + <enum>QSizePolicy::Maximum</enum> </property> - </widget> - </item> - <item row="5" column="1"> - <widget class="QPushButton" name="btnLogin"> - <property name="text"> - <string>Log In</string> + <property name="sizeHint" stdset="0"> + <size> + <width>12</width> + <height>0</height> + </size> </property> - </widget> + </spacer> </item> - <item row="0" column="0" colspan="3"> - <widget class="QLabel" name="lblStatus"> + <item row="0" column="0" colspan="4"> + <widget class="ClickableLabel" name="clblErrorMsg"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>50</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true">background-color: rgb(255, 127, 114);</string> + </property> <property name="text"> <string/> </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> - <property name="wordWrap"> - <bool>true</bool> - </property> </widget> </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>ClickableLabel</class> + <extends>QLabel</extends> + <header>clickablelabel.h</header> + </customwidget> + </customwidgets> <tabstops> - <tabstop>cmbProviders</tabstop> - <tabstop>lnUser</tabstop> - <tabstop>lnPassword</tabstop> <tabstop>chkRemember</tabstop> - <tabstop>btnLogin</tabstop> - <tabstop>btnCreateAccount</tabstop> </tabstops> - <resources/> + <resources> + <include location="../../../../../data/resources/mainwindow.qrc"/> + </resources> <connections/> </ui> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MailStatusWidget</class> + <widget class="QWidget" name="MailStatusWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>72</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="1" rowspan="2"> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="1" column="0" colspan="3"> + <widget class="QLabel" name="lblMailStatus"> + <property name="styleSheet"> + <string notr="true">color: rgb(80, 80, 80);</string> + </property> + <property name="text"> + <string>You must login to use encrypted email.</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_4"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Email</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="lblMailStatusIcon"> + <property name="maximumSize"> + <size> + <width>16</width> + <height>16</height> + </size> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/black/32/off.png</pixmap> + </property> + <property name="scaledContents"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/black/32/email.png</pixmap> + </property> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="../../../../../data/resources/mainwindow.qrc"/> + </resources> + <connections/> +</ui> 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 @@ <rect> <x>0</x> <y>0</y> - <width>429</width> - <height>579</height> + <width>524</width> + <height>722</height> </rect> </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>524</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>524</width> + <height>16777215</height> + </size> + </property> <property name="windowTitle"> <string>Bitmask</string> </property> @@ -28,9 +46,222 @@ </property> <widget class="QWidget" name="centralwidget"> <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0" colspan="5"> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>0</number> + </property> + <item row="1" column="0" colspan="2"> + <widget class="QScrollArea" name="scrollArea"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <property name="lineWidth"> + <number>0</number> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>524</width> + <height>635</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <property name="margin"> + <number>0</number> + </property> + <item> + <widget class="QWidget" name="widget_2" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="leftMargin"> + <number>24</number> + </property> + <property name="rightMargin"> + <number>24</number> + </property> + <item> + <widget class="QLabel" name="label_2"> + <property name="font"> + <font> + <pointsize>16</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Encrypted Internet</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnEIPPreferences"> + <property name="maximumSize"> + <size> + <width>48</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../../../../../data/resources/mainwindow.qrc"> + <normaloff>:/images/black/32/gear.png</normaloff>:/images/black/32/gear.png</iconset> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + <property name="default"> + <bool>false</bool> + </property> + <property name="flat"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="eipLayout"> + <property name="leftMargin"> + <number>12</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>12</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + </layout> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="widget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="leftMargin"> + <number>24</number> + </property> + <property name="rightMargin"> + <number>24</number> + </property> + <item> + <widget class="QLabel" name="lblLoginProvider"> + <property name="font"> + <font> + <pointsize>16</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Login</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnPreferences"> + <property name="maximumSize"> + <size> + <width>48</width> + <height>20</height> + </size> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../../../../../data/resources/mainwindow.qrc"> + <normaloff>:/images/black/32/gear.png</normaloff>:/images/black/32/gear.png</iconset> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="loginLayout"/> + </item> + <item> + <widget class="Line" name="line_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="mailLayout"> + <property name="margin"> + <number>12</number> + </property> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </widget> + </item> + <item row="0" column="0" colspan="2"> <layout class="QGridLayout" name="gridLayout_4"> - <item row="2" column="0"> + <item row="1" column="1"> + <widget class="QLabel" name="lblNewUpdates"> + <property name="text"> + <string>There are new updates available, please restart.</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="0"> <spacer name="horizontalSpacer_8"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -43,7 +274,7 @@ </property> </spacer> </item> - <item row="1" column="1"> + <item row="0" column="1"> <spacer name="horizontalSpacer_7"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -56,17 +287,7 @@ </property> </spacer> </item> - <item row="2" column="1"> - <widget class="QLabel" name="lblNewUpdates"> - <property name="text"> - <string>There are new updates available, please restart.</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="2" column="2"> + <item row="1" column="2"> <widget class="QPushButton" name="btnMore"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> @@ -82,7 +303,7 @@ </property> </widget> </item> - <item row="2" column="3"> + <item row="1" column="3"> <spacer name="horizontalSpacer_9"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -97,164 +318,6 @@ </item> </layout> </item> - <item row="6" column="2"> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item row="10" column="0" colspan="5"> - <widget class="QStackedWidget" name="stackedWidget"> - <property name="currentIndex"> - <number>1</number> - </property> - <widget class="QWidget" name="loginPage"> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="0" column="1"> - <layout class="QHBoxLayout" name="loginLayout"/> - </item> - <item row="0" column="2"> - <spacer name="horizontalSpacer_4"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="0"> - <spacer name="horizontalSpacer_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - <widget class="QWidget" name="page_2"> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="statusLayout"/> - </item> - </layout> - </widget> - </widget> - </item> - <item row="7" column="2"> - <widget class="QLabel" name="label"> - <property name="autoFillBackground"> - <bool>false</bool> - </property> - <property name="text"> - <string/> - </property> - <property name="pixmap"> - <pixmap resource="../../../../../data/resources/mainwindow.qrc">:/images/mask-launcher.png</pixmap> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item row="7" column="3" colspan="2"> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="17" column="2"> - <spacer name="verticalSpacer_2"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - <item row="7" column="0" colspan="2"> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="18" column="2"> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QPushButton" name="btnPreferences"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="text"> - <string>Preferences</string> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_10"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="btnShowLog"> - <property name="text"> - <string>Show Log</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="checked"> - <bool>false</bool> - </property> - <property name="flat"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </item> </layout> </widget> <widget class="QMenuBar" name="menubar"> @@ -262,15 +325,15 @@ <rect> <x>0</x> <y>0</y> - <width>429</width> - <height>21</height> + <width>524</width> + <height>22</height> </rect> </property> - <widget class="QMenu" name="menuSession"> + <widget class="QMenu" name="menuFile"> <property name="title"> - <string>&Session</string> + <string>&Bitmask</string> </property> - <addaction name="action_log_out"/> + <addaction name="action_create_new_account"/> <addaction name="separator"/> <addaction name="action_quit"/> </widget> @@ -279,16 +342,17 @@ <string>Help</string> </property> <addaction name="action_help"/> + <addaction name="action_show_logs"/> <addaction name="separator"/> <addaction name="action_about_leap"/> </widget> - <addaction name="menuSession"/> + <addaction name="menuFile"/> <addaction name="menuHelp"/> </widget> <widget class="QStatusBar" name="statusbar"/> - <action name="action_log_out"> + <action name="action_preferences"> <property name="text"> - <string>Log &out</string> + <string>Preferences...</string> </property> </action> <action name="action_quit"> @@ -313,7 +377,12 @@ </action> <action name="action_show_logs"> <property name="text"> - <string>Show &logs</string> + <string>Show &Log</string> + </property> + </action> + <action name="action_create_new_account"> + <property name="text"> + <string>Create a new account...</string> </property> </action> </widget> 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 @@ <x>0</x> <y>0</y> <width>503</width> - <height>529</height> + <height>401</height> </rect> </property> <property name="windowTitle"> @@ -18,7 +18,7 @@ <normaloff>:/images/mask-icon.png</normaloff>:/images/mask-icon.png</iconset> </property> <layout class="QGridLayout" name="gridLayout_3"> - <item row="5" column="0"> + <item row="4" column="0"> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -31,64 +31,80 @@ </property> </spacer> </item> - <item row="1" column="0"> - <widget class="QGroupBox" name="gbGatewaySelector"> + <item row="0" column="0"> + <widget class="QGroupBox" name="gbPasswordChange"> <property name="enabled"> - <bool>true</bool> + <bool>false</bool> </property> <property name="title"> - <string>Select gateway for provider</string> - </property> - <property name="checkable"> - <bool>false</bool> + <string>Password Change</string> </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="1" colspan="2"> - <widget class="QComboBox" name="cbProvidersGateway"> - <item> - <property name="text"> - <string><Select provider></string> - </property> - </item> - </widget> - </item> + <layout class="QFormLayout" name="formLayout"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::ExpandingFieldsGrow</enum> + </property> <item row="0" column="0"> - <widget class="QLabel" name="lblSelectProvider"> + <widget class="QLabel" name="lblCurrentPassword"> <property name="text"> - <string>&Select provider:</string> + <string>&Current password:</string> </property> <property name="buddy"> - <cstring>cbProvidersGateway</cstring> + <cstring>leCurrentPassword</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="leCurrentPassword"> + <property name="echoMode"> + <enum>QLineEdit::Password</enum> </property> </widget> </item> <item row="1" column="0"> - <widget class="QLabel" name="label"> + <widget class="QLabel" name="lblNewPassword"> <property name="text"> - <string>Select gateway:</string> + <string>&New password:</string> + </property> + <property name="buddy"> + <cstring>leNewPassword</cstring> </property> </widget> </item> - <item row="1" column="1" colspan="2"> - <widget class="QComboBox" name="cbGateways"> - <item> - <property name="text"> - <string>Automatic</string> - </property> - </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="leNewPassword"> + <property name="echoMode"> + <enum>QLineEdit::Password</enum> + </property> </widget> </item> - <item row="5" column="2"> - <widget class="QPushButton" name="pbSaveGateway"> + <item row="2" column="0"> + <widget class="QLabel" name="lblNewPassword2"> <property name="text"> - <string>Save this provider settings</string> + <string>&Re-enter new password:</string> + </property> + <property name="buddy"> + <cstring>leNewPassword2</cstring> </property> </widget> </item> - <item row="2" column="0" colspan="3"> - <widget class="QLabel" name="lblProvidersGatewayStatus"> + <item row="2" column="1"> + <widget class="QLineEdit" name="leNewPassword2"> + <property name="echoMode"> + <enum>QLineEdit::Password</enum> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QPushButton" name="pbChangePassword"> <property name="text"> - <string>< Providers Gateway Status ></string> + <string>Change</string> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QLabel" name="lblPasswordChangeStatus"> + <property name="text"> + <string><Password change status></string> </property> <property name="alignment"> <set>Qt::AlignCenter</set> @@ -98,7 +114,7 @@ </layout> </widget> </item> - <item row="4" column="0"> + <item row="3" column="0"> <widget class="QGroupBox" name="gbEnabledServices"> <property name="title"> <string>Enabled services</string> @@ -155,89 +171,6 @@ </layout> </widget> </item> - <item row="0" column="0"> - <widget class="QGroupBox" name="gbPasswordChange"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="title"> - <string>Password Change</string> - </property> - <layout class="QFormLayout" name="formLayout"> - <property name="fieldGrowthPolicy"> - <enum>QFormLayout::ExpandingFieldsGrow</enum> - </property> - <item row="0" column="0"> - <widget class="QLabel" name="lblCurrentPassword"> - <property name="text"> - <string>&Current password:</string> - </property> - <property name="buddy"> - <cstring>leCurrentPassword</cstring> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QLineEdit" name="leCurrentPassword"> - <property name="echoMode"> - <enum>QLineEdit::Password</enum> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="lblNewPassword"> - <property name="text"> - <string>&New password:</string> - </property> - <property name="buddy"> - <cstring>leNewPassword</cstring> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLineEdit" name="leNewPassword"> - <property name="echoMode"> - <enum>QLineEdit::Password</enum> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="lblNewPassword2"> - <property name="text"> - <string>&Re-enter new password:</string> - </property> - <property name="buddy"> - <cstring>leNewPassword2</cstring> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLineEdit" name="leNewPassword2"> - <property name="echoMode"> - <enum>QLineEdit::Password</enum> - </property> - </widget> - </item> - <item row="4" column="1"> - <widget class="QPushButton" name="pbChangePassword"> - <property name="text"> - <string>Change</string> - </property> - </widget> - </item> - <item row="3" column="0" colspan="2"> - <widget class="QLabel" name="lblPasswordChangeStatus"> - <property name="text"> - <string><Password change status></string> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - </layout> - </widget> - </item> </layout> </widget> <resources> 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 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>StatusPanel</class> - <widget class="QWidget" name="StatusPanel"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>470</width> - <height>477</height> - </rect> - </property> - <property name="windowTitle"> - <string>Form</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QLabel" name="lblProvider"> - <property name="styleSheet"> - <string notr="true">font: bold;</string> - </property> - <property name="text"> - <string>user@domain.org</string> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QWidget" name="status_rows" native="true"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="styleSheet"> - <string notr="true"/> - </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="5" column="1"> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="1" column="0" colspan="3"> - <widget class="QLabel" name="lblUnread"> - <property name="text"> - <string>0 Unread Emails</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QLabel" name="lblMailStatus"> - <property name="styleSheet"> - <string notr="true">font: bold;</string> - </property> - <property name="text"> - <string>Disabled</string> - </property> - </widget> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label_4"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="text"> - <string>Encrypted Mail:</string> - </property> - </widget> - </item> - <item row="0" column="2"> - <spacer name="horizontalSpacer_4"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="2" column="0" colspan="3"> - <spacer name="verticalSpacer_3"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>1</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item row="1" column="1"> - <layout class="QHBoxLayout" name="eip_bandwidth"> - <property name="spacing"> - <number>4</number> - </property> - <property name="sizeConstraint"> - <enum>QLayout::SetDefaultConstraint</enum> - </property> - <item> - <widget class="QLabel" name="label_5"> - <property name="text"> - <string/> - </property> - <property name="pixmap"> - <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/down-arrow.png</pixmap> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="btnDownload"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>100</width> - <height>0</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>120</width> - <height>16777215</height> - </size> - </property> - <property name="cursor"> - <cursorShape>PointingHandCursor</cursorShape> - </property> - <property name="text"> - <string>0.0 KB/s</string> - </property> - <property name="flat"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Fixed</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QLabel" name="label_7"> - <property name="text"> - <string/> - </property> - <property name="pixmap"> - <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/16/up-arrow.png</pixmap> - </property> - </widget> - </item> - <item alignment="Qt::AlignLeft"> - <widget class="QPushButton" name="btnUpload"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>100</width> - <height>0</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>120</width> - <height>16777215</height> - </size> - </property> - <property name="cursor"> - <cursorShape>PointingHandCursor</cursorShape> - </property> - <property name="text"> - <string>0.0 KB/s</string> - </property> - <property name="flat"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_2"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - </layout> - </item> - <item row="2" column="0"> - <spacer name="verticalSpacer_2"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeType"> - <enum>QSizePolicy::Preferred</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>0</width> - <height>11</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="0" rowspan="2"> - <widget class="QLabel" name="lblVPNStatusIcon"> - <property name="maximumSize"> - <size> - <width>64</width> - <height>64</height> - </size> - </property> - <property name="text"> - <string/> - </property> - <property name="pixmap"> - <pixmap resource="../../../../../data/resources/icons.qrc">:/images/light/64/network-eip-down.png</pixmap> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item row="4" column="0" rowspan="2"> - <widget class="QLabel" name="lblMailIcon"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>64</width> - <height>64</height> - </size> - </property> - <property name="maximumSize"> - <size> - <width>64</width> - <height>999</height> - </size> - </property> - <property name="text"> - <string/> - </property> - <property name="pixmap"> - <pixmap resource="../../../../../data/resources/icons.qrc">:/images/mail-unlocked.png</pixmap> - </property> - </widget> - </item> - <item row="7" column="0" colspan="2"> - <widget class="QGroupBox" name="grpMailStatus"> - <property name="title"> - <string/> - </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QLabel" name="lblLongMailStatus"> - <property name="text"> - <string>...</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item row="3" column="0" colspan="2"> - <widget class="QGroupBox" name="globalStatusBox"> - <property name="enabled"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="0" column="0"> - <widget class="QLabel" name="lblGlobalStatus"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="text"> - <string>...</string> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item row="0" column="1"> - <layout class="QHBoxLayout" name="eip_controls"> - <item> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Encrypted Internet: </string> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="lblEIPStatus"> - <property name="styleSheet"> - <string notr="true">font: bold;</string> - </property> - <property name="text"> - <string>Off</string> - </property> - <property name="textFormat"> - <enum>Qt::AutoText</enum> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - <property name="wordWrap"> - <bool>false</bool> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="btnEipStartStop"> - <property name="text"> - <string>Turn On</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> - </layout> - </widget> - <resources> - <include location="../../../../../data/resources/icons.qrc"/> - </resources> - <connections/> -</ui> diff --git a/src/leap/bitmask/gui/ui/wizard.ui b/src/leap/bitmask/gui/ui/wizard.ui index 2a412784..0f6eef6e 100644 --- a/src/leap/bitmask/gui/ui/wizard.ui +++ b/src/leap/bitmask/gui/ui/wizard.ui @@ -134,17 +134,7 @@ </property> </spacer> </item> - <item row="1" column="1"> - <widget class="QLineEdit" name="lnProvider"/> - </item> - <item row="1" column="2"> - <widget class="QPushButton" name="btnCheck"> - <property name="text"> - <string>Check</string> - </property> - </widget> - </item> - <item row="3" column="1"> + <item row="5" column="1"> <spacer name="verticalSpacer_2"> <property name="orientation"> <enum>Qt::Vertical</enum> @@ -157,17 +147,7 @@ </property> </spacer> </item> - <item row="1" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>https://</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="4" column="0" colspan="3"> + <item row="6" column="0" colspan="3"> <widget class="QGroupBox" name="grpCheckProvider"> <property name="title"> <string>Checking for a valid provider</string> @@ -276,13 +256,76 @@ </layout> </widget> </item> - <item row="2" column="1" colspan="2"> + <item row="4" column="1" colspan="2"> <widget class="QLabel" name="lblProviderSelectStatus"> <property name="text"> <string/> </property> </widget> </item> + <item row="1" column="0" colspan="3"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Configure or select a provider</string> + </property> + <layout class="QGridLayout" name="gridLayout_5"> + <item row="0" column="0"> + <widget class="QRadioButton" name="rbNewProvider"> + <property name="text"> + <string>Configure new provider:</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QRadioButton" name="rbExistingProvider"> + <property name="text"> + <string>Use existing one:</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>https://</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="lnProvider"/> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="btnCheck"> + <property name="text"> + <string>Check</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>https://</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QComboBox" name="cbProviders"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> </layout> </widget> <widget class="QWizardPage" name="provider_info_page"> @@ -626,10 +669,18 @@ </widget> </item> <item row="3" column="1" colspan="2"> - <widget class="QLineEdit" name="lblPassword"/> + <widget class="QLineEdit" name="lblPassword"> + <property name="echoMode"> + <enum>QLineEdit::Password</enum> + </property> + </widget> </item> <item row="4" column="1" colspan="2"> - <widget class="QLineEdit" name="lblPassword2"/> + <widget class="QLineEdit" name="lblPassword2"> + <property name="echoMode"> + <enum>QLineEdit::Password</enum> + </property> + </widget> </item> <item row="2" column="1" colspan="2"> <widget class="QLineEdit" name="lblUser"/> @@ -756,12 +807,91 @@ <tabstop>btnRegister</tabstop> <tabstop>rdoRegister</tabstop> <tabstop>rdoLogin</tabstop> - <tabstop>lnProvider</tabstop> - <tabstop>btnCheck</tabstop> </tabstops> <resources> <include location="../../../../../data/resources/mainwindow.qrc"/> <include location="../../../../../data/resources/locale.qrc"/> </resources> - <connections/> + <connections> + <connection> + <sender>rbExistingProvider</sender> + <signal>toggled(bool)</signal> + <receiver>cbProviders</receiver> + <slot>setFocus()</slot> + <hints> + <hint type="sourcelabel"> + <x>167</x> + <y>192</y> + </hint> + <hint type="destinationlabel"> + <x>265</x> + <y>191</y> + </hint> + </hints> + </connection> + <connection> + <sender>rbNewProvider</sender> + <signal>toggled(bool)</signal> + <receiver>lnProvider</receiver> + <slot>setFocus()</slot> + <hints> + <hint type="sourcelabel"> + <x>171</x> + <y>164</y> + </hint> + <hint type="destinationlabel"> + <x>246</x> + <y>164</y> + </hint> + </hints> + </connection> + <connection> + <sender>rbExistingProvider</sender> + <signal>toggled(bool)</signal> + <receiver>lnProvider</receiver> + <slot>setDisabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>169</x> + <y>196</y> + </hint> + <hint type="destinationlabel"> + <x>327</x> + <y>163</y> + </hint> + </hints> + </connection> + <connection> + <sender>rbNewProvider</sender> + <signal>toggled(bool)</signal> + <receiver>cbProviders</receiver> + <slot>setDisabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>169</x> + <y>162</y> + </hint> + <hint type="destinationlabel"> + <x>269</x> + <y>193</y> + </hint> + </hints> + </connection> + <connection> + <sender>rbExistingProvider</sender> + <signal>toggled(bool)</signal> + <receiver>btnCheck</receiver> + <slot>setDisabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>154</x> + <y>193</y> + </hint> + <hint type="destinationlabel"> + <x>498</x> + <y>170</y> + </hint> + </hints> + </connection> + </connections> </ui> diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py index 45734b81..e3f5904e 100644 --- a/src/leap/bitmask/gui/wizard.py +++ b/src/leap/bitmask/gui/wizard.py @@ -14,28 +14,27 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ First run wizard """ import os import logging import json +import random from functools import partial from PySide import QtCore, QtGui from twisted.internet import threads -from leap.bitmask.config import flags +from leap.bitmask.config.leapsettings import LeapSettings 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 @@ -84,6 +83,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) @@ -113,9 +114,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) @@ -128,6 +126,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)) @@ -147,6 +147,40 @@ class Wizard(QtGui.QWizard): self.ui.label_12.setVisible(False) self.ui.lblProviderPolicy.setVisible(False) + 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() + 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 = [] + + # 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 @@ -318,6 +352,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 +617,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) 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/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 <http://www.gnu.org/licenses/>. +""" +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/providerbootstrapper.py b/src/leap/bitmask/provider/providerbootstrapper.py index 3b7c9899..1b5947e1 100644 --- a/src/leap/bitmask/services/eip/providerbootstrapper.py +++ b/src/leap/bitmask/provider/providerbootstrapper.py @@ -14,7 +14,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ Provider bootstrapping """ @@ -28,15 +27,15 @@ 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 +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__) @@ -85,16 +84,32 @@ class ProviderBootstrapper(AbstractBootstrapper): 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): @@ -102,24 +117,29 @@ class ProviderBootstrapper(AbstractBootstrapper): 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 + # 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=not self._bypass_checks, + verify=self.verify, timeout=REQUEST_TIMEOUT) res.raise_for_status() - except requests.exceptions.SSLError: + except requests.exceptions.SSLError as exc: + logger.exception(exc) self._err_msg = self.tr("Provider certificate could " "not be verified") raise - except Exception: + 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 @@ -129,12 +149,16 @@ class ProviderBootstrapper(AbstractBootstrapper): """ leap_assert(self._domain, "Cannot download provider info without a domain") - logger.debug("Downloading provider info for %s" % (self._domain)) - headers = {} + # -------------------------------------------------------------- + # TODO factor out with the download routines in services. + # Watch out! We're handling the verify paramenter differently here. - provider_json = os.path.join(get_path_prefix(), "leap", "providers", + headers = {} + provider_json = os.path.join(util.get_path_prefix(), + "leap", + "providers", self._domain, "provider.json") mtime = get_mtime(provider_json) @@ -142,16 +166,18 @@ class ProviderBootstrapper(AbstractBootstrapper): headers['if-modified-since'] = mtime uri = "https://%s/%s" % (self._domain, "provider.json") - verify = not self._bypass_checks + verify = self.verify if mtime: # the provider.json exists - provider_config = ProviderConfig() - provider_config.load(provider_json) + # So, we're getting it from the api.* and checking against + # the provider ca. try: - verify = provider_config.get_ca_cert_path() + 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: - # get_ca_cert_path fails if the certificate does not exists. + # no ca? then download from main domain again. pass logger.debug("Requesting for provider.json... " @@ -165,6 +191,9 @@ class ProviderBootstrapper(AbstractBootstrapper): # 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) @@ -181,8 +210,8 @@ class ProviderBootstrapper(AbstractBootstrapper): else: api_supported = ', '.join(SupportedAPIs.SUPPORTED_APIS) error = ('Unsupported provider API version. ' - 'Supported versions are: {}. ' - 'Found: {}.').format(api_supported, api_version) + 'Supported versions are: {0}. ' + 'Found: {1}.').format(api_supported, api_version) logger.error(error) raise UnsupportedProviderAPI(error) @@ -230,7 +259,8 @@ class ProviderBootstrapper(AbstractBootstrapper): """ 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!") @@ -244,7 +274,7 @@ class ProviderBootstrapper(AbstractBootstrapper): return res = self._session.get(self._provider_config.get_ca_cert_uri(), - verify=not self._bypass_checks, + verify=self.verify, timeout=REQUEST_TIMEOUT) res.raise_for_status() diff --git a/src/leap/bitmask/config/__init__.py b/src/leap/bitmask/provider/tests/__init__.py index e69de29b..e69de29b 100644 --- a/src/leap/bitmask/config/__init__.py +++ b/src/leap/bitmask/provider/tests/__init__.py diff --git a/src/leap/bitmask/services/eip/tests/test_providerbootstrapper.py b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py index b0685676..88a4ff0b 100644 --- a/src/leap/bitmask/services/eip/tests/test_providerbootstrapper.py +++ b/src/leap/bitmask/provider/tests/test_providerbootstrapper.py @@ -14,15 +14,12 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - - """ 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 @@ -39,13 +36,13 @@ 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.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.common.files import mkdir_p from leap.common.testing.https_server import where from leap.common.testing.basetest import BaseLeapTest @@ -319,16 +316,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 @@ -376,10 +376,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() @@ -404,10 +405,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(util.get_path_prefix(), + "leap", "providers", self.pb._domain) mkdir_p(provider_dir) provider_path = os.path.join(provider_dir, @@ -417,6 +416,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) @@ -435,10 +437,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) @@ -454,10 +453,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) @@ -473,10 +469,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) @@ -488,11 +481,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', @@ -503,10 +493,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..f9456159 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,10 +105,11 @@ 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(util.get_path_prefix(), "leap", "providers", provider_config.get_domain(), service_json)) + if download_if_needed and mtime: headers['if-modified-since'] = mtime @@ -125,10 +126,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, 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/__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/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): 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 <http://www.gnu.org/licenses/>. +""" +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/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 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): 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 <http://www.gnu.org/licenses/>. +""" +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/tests/test_eipbootstrapper.py b/src/leap/bitmask/services/eip/tests/test_eipbootstrapper.py index d0d78eed..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()) @@ -150,10 +151,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 @@ -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") 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() 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 <http://www.gnu.org/licenses/>. +""" +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/vpnlaunchers.py b/src/leap/bitmask/services/eip/vpnlaunchers.py deleted file mode 100644 index daa0d81f..00000000 --- a/src/leap/bitmask/services/eip/vpnlaunchers.py +++ /dev/null @@ -1,963 +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 <http://www.gnu.org/licenses/>. - -""" -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.UPDOWN_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) - - 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=None, providerconfig=None, - socket_host=None, 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) - - Might raise: - VPNLauncherException, - OpenVPNNotFoundException. - - :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!") - - 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 = [] - - pkexec = self.maybe_pkexec() - if pkexec: - args.append(openvpn) - openvpn = first(pkexec) - - 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] - - ############################################################## - # 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 - # ] - - if socket_port == "unix": # that's always the case for linux - args += [ - '--management-client-user', getpass.getuser() - ] - - args += [ - '--management-signal', - '--management', socket_host, socket_port, - '--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 plugin_path and _has_updown_scripts(self.UP_DOWN_PATH): - 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 - ] - - 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 { - "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 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 <http://www.gnu.org/licenses/>. +""" +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 diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index cac91440..7968dd6a 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -14,27 +14,28 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. - """ Soledad bootstrapping """ - import logging import os import socket +from ssl import SSLError + from PySide import QtCore 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.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.common.files import which from leap.keymanager import KeyManager, openpgp from leap.keymanager.errors import KeyNotFound from leap.soledad.client import Soledad @@ -42,17 +43,28 @@ 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 """ - 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} @@ -68,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 @@ -109,68 +122,169 @@ 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() + # TODO this method is still too large + 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, and catch it properly! + 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 = self.MAX_SYNC_RETRIES + 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() + + # 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 + 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 + :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: + 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 + + # 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_timeout.emit() + + # unrecoverable + 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): """ @@ -179,86 +293,57 @@ 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() - - 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, - "We need a provider configuration!") - - 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: + 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 + + 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(),), @@ -269,15 +354,46 @@ 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) + 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, 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 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 <http://www.gnu.org/licenses/>. SIGNUP_TIMEOUT = 5 -REQUEST_TIMEOUT = 10 +REQUEST_TIMEOUT = 15 |