This splits that one big patch from last time into several fun-sized patches.
Successful GUICheck now return a constant (defined in the GUICheck class), and strength is spelled correctly. I answered Vratislav's question about removing kickstarted passwords with more questions, so no changes there.
--- pyanaconda/ui/gui/__init__.py | 3 --- 1 file changed, 3 deletions(-)
diff --git a/pyanaconda/ui/gui/__init__.py b/pyanaconda/ui/gui/__init__.py index 30b595f..e05dc1c 100644 --- a/pyanaconda/ui/gui/__init__.py +++ b/pyanaconda/ui/gui/__init__.py @@ -294,9 +294,6 @@ class GUIObject(common.UIObject): the set_error function. By default, set_error will call self.set_warning with the status of the first failed check.
- Checks are not enabled when they are created. To activate the - check, call .enable() on the returned GUICheck object. - :param editable: the input field to validate :type editable: GtkEditable
Removed unusued parameters from add_check calls. Added a parameter to on_advanced_clicked to ensure the function signature matches the signal specification. Renamed callbacks to be more consistent. --- pyanaconda/ui/gui/spokes/user.glade | 8 ++++---- pyanaconda/ui/gui/spokes/user.py | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/user.glade b/pyanaconda/ui/gui/spokes/user.glade index 377f9f2..c979efa 100644 --- a/pyanaconda/ui/gui/spokes/user.glade +++ b/pyanaconda/ui/gui/spokes/user.glade @@ -102,7 +102,7 @@ <property name="invisible_char">●</property> <property name="invisible_char_set">True</property> <property name="caps_lock_warning">False</property> - <signal name="changed" handler="_guessNames" swapped="no"/> + <signal name="changed" handler="full_name_changed" swapped="no"/> </object> <packing> <property name="left_attach">1</property> @@ -117,7 +117,7 @@ <property name="can_focus">True</property> <property name="invisible_char">●</property> <property name="invisible_char_set">True</property> - <signal name="changed" handler="_guessNameDisabler" swapped="no"/> + <signal name="changed" handler="username_changed" swapped="no"/> </object> <packing> <property name="left_attach">1</property> @@ -172,7 +172,7 @@ <property name="can_focus">True</property> <property name="visibility">False</property> <property name="invisible_char">●</property> - <signal name="changed" handler="_updatePwQuality" swapped="no"/> + <signal name="changed" handler="password_changed" swapped="no"/> </object> <packing> <property name="left_attach">1</property> @@ -219,7 +219,7 @@ <property name="xalign">0</property> <property name="active">True</property> <property name="draw_indicator">True</property> - <signal name="toggled" handler="_passwordDisabler" swapped="no"/> + <signal name="toggled" handler="usepassword_toggled" swapped="no"/> </object> <packing> <property name="left_attach">1</property> diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py index aca7d95..295e236 100644 --- a/pyanaconda/ui/gui/spokes/user.py +++ b/pyanaconda/ui/gui/spokes/user.py @@ -281,7 +281,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): # - if a password is specified and there is data in the confirm box, do they match? # - if a password is specified and the confirm box is empty or match, how strong is it? # - if a password is required, is there any data in the confirm box? - self.add_check(self.pw, self._checkPasswordEmpty, None) + self.add_check(self.pw, self._checkPasswordEmpty)
# The password confirmation needs to be checked whenever either of the password # fields change. Separate checks are created on each field so that edits on @@ -289,14 +289,14 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): # when Done is clicked. Whichever check is run needs to run the other check in # order to reset the status. The check_data field is used as a flag to prevent # infinite recursion. - self._confirm_check = self.add_check(self.confirm, self._checkPasswordConfirm, None) - self._password_check = self.add_check(self.pw, self._checkPasswordConfirm, None) + self._confirm_check = self.add_check(self.confirm, self._checkPasswordConfirm) + self._password_check = self.add_check(self.pw, self._checkPasswordConfirm)
# Keep a reference to this check, since it has to be manually run for the # click Done twice check. - self._pwStrengthCheck = self.add_check(self.pw, self._checkPasswordStrength, None) + self._pwStrengthCheck = self.add_check(self.pw, self._checkPasswordStrength)
- self.add_check(self.confirm, self._checkPasswordEmpty, None) + self.add_check(self.confirm, self._checkPasswordEmpty)
# Allow empty usernames so the spoke can be exited without creating a user self.add_check(self.username, _checkUsername, @@ -400,7 +400,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): def completed(self): return len(self.data.user.userList) > 0
- def _updatePwQuality(self, editable=None, data=None): + def password_changed(self, editable=None, data=None): """This method updates the password indicators according to the password entered by the user. It is called by the changed Gtk event handler. @@ -436,7 +436,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): self.pw_bar.set_value(val) self.pw_label.set_text(text)
- def _passwordDisabler(self, editable = None, data = None): + def usepassword_toggled(self, togglebutton = None, data = None): """Called by Gtk callback when the "Use password" check button is toggled. It will make password entries in/sensitive."""
@@ -445,7 +445,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): self.pw.emit("changed") self.confirm.emit("changed")
- def _guessNameDisabler(self, editable = None, data = None): + def username_changed(self, editable = None, data = None): """Called by Gtk callback when the username or hostname entry changes. It disables the guess algorithm if the user added his own text there and reenable it when the @@ -458,7 +458,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): self.guesser[editable] = False self.b_advanced.set_sensitive(True)
- def _guessNames(self, editable = None, data = None): + def full_name_changed(self, editable = None, data = None): """Called by Gtk callback when the full name field changes. It guesses the username and hostname, strips diacritics and make those lowercase. @@ -565,7 +565,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): else: return None
- def on_advanced_clicked(self, _button): + def on_advanced_clicked(self, _button, data=None): """Handler for the Advanced.. button. It starts the Advanced dialog for setting homedit, uid, gid and groups. """
Update the interface-requires to the version of Gtk that introduced GtkLevelBar (3.6). Remove redundant properties. Change the value of the "orientation" property to the format that gladeui expects. --- pyanaconda/ui/gui/spokes/password.glade | 13 ++++--------- pyanaconda/ui/gui/spokes/user.glade | 9 +++------ 2 files changed, 7 insertions(+), 15 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/password.glade b/pyanaconda/ui/gui/spokes/password.glade index 37942eb..3ad5055 100644 --- a/pyanaconda/ui/gui/spokes/password.glade +++ b/pyanaconda/ui/gui/spokes/password.glade @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <interface> - <!-- interface-requires gtk+ 3.0 --> + <!-- interface-requires gtk+ 3.6 --> <!-- interface-requires AnacondaWidgets 1.0 --> <object class="AnacondaSpokeWindow" id="passwordWindow"> <property name="startup_id">filler</property> @@ -134,15 +134,10 @@ <object class="GtkLevelBar" id="password_bar"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <property name="spacing">2</property> - <property name="mode">GTK_LEVEL_BAR_MODE_DISCRETE</property> - <property name="min-value">0</property> - <property name="max-value">4</property> - <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property> - <property name="value">2</property> - <property name="halign">fill</property> <property name="valign">center</property> + <property name="value">2</property> + <property name="max_value">4</property> + <property name="mode">discrete</property> </object> <packing> <property name="expand">True</property> diff --git a/pyanaconda/ui/gui/spokes/user.glade b/pyanaconda/ui/gui/spokes/user.glade index c979efa..d2f701f 100644 --- a/pyanaconda/ui/gui/spokes/user.glade +++ b/pyanaconda/ui/gui/spokes/user.glade @@ -236,13 +236,10 @@ <object class="GtkLevelBar" id="password_bar"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="mode">GTK_LEVEL_BAR_MODE_DISCRETE</property> - <property name="min-value">0</property> - <property name="max-value">4</property> - <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property> - <property name="value">2</property> - <property name="halign">fill</property> <property name="valign">center</property> + <property name="value">2</property> + <property name="max_value">4</property> + <property name="mode">discrete</property> </object> <packing> <property name="expand">True</property>
The PWQError exceptions created by the pwquality library do not contain any named members, so treat them as tuples intead. --- pyanaconda/ui/gui/spokes/lib/passphrase.py | 2 +- pyanaconda/ui/gui/spokes/password.py | 4 ++-- pyanaconda/ui/gui/spokes/user.py | 2 +- pyanaconda/ui/tui/spokes/__init__.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/lib/passphrase.py b/pyanaconda/ui/gui/spokes/lib/passphrase.py index 2cd6887..0fcefd9 100644 --- a/pyanaconda/ui/gui/spokes/lib/passphrase.py +++ b/pyanaconda/ui/gui/spokes/lib/passphrase.py @@ -107,7 +107,7 @@ class PassphraseDialog(GUIObject): try: strength = self._pwq.check(passphrase, None, None) except pwquality.PWQError as e: - self._pwq_error = e.message + self._pwq_error = e[1]
if strength < 50: val = 1 diff --git a/pyanaconda/ui/gui/spokes/password.py b/pyanaconda/ui/gui/spokes/password.py index b828317..dc0782d 100644 --- a/pyanaconda/ui/gui/spokes/password.py +++ b/pyanaconda/ui/gui/spokes/password.py @@ -115,7 +115,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke): strength = checkPassword(self.pw.get_text()) _pwq_error = None except PWQError as e: - _pwq_error = e.message + _pwq_error = e[1] strength = 0
if strength < 50: @@ -181,7 +181,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke): pass else: self._error = _("You have provided a weak password: %s. " - " Press Done again to use anyway.") % e.message + " Press Done again to use anyway.") % e[1] self._oldweak = pw return False
diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py index 295e236..2b9e089 100644 --- a/pyanaconda/ui/gui/spokes/user.py +++ b/pyanaconda/ui/gui/spokes/user.py @@ -414,7 +414,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): strength = self._pwq.check(pwtext, None, None) self._pwq_error = None except pwquality.PWQError as e: - self._pwq_error = e.message + self._pwq_error = e[1] strength = 0
if not pwtext: diff --git a/pyanaconda/ui/tui/spokes/__init__.py b/pyanaconda/ui/tui/spokes/__init__.py index 5dd8424..f4d1a36 100644 --- a/pyanaconda/ui/tui/spokes/__init__.py +++ b/pyanaconda/ui/tui/spokes/__init__.py @@ -120,9 +120,9 @@ class EditTUIDialog(NormalTUISpoke): return None strength = checkPassword(pw) if strength < 50: - raise PWQError("The password you have provided is weak.") + raise PWQError((-1, "The password you have provided is weak.")) except PWQError as e: - error = _("You have provided a weak password: %s. " % e.message) + error = _("You have provided a weak password: %s. " % e[1]) error += _("\nWould you like to use it anyway?") question_window = YesNoDialog(self._app, error) self._app.switch_screen_modal(question_window)
On Fri, 2013-10-04 at 14:28 -0400, David Shea wrote:
The PWQError exceptions created by the pwquality library do not contain any named members, so treat them as tuples intead.
Could you please file a bug on that?
On 10/07/2013 04:21 AM, Vratislav Podzimek wrote:
On Fri, 2013-10-04 at 14:28 -0400, David Shea wrote:
The PWQError exceptions created by the pwquality library do not contain any named members, so treat them as tuples intead.
Could you please file a bug on that?
https://bugzilla.redhat.com/show_bug.cgi?id=1016154
There's an issue tracker for libpwquality on fedorahosted but it looks kind of empty.
--- pyanaconda/ui/gui/spokes/user.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py index 2b9e089..25b287a 100644 --- a/pyanaconda/ui/gui/spokes/user.py +++ b/pyanaconda/ui/gui/spokes/user.py @@ -220,7 +220,6 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): def __init__(self, *args): NormalSpoke.__init__(self, *args) self._oldweak = None - self._error = False
def initialize(self): NormalSpoke.initialize(self) @@ -332,9 +331,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
@property def status(self): - if self._error: - return _("Error creating user account: %s") % self._error - elif len(self.data.user.userList) == 0: + if len(self.data.user.userList) == 0: return _("No user will be created") elif self._wheel.name in self.data.user.userList[0].groups: return _("Administrator %s will be created") % self.data.user.userList[0].name
--- pyanaconda/ui/gui/__init__.py | 17 ++++++++++------- pyanaconda/ui/gui/spokes/user.py | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 17 deletions(-)
diff --git a/pyanaconda/ui/gui/__init__.py b/pyanaconda/ui/gui/__init__.py index e05dc1c..44d7d90 100644 --- a/pyanaconda/ui/gui/__init__.py +++ b/pyanaconda/ui/gui/__init__.py @@ -42,6 +42,9 @@ ANACONDA_WINDOW_GROUP = Gtk.WindowGroup() class GUICheck(object): """Handle an input validation check."""
+ # Use as a return value to indicate a passed check + CHECK_OK = None + def __init__(self, parent, editable, run_check, check_data, set_error): """Create a new input validation check.
@@ -54,7 +57,7 @@ class GUICheck(object):
:param run_check: The check function. The function is called as check(editable, check_data). The return value is an - error state object or None if the check succeeds. + error state object or CHECK_OK if the check succeeds. :type run_check: function
:param check_data: An optional parameter passed to check(). @@ -299,8 +302,8 @@ class GUIObject(common.UIObject):
:param run_check: a function called to validate the input field. The parameters are (editable, check_data). The return - value is an object used by update_check, or None - if the check passes. + value is an object used by update_check, or + GUICheck.CHECK_OK if the check passes. :type run_check: function
:param check_data: additional data to pass to the check function @@ -408,8 +411,8 @@ class GUIDialog(GUIObject):
:param run_check: a function called to validate the input field. The parameters are (editable, check_data). The return - value is an object used by update_check, or None - if the check passes. + value is an object used by update_check, or + GUICheck.CHECK_OK if the check passes. :type run_check: function
:param check_data: additional data to pass to the check function @@ -806,9 +809,9 @@ def check_re(editable, data): 'regex' and 'message'. :type data: dict
- :returns: error_data if the check fails, otherwise None. + :returns: error_data if the check fails, otherwise GUICheck.CHECK_OK. """ if data['regex'].match(editable.get_text()): - return None + return GUICheck.CHECK_OK else: return data['message'] diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py index 25b287a..47e7b6c 100644 --- a/pyanaconda/ui/gui/spokes/user.py +++ b/pyanaconda/ui/gui/spokes/user.py @@ -23,7 +23,7 @@ from pyanaconda.i18n import _, N_ from pyanaconda.users import cryptPassword, validatePassword, guess_username
from pyanaconda.ui.gui.spokes import NormalSpoke -from pyanaconda.ui.gui import GUIObject, GUIDialog, check_re +from pyanaconda.ui.gui import GUIObject, GUIDialog, check_re, GUICheck from pyanaconda.ui.gui.categories.user_settings import UserSettingsCategory from pyanaconda.ui.common import FirstbootSpokeMixIn from pyanaconda.ui.gui.utils import enlightbox @@ -39,7 +39,7 @@ __all__ = ["UserSpoke", "AdvancedUserDialog"] def _checkUsername(editable, data): """Validate a username. Allow empty usernames.""" if not (editable.get_text()): - return None + return GUICheck.CHECK_OK else: return check_re(editable, data)
@@ -52,7 +52,7 @@ def _validateGroups(editable, data): if not GROUPNAME_VALID.match(group_name): return _("Invalid group name: %s") % group_name
- return None + return GUICheck.CHECK_OK
class AdvancedUserDialog(GUIDialog): builderObjects = ["advancedUserDialog", "uid", "gid"] @@ -476,18 +476,18 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
# If the password was set by kickstart, skip the strength check if self._user.password_kickstarted: - return True + return GUICheck.CHECK_OK
# Skip the check if no password is required if (not self.usepassword.get_active()) or self._user.password_kickstarted: - return None + return GUICheck.CHECK_OK elif not editable.get_text(): if editable == self.pw: return _("The password is empty") else: return _("The passwords do not match.") else: - return None + return GUICheck.CHECK_OK
def _checkPasswordConfirm(self, editable=None, reset_status=None): """If the user has entered confirmation data, check whether it matches the password.""" @@ -496,7 +496,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): # confirmation field. If this method is being run from a successful check # to reset the status, just return success if reset_status: - return None + return GUICheck.CHECK_OK
# Skip the check if no password is required if (not self.usepassword.get_active()) or self._user.password_kickstarted: @@ -529,7 +529,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
# Skip the check if no password is required if (not self.usepassword.get_active()) or self._user.password_kickstarted: - return None + return GUICheck.CHECK_OK
pwstrength = self.pw_bar.get_value()
@@ -543,7 +543,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): if pwstrength < 2: # If Done has been clicked twice, waive the check if self._waivePasswordClicks > 1: - return None + return GUICheck.CHECK_OK elif self._waivePasswordClicks == 1: if self._pwq_error: return _("You have provided a weak password: %s. " @@ -560,7 +560,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): error += _("You will have to press Done twice to confirm it.") return error else: - return None + return GUICheck.CHECK_OK
def on_advanced_clicked(self, _button, data=None): """Handler for the Advanced.. button. It starts the Advanced dialog
--- pyanaconda/constants.py | 11 +++++++++++ pyanaconda/ui/gui/spokes/password.py | 24 +++++++++++------------- pyanaconda/ui/gui/spokes/user.py | 36 +++++++++++++++--------------------- pyanaconda/users.py | 4 ++-- 4 files changed, 39 insertions(+), 36 deletions(-)
diff --git a/pyanaconda/constants.py b/pyanaconda/constants.py index 0d1956b..73d2c86 100644 --- a/pyanaconda/constants.py +++ b/pyanaconda/constants.py @@ -130,3 +130,14 @@ FIRSTBOOT_ENVIRON = "firstboot"
# Tainted hardware UNSUPPORTED_HW = 1 << 28 + +# Password validation +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.") +PASSWORD_WEAK = _("The password you have provided is weak. You will have to press Done twice to confirm it.") +PASSWORD_WEAK_WITH_ERROR = _("The password you have provided is weak: %s. You will have to press Done twice to confirm it.") +PASSWORD_WEAK_CONFIRM = _("You have provided a weak password. Press Done again to use anyway.") +PASSWORD_WEAK_CONFIRM_WITH_ERROR = _("You have provided a weak password: %s. Press Done again to use anyway.") + +PASSWORD_STRENGTH_DESC = [_("Empty"), _("Weak"), _("Fair"), _("Good"), _("Strong")] diff --git a/pyanaconda/ui/gui/spokes/password.py b/pyanaconda/ui/gui/spokes/password.py index dc0782d..022b287 100644 --- a/pyanaconda/ui/gui/spokes/password.py +++ b/pyanaconda/ui/gui/spokes/password.py @@ -27,6 +27,10 @@ from pyanaconda.ui.gui.spokes import NormalSpoke from pyanaconda.ui.gui.categories.user_settings import UserSettingsCategory from pyanaconda.ui.common import FirstbootSpokeMixIn
+from pyanaconda.constants import PASSWORD_EMPTY_ERROR, PASSWORD_CONFIRM_ERROR_GUI,\ + PASSWORD_STRENGTH_DESC, PASSWORD_WEAK, PASSWORD_WEAK_WITH_ERROR,\ + PASSWORD_WEAK_CONFIRM_WITH_ERROR + __all__ = ["PasswordSpoke"]
@@ -120,32 +124,27 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
if strength < 50: val = 1 - text = _("Weak") - self._error = _("The password you have provided is weak") if _pwq_error: - self._error += ": %s. " % _pwq_error + self._error = PASSWORD_WEAK_WITH_ERROR % _pwq_error else: - self._error += ". " - self._error += _("You will have to press Done twice to confirm it.") + self._error = PASSWORD_WEAK elif strength < 75: val = 2 - text = _("Fair") self._error = False elif strength < 90: val = 3 - text = _("Good") self._error = False else: val = 4 - text = _("Strong") self._error = False
if not self.pw.get_text(): val = 0 - text = _("Empty") - self._error = _("The password is empty.") + self._error = PASSWORD_EMPTY_ERROR elif self.confirm.get_text() and self.pw.get_text() != self.confirm.get_text(): - self._error = _("The passwords do not match.") + self._error = PASSWORD_CONFIRM_ERROR_GUI + + text = PASSWORD_STRENGTH_DESC[val]
self.pw_bar.set_value(val) self.pw_label.set_text(text) @@ -180,8 +179,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke): # We got a second attempt with the same weak password pass else: - self._error = _("You have provided a weak password: %s. " - " Press Done again to use anyway.") % e[1] + self._error = PASSWORD_WEAK_CONFIRM_WITH_ERROR % e[1] self._oldweak = pw return False
diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py index 47e7b6c..47c5588 100644 --- a/pyanaconda/ui/gui/spokes/user.py +++ b/pyanaconda/ui/gui/spokes/user.py @@ -29,7 +29,10 @@ from pyanaconda.ui.common import FirstbootSpokeMixIn from pyanaconda.ui.gui.utils import enlightbox
from pykickstart.constants import FIRSTBOOT_RECONFIG -from pyanaconda.constants import ANACONDA_ENVIRON, FIRSTBOOT_ENVIRON +from pyanaconda.constants import ANACONDA_ENVIRON, FIRSTBOOT_ENVIRON,\ + PASSWORD_EMPTY_ERROR, PASSWORD_CONFIRM_ERROR_GUI, PASSWORD_STRENGTH_DESC,\ + PASSWORD_WEAK, PASSWORD_WEAK_WITH_ERROR, PASSWORD_WEAK_CONFIRM,\ + PASSWORD_WEAK_CONFIRM_WITH_ERROR from pyanaconda.regexes import GECOS_VALID, USERNAME_VALID, GROUPNAME_VALID, GROUPLIST_FANCY_PARSE
import pwquality @@ -416,19 +419,15 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
if not pwtext: val = 0 - text = _("Empty") elif strength < 50: val = 1 - text = _("Weak") elif strength < 75: val = 2 - text = _("Fair") elif strength < 90: val = 3 - text = _("Good") else: val = 4 - text = _("Strong") + text = PASSWORD_STRENGTH_DESC[val]
self.pw_bar.set_value(val) self.pw_label.set_text(text) @@ -483,9 +482,9 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): return GUICheck.CHECK_OK elif not editable.get_text(): if editable == self.pw: - return _("The password is empty") + return PASSWORD_EMPTY_ERROR else: - return _("The passwords do not match.") + return PASSWORD_CONFIRM_ERROR_GUI else: return GUICheck.CHECK_OK
@@ -500,14 +499,14 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
# Skip the check if no password is required if (not self.usepassword.get_active()) or self._user.password_kickstarted: - result = None + result = GUICheck.CHECK_OK elif self.confirm.get_text() and (self.pw.get_text() != self.confirm.get_text()): - result = _("The passwords do not match.") + result = PASSWORD_CONFIRM_ERROR_GUI else: - result = None + result = GUICheck.CHECK_OK
# If the check succeeded, reset the status of the other check object - if result is None: + if result == GUICheck.CHECK_OK: if editable == self.confirm: self._password_check.update_check_status(check_data=True) else: @@ -546,19 +545,14 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): return GUICheck.CHECK_OK elif self._waivePasswordClicks == 1: if self._pwq_error: - return _("You have provided a weak password: %s. " - " Press Done again to use anyway.") % self._pwq_error + return PASSWORD_WEAK_CONFIRM_WITH_ERROR % self._pwq_error else: - return _("You have provided a weak password. " - " Press Done again to use anyway.") + return PASSWORD_WEAK_CONFIRM else: - error = _("The password you have provided is weak") if self._pwq_error: - error += ": %s. " % self._pwq_error + return PASSWORD_WEAK_WITH_ERROR % self._pwq_error else: - error += ". " - error += _("You will have to press Done twice to confirm it.") - return error + return PASSWORD_WEAK else: return GUICheck.CHECK_OK
diff --git a/pyanaconda/users.py b/pyanaconda/users.py index 67baf72..0121bc1 100644 --- a/pyanaconda/users.py +++ b/pyanaconda/users.py @@ -30,6 +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
import logging log = logging.getLogger("anaconda") @@ -125,8 +126,7 @@ def validatePassword(pw, confirm=None, minlen=6, user="root"): return error
if confirm != None and pw != confirm: - error = _("The passwords you entered were " - "different. Please try again.") + error = PASSWORD_CONFIRM_ERROR_TUI return error
legal = string.digits + string.ascii_letters + string.punctuation + " "
--- pyanaconda/ui/gui/spokes/password.glade | 3 +- pyanaconda/ui/gui/spokes/password.py | 213 ++++++++++++++++++++------------ 2 files changed, 132 insertions(+), 84 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/password.glade b/pyanaconda/ui/gui/spokes/password.glade index 3ad5055..b57b7e4 100644 --- a/pyanaconda/ui/gui/spokes/password.glade +++ b/pyanaconda/ui/gui/spokes/password.glade @@ -87,7 +87,7 @@ <property name="can_focus">True</property> <property name="visibility">False</property> <property name="invisible_char">●</property> - <signal name="changed" handler="_checkPassword" swapped="no"/> + <signal name="changed" handler="_updatePwQuality" swapped="no"/> </object> <packing> <property name="left_attach">1</property> @@ -103,7 +103,6 @@ <property name="visibility">False</property> <property name="invisible_char">●</property> <property name="activates_default">True</property> - <signal name="changed" handler="_checkPassword" swapped="no"/> </object> <packing> <property name="left_attach">1</property> diff --git a/pyanaconda/ui/gui/spokes/password.py b/pyanaconda/ui/gui/spokes/password.py index 022b287..0ce00a3 100644 --- a/pyanaconda/ui/gui/spokes/password.py +++ b/pyanaconda/ui/gui/spokes/password.py @@ -23,13 +23,14 @@ from pyanaconda.i18n import _, N_ from pyanaconda.users import cryptPassword, validatePassword, checkPassword from pwquality import PWQError
+from pyanaconda.ui.gui import GUICheck from pyanaconda.ui.gui.spokes import NormalSpoke from pyanaconda.ui.gui.categories.user_settings import UserSettingsCategory from pyanaconda.ui.common import FirstbootSpokeMixIn
from pyanaconda.constants import PASSWORD_EMPTY_ERROR, PASSWORD_CONFIRM_ERROR_GUI,\ PASSWORD_STRENGTH_DESC, PASSWORD_WEAK, PASSWORD_WEAK_WITH_ERROR,\ - PASSWORD_WEAK_CONFIRM_WITH_ERROR + PASSWORD_WEAK_CONFIRM, PASSWORD_WEAK_CONFIRM_WITH_ERROR
__all__ = ["PasswordSpoke"]
@@ -47,9 +48,6 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
def __init__(self, *args): NormalSpoke.__init__(self, *args) - self._password = None - self._error = False - self._oldweak = None self._kickstarted = False
def initialize(self): @@ -58,6 +56,34 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke): self.pw = self.builder.get_object("pw") self.confirm = self.builder.get_object("confirm")
+ # Install the password checks: + # - Has a password been specified? + # - If a password has been specified and there is data in the confirm box, do they match? + # - How strong is the password? + # - Is there any data in the confirm box? + self.add_check(self.pw, self._checkPasswordEmpty) + + # The password confirmation needs to be checked whenever either of the password + # fields change. Separate checks are created for each field so that edits on either + # will trigger a new check and so that the last edited field will get focus when + # Done is clicked. The checks are saved here so that either check can trigger the + # other check in order to reset the status on both when either field is changed. + # The check_data field is used as a flag to prevent infinite recursion. + self._confirm_check = self.add_check(self.confirm, self._checkPasswordConfirm) + self._password_check = self.add_check(self.pw, self._checkPasswordConfirm) + + # Keep a reference for this check, since it has to be manually run for the + # click Done twice check. + self._pwStrengthCheck = self.add_check(self.pw, self._checkPasswordStrength) + + self.add_check(self.confirm, self._checkPasswordEmpty) + + # Counter for the click Done twice check override + self._waivePasswordClicks = 0 + + # Password validation data + self._pwq_error = None + self._kickstarted = self.data.rootpw.seen if self._kickstarted: self.pw.set_placeholder_text(_("The password is set.")) @@ -73,13 +99,11 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
def refresh(self): self.pw.grab_focus() - self._checkPassword() + self.pw.emit("changed")
@property def status(self): - if self._error: - return _("Error setting root password") - elif self.data.rootpw.password: + if self.data.rootpw.password: return _("Root password is set") elif self.data.rootpw.lock: return _("Root account is disabled") @@ -92,10 +116,11 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke): if "wheel" in user.groups)
def apply(self): - if self._password is None and self._kickstarted: + pw = self.pw.get_text() + if (not pw) and (self._kickstarted): return
- self.data.rootpw.password = cryptPassword(self._password) + self.data.rootpw.password = cryptPassword(pw) self.data.rootpw.isCrypted = True self.data.rootpw.lock = False
@@ -110,102 +135,126 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke): def completed(self): return bool(self.data.rootpw.password or self.data.rootpw.lock)
- def _checkPassword(self, editable = None, data = None): - """This method updates the password indicators according - to the passwords entered by the user. It is called by - the changed Gtk event handler. + def _checkPasswordEmpty(self, editable, data): + """Check whether a password has been specified at all.""" + + # If the password was set by kickstart, skip this check + if self._kickstarted: + return GUICheck.CHECK_OK + + if not editable.get_text(): + if editable == self.pw: + return PASSWORD_EMPTY_ERROR + else: + return PASSWORD_CONFIRM_ERROR_GUI + else: + return GUICheck.CHECK_OK + + def _checkPasswordConfirm(self, editable=None, reset_status=None): + """Check whether the password matches the confirmation data.""" + + # This check is triggered by changes to either the password field or the + # confirmation field. If this method is being run from a successful check + # to reset the status, just return success + if reset_status: + return GUICheck.CHECK_OK + + pw = self.pw.get_text() + confirm = self.confirm.get_text() + + # Skip the check if no password is required + if (not pw and not confirm) and self._kickstarted: + result = GUICheck.CHECK_OK + elif confirm and (pw != confirm): + result = PASSWORD_CONFIRM_ERROR_GUI + else: + result = GUICheck.CHECK_OK + + # If the check succeeded, reset the status of the other check object + if result == GUICheck.CHECK_OK: + if editable == self.confirm: + self._password_check.update_check_status(check_data=True) + else: + self._confirm_check.update_check_status(check_data=True) + + return result + + def _updatePwQuality(self, editable=None, data=None): + """Update the password quality information. + + This function is called by the ::changed signal handler on the + password field. """ + + pwtext = self.pw.get_text() + + # Reset the counter used for the "press Done twice" logic + self._waivePasswordClicks = 0 + try: - strength = checkPassword(self.pw.get_text()) + strength = checkPassword(pwtext) _pwq_error = None except PWQError as e: _pwq_error = e[1] strength = 0
- if strength < 50: + if not pwtext: + val = 0 + elif strength < 50: val = 1 - if _pwq_error: - self._error = PASSWORD_WEAK_WITH_ERROR % _pwq_error - else: - self._error = PASSWORD_WEAK elif strength < 75: val = 2 - self._error = False elif strength < 90: val = 3 - self._error = False else: val = 4 - self._error = False - - if not self.pw.get_text(): - val = 0 - self._error = PASSWORD_EMPTY_ERROR - elif self.confirm.get_text() and self.pw.get_text() != self.confirm.get_text(): - self._error = PASSWORD_CONFIRM_ERROR_GUI - text = PASSWORD_STRENGTH_DESC[val]
self.pw_bar.set_value(val) self.pw_label.set_text(text)
- self.clear_info() - if self._error: - self.set_warning(self._error) - self.window.show_all() - return False + def _checkPasswordStrength(self, editable=None, data=None): + """Update the error message based on password strength.
- return True + Convert the strength set by _updatePwQuality into an error message. + """
- def _validatePassword(self): - # Do various steps to validate the password - # sets self._error to an error string - # Return True if valid, False otherwise - self._error = False pw = self.pw.get_text() confirm = self.confirm.get_text()
- if not pw and not confirm: - if self._kickstarted: - return True - else: - self._error = _("You must provide and confirm a password.") - return False - - try: - self._error = validatePassword(pw, confirm) - except PWQError as e: - if pw == self._oldweak: - # We got a second attempt with the same weak password - pass + # Skip the check if no password is required + if (not pw and not confirm) and self._kickstarted: + return GUICheck.CHECK_OK + + pwstrength = self.pw_bar.get_value() + + # 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 + + if pwstrength < 2: + # If Done has been clicked twice, waive the check + if self._waivePasswordClicks > 1: + return GUICheck.CHECK_OK + elif self._waivePasswordClicks == 1: + if self._pwq_error: + return PASSWORD_WEAK_CONFIRM_WITH_ERROR % self._pwq_error + else: + return PASSWORD_WEAK_CONFIRM else: - self._error = PASSWORD_WEAK_CONFIRM_WITH_ERROR % e[1] - self._oldweak = pw - return False - - if self._error: - return False - - # the self._checkPassword function is used to indicate the password - # strength and need of hitting the Done button twice so use it here as - # well - if not self._checkPassword() and pw != self._oldweak: - # check failed and the Done button was clicked for the first time - self._oldweak = pw - return False - - # if no errors, clear the info for next time we go into the spoke - self._password = pw - self.clear_info() - self._error = False - return True + if self._pwq_error: + return PASSWORD_WEAK_WITH_ERROR % self._pwq_error + else: + return PASSWORD_WEAK + else: + return GUICheck.CHECK_OK
def on_back_clicked(self, button): - if self._validatePassword(): - self.clear_info() - NormalSpoke.on_back_clicked(self, button) - else: - self.clear_info() - self.set_warning(self._error) - self.pw.grab_focus() - self.window.show_all() + # Add a click and re-check the password strength + self._waivePasswordClicks += 1 + self._pwStrengthCheck.update_check_status() + + NormalSpoke.on_back_clicked(self, button)
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()
--- pyanaconda/ui/gui/spokes/user.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py index 33d6041..7a314f1 100644 --- a/pyanaconda/ui/gui/spokes/user.py +++ b/pyanaconda/ui/gui/spokes/user.py @@ -395,10 +395,9 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): def completed(self): return len(self.data.user.userList) > 0
- def password_changed(self, editable=None, data=None): + def _updatePwQuality(self): """This method updates the password indicators according - to the password entered by the user. It is called by - the changed Gtk event handler. + to the password entered by the user. """ pwtext = self.pw.get_text() username = self.username.get_text() @@ -429,8 +428,13 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke):
self.pw.set_sensitive(self.usepassword.get_active()) self.confirm.set_sensitive(self.usepassword.get_active()) + + # Re-check the password self.pw.emit("changed") - self.confirm.emit("changed") + + def password_changed(self, editable=None, data=None): + """Update the password strength level bar""" + self._updatePwQuality()
def username_changed(self, editable = None, data = None): """Called by Gtk callback when the username or hostname @@ -445,16 +449,19 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): self.guesser[editable] = False self.b_advanced.set_sensitive(True)
+ # Re-run the password checks against the new username + self.pw.emit("changed") + def full_name_changed(self, editable = None, data = None): """Called by Gtk callback when the full name field changes. It guesses the username and hostname, strips diacritics and make those lowercase. """ - fullname = self.fullname.get_text() - username = guess_username(fullname)
# after the text is updated in guesser, the guess has to be reenabled if self.guesser[self.username]: + fullname = self.fullname.get_text() + username = guess_username(fullname) self.username.set_text(username) self.guesser[self.username] = True
This makes the behavior of the root password spoke similar to that of the user spoke, in that you can enter it, do nothing, and hit "Done" to return to the progress hub. --- pyanaconda/ui/gui/spokes/password.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/pyanaconda/ui/gui/spokes/password.py b/pyanaconda/ui/gui/spokes/password.py index 57f2649..58e6719 100644 --- a/pyanaconda/ui/gui/spokes/password.py +++ b/pyanaconda/ui/gui/spokes/password.py @@ -98,6 +98,10 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke): self.pw_bar.add_offset_value("high", 4)
def refresh(self): + # Enable the input checks in case they were disabled on the last exit + for check in self.checks: + check.enable() + self.pw.grab_focus() self.pw.emit("changed")
@@ -117,7 +121,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke):
def apply(self): pw = self.pw.get_text() - if (not pw) and (self._kickstarted): + if not pw: return
self.data.rootpw.password = cryptPassword(pw) @@ -249,4 +253,9 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke): self._waivePasswordClicks += 1 self._pwStrengthCheck.update_check_status()
+ # If neither the password nor the confirm field are set, skip the checks + if (not self.pw.get_text()) and (not self.confirm.get_text()): + for check in self.checks: + check.disable() + NormalSpoke.on_back_clicked(self, button)
--- pyanaconda/ui/gui/spokes/user.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py index 7a314f1..b6d855b 100644 --- a/pyanaconda/ui/gui/spokes/user.py +++ b/pyanaconda/ui/gui/spokes/user.py @@ -525,7 +525,8 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke): """
# Skip the check if no password is required - if (not self.usepassword.get_active()) or self._user.password_kickstarted: + if (not self.usepassword.get_active()) or \ + ((not self.pw.get_text()) and (self._user.password_kickstarted)): return GUICheck.CHECK_OK
# If the password failed the validity check, fail this check
On Fri, 2013-10-04 at 14:28 -0400, David Shea wrote:
This splits that one big patch from last time into several fun-sized patches.
Successful GUICheck now return a constant (defined in the GUICheck class), and strength is spelled correctly. I answered Vratislav's question about removing kickstarted passwords with more questions, so no changes there.
These all look good to me. The question about removing kickstarted passwords is something that should be discussed a bit more and maybe some bigger change of the spoke will be needed here.
anaconda-patches@lists.fedorahosted.org