From 4f8e7fe19eecd7553f9c21f91393f53d1a7494b9 Mon Sep 17 00:00:00 2001 From: Brian C. Lane Date: Mar 20 2015 15:23:22 +0000 Subject: Add pwpolicy for the user spoke. Use the new kickstart pwpolicy settings for the root password spoke. Use the 'user' keyword like so: pwpolicy user --strict --minlen=16 --emptyok --- diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py index 3762edf..bffb825 100644 --- a/pyanaconda/ui/gui/spokes/user.py +++ b/pyanaconda/ui/gui/spokes/user.py @@ -36,7 +36,8 @@ from pyanaconda.ui.gui.helpers import GUISpokeInputCheckHandler, GUIDialogInputC from pykickstart.constants import FIRSTBOOT_RECONFIG 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, PASSWORD_WEAK_WITH_ERROR, PASSWORD_WEAK_CONFIRM,\ + PASSWORD_WEAK_CONFIRM_WITH_ERROR, PASSWORD_DONE_TWICE,\ PW_ASCII_CHARS, PASSWORD_ASCII from pyanaconda.regexes import GECOS_VALID, USERNAME_VALID, GROUPNAME_VALID, GROUPLIST_FANCY_PARSE @@ -255,6 +256,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler): self.b_advanced = self.builder.get_object("b_advanced") # Counters for checks that ask the user to click Done to confirm + self._waiveStrengthClicks = 0 self._waiveASCIIClicks = 0 self.guesser = { @@ -274,6 +276,11 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler): self.pw_bar.add_offset_value("medium", 3) self.pw_bar.add_offset_value("high", 4) + # Configure the password policy, if available. Otherwise use defaults. + self.policy = self.data.pwpolicy.get_policy("user") + if not self.policy: + self.policy = self.data.pwpolicy.handler.PwPolicyData() + # indicate when the password was set by kickstart self._user.password_kickstarted = self.data.user.seen if self._user.password_kickstarted: @@ -285,6 +292,13 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler): self.usepassword.set_active(True) self.pw.set_placeholder_text(_("The password was set by kickstart.")) self.confirm.set_placeholder_text(_("The password was set by kickstart.")) + elif not self.policy.emptyok: + # Policy is that a non-empty password is required + self.usepassword.set_active(True) + + if not self.policy.emptyok: + # User isn't allowed to change whether password is required or not + self.usepassword.set_sensitive(False) # Password checks, in order of importance: # - if a password is required, is one specified? @@ -410,8 +424,10 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler): @property def sensitive(self): + # Spoke cannot be entered if a user was set in the kickstart and the user + # policy doesn't allow changes. return not (self.completed and flags.automatedInstall - and self.data.user.seen) + and self.data.user.seen and not self.policy.changesok) @property def completed(self): @@ -425,6 +441,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler): username = self.username.get_text() # Reset the counters used for the "press Done twice" logic + self._waiveStrengthClicks = 0 self._waiveASCIIClicks = 0 self._pwq_valid, strength, self._pwq_error = validatePassword(pwtext, username) @@ -496,7 +513,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler): """ # If the password was set by kickstart, skip the strength check - if self._user.password_kickstarted: + if self._user.password_kickstarted and not self.policy.changesok: return InputCheck.CHECK_OK # Skip the check if no password is required @@ -539,6 +556,10 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler): The password strength has already been checked in _updatePwQuality, called previously in the signal chain. This method converts the data set from there into an error message. + + The password strength check can be waived by pressing "Done" twice. This + is controlled through the self._waiveStrengthClicks counter. The counter + is set in on_back_clicked, which also re-runs this check manually. """ # Skip the check if no password is required @@ -550,13 +571,31 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler): if (not self._pwq_valid) and (self._pwq_error): return self._pwq_error - pwstrength = self.pw_bar.get_value() - - if pwstrength < 2: - if self._pwq_error: - return _(PASSWORD_WEAK_WITH_ERROR) % self._pwq_error + # use strength from policy, not bars + pw = self.pw.get_text() + username = self.username.get_text() + _valid, pwstrength, _error = validatePassword(pw, username, minlen=self.policy.minlen) + + if pwstrength < self.policy.minquality: + # If Done has been clicked twice, waive the check + if self._waiveStrengthClicks > 1: + return InputCheck.CHECK_OK + elif self._waiveStrengthClicks == 1: + if self._pwq_error: + return _(PASSWORD_WEAK_CONFIRM_WITH_ERROR) % self._pwq_error + else: + return _(PASSWORD_WEAK_CONFIRM) else: - return _(PASSWORD_WEAK) + # non-strict allows done to be clicked twice + if self.policy.strict: + done_msg = "" + else: + done_msg = _(PASSWORD_DONE_TWICE) + + if self._pwq_error: + return _(PASSWORD_WEAK_WITH_ERROR) % (self._pwq_error, done_msg) + else: + return _(PASSWORD_WEAK) % done_msg else: return InputCheck.CHECK_OK @@ -601,7 +640,10 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler): # If the failed check is for non-ASCII characters, # add a click to the counter and check again failed_check = next(self.failed_checks_with_message, None) - if failed_check == self._pwASCIICheck: + if not self.policy.strict and failed_check == self._pwStrengthCheck: + self._waiveStrengthClicks += 1 + self._pwStrengthCheck.update_check_status() + elif failed_check == self._pwASCIICheck: self._waiveASCIIClicks += 1 self._pwASCIICheck.update_check_status()