From ef5a03b68c53fef55447d30744c7c1a1086b20fc Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 15 Aug 2013 15:25:14 -0300 Subject: Remove unused resources path. --- src/leap/bitmask/gui/ui/mainwindow.ui | 1 - 1 file changed, 1 deletion(-) diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index 9c4e6ff0..b46b62b0 100644 --- a/src/leap/bitmask/gui/ui/mainwindow.ui +++ b/src/leap/bitmask/gui/ui/mainwindow.ui @@ -310,7 +310,6 @@ - -- cgit v1.2.3 From 7001408dd893b5e8302c4ff8a0dfe63f50e283fa Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Thu, 15 Aug 2013 15:25:43 -0300 Subject: Add preferences panel. --- Makefile | 2 +- src/leap/bitmask/gui/mainwindow.py | 20 +++ src/leap/bitmask/gui/preferenceswindow.py | 40 ++++++ src/leap/bitmask/gui/ui/mainwindow.ui | 10 ++ src/leap/bitmask/gui/ui/preferences.ui | 219 ++++++++++++++++++++++++++++++ 5 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 src/leap/bitmask/gui/preferenceswindow.py create mode 100644 src/leap/bitmask/gui/ui/preferences.ui diff --git a/Makefile b/Makefile index 011c060e..4f4d77c4 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 statuspanel.ui +UI_FILES = loggerwindow.ui mainwindow.ui wizard.ui login.ui statuspanel.ui preferences.ui #Qt resource files to compile RESOURCES = locale.qrc loggerwindow.qrc mainwindow.qrc icons.qrc diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 17275983..5447d993 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -33,6 +33,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.preferenceswindow import PreferencesWindow from leap.bitmask.gui.wizard import Wizard from leap.bitmask.gui.login import LoginWidget from leap.bitmask.gui.statuspanel import StatusPanelWidget @@ -162,6 +163,7 @@ class MainWindow(QtGui.QMainWindow): self._launch_wizard) self.ui.btnShowLog.clicked.connect(self._show_logger_window) + self.ui.btnPreferences.clicked.connect(self._show_preferences) self._status_panel = StatusPanelWidget( self.ui.stackedWidget.widget(self.EIP_STATUS_INDEX)) @@ -292,6 +294,7 @@ class MainWindow(QtGui.QMainWindow): self._wizard_firstrun = False self._logger_window = None + self._preferences_window = None self._bypass_checks = bypass_checks @@ -403,6 +406,21 @@ class MainWindow(QtGui.QMainWindow): self._logger_window.finished.connect(self._uncheck_logger_button) + def _show_preferences(self): + """ + SLOT + TRIGGERS: + self.ui.action_show_preferences.triggered + self.ui.btnPreferences.clicked + + Displays the preferences window. + """ + preferences = self._preferences_window + if preferences is None: + preferences = PreferencesWindow(self, self._srp_auth) + + preferences.show() + def _uncheck_logger_button(self): """ SLOT @@ -925,6 +943,7 @@ class MainWindow(QtGui.QMainWindow): # panel QtCore.QTimer.singleShot(1000, self._switch_to_status) self._login_defer = None + self.ui.btnPreferences.setEnabled(True) else: self._login_widget.set_enabled(True) @@ -1399,6 +1418,7 @@ class MainWindow(QtGui.QMainWindow): self._login_widget.set_password("") self._login_widget.set_enabled(True) self._login_widget.set_status("") + self.ui.btnPreferences.setEnabled(False) def _intermediate_stage(self, data): """ diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py new file mode 100644 index 00000000..5f8dd5cc --- /dev/null +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# preferenceswindow.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +Preferences log window +""" +import logging + +from PySide import QtGui + +from leap.bitmask.gui.ui_preferences import Ui_Preferences +from leap.bitmask.crypto.srpauth import SRPAuthBadPassword + +logger = logging.getLogger(__name__) + + +class PreferencesWindow(QtGui.QDialog): + """ + Window that displays the preferences. + """ + def __init__(self, parent): + QtGui.QDialog.__init__(self, parent) + + # Load UI + self.ui = Ui_Preferences() + self.ui.setupUi(self) diff --git a/src/leap/bitmask/gui/ui/mainwindow.ui b/src/leap/bitmask/gui/ui/mainwindow.ui index b46b62b0..834a562e 100644 --- a/src/leap/bitmask/gui/ui/mainwindow.ui +++ b/src/leap/bitmask/gui/ui/mainwindow.ui @@ -214,6 +214,16 @@ + + + + false + + + Preferences + + + diff --git a/src/leap/bitmask/gui/ui/preferences.ui b/src/leap/bitmask/gui/ui/preferences.ui new file mode 100644 index 00000000..ffca381e --- /dev/null +++ b/src/leap/bitmask/gui/ui/preferences.ui @@ -0,0 +1,219 @@ + + + Preferences + + + + 0 + 0 + 451 + 267 + + + + Preferences + + + + :/images/mask-icon.png:/images/mask-icon.png + + + + + + Password Change + + + + QFormLayout::ExpandingFieldsGrow + + + + + &Current password: + + + leCurrentPassword + + + + + + + QLineEdit::Password + + + + + + + &New password: + + + leNewPassword + + + + + + + QLineEdit::Password + + + + + + + &Re-enter new password: + + + leNewPassword2 + + + + + + + QLineEdit::Password + + + + + + + Change + + + + + + + <Password change status> + + + Qt::AlignCenter + + + + + + + + + + false + + + Select gateway for provider + + + false + + + + + + &Select provider: + + + cbProviders + + + + + + + + <Select provider> + + + + + + + + Select gateway: + + + + + + + + Automatic + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + leCurrentPassword + returnPressed() + leNewPassword + setFocus() + + + 273 + 113 + + + 272 + 135 + + + + + leNewPassword + returnPressed() + leNewPassword2 + setFocus() + + + 349 + 139 + + + 351 + 163 + + + + + leNewPassword2 + returnPressed() + pbChangePassword + setFocus() + + + 178 + 169 + + + 178 + 198 + + + + + -- cgit v1.2.3 From 1801a78f9d05ea9d2f3c0321f3b1cc257d1ad278 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 21 Aug 2013 13:02:44 -0300 Subject: Add password change feature. --- src/leap/bitmask/crypto/srpauth.py | 82 +++++++++++++++ src/leap/bitmask/gui/mainwindow.py | 11 +- src/leap/bitmask/gui/preferenceswindow.py | 160 +++++++++++++++++++++++++++++- src/leap/bitmask/gui/ui/preferences.ui | 51 +--------- 4 files changed, 246 insertions(+), 58 deletions(-) diff --git a/src/leap/bitmask/crypto/srpauth.py b/src/leap/bitmask/crypto/srpauth.py index 7b91205f..41ce130a 100644 --- a/src/leap/bitmask/crypto/srpauth.py +++ b/src/leap/bitmask/crypto/srpauth.py @@ -134,6 +134,8 @@ class SRPAuth(QtCore.QObject): A_KEY = "A" CLIENT_AUTH_KEY = "client_auth" SESSION_ID_KEY = "_session_id" + USER_VERIFIER_KEY = 'user[password_verifier]' + USER_SALT_KEY = 'user[password_salt]' def __init__(self, provider_config): """ @@ -169,6 +171,10 @@ class SRPAuth(QtCore.QObject): self._srp_user = None self._srp_a = None + # User credentials stored for password changing checks + self._username = None + self._password = None + def _safe_unhexlify(self, val): """ Rounds the val to a multiple of 2 and returns the @@ -438,6 +444,51 @@ class SRPAuth(QtCore.QObject): def _threader(self, cb, res, *args, **kwargs): return threads.deferToThread(cb, res, *args, **kwargs) + def change_password(self, current_password, new_password): + """ + Changes the password for the currently logged user if the current + password match. + It requires to be authenticated. + + Might raise: + SRPAuthBadPassword + requests.exceptions.HTTPError + + :param current_password: the current password for the logged user. + :type current_password: str + :param new_password: the new password for the user + :type new_password: str + """ + leap_assert(self.get_uid() is not None) + + if current_password != self._password: + raise SRPAuthBadPassword + + url = "%s/%s/users/%s.json" % ( + self._provider_config.get_api_uri(), + self._provider_config.get_api_version(), + self.get_uid()) + + salt, verifier = self._srp.create_salted_verification_key( + self._username, new_password, self._hashfun, self._ng) + + cookies = {self.SESSION_ID_KEY: self.get_session_id()} + user_data = { + self.USER_VERIFIER_KEY: binascii.hexlify(verifier), + self.USER_SALT_KEY: binascii.hexlify(salt) + } + + change_password = self._session.put( + url, data=user_data, + verify=self._provider_config.get_ca_cert_path(), + cookies=cookies, + timeout=REQUEST_TIMEOUT) + + # In case of non 2xx it raises HTTPError + change_password.raise_for_status() + + self._password = new_password + def authenticate(self, username, password): """ Executes the whole authentication process for a user @@ -454,6 +505,10 @@ class SRPAuth(QtCore.QObject): """ leap_assert(self.get_session_id() is None, "Already logged in") + # User credentials stored for password changing checks + self._username = username.lower() + self._password = password + d = threads.deferToThread(self._authentication_preprocessing, username=username, password=password) @@ -565,6 +620,33 @@ class SRPAuth(QtCore.QObject): d.addErrback(self._errback) return d + def change_password(self, current_password, new_password): + """ + Changes the user's password. + + :param current_password: the current password of the user. + :type current_password: str + :param new_password: the new password for the user. + :type new_password: str + + :returns: a defer to interact with. + :rtype: twisted.internet.defer.Deferred + """ + d = threads.deferToThread( + self.__instance.change_password, current_password, new_password) + return d + + def get_username(self): + """ + Returns the username of the currently authenticated user or None if + no user is logged. + + :rtype: str or None + """ + if self.get_uid() is None: + return None + return self.__instance._username + def _gui_notify(self, _): """ Callback that notifies the UI with the proper signal. diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index 5447d993..c832887a 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -288,6 +288,10 @@ class MainWindow(QtGui.QMainWindow): ################################# end Qt Signals connection ######## + # Enable the password change when soledad is ready + self.soledad_ready.connect( + partial(self.ui.btnPreferences.setEnabled, True)) + init_platform() self._wizard = None @@ -415,11 +419,7 @@ class MainWindow(QtGui.QMainWindow): Displays the preferences window. """ - preferences = self._preferences_window - if preferences is None: - preferences = PreferencesWindow(self, self._srp_auth) - - preferences.show() + PreferencesWindow(self, self._srp_auth, self._soledad).show() def _uncheck_logger_button(self): """ @@ -943,7 +943,6 @@ class MainWindow(QtGui.QMainWindow): # panel QtCore.QTimer.singleShot(1000, self._switch_to_status) self._login_defer = None - self.ui.btnPreferences.setEnabled(True) else: self._login_widget.set_enabled(True) diff --git a/src/leap/bitmask/gui/preferenceswindow.py b/src/leap/bitmask/gui/preferenceswindow.py index 5f8dd5cc..67448768 100644 --- a/src/leap/bitmask/gui/preferenceswindow.py +++ b/src/leap/bitmask/gui/preferenceswindow.py @@ -16,13 +16,15 @@ # along with this program. If not, see . """ -Preferences log window +Preferences window """ import logging +from functools import partial from PySide import QtGui from leap.bitmask.gui.ui_preferences import Ui_Preferences +from leap.soledad.client import NoStorageSecret from leap.bitmask.crypto.srpauth import SRPAuthBadPassword logger = logging.getLogger(__name__) @@ -32,9 +34,163 @@ class PreferencesWindow(QtGui.QDialog): """ Window that displays the preferences. """ - def __init__(self, parent): + + WEAK_PASSWORDS = ("123456", "qweasd", "qwerty", "password") + + def __init__(self, parent, srp_auth, soledad): + """ + :param parent: parent object of the PreferencesWindow. + :parent type: QWidget + :param srp_auth: SRPAuth object configured in the main app. + :type srp_auth: SRPAuth + :param soledad: Soledad object configured in the main app. + :type soledad: Soledad + """ QtGui.QDialog.__init__(self, parent) + self._srp_auth = srp_auth + self._soledad = soledad + # Load UI self.ui = Ui_Preferences() self.ui.setupUi(self) + self.ui.lblPasswordChangeStatus.setVisible(False) + + # Connections + self.ui.pbChangePassword.clicked.connect(self._change_password) + + def _basic_password_checks(self, username, password, password2): + """ + Performs basic password checks to avoid really easy passwords. + + :param username: username provided at the registrarion form + :type username: str + :param password: password from the registration form + :type password: str + :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) + """ + message = None + + if message is None and password != password2: + message = self.tr("Passwords don't match") + + if message is None and len(password) < 6: + message = self.tr("Password too short") + + if message is None and password in self.WEAK_PASSWORDS: + message = self.tr("Password too easy") + + if message is None and username == password: + message = self.tr("Password equal to username") + + return message is None, message + + def _set_password_change_status(self, status, error=False, success=False): + """ + Sets the status label for the password change. + + :param status: status message to display, can be HTML + :type status: str + """ + if error: + status = "%s" % (status,) + elif success: + status = "%s" % (status,) + + self.ui.lblPasswordChangeStatus.setVisible(True) + self.ui.lblPasswordChangeStatus.setText(status) + + def _set_changing_password(self, disable): + """ + Enables or disables the widgets in the password change group box. + + :param disable: True if the widgets should be disabled and + it displays the status label that shows that is + changing the password. + False if they should be enabled. + :type disable: bool + """ + if disable: + self._set_password_change_disable(self.tr("Changing password...")) + + self.ui.leCurrentPassword.setEnabled(not disable) + self.ui.leNewPassword.setEnabled(not disable) + self.ui.leNewPassword2.setEnabled(not disable) + self.ui.pbChangePassword.setEnabled(not disable) + + def _change_password(self): + """ + Changes the user's password if the inputboxes are correctly filled. + """ + username = self._srp_auth.get_username() + current_password = self.ui.leCurrentPassword.text() + new_password = self.ui.leNewPassword.text() + new_password2 = self.ui.leNewPassword2.text() + + ok, msg = self._basic_password_checks( + username, new_password, new_password2) + + if not ok: + self._set_changing_password(False) + self._set_password_change_status(msg, error=True) + self.ui.leNewPassword.setFocus() + return + + self._set_changing_password(True) + d = self._srp_auth.change_password(current_password, new_password) + d.addCallback(partial(self._change_password_success, new_password)) + d.addErrback(self._change_password_problem) + + def _change_password_success(self, new_password, _): + """ + Callback used to display a successfully performed action. + + :param new_password: the new password for the user. + :type new_password: str. + :param _: the returned data from self._srp_auth.change_password + Ignored + """ + logger.debug("SRP password changed successfully.") + try: + self._soledad.change_passphrase(str(new_password)) + logger.debug("Soledad password changed successfully.") + except NoStorageSecret: + logger.debug( + "No storage secret for password change in Soledad.") + + self._set_password_change_status( + self.tr("Password changed successfully."), success=True) + self._clear_inputs() + self._set_changing_password(False) + + def _change_password_problem(self, failure): + """ + Errback called if there was a problem with the deferred. + Also is used to display an error message. + + :param failure: the cause of the method failed. + :type failure: twisted.python.Failure + """ + logger.error("Error changing password: %s", (failure, )) + problem = self.tr("There was a problem changing the password.") + + if failure.check(SRPAuthBadPassword): + problem = self.tr("You did not enter a correct current password.") + + self._set_password_change_status(problem, error=True) + + self._set_changing_password(False) + failure.trap(Exception) + + def _clear_inputs(self): + """ + Clear the contents of the inputs. + """ + self.ui.leCurrentPassword.setText("") + self.ui.leNewPassword.setText("") + self.ui.leNewPassword2.setText("") diff --git a/src/leap/bitmask/gui/ui/preferences.ui b/src/leap/bitmask/gui/ui/preferences.ui index ffca381e..8c63ccad 100644 --- a/src/leap/bitmask/gui/ui/preferences.ui +++ b/src/leap/bitmask/gui/ui/preferences.ui @@ -166,54 +166,5 @@ - - - leCurrentPassword - returnPressed() - leNewPassword - setFocus() - - - 273 - 113 - - - 272 - 135 - - - - - leNewPassword - returnPressed() - leNewPassword2 - setFocus() - - - 349 - 139 - - - 351 - 163 - - - - - leNewPassword2 - returnPressed() - pbChangePassword - setFocus() - - - 178 - 169 - - - 178 - 198 - - - - + -- cgit v1.2.3 From 7d10092df85a91db5e40b0eb36d4bb2cc67f6d05 Mon Sep 17 00:00:00 2001 From: Ivan Alejandro Date: Wed, 21 Aug 2013 13:15:44 -0300 Subject: Add changes file. Closes #2798, #3500, #3533. --- changes/feature-preferences-window-password-change | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/feature-preferences-window-password-change diff --git a/changes/feature-preferences-window-password-change b/changes/feature-preferences-window-password-change new file mode 100644 index 00000000..df255500 --- /dev/null +++ b/changes/feature-preferences-window-password-change @@ -0,0 +1 @@ + o Add a preference panel that lets you change your password. Closes #3500 #2798 #3533. -- cgit v1.2.3