[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