summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Alejandro <ivanalejandro0@gmail.com>2013-08-21 13:02:44 -0300
committerIvan Alejandro <ivanalejandro0@gmail.com>2013-08-23 11:25:03 -0300
commit1801a78f9d05ea9d2f3c0321f3b1cc257d1ad278 (patch)
treed174a9f396d8fda8e4a925b6838f9ef3cc3aa154
parent7001408dd893b5e8302c4ff8a0dfe63f50e283fa (diff)
Add password change feature.
-rw-r--r--src/leap/bitmask/crypto/srpauth.py82
-rw-r--r--src/leap/bitmask/gui/mainwindow.py11
-rw-r--r--src/leap/bitmask/gui/preferenceswindow.py160
-rw-r--r--src/leap/bitmask/gui/ui/preferences.ui51
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 <http://www.gnu.org/licenses/>.
"""
-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 = "<font color='red'><b>%s</b></font>" % (status,)
+ elif success:
+ status = "<font color='green'><b>%s</b></font>" % (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 @@
<resources>
<include location="../../../../../data/resources/mainwindow.qrc"/>
</resources>
- <connections>
- <connection>
- <sender>leCurrentPassword</sender>
- <signal>returnPressed()</signal>
- <receiver>leNewPassword</receiver>
- <slot>setFocus()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>273</x>
- <y>113</y>
- </hint>
- <hint type="destinationlabel">
- <x>272</x>
- <y>135</y>
- </hint>
- </hints>
- </connection>
- <connection>
- <sender>leNewPassword</sender>
- <signal>returnPressed()</signal>
- <receiver>leNewPassword2</receiver>
- <slot>setFocus()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>349</x>
- <y>139</y>
- </hint>
- <hint type="destinationlabel">
- <x>351</x>
- <y>163</y>
- </hint>
- </hints>
- </connection>
- <connection>
- <sender>leNewPassword2</sender>
- <signal>returnPressed()</signal>
- <receiver>pbChangePassword</receiver>
- <slot>setFocus()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>178</x>
- <y>169</y>
- </hint>
- <hint type="destinationlabel">
- <x>178</x>
- <y>198</y>
- </hint>
- </hints>
- </connection>
- </connections>
+ <connections/>
</ui>