[PATCH 09/12] Only call pwquality once per password.
David Shea
dshea at redhat.com
Fri Oct 4 18:28:15 UTC 2013
Redefined validatePassword so that it returns validity, strength, and a
reason all at once. validatePassword no longer raises PWQError on weak
passwords.
---
pyanaconda/constants.py | 1 +
pyanaconda/ui/gui/spokes/password.py | 22 +++-------
pyanaconda/ui/gui/spokes/user.py | 26 +++--------
pyanaconda/ui/tui/spokes/__init__.py | 40 ++++++++++-------
pyanaconda/users.py | 85 ++++++++++++++++++++++--------------
5 files changed, 92 insertions(+), 82 deletions(-)
diff --git a/pyanaconda/constants.py b/pyanaconda/constants.py
index 73d2c86..13440e2 100644
--- a/pyanaconda/constants.py
+++ b/pyanaconda/constants.py
@@ -132,6 +132,7 @@ FIRSTBOOT_ENVIRON = "firstboot"
UNSUPPORTED_HW = 1 << 28
# Password validation
+PASSWORD_MIN_LEN = 6
PASSWORD_EMPTY_ERROR = _("The password is empty.")
PASSWORD_CONFIRM_ERROR_GUI = _("The passwords do not match.")
PASSWORD_CONFIRM_ERROR_TUI = _("The passwords you entered were different. Please try again.")
diff --git a/pyanaconda/ui/gui/spokes/password.py b/pyanaconda/ui/gui/spokes/password.py
index 0ce00a3..57f2649 100644
--- a/pyanaconda/ui/gui/spokes/password.py
+++ b/pyanaconda/ui/gui/spokes/password.py
@@ -20,8 +20,7 @@
#
from pyanaconda.i18n import _, N_
-from pyanaconda.users import cryptPassword, validatePassword, checkPassword
-from pwquality import PWQError
+from pyanaconda.users import cryptPassword, validatePassword
from pyanaconda.ui.gui import GUICheck
from pyanaconda.ui.gui.spokes import NormalSpoke
@@ -83,6 +82,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
# Password validation data
self._pwq_error = None
+ self._pwq_valid = True
self._kickstarted = self.data.rootpw.seen
if self._kickstarted:
@@ -191,12 +191,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
# Reset the counter used for the "press Done twice" logic
self._waivePasswordClicks = 0
- try:
- strength = checkPassword(pwtext)
- _pwq_error = None
- except PWQError as e:
- _pwq_error = e[1]
- strength = 0
+ self._pwq_valid, strength, self._pwq_error = validatePassword(pwtext, "root")
if not pwtext:
val = 0
@@ -226,14 +221,11 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
if (not pw and not confirm) and self._kickstarted:
return GUICheck.CHECK_OK
- pwstrength = self.pw_bar.get_value()
+ # Check for validity errors
+ if (not self._pwq_valid) and (self._pwq_error):
+ return self._pwq_error
- # If the password passed the pwquality tesxt, see if validatePassword
- # catches anything else
- if pwstrength >= 2:
- self._pwq_error = validatePassword(self.pw.get_text())
- if self._pwq_error:
- pwstrength = 0
+ pwstrength = self.pw_bar.get_value()
if pwstrength < 2:
# If Done has been clicked twice, waive the check
diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py
index 47c5588..33d6041 100644
--- a/pyanaconda/ui/gui/spokes/user.py
+++ b/pyanaconda/ui/gui/spokes/user.py
@@ -35,8 +35,6 @@ from pyanaconda.constants import ANACONDA_ENVIRON, FIRSTBOOT_ENVIRON,\
PASSWORD_WEAK_CONFIRM_WITH_ERROR
from pyanaconda.regexes import GECOS_VALID, USERNAME_VALID, GROUPNAME_VALID, GROUPLIST_FANCY_PARSE
-import pwquality
-
__all__ = ["UserSpoke", "AdvancedUserDialog"]
def _checkUsername(editable, data):
@@ -250,13 +248,10 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
self.username: True
}
- # set up passphrase quality checker
- self._pwq = pwquality.PWQSettings()
- self._pwq.read_config()
-
# Updated during the password changed event and used by the password
# field validity checker
self._pwq_error = None
+ self._pwq_valid = True
self.pw_bar = self.builder.get_object("password_bar")
self.pw_label = self.builder.get_object("password_label")
@@ -406,16 +401,12 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
the changed Gtk event handler.
"""
pwtext = self.pw.get_text()
+ username = self.username.get_text()
# Reset the counter used for the "press Done twice" logic
self._waivePasswordClicks = 0
- try:
- strength = self._pwq.check(pwtext, None, None)
- self._pwq_error = None
- except pwquality.PWQError as e:
- self._pwq_error = e[1]
- strength = 0
+ self._pwq_valid, strength, self._pwq_error = validatePassword(pwtext, username)
if not pwtext:
val = 0
@@ -530,15 +521,12 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
if (not self.usepassword.get_active()) or self._user.password_kickstarted:
return GUICheck.CHECK_OK
+ # If the password failed the validity check, fail this check
+ if (not self._pwq_valid) and (self._pwq_error):
+ return self._pwq_error
+
pwstrength = self.pw_bar.get_value()
- # If the password passed the pwquality test, see if validatePassword
- # catches anything else.
- if pwstrength >= 2:
- self._pwq_error = validatePassword(self.pw.get_text())
- if self._pwq_error:
- pwstrength = 0
-
if pwstrength < 2:
# If Done has been clicked twice, waive the check
if self._waivePasswordClicks > 1:
diff --git a/pyanaconda/ui/tui/spokes/__init__.py b/pyanaconda/ui/tui/spokes/__init__.py
index f4d1a36..c9c80c7 100644
--- a/pyanaconda/ui/tui/spokes/__init__.py
+++ b/pyanaconda/ui/tui/spokes/__init__.py
@@ -21,12 +21,12 @@
from pyanaconda.ui.tui import simpleline as tui
from pyanaconda.ui.tui.tuiobject import TUIObject, YesNoDialog
from pyanaconda.ui.common import Spoke, StandaloneSpoke, NormalSpoke, PersonalizationSpoke, collect
-from pyanaconda.users import validatePassword, checkPassword, cryptPassword
-from pwquality import PWQError
+from pyanaconda.users import validatePassword, cryptPassword
import re
from collections import namedtuple
from pyanaconda.iutil import setdeepattr, getdeepattr
from pyanaconda.i18n import _
+from pyanaconda.constants import PASSWORD_CONFIRM_ERROR_TUI
__all__ = ["TUISpoke", "EditTUISpoke", "EditTUIDialog", "EditTUISpokeEntry", "StandaloneSpoke", "NormalSpoke", "PersonalizationSpoke",
"collect_spokes", "collect_categories"]
@@ -110,20 +110,28 @@ class EditTUIDialog(NormalTUISpoke):
if entry.aux == self.PASSWORD:
pw = self._app.raw_input(_("%s: ") % entry.title, hidden=True)
confirm = self._app.raw_input(_("%s (confirm): ") % entry.title, hidden=True)
- error = None
- # just returning an error is either blank or mismatched
- # passwords. Raising is because of poor quality.
- try:
- error = validatePassword(pw, confirm)
- if error:
- print(error)
- return None
- strength = checkPassword(pw)
- if strength < 50:
- raise PWQError((-1, "The password you have provided is weak."))
- except PWQError as e:
- error = _("You have provided a weak password: %s. " % e[1])
- error += _("\nWould you like to use it anyway?")
+
+ if (pw and not confirm) or (confirm and not pw):
+ print(_("You must enter your root password and confirm it by typing"
+ " it a second time to continue."))
+ return None
+ if (pw != confirm):
+ print(PASSWORD_CONFIRM_ERROR_TUI)
+ return None
+
+ valid, strength, message = validatePassword(pw, user=None)
+
+ if not valid:
+ print(message)
+ return None
+
+ if strength < 50:
+ if message:
+ error = _("You have provided a weak password: %s\n"
+ "Would you like to use it anyway?") % message
+ else:
+ error = _("You have provided a weak password.\n"
+ "Would you like to use it anyway?")
question_window = YesNoDialog(self._app, error)
self._app.switch_screen_modal(question_window)
if not question_window.answer:
diff --git a/pyanaconda/users.py b/pyanaconda/users.py
index 0121bc1..335f121 100644
--- a/pyanaconda/users.py
+++ b/pyanaconda/users.py
@@ -30,7 +30,7 @@ from pyanaconda import iutil
import pwquality
from pyanaconda.iutil import strip_accents
from pyanaconda.i18n import _
-from pyanaconda.constants import PASSWORD_CONFIRM_ERROR_TUI
+from pyanaconda.constants import PASSWORD_MIN_LEN
import logging
log = logging.getLogger("anaconda")
@@ -112,46 +112,67 @@ def cryptPassword(password, algo=None):
return crypt.crypt (password, saltstr)
-def validatePassword(pw, confirm=None, minlen=6, user="root"):
- # Do various steps to validate the password
- # Return an error string, or None for no errors
- # If inital checks pass, pwquality will be tested. Raises
- # from pwquality will pass up to the calling code
+def validatePassword(pw, user="root", settings=None):
+ """Check the quality of a password.
- # if both pw and confirm are blank, password is disabled.
- if (pw and confirm == '') or (confirm and not pw):
- error = _("You must enter your root password "
- "and confirm it by typing it a second "
- "time to continue.")
- return error
+ This function does three things: given a password and an optional
+ username, it will tell if this password can be used at all, how
+ strong the password is on a scale of 1-100, and, if the password is
+ unusable, why it is unusuable.
- if confirm != None and pw != confirm:
- error = PASSWORD_CONFIRM_ERROR_TUI
- return error
+ This function uses libpwquality to check the password strength.
+ pwquality will raise a PWQError on a weak password, which, honestly,
+ is kind of dumb behavior. A weak password isn't exceptional, it's what
+ we're asking about! Anyway, this function does not raise PWQError. If
+ the password fails the PWQSettings conditions, the first member of the
+ return tuple will be False and the second member of the tuple will be 0.
+
+ :param pw: the password to check
+ :type pw: string
+
+ :param user: the username for which the password is being set. If no
+ username is provided, "root" will be used. Use user=None
+ to disable the username check.
+ :type user: string
+
+ :param settings: an optional PWQSettings object
+ :type settings: pwquality.PWQSettings
+
+ :returns: A tuple containing (bool(valid), int(score), str(message))
+ :rtype: tuple
+ """
+
+ valid = True
+ message = None
+ strength = 0
+
+ if settings is None:
+ # Generate a default PWQSettings once and save it as a member of this function
+ if not hasattr(validatePassword, "pwqsettings"):
+ validatePassword.pwqsettings = pwquality.PWQSettings()
+ validatePassword.pwqsettings.read_config()
+ validatePassword.pwqsettings.minlen = PASSWORD_MIN_LEN
+ settings = validatePassword.pwqsettings
legal = string.digits + string.ascii_letters + string.punctuation + " "
for letter in pw:
if letter not in legal:
- error = _("Requested password contains "
+ message = _("Requested password contains "
"non-ASCII characters, which are "
"not allowed.")
- return error
-
- if pw:
- settings = pwquality.PWQSettings()
- settings.read_config()
- settings.minlen = minlen
- settings.check(pw, None, user)
-
- return None
+ valid = False
+ break
-def checkPassword(pw):
- """ Check the quality of a password passed in and return a numeric
- value.
- """
- pwq = pwquality.PWQSettings()
- pwq.read_config()
- return pwq.check(pw, None, None)
+ if valid:
+ try:
+ strength = settings.check(pw, None, user)
+ except pwquality.PWQError as e:
+ # Leave valid alone here: the password is weak but can still
+ # be accepted.
+ # PWQError values are built as a tuple of (int, str)
+ message = e[1]
+
+ return (valid, strength, message)
def guess_username(fullname):
fullname = fullname.split()
--
1.8.3.1
More information about the anaconda-patches
mailing list