diff options
| -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 | 
