summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2013-11-15 10:35:43 -0300
committerTomás Touceda <chiiph@leap.se>2013-11-15 10:35:43 -0300
commitd51d6bedfecd41491f2c8243235e7d3db043a4d7 (patch)
tree3d6152565be983d0e6ddc1fb0ce764bee060fc25
parent23e9034664edf6b2a01a677c4fa4e6efd880e364 (diff)
parent72f53cc32bf20b00bcbd5f28bab5fc25250215bf (diff)
Merge branch 'release-0.3.7'0.3.7
-rw-r--r--CHANGELOG.rst27
-rw-r--r--Makefile2
-rw-r--r--changes/log_eip_status1
-rw-r--r--docs/dev/encodings.rst64
-rw-r--r--docs/dev/quickstart.rst2
-rw-r--r--docs/index.rst1
-rw-r--r--pkg/requirements.pip4
-rwxr-xr-xpkg/scripts/bootstrap_develop.sh26
-rw-r--r--relnotes.txt19
-rw-r--r--src/leap/bitmask/crypto/srpauth.py1
-rw-r--r--src/leap/bitmask/crypto/srpregister.py6
-rw-r--r--src/leap/bitmask/gui/advanced_key_management.py185
-rw-r--r--src/leap/bitmask/gui/eip_status.py30
-rw-r--r--src/leap/bitmask/gui/loggerwindow.py3
-rw-r--r--src/leap/bitmask/gui/mail_status.py33
-rw-r--r--src/leap/bitmask/gui/mainwindow.py75
-rw-r--r--src/leap/bitmask/gui/preferenceswindow.py35
-rw-r--r--src/leap/bitmask/gui/statemachines.py2
-rw-r--r--src/leap/bitmask/gui/systray.py49
-rw-r--r--src/leap/bitmask/gui/ui/advanced_key_management.ui153
-rw-r--r--src/leap/bitmask/gui/ui/mainwindow.ui13
-rw-r--r--src/leap/bitmask/gui/wizard.py83
-rw-r--r--src/leap/bitmask/services/__init__.py4
-rw-r--r--src/leap/bitmask/services/mail/conductor.py42
-rw-r--r--src/leap/bitmask/services/soledad/soledadbootstrapper.py61
25 files changed, 758 insertions, 163 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 09ea992e..4fc4246e 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -3,7 +3,34 @@
History
-------
+0.3.7 Nov 15 -- the "The Big Lebowsky" release:
++++++++++++++++++++++++++++++++++++++++++++++++
+- Use custom SysTray in order to display per-service tooltip easily.
+ Closes #3998.
+- Escape logs with html contents so they get displayed in plaintext
+ on the log viewer. Closes #4146.
+- Wizard now behaves correctly in provider selection after click
+ 'cancel' or 'back'. Closes #4148.
+- Handle Timeout errors during register process. Closes #4358.
+- Send user's key to nickserver whenever keymanager is
+ initialized. Closes #4364.
+- Password change dialog is now properly enabled. Closes #4449.
+- Remember provider checks in wizard, do not re-run them if the user
+ goes back and forth through the wizard. Closes #3814 and #3815.
+- Improve compatibility with OSX Mavericks. Fixes #4379.
+- Initialize mail service with the userid after login, to allow
+ multiple accounts. Closes: #4394
+- Give SMTP the current logged in userid. Related to #3952.
+- Do not wait for initial soledad sync to complete to launch mail
+ services. Closes: #4452
+- Add hint to user about the duration of the key generation. Closes
+ #3958.
+- Add advanced key management feature. Closes #4448.
+- Properly log EIP status changes.
+
0.3.6 Nov 1 -- the "bạn có thể đọc này?" release:
++++++++++++++++++++++++++++++++++++++++++++++++++
+
- Fix problem changing a non-ascii password. Closes #4003.
- Enable password change in the client only if it has started the
correct services. Closes #4093.
diff --git a/Makefile b/Makefile
index 1b4eed5d..00fec2ed 100644
--- a/Makefile
+++ b/Makefile
@@ -20,7 +20,7 @@ TRANSLAT_DIR = data/translations
PROJFILE = data/bitmask.pro
#UI files to compile
-UI_FILES = loggerwindow.ui mainwindow.ui wizard.ui login.ui preferences.ui eip_status.ui mail_status.ui eippreferences.ui
+UI_FILES = loggerwindow.ui mainwindow.ui wizard.ui login.ui preferences.ui eip_status.ui mail_status.ui eippreferences.ui advanced_key_management.ui
#Qt resource files to compile
RESOURCES = locale.qrc loggerwindow.qrc mainwindow.qrc icons.qrc
diff --git a/changes/log_eip_status b/changes/log_eip_status
deleted file mode 100644
index 32ac2b42..00000000
--- a/changes/log_eip_status
+++ /dev/null
@@ -1 +0,0 @@
-- Properly log EIP status changes.
diff --git a/docs/dev/encodings.rst b/docs/dev/encodings.rst
new file mode 100644
index 00000000..a3fc4b70
--- /dev/null
+++ b/docs/dev/encodings.rst
@@ -0,0 +1,64 @@
+.. _encodings:
+
+Strings encoding problems
+=========================
+
+This document is meant to avoid ``UnicodeError`` (``UnicodeEncodeError`` , ``UnicodeDecodeError``) and to set a base that allows the users to keep away headaches.
+
+
+First approach
+--------------
+
+One of the problems with python 2 that makes hard to find out problems is the implicit conversion between ``str`` and ``unicode``.
+
+Look at this code::
+
+ >>> u'ä'.encode('utf-8')
+ '\xc3\xa4'
+ >>>
+ >>> u'ä'.decode('utf-8')
+ Traceback (most recent call last):
+ File "<stdin>", line 1, in <module>
+ File "/usr/lib/python2.7/encodings/utf_8.py", line 16, in decode
+ return codecs.utf_8_decode(input, errors, True)
+ UnicodeEncodeError: 'ascii' codec can't encode character u'\xe4' in position 0: ordinal not in range(128)
+
+A situation like this could happen if the user confuse one type for another. 'encode' is a method of ``unicode`` and 'decode' is a method of ``str``, since you call 'decode', python "knows" how to convert from ``unicode`` to ``str`` and then call the 'decode' method, *that* conversion is made with the safe default "ascii" which raises an exception.
+
+
+We need to know which one we are using **every time**. A possible way to avoid mistakes is to use ``leap_assert_type`` at the beginning of each method that has a ``str``/``unicode`` parameter.
+The best approach we need to use ``unicode`` internally and when we read/write/transmit data, encode it to bytes (``str``).
+
+
+Examples of problems found
+--------------------------
+
+* **logging data**: ``logger.debug("some string {0}".format(some_data))`` may fail if we have an ``unicode`` parameter because of the conversion needed to output it.
+ We need to use ``repr(some_data)`` to avoid encoding problems when sending data to the stdout. An easy way to do it is: ``logger.debug("some string {0!r}".format(some_data))``
+
+- **paths encoding**: we should return always ``unicode`` values from helpers and encode them when we need to use it.
+ The stdlib handles correctly ``unicode`` strings path parameters.
+ If we want to do something else with the paths, we need to convert them manually using the system encoding.
+
+Regarding the encoding, use a hardcoded encoding may be wrong.
+Instead of encode/decode using for instance 'utf-8', we should use this ``sys.getfilesystemencoding()``
+
+For the data stored in a db (or something that is some way isolated from the system) we may want to choose 'utf-8' explicitly.
+
+Steps to improve code
+---------------------
+
+#. From now on, keep in mind the difference between ``str`` and ``unicode`` and write code consequently.
+#. For each method we can add a ``leap_assert_type(parameter_name, unicode)`` (or ``str``) to avoid type problems.
+#. Each time that is possible move towards the unicode 'frontier' (``unicode`` inside, ``str`` (bytes) outside).
+#. When is possible update the methods parameters in order to be certain of the data types that we are handling.
+
+Recommended info
+----------------
+
+* PyCon 2012 talk: https://www.youtube.com/watch?v=sgHbC6udIqc
+ * article and transcription: http://nedbatchelder.com/text/unipain.html
+* PyConAr 2012 (Spanish): http://www.youtube.com/watch?v=pQJ0emlYv50
+* Overcoming frustrations: http://pythonhosted.org/kitchen/unicode-frustrations.html
+* Python's Unicode howto: http://docs.python.org/2/howto/unicode.html
+* An encoding primer: http://www.danielmiessler.com/study/encoding/
diff --git a/docs/dev/quickstart.rst b/docs/dev/quickstart.rst
index 8ef7dfb8..96dcaeb9 100644
--- a/docs/dev/quickstart.rst
+++ b/docs/dev/quickstart.rst
@@ -61,7 +61,7 @@ also install the needed dependencies::
Compile the resource files::
- (bitmask)$ make resources
+ (bitmask)$ make
Copy necessary files into system folders, with root privileges::
diff --git a/docs/index.rst b/docs/index.rst
index f210be8c..acf97ff3 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -50,6 +50,7 @@ If you want to contribute to the project, we wrote this for you.
dev/workflow
dev/resources
dev/internationalization
+ dev/encodings
.. dev/internals
dev/authors
diff --git a/pkg/requirements.pip b/pkg/requirements.pip
index 885b19f8..04cd33de 100644
--- a/pkg/requirements.pip
+++ b/pkg/requirements.pip
@@ -20,8 +20,8 @@ zope.proxy
leap.common>=0.3.4
leap.soledad.client>=0.4.2
-leap.keymanager>=0.3.4
-leap.mail>=0.3.6
+leap.keymanager>=0.3.6
+leap.mail>=0.3.7
# Remove this when u1db fixes its dependency on oauth
oauth
diff --git a/pkg/scripts/bootstrap_develop.sh b/pkg/scripts/bootstrap_develop.sh
index 4660c499..07063a81 100755
--- a/pkg/scripts/bootstrap_develop.sh
+++ b/pkg/scripts/bootstrap_develop.sh
@@ -38,12 +38,21 @@ clone_repos() {
echo "${cc_green}Status: $status...${cc_normal}"
set -x # show commands
- # clone the leap.se repos
- git clone ssh://gitolite@leap.se/bitmask_client
- git clone ssh://gitolite@leap.se/leap_pycommon
- git clone ssh://gitolite@leap.se/soledad
- git clone ssh://gitolite@leap.se/keymanager
- git clone ssh://gitolite@leap.se/leap_mail
+ if [[ "$1" == "ro" ]]; then
+ # read-only remotes:
+ git clone https://leap.se/git/bitmask_client
+ git clone https://leap.se/git/leap_pycommon
+ git clone https://leap.se/git/soledad
+ git clone https://leap.se/git/keymanager
+ git clone https://leap.se/git/leap_mail
+ else
+ # read-write remotes:
+ git clone ssh://gitolite@leap.se/bitmask_client
+ git clone ssh://gitolite@leap.se/leap_pycommon
+ git clone ssh://gitolite@leap.se/soledad
+ git clone ssh://gitolite@leap.se/keymanager
+ git clone ssh://gitolite@leap.se/leap_mail
+ fi
set +x
echo "${cc_green}Status: $status done!${cc_normal}"
@@ -129,7 +138,7 @@ finish(){
}
initialize() {
- clone_repos
+ clone_repos $1
checkout_develop
create_venv
setup_develop
@@ -169,6 +178,7 @@ help() {
echo "Usage: $0 {init | update | help}"
echo
echo " init : Initialize repositories, create virtualenv and \`python setup.py develop\` all."
+ echo " You can use \`init ro\` in order to use the https remotes if you don't have rw access."
echo " update : Update the repositories and install new deps (if needed)."
echo " run : Runs the client (any extra parameters will be sent to the app)."
echo " help : Show this help"
@@ -177,7 +187,7 @@ help() {
case "$1" in
init)
- initialize
+ initialize $2
;;
update)
update
diff --git a/relnotes.txt b/relnotes.txt
index 2e32ca6c..99898aa2 100644
--- a/relnotes.txt
+++ b/relnotes.txt
@@ -1,8 +1,8 @@
-ANNOUNCING Bitmask, the internet encryption toolkit, release 0.3.5
+ANNOUNCING Bitmask, the internet encryption toolkit, release 0.3.7
The LEAP team is pleased to announce the immediate availability of
-version 0.3.5 of Bitmask, the Internet Encryption Toolkit, codename
-"I can stand on one foot".
+version 0.3.7 of Bitmask, the Internet Encryption Toolkit, codename
+"The Big Lebowsky".
https://downloads.leap.se/client/
@@ -34,14 +34,9 @@ NOT trust your life to it (yet!).
WHAT CAN THIS VERSION OF BITMASK DO FOR ME?
-Bitmask 0.3.5 is a bugfix release but it also has improvements to the
-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 again,
-better mail support, with detached signatures and compliance with a
-couple of RFC. You can refer to the CHANGELOG for the meat.
-
-We also re-added support for Windows, although it might be a bumpy
-ride until we smooth the corners a little bit more.
+Bitmask 0.3.7 improves greatly its mail support and stability in
+general, among other various bug fixes. 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
@@ -98,6 +93,6 @@ beyond any border.
The LEAP team,
-Oct 18, 2013
+Nov 15, 2013
Somewhere in the middle of the intertubes.
EOF
diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py
index ab98850d..85b9b003 100644
--- a/src/leap/bitmask/crypto/srpauth.py
+++ b/src/leap/bitmask/crypto/srpauth.py
@@ -581,6 +581,7 @@ class SRPAuth(QtCore.QObject):
else:
self.set_session_id(None)
self.set_uid(None)
+ self.set_token(None)
# Also reset the session
self._session = self._fetcher.session()
logger.debug("Successfully logged out.")
diff --git a/src/leap/bitmask/crypto/srpregister.py b/src/leap/bitmask/crypto/srpregister.py
index 5c0a8efc..02a1ea63 100644
--- a/src/leap/bitmask/crypto/srpregister.py
+++ b/src/leap/bitmask/crypto/srpregister.py
@@ -108,7 +108,8 @@ class SRPRegister(QtCore.QObject):
:rparam: (ok, request)
"""
- username = username.lower()
+ username = username.lower().encode('utf-8')
+ password = password.encode('utf-8')
salt, verifier = self._srp.create_salted_verification_key(
username,
@@ -140,8 +141,7 @@ class SRPRegister(QtCore.QObject):
verify=self._provider_config.
get_ca_cert_path())
- except (requests.exceptions.SSLError,
- requests.exceptions.ConnectionError) as exc:
+ except requests.exceptions.RequestException as exc:
logger.error(exc.message)
ok = False
else:
diff --git a/src/leap/bitmask/gui/advanced_key_management.py b/src/leap/bitmask/gui/advanced_key_management.py
new file mode 100644
index 00000000..2c0fa034
--- /dev/null
+++ b/src/leap/bitmask/gui/advanced_key_management.py
@@ -0,0 +1,185 @@
+# -*- coding: utf-8 -*-
+# advanced_key_management.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/>.
+"""
+Advanced Key Management
+"""
+import logging
+
+from PySide import QtGui
+from zope.proxy import sameProxiedObjects
+
+from leap.keymanager import openpgp
+from leap.keymanager.errors import KeyAddressMismatch, KeyFingerprintMismatch
+from leap.bitmask.services import get_service_display_name, MX_SERVICE
+from ui_advanced_key_management import Ui_AdvancedKeyManagement
+
+logger = logging.getLogger(__name__)
+
+
+class AdvancedKeyManagement(QtGui.QWidget):
+ """
+ Advanced Key Management
+ """
+ def __init__(self, user, keymanager, soledad):
+ """
+ :param user: the current logged in user.
+ :type user: unicode
+ :param keymanager: the existing keymanager instance
+ :type keymanager: KeyManager
+ :param soledad: a loaded instance of Soledad
+ :type soledad: Soledad
+ """
+ QtGui.QWidget.__init__(self)
+
+ self.ui = Ui_AdvancedKeyManagement()
+ self.ui.setupUi(self)
+
+ # if Soledad is not started yet
+ if sameProxiedObjects(soledad, None):
+ self.ui.container.setEnabled(False)
+ msg = self.tr("<span style='color:#0000FF;'>NOTE</span>: "
+ "To use this, you need to enable/start {0}.")
+ msg = msg.format(get_service_display_name(MX_SERVICE))
+ self.ui.lblStatus.setText(msg)
+ return
+ else:
+ msg = self.tr(
+ "<span style='color:#ff0000;'>WARNING</span>:<br>"
+ "This is an experimental feature, you can lose access to "
+ "existing e-mails.")
+ self.ui.lblStatus.setText(msg)
+
+ self._keymanager = keymanager
+ self._soledad = soledad
+
+ self._key = keymanager.get_key(user, openpgp.OpenPGPKey)
+ self._key_priv = keymanager.get_key(
+ user, openpgp.OpenPGPKey, private=True)
+
+ # show current key information
+ self.ui.leUser.setText(user)
+ self.ui.leKeyID.setText(self._key.key_id)
+ self.ui.leFingerprint.setText(self._key.fingerprint)
+
+ # set up connections
+ self.ui.pbImportKeys.clicked.connect(self._import_keys)
+ self.ui.pbExportKeys.clicked.connect(self._export_keys)
+
+ def _import_keys(self):
+ """
+ Imports the user's key pair.
+ Those keys need to be ascii armored.
+ """
+ fileName, filtr = QtGui.QFileDialog.getOpenFileName(
+ self, self.tr("Open keys file"),
+ options=QtGui.QFileDialog.DontUseNativeDialog)
+
+ if fileName:
+ new_key = ''
+ try:
+ with open(fileName, 'r') as keys_file:
+ new_key = keys_file.read()
+ except IOError as e:
+ logger.error("IOError importing key. {0!r}".format(e))
+ QtGui.QMessageBox.critical(
+ self, self.tr("Input/Output error"),
+ self.tr("There was an error accessing the file.\n"
+ "Import canceled."))
+ return
+
+ keymanager = self._keymanager
+ try:
+ public_key, private_key = keymanager.parse_openpgp_ascii_key(
+ new_key)
+ except (KeyAddressMismatch, KeyFingerprintMismatch) as e:
+ logger.error(repr(e))
+ QtGui.QMessageBox.warning(
+ self, self.tr("Data mismatch"),
+ self.tr("The public and private key should have the "
+ "same address and fingerprint.\n"
+ "Import canceled."))
+ return
+
+ if public_key is None or private_key is None:
+ QtGui.QMessageBox.warning(
+ self, self.tr("Missing key"),
+ self.tr("You need to provide the public AND private "
+ "key in the same file.\n"
+ "Import canceled."))
+ return
+
+ if public_key.address != self._key.address:
+ logger.error("The key does not match the ID")
+ QtGui.QMessageBox.warning(
+ self, self.tr("Address mismatch"),
+ self.tr("The identity for the key needs to be the same "
+ "as your user address.\n"
+ "Import canceled."))
+ return
+
+ question = self.tr("Are you sure that you want to replace "
+ "the current key pair whith the imported?")
+ res = QtGui.QMessageBox.question(
+ None, "Change key pair", question,
+ QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
+ QtGui.QMessageBox.No) # default No
+
+ if res == QtGui.QMessageBox.No:
+ return
+
+ keymanager.delete_key(self._key)
+ keymanager.delete_key(self._key_priv)
+ keymanager.put_key(public_key)
+ keymanager.put_key(private_key)
+ keymanager.send_key(openpgp.OpenPGPKey)
+
+ logger.debug('Import ok')
+
+ QtGui.QMessageBox.information(
+ self, self.tr("Import Successful"),
+ self.tr("The key pair was imported successfully."))
+ else:
+ logger.debug('Import canceled by the user.')
+
+ def _export_keys(self):
+ """
+ Exports the user's key pair.
+ """
+ fileName, filtr = QtGui.QFileDialog.getSaveFileName(
+ self, self.tr("Save keys file"),
+ options=QtGui.QFileDialog.DontUseNativeDialog)
+
+ if fileName:
+ try:
+ with open(fileName, 'w') as keys_file:
+ keys_file.write(self._key.key_data)
+ keys_file.write(self._key_priv.key_data)
+
+ logger.debug('Export ok')
+ QtGui.QMessageBox.information(
+ self, self.tr("Export Successful"),
+ self.tr("The key pair was exported successfully.\n"
+ "Please, store your private key in a safe place."))
+ except IOError as e:
+ logger.error("IOError exporting key. {0!r}".format(e))
+ QtGui.QMessageBox.critical(
+ self, self.tr("Input/Output error"),
+ self.tr("There was an error accessing the file.\n"
+ "Export canceled."))
+ return
+ else:
+ logger.debug('Export canceled by the user.')
diff --git a/src/leap/bitmask/gui/eip_status.py b/src/leap/bitmask/gui/eip_status.py
index 324586c0..1899d6a4 100644
--- a/src/leap/bitmask/gui/eip_status.py
+++ b/src/leap/bitmask/gui/eip_status.py
@@ -26,6 +26,7 @@ from PySide import QtCore, QtGui
from leap.bitmask.services.eip.connection import EIPConnection
from leap.bitmask.services.eip.vpnprocess import VPNManager
+from leap.bitmask.services import get_service_display_name, EIP_SERVICE
from leap.bitmask.platform_init import IS_LINUX
from leap.bitmask.util.averages import RateMovingAverage
from leap.common.check import leap_assert_type
@@ -58,6 +59,7 @@ class EIPStatusWidget(QtGui.QWidget):
# set systray tooltip status
self._eip_status = ""
+ self._service_name = get_service_display_name(EIP_SERVICE)
self.ui.eip_bandwidth.hide()
@@ -181,21 +183,24 @@ class EIPStatusWidget(QtGui.QWidget):
def set_systray(self, systray):
"""
- Sets the systray object to use.
+ Sets the systray object to use and adds the service line for EIP.
: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"))
+ eip_status = self.tr("{0}: OFF").format(self._service_name)
+ self._systray.set_service_tooltip(EIP_SERVICE, eip_status)
def _update_systray_tooltip(self):
"""
- Updates the system tray icon tooltip using the eip and mx status.
+ Updates the system tray tooltip using the eip status.
"""
- status = self.tr("Encrypted Internet: {0}").format(self._eip_status)
- self._systray.setToolTip(status)
+ if self._systray is not None:
+ eip_status = u"{0}: {1}".format(
+ self._service_name, self._eip_status)
+ self._systray.set_service_tooltip(EIP_SERVICE, eip_status)
def set_action_eip_startstop(self, action_eip_startstop):
"""
@@ -245,7 +250,7 @@ class EIPStatusWidget(QtGui.QWidget):
# 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")
+ msg = self.tr("You must login to use {0}".format(self._service_name))
self.eip_label.setText(msg)
@QtCore.Slot()
@@ -393,10 +398,8 @@ class EIPStatusWidget(QtGui.QWidget):
# 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.")))
+ msg = self.tr("Unable to start VPN, it's already running.")
+ QtCore.QTimer.singleShot(0, partial(self.set_eip_status, msg))
else:
self.set_eip_status(status)
@@ -418,14 +421,15 @@ class EIPStatusWidget(QtGui.QWidget):
"""
selected_pixmap = self.ERROR_ICON
selected_pixmap_tray = self.ERROR_ICON_TRAY
- tray_message = self.tr("Encrypted Internet: OFF")
+ tray_message = self.tr("{0}: OFF".format(self._service_name))
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...")
+ tray_message = self.tr("{0}: Starting...").format(
+ self._service_name)
elif status in ("CONNECTED"):
- tray_message = self.tr("Encrypted Internet: ON")
+ tray_message = self.tr("{0}: ON".format(self._service_name))
selected_pixmap = self.CONNECTED_ICON
selected_pixmap_tray = self.CONNECTED_ICON_TRAY
self._eip_status = 'ON'
diff --git a/src/leap/bitmask/gui/loggerwindow.py b/src/leap/bitmask/gui/loggerwindow.py
index ad2ceded..6ef58558 100644
--- a/src/leap/bitmask/gui/loggerwindow.py
+++ b/src/leap/bitmask/gui/loggerwindow.py
@@ -19,6 +19,7 @@
History log window
"""
import logging
+import cgi
from PySide import QtGui
@@ -90,7 +91,7 @@ class LoggerWindow(QtGui.QDialog):
logging.CRITICAL: "background: red; color: white; font: bold;"
}
level = log[LeapLogHandler.RECORD_KEY].levelno
- message = log[LeapLogHandler.MESSAGE_KEY]
+ message = cgi.escape(log[LeapLogHandler.MESSAGE_KEY])
if self._logs_to_display[level]:
open_tag = "<tr style='" + html_style[level] + "'>"
diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py
index c1e82d4d..3c933c9a 100644
--- a/src/leap/bitmask/gui/mail_status.py
+++ b/src/leap/bitmask/gui/mail_status.py
@@ -22,6 +22,7 @@ import logging
from PySide import QtCore, QtGui
from leap.bitmask.platform_init import IS_LINUX
+from leap.bitmask.services import get_service_display_name, MX_SERVICE
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
@@ -58,6 +59,7 @@ class MailStatusWidget(QtGui.QWidget):
# set systray tooltip status
self._mx_status = ""
+ self._service_name = get_service_display_name(MX_SERVICE)
# Set the Mail status icons
self.CONNECTING_ICON = None
@@ -150,29 +152,23 @@ class MailStatusWidget(QtGui.QWidget):
def set_systray(self, systray):
"""
- Sets the systray object to use.
+ Sets the systray object to use and adds the service line for MX.
: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"))
+ mx_status = self.tr("{0}: OFF").format(self._service_name)
+ self._systray.set_service_tooltip(MX_SERVICE, mx_status)
def _update_systray_tooltip(self):
"""
- Updates the system tray icon tooltip using the eip and mx status.
+ Updates the system tray tooltip using the mx status.
"""
- # TODO: Figure out how to handle this with the two status in different
- # classes
- # XXX right now we could connect the state transition signals of the
- # two connection machines (EIP/Mail) to a class that keeps track of the
- # state -- kali
- # 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
+ if self._systray is not None:
+ mx_status = u"{0}: {1}".format(self._service_name, self._mx_status)
+ self._systray.set_service_tooltip(MX_SERVICE, mx_status)
def set_action_mail_status(self, action_mail_status):
"""
@@ -213,7 +209,8 @@ class MailStatusWidget(QtGui.QWidget):
icon = self.ERROR_ICON
if ready == 0:
self.ui.lblMailStatus.setText(
- self.tr("You must be logged in to use encrypted email."))
+ self.tr("You must be logged in to use {0}.").format(
+ self._service_name))
elif ready == 1:
icon = self.CONNECTING_ICON
self._mx_status = self.tr('Starting..')
@@ -296,7 +293,8 @@ class MailStatusWidget(QtGui.QWidget):
# 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...")
+ ext_status = self.tr(
+ "Generating new key, this may take a few minutes.")
elif req.event == proto.KEYMANAGER_FINISHED_KEY_GENERATION:
ext_status = self.tr("Finished generating key!")
elif req.event == proto.KEYMANAGER_DONE_UPLOADING_KEYS:
@@ -434,5 +432,6 @@ class MailStatusWidget(QtGui.QWidget):
Displays the correct UI for the disabled state.
"""
self._disabled = True
- self._set_mail_status(
- self.tr("You must be logged in to use encrypted email."), -1)
+ status = self.tr("You must be logged in to use {0}.").format(
+ self._service_name)
+ self._set_mail_status(status, -1)
diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py
index 5eb9e6dc..b0f25af1 100644
--- a/src/leap/bitmask/gui/mainwindow.py
+++ b/src/leap/bitmask/gui/mainwindow.py
@@ -29,6 +29,7 @@ from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.crypto.srpauth import SRPAuth
from leap.bitmask.gui.loggerwindow import LoggerWindow
+from leap.bitmask.gui.advanced_key_management import AdvancedKeyManagement
from leap.bitmask.gui.login import LoginWidget
from leap.bitmask.gui.preferenceswindow import PreferencesWindow
from leap.bitmask.gui.eip_preferenceswindow import EIPPreferencesWindow
@@ -36,6 +37,7 @@ from leap.bitmask.gui import statemachines
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.gui.systray import SysTray
from leap.bitmask import provider
from leap.bitmask.platform_init import IS_WIN, IS_MAC
@@ -44,6 +46,7 @@ from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper
from leap.bitmask.services.mail import conductor as mail_conductor
+from leap.bitmask.services import EIP_SERVICE, MX_SERVICE
from leap.bitmask.services.eip import eipconfig
from leap.bitmask.services.eip import get_openvpn_management
from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper
@@ -86,9 +89,6 @@ class MainWindow(QtGui.QMainWindow):
LOGIN_INDEX = 0
EIP_STATUS_INDEX = 1
- OPENVPN_SERVICE = "openvpn"
- MX_SERVICE = "mx"
-
# Signals
eip_needs_login = QtCore.Signal([])
new_updates = QtCore.Signal(object)
@@ -253,6 +253,9 @@ class MainWindow(QtGui.QMainWindow):
self.ui.action_create_new_account.triggered.connect(
self._launch_wizard)
+ self.ui.action_advanced_key_management.triggered.connect(
+ self._show_AKM)
+
if IS_MAC:
self.ui.menuFile.menuAction().setText(self.tr("File"))
@@ -334,7 +337,7 @@ class MainWindow(QtGui.QMainWindow):
self.eip_machine = None
# start event machines
self.start_eip_machine()
- self._mail_conductor.start_mail_machine(parent=self)
+ self._mail_conductor.start_mail_machine()
if self._first_run():
self._wizard_firstrun = True
@@ -438,6 +441,20 @@ class MainWindow(QtGui.QMainWindow):
else:
self._logger_window.setVisible(not self._logger_window.isVisible())
+ def _show_AKM(self):
+ """
+ SLOT
+ TRIGGERS:
+ self.ui.action_advanced_key_management.triggered
+
+ Displays the Advanced Key Management dialog.
+ """
+ domain = self._login_widget.get_selected_provider()
+ logged_user = "{0}@{1}".format(self._logged_user, domain)
+ self._akm = AdvancedKeyManagement(
+ logged_user, self._keymanager, self._soledad)
+ self._akm.show()
+
def _show_preferences(self):
"""
SLOT
@@ -447,11 +464,10 @@ class MainWindow(QtGui.QMainWindow):
Displays the preferences window.
"""
preferences_window = PreferencesWindow(self, self._srp_auth,
- self._provider_config)
-
- self.soledad_ready.connect(
- lambda: preferences_window.set_soledad_ready(self._soledad))
+ self._provider_config,
+ self._soledad)
+ self.soledad_ready.connect(preferences_window.set_soledad_ready)
preferences_window.show()
def _show_eip_preferences(self):
@@ -597,8 +613,8 @@ class MainWindow(QtGui.QMainWindow):
for service in provider_config.get_services():
services.add(service)
- self.ui.eipWidget.setVisible(self.OPENVPN_SERVICE in services)
- self.ui.mailWidget.setVisible(self.MX_SERVICE in services)
+ self.ui.eipWidget.setVisible(EIP_SERVICE in services)
+ self.ui.mailWidget.setVisible(MX_SERVICE in services)
#
# systray
@@ -628,12 +644,13 @@ class MainWindow(QtGui.QMainWindow):
systrayMenu.addAction(self._action_mail_status)
systrayMenu.addSeparator()
systrayMenu.addAction(self.ui.action_quit)
- self._systray = QtGui.QSystemTrayIcon(self)
+ self._systray = SysTray(self)
self._systray.setContextMenu(systrayMenu)
self._systray.setIcon(self._eip_status.ERROR_ICON_TRAY)
self._systray.setVisible(True)
self._systray.activated.connect(self._tray_activated)
+ self._mail_status.set_systray(self._systray)
self._eip_status.set_systray(self._systray)
def _tray_activated(self, reason=None):
@@ -674,10 +691,9 @@ class MainWindow(QtGui.QMainWindow):
Toggles the window visibility
"""
visible = self.isVisible() and self.isActiveWindow()
- qApp = QtCore.QCoreApplication.instance()
if not visible:
- qApp.setQuitOnLastWindowClosed(True)
+ QtGui.QApplication.setQuitOnLastWindowClosed(True)
self.show()
self.activateWindow()
self.raise_()
@@ -685,7 +701,7 @@ class MainWindow(QtGui.QMainWindow):
# We set this in order to avoid dialogs shutting down the
# app on close, as they will be the only visible window.
# e.g.: PreferencesWindow, LoggerWindow
- qApp.setQuitOnLastWindowClosed(False)
+ QtGui.QApplication.setQuitOnLastWindowClosed(False)
self.hide()
# Wait a bit until the window visibility has changed so
@@ -905,7 +921,6 @@ class MainWindow(QtGui.QMainWindow):
Once the user is properly authenticated, try starting the EIP
service
"""
-
# In general we want to "filter" likely complicated error
# messages, but in this case, the messages make more sense as
# they come. Since they are "Unknown user" or "Unknown
@@ -914,19 +929,19 @@ class MainWindow(QtGui.QMainWindow):
if ok:
self._logged_user = self._login_widget.get_user()
- # We leave a bit of room for the user to see the
- # "Succeeded" message and then we switch to the EIP status
- # panel
- QtCore.QTimer.singleShot(1000, self._switch_to_status)
+ user = self._logged_user
+ domain = self._provider_config.get_domain()
+ userid = "%s@%s" % (user, domain)
+ self._mail_conductor.userid = userid
self._login_defer = None
+ self._start_eip_bootstrap()
else:
self._login_widget.set_enabled(True)
- def _switch_to_status(self):
- # TODO this method name is confusing as hell.
+ def _start_eip_bootstrap(self):
"""
Changes the stackedWidget index to the EIP status one and
- triggers the eip bootstrapping
+ triggers the eip bootstrapping.
"""
self._login_widget.logged_in()
@@ -938,7 +953,7 @@ class MainWindow(QtGui.QMainWindow):
# TODO separate UI from logic.
# TODO soledad should check if we want to run only over EIP.
if self._provider_config.provides_mx() and \
- self._enabled_services.count(self.MX_SERVICE) > 0:
+ self._enabled_services.count(MX_SERVICE) > 0:
self._mail_status.about_to_start()
self._soledad_bootstrapper.run_soledad_setup_checks(
@@ -949,6 +964,8 @@ class MainWindow(QtGui.QMainWindow):
else:
self._mail_status.set_disabled()
+ # XXX the config should be downloaded from the start_eip
+ # method.
self._download_eip_config()
###################################################################
@@ -1037,7 +1054,7 @@ class MainWindow(QtGui.QMainWindow):
# TODO for simmetry, this should be called start_smtp_service
# (and delegate all the checks to the conductor)
if self._provider_config.provides_mx() and \
- self._enabled_services.count(self.MX_SERVICE) > 0:
+ self._enabled_services.count(MX_SERVICE) > 0:
self._mail_conductor.smtp_bootstrapper.run_smtp_setup_checks(
self._provider_config,
self._mail_conductor.smtp_config,
@@ -1066,7 +1083,7 @@ class MainWindow(QtGui.QMainWindow):
self.soledad_ready
"""
if self._provider_config.provides_mx() and \
- self._enabled_services.count(self.MX_SERVICE) > 0:
+ self._enabled_services.count(MX_SERVICE) > 0:
self._mail_conductor.start_imap_service()
def _on_mail_client_logged_in(self, req):
@@ -1416,7 +1433,7 @@ class MainWindow(QtGui.QMainWindow):
provider_config = self._get_best_provider_config()
if provider_config.provides_eip() and \
- self._enabled_services.count(self.OPENVPN_SERVICE) > 0 and \
+ self._enabled_services.count(EIP_SERVICE) > 0 and \
not self._already_started_eip:
# XXX this should be handled by the state machine.
@@ -1427,7 +1444,7 @@ class MainWindow(QtGui.QMainWindow):
download_if_needed=True)
self._already_started_eip = True
elif not self._already_started_eip:
- if self._enabled_services.count(self.OPENVPN_SERVICE) > 0:
+ if self._enabled_services.count(EIP_SERVICE) > 0:
self._eip_status.set_eip_status(
self.tr("Not supported"),
error=True)
@@ -1522,6 +1539,7 @@ class MainWindow(QtGui.QMainWindow):
"""
self._soledad_bootstrapper.cancel_bootstrap()
+ setProxiedObject(self._soledad, None)
# XXX: If other defers are doing authenticated stuff, this
# might conflict with those. CHECK!
@@ -1657,8 +1675,7 @@ class MainWindow(QtGui.QMainWindow):
# UI stuff.
# Set this in case that the app is hidden
- qApp = QtCore.QCoreApplication.instance()
- qApp.setQuitOnLastWindowClosed(True)
+ QtGui.QApplication.setQuitOnLastWindowClosed(True)
self._cleanup_and_quit()
diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py
index acb39b07..8e9ef95a 100644
--- a/src/leap/bitmask/gui/preferenceswindow.py
+++ b/src/leap/bitmask/gui/preferenceswindow.py
@@ -22,7 +22,9 @@ import os
import logging
from functools import partial
+
from PySide import QtCore, QtGui
+from zope.proxy import sameProxiedObjects
from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.gui.ui_preferences import Ui_Preferences
@@ -31,7 +33,7 @@ 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 import get_service_display_name
+from leap.bitmask.services import get_service_display_name, MX_SERVICE
logger = logging.getLogger(__name__)
@@ -40,7 +42,7 @@ class PreferencesWindow(QtGui.QDialog):
"""
Window that displays the preferences.
"""
- def __init__(self, parent, srp_auth, provider_config):
+ def __init__(self, parent, srp_auth, provider_config, soledad):
"""
:param parent: parent object of the PreferencesWindow.
:parent type: QWidget
@@ -48,13 +50,15 @@ class PreferencesWindow(QtGui.QDialog):
:type srp_auth: SRPAuth
:param provider_config: ProviderConfig object.
:type provider_config: ProviderConfig
+ :param soledad: Soledad instance
+ :type soledad: Soledad
"""
QtGui.QDialog.__init__(self, parent)
self.AUTOMATIC_GATEWAY_LABEL = self.tr("Automatic")
self._srp_auth = srp_auth
self._settings = LeapSettings()
- self._soledad = None
+ self._soledad = soledad
# Load UI
self.ui = Ui_Preferences()
@@ -81,20 +85,25 @@ class PreferencesWindow(QtGui.QDialog):
# check if provider has 'mx' ...
domain = provider_config.get_domain()
self._select_provider_by_name(domain)
+
if provider_config.provides_mx():
enabled_services = self._settings.get_enabled_services(domain)
- mx_name = get_service_display_name('mx')
+ mx_name = get_service_display_name(MX_SERVICE)
# ... and if the user have it enabled
- if 'mx' not in enabled_services:
+ if MX_SERVICE not in enabled_services:
msg = self.tr("You need to enable {0} in order to change "
"the password.".format(mx_name))
self._set_password_change_status(msg, error=True)
else:
- msg = self.tr(
- "You need to wait until {0} is ready in "
- "order to change the password.".format(mx_name))
- self._set_password_change_status(msg)
+ if sameProxiedObjects(self._soledad, None):
+ msg = self.tr(
+ "You need to wait until {0} is ready in "
+ "order to change the password.".format(mx_name))
+ self._set_password_change_status(msg)
+ else:
+ # Soledad is bootstrapped
+ pw_enabled = True
else:
pw_enabled = True
else:
@@ -104,18 +113,14 @@ class PreferencesWindow(QtGui.QDialog):
self.ui.gbPasswordChange.setEnabled(pw_enabled)
- def set_soledad_ready(self, soledad):
+ def set_soledad_ready(self):
"""
SLOT
TRIGGERS:
parent.soledad_ready
- It sets the soledad object as ready to use.
-
- :param soledad: Soledad object configured in the main app.
- :type soledad: Soledad
+ It notifies when the soledad object as ready to use.
"""
- self._soledad = soledad
self.ui.lblPasswordChangeStatus.setVisible(False)
self.ui.gbPasswordChange.setEnabled(True)
diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py
index 386cb75f..93731ce0 100644
--- a/src/leap/bitmask/gui/statemachines.py
+++ b/src/leap/bitmask/gui/statemachines.py
@@ -357,7 +357,7 @@ class ConnectionMachineBuilder(object):
parent = kwargs.get('parent', None)
# 1. create machine
- machine = CompositeMachine(parent=parent)
+ machine = CompositeMachine()
# 2. create states
off = States.Off(conn.qtsigs.disconnected_signal,
diff --git a/src/leap/bitmask/gui/systray.py b/src/leap/bitmask/gui/systray.py
new file mode 100644
index 00000000..d30b5f32
--- /dev/null
+++ b/src/leap/bitmask/gui/systray.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# systray.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/>.
+"""
+Custom system tray manager.
+"""
+
+from PySide import QtGui
+
+from leap.common.check import leap_assert_type
+
+
+class SysTray(QtGui.QSystemTrayIcon):
+ """
+ Custom system tray that allows us to use a more 'intelligent' tooltip.
+ """
+
+ def __init__(self, parent=None):
+ QtGui.QSystemTrayIcon.__init__(self, parent)
+ self._services = {}
+
+ def set_service_tooltip(self, service, tooltip):
+ """
+ Sets the service tooltip.
+
+ :param service: service name identifier
+ :type service: unicode
+ :param tooltip: tooltip to display for that service
+ :type tooltip: unicode
+ """
+ leap_assert_type(service, unicode)
+ leap_assert_type(tooltip, unicode)
+
+ self._services[service] = tooltip
+ tooltip = "\n".join(self._services.values())
+ self.setToolTip(tooltip)
diff --git a/src/leap/bitmask/gui/ui/advanced_key_management.ui b/src/leap/bitmask/gui/ui/advanced_key_management.ui
new file mode 100644
index 00000000..d61aa87e
--- /dev/null
+++ b/src/leap/bitmask/gui/ui/advanced_key_management.ui
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AdvancedKeyManagement</class>
+ <widget class="QWidget" name="AdvancedKeyManagement">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>431</width>
+ <height>188</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Advanced Key Management</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_3">
+ <item row="0" column="0">
+ <widget class="QWidget" name="container" native="true">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>User:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="leUser">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>user_name@provider</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Key ID:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="leKeyID">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>key ID</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Key fingerprint:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="leFingerprint">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>fingerprint</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <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="4" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="1">
+ <widget class="QPushButton" name="pbExportKeys">
+ <property name="text">
+ <string>Export current key pair</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" rowspan="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="2" column="1">
+ <widget class="QPushButton" name="pbImportKeys">
+ <property name="text">
+ <string>Import custom key pair</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="lblStatus">
+ <property name="text">
+ <string/>
+ </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 badd291d..3b83788e 100644
--- a/src/leap/bitmask/gui/ui/mainwindow.ui
+++ b/src/leap/bitmask/gui/ui/mainwindow.ui
@@ -75,7 +75,7 @@
<x>0</x>
<y>0</y>
<width>524</width>
- <height>636</height>
+ <height>651</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -380,7 +380,7 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb
<x>0</x>
<y>0</y>
<width>524</width>
- <height>22</height>
+ <height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@@ -388,6 +388,7 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb
<string>&amp;Bitmask</string>
</property>
<addaction name="action_create_new_account"/>
+ <addaction name="action_advanced_key_management"/>
<addaction name="separator"/>
<addaction name="action_quit"/>
</widget>
@@ -439,6 +440,14 @@ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgb
<string>Create a new account...</string>
</property>
</action>
+ <action name="action_advanced_key_management">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Advanced Key Management</string>
+ </property>
+ </action>
</widget>
<resources>
<include location="../../../../../data/resources/mainwindow.qrc"/>
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index 6ba65410..5f5224ae 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -111,8 +111,9 @@ class Wizard(QtGui.QWizard):
self.currentIdChanged.connect(self._current_id_changed)
- self.ui.lnProvider.textChanged.connect(
- self._enable_check)
+ self.ui.lnProvider.textChanged.connect(self._enable_check)
+ self.ui.rbNewProvider.toggled.connect(
+ lambda x: self._enable_check())
self.ui.lblUser.returnPressed.connect(
self._focus_password)
@@ -146,6 +147,26 @@ class Wizard(QtGui.QWizard):
self._load_configured_providers()
+ self._provider_checks_ok = False
+ self._provider_setup_ok = False
+ self.finished.connect(self._wizard_finished)
+
+ @QtCore.Slot()
+ def _wizard_finished(self):
+ """
+ SLOT
+ TRIGGER:
+ self.finished
+
+ This method is called when the wizard is accepted or rejected.
+ Here we do the cleanup needed to use the wizard again reusing the
+ instance.
+ """
+ self._provider_checks_ok = False
+ self._provider_setup_ok = False
+ self.ui.lnProvider.setText('')
+ self.ui.grpCheckProvider.setVisible(False)
+
def _load_configured_providers(self):
"""
Loads the configured providers into the wizard providers combo box.
@@ -193,9 +214,22 @@ class Wizard(QtGui.QWizard):
def get_services(self):
return self._selected_services
- def _enable_check(self, text):
- self.ui.btnCheck.setEnabled(len(self.ui.lnProvider.text()) != 0)
- self._reset_provider_check()
+ @QtCore.Slot()
+ def _enable_check(self, reset=True):
+ """
+ SLOT
+ TRIGGER:
+ self.ui.lnProvider.textChanged
+
+ Enables/disables the 'check' button in the SELECT_PROVIDER_PAGE
+ depending on the lnProvider content.
+ """
+ enabled = len(self.ui.lnProvider.text()) != 0
+ enabled = enabled and self.ui.rbNewProvider.isChecked()
+ self.ui.btnCheck.setEnabled(enabled)
+
+ if reset:
+ self._reset_provider_check()
def _focus_password(self):
"""
@@ -226,9 +260,7 @@ class Wizard(QtGui.QWizard):
self._registration_finished)
threads.deferToThread(
- partial(register.register_user,
- username.encode("utf8"),
- password.encode("utf8")))
+ partial(register.register_user, username, password))
self._username = username
self._password = password
@@ -282,7 +314,8 @@ class Wizard(QtGui.QWizard):
old_username = self._username
self._username = None
self._password = None
- error_msg = self.tr("Unknown error")
+ error_msg = self.tr("Something has gone wrong. "
+ "Please try again.")
try:
content, _ = get_content(req)
json_content = json.loads(content)
@@ -339,6 +372,12 @@ class Wizard(QtGui.QWizard):
if len(self.ui.lnProvider.text()) == 0:
return
+ self._provider_checks_ok = False
+
+ # just in case that the user has already setup a provider and
+ # go 'back' to check a provider
+ self._provider_setup_ok = False
+
self.ui.grpCheckProvider.setVisible(True)
self.ui.btnCheck.setEnabled(False)
self.ui.lnProvider.setEnabled(False)
@@ -448,6 +487,7 @@ class Wizard(QtGui.QWizard):
"provider.json")):
self._complete_task(data, self.ui.lblProviderInfo,
True, self.SELECT_PROVIDER_PAGE)
+ self._provider_checks_ok = True
else:
new_data = {
self._provider_bootstrapper.PASSED_KEY: False,
@@ -499,6 +539,7 @@ class Wizard(QtGui.QWizard):
"""
self._complete_task(data, self.ui.lblCheckApiCert,
True, self.SETUP_PROVIDER_PAGE)
+ self._provider_setup_ok = True
def _service_selection_changed(self, service, state):
"""
@@ -556,18 +597,22 @@ class Wizard(QtGui.QWizard):
Prepares the pages when they appear
"""
if pageId == self.SELECT_PROVIDER_PAGE:
- self._reset_provider_check()
- self._enable_check("")
+ skip = self.ui.rbExistingProvider.isChecked()
+ if not self._provider_checks_ok:
+ self._enable_check()
+ self._skip_provider_checks(skip)
+ else:
+ self._enable_check(reset=False)
if pageId == self.SETUP_PROVIDER_PAGE:
- self._reset_provider_setup()
- self.page(pageId).setSubTitle(self.tr("Gathering configuration "
- "options for %s") %
- (self._provider_config
- .get_name(),))
- self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON)
- self._provider_setup_defer = self._provider_bootstrapper.\
- run_provider_setup_checks(self._provider_config)
+ if not self._provider_setup_ok:
+ self._reset_provider_setup()
+ sub_title = self.tr("Gathering configuration options for {0}")
+ sub_title = sub_title.format(self._provider_config.get_name())
+ self.page(pageId).setSubTitle(sub_title)
+ self.ui.lblDownloadCaCert.setPixmap(self.QUESTION_ICON)
+ self._provider_setup_defer = self._provider_bootstrapper.\
+ run_provider_setup_checks(self._provider_config)
if pageId == self.PRESENT_PROVIDER_PAGE:
self.page(pageId).setSubTitle(self.tr("Description of services "
diff --git a/src/leap/bitmask/services/__init__.py b/src/leap/bitmask/services/__init__.py
index e62277b6..ba12ba4e 100644
--- a/src/leap/bitmask/services/__init__.py
+++ b/src/leap/bitmask/services/__init__.py
@@ -37,7 +37,9 @@ from leap.common.files import get_mtime
logger = logging.getLogger(__name__)
-DEPLOYED = ["openvpn", "mx"]
+EIP_SERVICE = u"openvpn"
+MX_SERVICE = u"mx"
+DEPLOYED = [EIP_SERVICE, MX_SERVICE]
def get_service_display_name(service):
diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py
index c294381b..addf9bef 100644
--- a/src/leap/bitmask/services/mail/conductor.py
+++ b/src/leap/bitmask/services/mail/conductor.py
@@ -81,16 +81,12 @@ class IMAPControl(object):
"We need a non-null keymanager for initializing imap "
"service")
- if self.imap_service is None:
- # first time.
- self.imap_service, \
- self.imap_port, \
+ self.imap_service, self.imap_port, \
self.imap_factory = imap.start_imap_service(
self._soledad,
- self._keymanager)
- else:
- # we have the fetcher. just start it.
- self.imap_service.start_loop()
+ self._keymanager,
+ userid=self.userid)
+ self.imap_service.start_loop()
def stop_imap_service(self):
"""
@@ -102,6 +98,7 @@ class IMAPControl(object):
logger.debug('Stopping imap service.')
# Stop the loop call in the fetcher
self.imap_service.stop()
+ self.imap_service = None
# Stop listening on the IMAP port
self.imap_port.stopListening()
# Stop the protocol
@@ -111,7 +108,6 @@ class IMAPControl(object):
"""
Fetches incoming mail.
"""
- # TODO have a mutex over fetch operation.
if self.imap_service:
logger.debug('Client connected, fetching mail...')
self.imap_service.fetch()
@@ -201,9 +197,10 @@ class SMTPControl(object):
# TODO remove hard-coded port and let leap.mail set
# the specific default.
self.smtp_connection.qtsigs.connecting_signal.emit()
- from leap.mail.smtp import setup_smtp_relay
- self._smtp_service, self._smtp_port = setup_smtp_relay(
+ from leap.mail.smtp import setup_smtp_gateway
+ self._smtp_service, self._smtp_port = setup_smtp_gateway(
port=2013,
+ userid=self.userid,
keymanager=self._keymanager,
smtp_host=host,
smtp_port=port,
@@ -339,11 +336,25 @@ class MailConductor(IMAPControl, SMTPControl):
SMTPControl.__init__(self)
self._soledad = soledad
self._keymanager = keymanager
-
self._mail_machine = None
-
self._mail_connection = mail_connection.MailConnection()
+ self.userid = None
+
+ @property
+ def userid(self):
+ return self._userid
+
+ @userid.setter
+ def userid(self, userid):
+ """
+ Sets the user id this conductor is configured for.
+
+ :param userid: the user id, in the form "user@provider"
+ :type userid: str
+ """
+ self._userid = userid
+
def start_mail_machine(self, **kwargs):
"""
Starts mail machine.
@@ -354,15 +365,10 @@ class MailConductor(IMAPControl, SMTPControl):
# we have instantiated the connections while building the composite
# machines, and we have to use the qtsigs instantiated there.
- # XXX we could probably use a proxy here too to make the thing
- # transparent.
self.set_imap_connection(imap.conn)
self.set_smtp_connection(smtp.conn)
self._mail_machine = mail
- # XXX -------------------
- # need to keep a reference?
- #self._mail_events = mail.events
self._mail_machine.start()
self._imap_machine = imap
diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
index 54ef67eb..e8c7e9ce 100644
--- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py
+++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py
@@ -28,6 +28,8 @@ from PySide import QtCore
from u1db import errors as u1db_errors
from zope.proxy import sameProxiedObjects
+from twisted.internet.threads import deferToThread
+
from leap.bitmask.config import flags
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.crypto.srpauth import SRPAuth
@@ -194,16 +196,17 @@ class SoledadBootstrapper(AbstractBootstrapper):
leap_assert(not sameProxiedObjects(self._soledad, None),
"Null soledad, error while initializing")
+ self.deferred = deferToThread(self._do_soledad_sync)
+ def _do_soledad_sync(self):
+ """
+ Does several retries to get an initial soledad sync.
+ """
# and now, let's sync
sync_tries = self.MAX_SYNC_RETRIES
while sync_tries > 0:
try:
self._try_soledad_sync()
-
- # at this point, sometimes the client
- # gets stuck and does not progress to
- # the _gen_key step. XXX investigate.
logger.debug("Soledad has been synced.")
# so long, and thanks for all the fish
return
@@ -259,26 +262,32 @@ class SoledadBootstrapper(AbstractBootstrapper):
except socket.timeout:
logger.debug("SOLEDAD initialization TIMED OUT...")
self.soledad_timeout.emit()
+ raise
except socket.error as exc:
logger.warning("Socket error while initializing soledad")
self.soledad_timeout.emit()
+ raise
except BootstrapSequenceError as exc:
logger.warning("Error while initializing soledad")
self.soledad_timeout.emit()
+ raise
# unrecoverable
except u1db_errors.Unauthorized:
logger.error("Error while initializing soledad "
"(unauthorized).")
self.soledad_failed.emit()
+ raise
except u1db_errors.HTTPError as exc:
logger.exception("Error whie initializing soledad "
"(HTTPError)")
self.soledad_failed.emit()
+ raise
except Exception as exc:
logger.exception("Unhandled error while initializating "
"soledad: %r" % (exc,))
self.soledad_failed.emit()
+ raise
def _try_soledad_sync(self):
"""
@@ -292,9 +301,8 @@ class SoledadBootstrapper(AbstractBootstrapper):
logger.error("%r" % (exc,))
raise SoledadSyncError("Failed to sync soledad")
except Exception as exc:
- logger.exception("Unhandled error while syncing"
+ logger.exception("Unhandled error while syncing "
"soledad: %r" % (exc,))
- self.soledad_failed.emit()
raise SoledadSyncError("Failed to sync soledad")
def _download_config(self):
@@ -356,17 +364,32 @@ class SoledadBootstrapper(AbstractBootstrapper):
"""
srp_auth = self.srpauth
logger.debug('initializing keymanager...')
- self._keymanager = KeyManager(
- address,
- "https://nicknym.%s:6425" % (self._provider_config.get_domain(),),
- self._soledad,
- #token=srp_auth.get_token(), # TODO: enable token usage
- session_id=srp_auth.get_session_id(),
- ca_cert_path=self._provider_config.get_ca_cert_path(),
- api_uri=self._provider_config.get_api_uri(),
- api_version=self._provider_config.get_api_version(),
- uid=srp_auth.get_uid(),
- gpgbinary=self._get_gpg_bin_path())
+ try:
+ self._keymanager = KeyManager(
+ address,
+ "https://nicknym.%s:6425" % (
+ self._provider_config.get_domain(),),
+ self._soledad,
+ #token=srp_auth.get_token(), # TODO: enable token usage
+ session_id=srp_auth.get_session_id(),
+ ca_cert_path=self._provider_config.get_ca_cert_path(),
+ api_uri=self._provider_config.get_api_uri(),
+ api_version=self._provider_config.get_api_version(),
+ uid=srp_auth.get_uid(),
+ gpgbinary=self._get_gpg_bin_path())
+ except Exception as exc:
+ logger.exception(exc)
+ raise
+
+ logger.debug('sending key to server...')
+
+ # make sure key is in server
+ try:
+ self._keymanager.send_key(openpgp.OpenPGPKey)
+ except Exception as exc:
+ logger.error("Error sending key to server.")
+ logger.exception(exc)
+ # but we do not raise
def _gen_key(self, _):
"""
@@ -393,7 +416,7 @@ class SoledadBootstrapper(AbstractBootstrapper):
try:
self._keymanager.gen_key(openpgp.OpenPGPKey)
except Exception as exc:
- logger.error("error while generating key!")
+ logger.error("Error while generating key!")
logger.exception(exc)
raise
@@ -401,7 +424,7 @@ class SoledadBootstrapper(AbstractBootstrapper):
try:
self._keymanager.send_key(openpgp.OpenPGPKey)
except Exception as exc:
- logger.error("error while sending key!")
+ logger.error("Error while sending key!")
logger.exception(exc)
raise