summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorelijah <elijah@riseup.net>2014-09-08 02:01:14 -0700
committerelijah <elijah@riseup.net>2014-09-19 14:23:14 -0700
commit6166ffedcae0763f3c00076c79e74847f5c80823 (patch)
treecb649abc87ed6030c20ccdb9ac95dce51b2f1ec5
parent4e7c4b48b4255ceac06900fa9e65824c52e15ba7 (diff)
single pref win: moved password change UI to a separate window, opened from account page in preferences.
-rw-r--r--src/leap/bitmask/gui/account.py16
-rw-r--r--src/leap/bitmask/gui/flashable.py75
-rw-r--r--src/leap/bitmask/gui/passwordwindow.py269
-rw-r--r--src/leap/bitmask/gui/preferences_account_page.py15
-rw-r--r--src/leap/bitmask/gui/ui/password_change.ui182
-rw-r--r--src/leap/bitmask/gui/wizard.py2
-rw-r--r--src/leap/bitmask/util/credentials.py24
7 files changed, 568 insertions, 15 deletions
diff --git a/src/leap/bitmask/gui/account.py b/src/leap/bitmask/gui/account.py
index b08053a9..ae8127c0 100644
--- a/src/leap/bitmask/gui/account.py
+++ b/src/leap/bitmask/gui/account.py
@@ -19,6 +19,7 @@ A frontend GUI object to hold the current username and domain.
from leap.bitmask.util import make_address
from leap.bitmask.config.leapsettings import LeapSettings
+from leap.bitmask.services import EIP_SERVICE, MX_SERVICE
class Account():
@@ -33,11 +34,16 @@ class Account():
self.address = self.domain
def services(self):
- """
- returns a list of service name strings
+ """
+ returns a list of service name strings
- TODO: this should depend not just on the domain
- """
- return self._settings.get_enabled_services(self.domain)
+ TODO: this should depend not just on the domain
+ """
+ return self._settings.get_enabled_services(self.domain)
+ def is_email_enabled(self):
+ MX_SERVICE in self.services()
+
+ def is_eip_enabled(self):
+ EIP_SERVICE in self.services()
diff --git a/src/leap/bitmask/gui/flashable.py b/src/leap/bitmask/gui/flashable.py
new file mode 100644
index 00000000..94e3ab60
--- /dev/null
+++ b/src/leap/bitmask/gui/flashable.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2014 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/>.
+
+class Flashable(object):
+ """
+ An abstract super class to give a QWidget handy methods for diplaying
+ alert messages inline. The widget inheriting from this class must have
+ label named 'flash_label' available at self.ui.flash_label, or pass
+ the QLabel object in the constructor.
+ """
+
+ def __init__(self, widget=None):
+ self._setup(widget)
+
+ def _setup(self, widget=None):
+ if not hasattr(self, 'widget'):
+ if widget:
+ self.widget = widget
+ else:
+ self.widget = self.ui.flash_label
+ self.widget.setVisible(False)
+
+ def flash_error(self, message):
+ """
+ Sets string for the flash message.
+
+ :param message: the text to be displayed
+ :type message: str
+ """
+ self._setup()
+ message = "<font color='red'><b>%s</b></font>" % (message,)
+ self.widget.setVisible(True)
+ self.widget.setText(message)
+
+ def flash_success(self, message):
+ """
+ Sets string for the flash message.
+
+ :param message: the text to be displayed
+ :type message: str
+ """
+ self._setup()
+ message = "<font color='green'><b>%s</b></font>" % (message,)
+ self.widget.setVisible(True)
+ self.widget.setText(message)
+
+ def flash_message(self, message):
+ """
+ Sets string for the flash message.
+
+ :param message: the text to be displayed
+ :type message: str
+ """
+ self._setup()
+ message = "<b>%s</b>" % (message,)
+ self.widget.setVisible(True)
+ self.widget.setText(message)
+
+ def hide_flash(self):
+ self._setup()
+ self.widget.setVisible(False)
+
diff --git a/src/leap/bitmask/gui/passwordwindow.py b/src/leap/bitmask/gui/passwordwindow.py
new file mode 100644
index 00000000..9946febe
--- /dev/null
+++ b/src/leap/bitmask/gui/passwordwindow.py
@@ -0,0 +1,269 @@
+# -*- coding: utf-8 -*-
+# passwordwindow.py
+# Copyright (C) 2014 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/>.
+
+"""
+Change password dialog window
+"""
+
+from PySide import QtCore, QtGui
+from leap.bitmask.util.credentials import password_checks
+
+from leap.bitmask.gui.ui_password_change import Ui_PasswordChange
+from leap.bitmask.gui.flashable import Flashable
+
+import logging
+logger = logging.getLogger(__name__)
+
+class PasswordWindow(QtGui.QDialog, Flashable):
+
+ def __init__(self, parent, account, app):
+ """
+ :param parent: parent object of the PreferencesWindow.
+ :parent type: QWidget
+
+ :param account: the user set in the login widget
+ :type account: Account
+
+ :param app: App instance
+ :type app: App
+ """
+ QtGui.QDialog.__init__(self, parent)
+
+ self.account = account
+ self.app = app
+ self._backend_connect()
+
+ self.ui = Ui_PasswordChange()
+ self.ui.setupUi(self)
+
+ self.hide_flash()
+ self.ui.ok_button.clicked.connect(self._change_password)
+ self.ui.cancel_button.clicked.connect(self._close)
+ self.ui.username_lineedit.setText(account.address)
+
+ self._disabled = False # if set to True, never again enable widgets.
+
+ if account.username is None:
+ # should not ever happen, but just in case
+ self._disabled = True
+ self._enable_password_widgets(False)
+ self.ui.cancel_button.setEnabled(True)
+ self.flash_error(self.tr("Please log in to change your password."))
+
+ if self.is_soledad_needed() and not self._soledad_ready:
+ self._enable_password_widgets(False)
+ self.ui.cancel_button.setEnabled(True)
+ self.flash_message(
+ self.tr("Please wait for data storage to be ready."))
+
+ def is_soledad_needed(self):
+ """
+ Returns true if the current account needs to change the soledad
+ password as well as the SRP password.
+ """
+ return self.account.is_email_enabled()
+
+ #
+ # MANAGE WIDGETS
+ #
+
+ def _enable_password_widgets(self, enabled):
+ """
+ Enables or disables the widgets in the password change group box.
+
+ :param enabled: True if the widgets should be enabled.
+ False if widgets should be disabled and
+ display the status label that shows that is
+ changing the password.
+ :type enabled: bool
+ """
+ if self._disabled:
+ return
+
+ if enabled:
+ self.hide_flash()
+ else:
+ self.flash_message(self.tr("Changing password..."))
+
+ self.ui.current_password_lineedit.setEnabled(enabled)
+ self.ui.new_password_lineedit.setEnabled(enabled)
+ self.ui.new_password_confirmation_lineedit.setEnabled(enabled)
+ self.ui.ok_button.setEnabled(enabled)
+ self.ui.cancel_button.setEnabled(enabled)
+
+ def _change_password_success(self):
+ """
+ Callback used to display a successfully changed password.
+ """
+ logger.debug("Password changed successfully.")
+ self._clear_password_inputs()
+ self._enable_password_widgets(True)
+ self.flash_success(self.tr("Password changed successfully."))
+
+ def _clear_password_inputs(self):
+ """
+ Clear the contents of the inputs.
+ """
+ self.ui.current_password_lineedit.setText("")
+ self.ui.new_password_lineedit.setText("")
+ self.ui.new_password_confirmation_lineedit.setText("")
+
+ #
+ # SLOTS
+ #
+
+ def _backend_connect(self):
+ """
+ Helper to connect to backend signals
+ """
+ sig = self.app.signaler
+
+ sig.srp_password_change_ok.connect(self._srp_change_password_ok)
+
+ pwd_change_error = lambda: self._srp_change_password_problem(
+ self.tr("There was a problem changing the password."),
+ None)
+ sig.srp_password_change_error.connect(pwd_change_error)
+
+ pwd_change_badpw = lambda: self._srp_change_password_problem(
+ self.tr("You did not enter a correct current password."),
+ 'current_password')
+ sig.srp_password_change_badpw.connect(pwd_change_badpw)
+
+ sig.soledad_password_change_ok.connect(
+ self._soledad_change_password_ok)
+
+ sig.soledad_password_change_error.connect(
+ self._soledad_change_password_problem)
+
+ self._soledad_ready = False
+ sig.soledad_bootstrap_finished.connect(self._on_soledad_ready)
+
+
+ @QtCore.Slot()
+ def _change_password(self):
+ """
+ TRIGGERS:
+ self.ui.buttonBox.accepted
+
+ Changes the user's password if the inputboxes are correctly filled.
+ """
+ current_password = self.ui.current_password_lineedit.text()
+ new_password = self.ui.new_password_lineedit.text()
+ new_password2 = self.ui.new_password_confirmation_lineedit.text()
+
+ self._enable_password_widgets(True)
+
+ if len(current_password) == 0:
+ self.flash_error(self.tr("Password is empty."))
+ self.ui.current_password_lineedit.setFocus()
+ return
+
+ ok, msg, field = password_checks(self.account.username, new_password,
+ new_password2)
+ if not ok:
+ self.flash_error(msg)
+ if field == 'new_password':
+ self.ui.new_password_lineedit.setFocus()
+ elif field == 'new_password_confirmation':
+ self.ui.new_password_confirmation_lineedit.setFocus()
+ return
+
+ self._enable_password_widgets(False)
+ self.app.backend.user_change_password(
+ current_password=current_password,
+ new_password=new_password)
+
+ @QtCore.Slot()
+ def _close(self):
+ """
+ TRIGGERS:
+ self.ui.buttonBox.rejected
+
+ Close this dialog
+ """
+ self.hide()
+
+ @QtCore.Slot()
+ def _srp_change_password_ok(self):
+ """
+ TRIGGERS:
+ self._backend.signaler.srp_password_change_ok
+
+ Callback used to display a successfully changed password.
+ """
+ new_password = self.ui.new_password_lineedit.text()
+ logger.debug("SRP password changed successfully.")
+
+ if self.is_soledad_needed():
+ self._backend.soledad_change_password(new_password=new_password)
+ else:
+ self._change_password_success()
+
+ @QtCore.Slot(unicode)
+ def _srp_change_password_problem(self, msg, field):
+ """
+ TRIGGERS:
+ self._backend.signaler.srp_password_change_error
+ self._backend.signaler.srp_password_change_badpw
+
+ Callback used to display an error on changing password.
+
+ :param msg: the message to show to the user.
+ :type msg: unicode
+ """
+ logger.error("Error changing password: %s" % (msg,))
+ self._enable_password_widgets(True)
+ self.flash_error(msg)
+ if field == 'current_password':
+ self.ui.current_password_lineedit.setFocus()
+
+ @QtCore.Slot()
+ def _soledad_change_password_ok(self):
+ """
+ TRIGGERS:
+ Signaler.soledad_password_change_ok
+
+ Soledad password change went OK.
+ """
+ logger.debug("Soledad password changed successfully.")
+ self._change_password_success()
+
+ @QtCore.Slot(unicode)
+ def _soledad_change_password_problem(self, msg):
+ """
+ TRIGGERS:
+ Signaler.soledad_password_change_error
+
+ Callback used to display an error on changing password.
+
+ :param msg: the message to show to the user.
+ :type msg: unicode
+ """
+ logger.error("Error changing soledad password: %s" % (msg,))
+ self._enable_password_widgets(True)
+ self.flash_error(msg)
+
+
+ @QtCore.Slot()
+ def _on_soledad_ready(self):
+ """
+ TRIGGERS:
+ Signaler.soledad_bootstrap_finished
+ """
+ self._enable_password_widgets(True)
+ self._soledad_ready = True
diff --git a/src/leap/bitmask/gui/preferences_account_page.py b/src/leap/bitmask/gui/preferences_account_page.py
index bb90aab5..895d84b5 100644
--- a/src/leap/bitmask/gui/preferences_account_page.py
+++ b/src/leap/bitmask/gui/preferences_account_page.py
@@ -22,6 +22,7 @@ from functools import partial
from PySide import QtCore, QtGui
from ui_preferences_account_page import Ui_PreferencesAccountPage
+from passwordwindow import PasswordWindow
from leap.bitmask.services import get_service_display_name
from leap.bitmask.config.leapsettings import LeapSettings
@@ -43,9 +44,17 @@ class PreferencesAccountPage(QtGui.QWidget):
self.ui.change_password_label.setVisible(False)
self.ui.provider_services_label.setVisible(False)
+ self.ui.change_password_button.clicked.connect(
+ self._show_change_password)
app.signaler.prov_get_supported_services.connect(self._load_services)
app.backend.provider_get_supported_services(domain=account.domain)
+ if account.username is None:
+ self.ui.change_password_label.setText(
+ self.tr('You must be logged in to change your password.'))
+ self.ui.change_password_label.setVisible(True)
+ self.ui.change_password_button.setEnabled(False)
+
@QtCore.Slot(str, int)
def _service_selection_changed(self, service, state):
"""
@@ -112,3 +121,9 @@ class PreferencesAccountPage(QtGui.QWidget):
except ValueError:
logger.error("Something went wrong while trying to "
"load service %s" % (service,))
+
+ @QtCore.Slot()
+ def _show_change_password(self):
+ change_password_window = PasswordWindow(self, self.account, self.app)
+ change_password_window.show()
+
diff --git a/src/leap/bitmask/gui/ui/password_change.ui b/src/leap/bitmask/gui/ui/password_change.ui
new file mode 100644
index 00000000..b7ceac38
--- /dev/null
+++ b/src/leap/bitmask/gui/ui/password_change.ui
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PasswordChange</class>
+ <widget class="QDialog" name="PasswordChange">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>459</width>
+ <height>231</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Change Password</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QGridLayout" name="grid_layout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="username_label">
+ <property name="text">
+ <string>Username:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="new_password_label">
+ <property name="text">
+ <string>New password:</string>
+ </property>
+ <property name="buddy">
+ <cstring>new_password_lineedit</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="new_password_lineedit">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="new_password_confirmation_label">
+ <property name="text">
+ <string>Re-enter new password:</string>
+ </property>
+ <property name="buddy">
+ <cstring>new_password_confirmation_lineedit</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="current_password_label">
+ <property name="text">
+ <string>Current password:</string>
+ </property>
+ <property name="buddy">
+ <cstring>current_password_lineedit</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLineEdit" name="new_password_confirmation_lineedit">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="current_password_lineedit">
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <spacer name="spacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLineEdit" name="username_lineedit">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QLabel" name="flash_label">
+ <property name="text">
+ <string>&lt;flash_label&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="spacer2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="button_layout">
+ <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="cancel_button">
+ <property name="text">
+ <string>Close</string>
+ </property>
+ <property name="autoDefault">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="ok_button">
+ <property name="text">
+ <string>OK</string>
+ </property>
+ <property name="autoDefault">
+ <bool>false</bool>
+ </property>
+ <property name="default">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>username_lineedit</tabstop>
+ <tabstop>current_password_lineedit</tabstop>
+ <tabstop>new_password_lineedit</tabstop>
+ <tabstop>new_password_confirmation_lineedit</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/leap/bitmask/gui/wizard.py b/src/leap/bitmask/gui/wizard.py
index 8182228d..4d55a39e 100644
--- a/src/leap/bitmask/gui/wizard.py
+++ b/src/leap/bitmask/gui/wizard.py
@@ -317,7 +317,7 @@ class Wizard(QtGui.QWizard):
user_ok, msg = username_checks(username)
if user_ok:
- pass_ok, msg = password_checks(username, password, password2)
+ pass_ok, msg, field = password_checks(username, password, password2)
if user_ok and pass_ok:
self._set_register_status(self.tr("Starting registration..."))
diff --git a/src/leap/bitmask/util/credentials.py b/src/leap/bitmask/util/credentials.py
index 757ce10c..dfc78a09 100644
--- a/src/leap/bitmask/util/credentials.py
+++ b/src/leap/bitmask/util/credentials.py
@@ -38,7 +38,7 @@ def username_checks(username):
valid = USERNAME_VALIDATOR.validate(username, 0)
valid_username = valid[0] == QtGui.QValidator.State.Acceptable
if message is None and not valid_username:
- message = _tr("Invalid username")
+ message = _tr("That username is not allowed. Try another.")
return message is None, message
@@ -54,28 +54,34 @@ def password_checks(username, password, password2):
:param password2: second password from the registration form
:type password: str
- :returns: True and empty message if all the checks pass,
- False and an error message otherwise
- :rtype: tuple(bool, str)
+ :returns: (True, None, None) if all the checks pass,
+ (False, message, field name) otherwise
+ :rtype: tuple(bool, str, str)
"""
# translation helper
_tr = QtCore.QObject().tr
message = None
+ field = None
if message is None and password != password2:
message = _tr("Passwords don't match")
+ field = 'new_password_confirmation'
if message is None and not password:
- message = _tr("You can't use an empty password")
+ message = _tr("Password is empty")
+ field = 'new_password'
if message is None and len(password) < 8:
- message = _tr("Password too short")
+ message = _tr("Password is too short")
+ field = 'new_password'
if message is None and password in WEAK_PASSWORDS:
- message = _tr("Password too easy")
+ message = _tr("Password is too easy")
+ field = 'new_password'
if message is None and username == password:
- message = _tr("Password equal to username")
+ message = _tr("Password can't be the same as username")
+ field = 'new_password'
- return message is None, message
+ return message is None, message, field