diff options
author | elijah <elijah@riseup.net> | 2014-09-08 02:01:14 -0700 |
---|---|---|
committer | elijah <elijah@riseup.net> | 2014-09-19 14:23:14 -0700 |
commit | 6166ffedcae0763f3c00076c79e74847f5c80823 (patch) | |
tree | cb649abc87ed6030c20ccdb9ac95dce51b2f1ec5 | |
parent | 4e7c4b48b4255ceac06900fa9e65824c52e15ba7 (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.py | 16 | ||||
-rw-r--r-- | src/leap/bitmask/gui/flashable.py | 75 | ||||
-rw-r--r-- | src/leap/bitmask/gui/passwordwindow.py | 269 | ||||
-rw-r--r-- | src/leap/bitmask/gui/preferences_account_page.py | 15 | ||||
-rw-r--r-- | src/leap/bitmask/gui/ui/password_change.ui | 182 | ||||
-rw-r--r-- | src/leap/bitmask/gui/wizard.py | 2 | ||||
-rw-r--r-- | src/leap/bitmask/util/credentials.py | 24 |
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><flash_label></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 |