[PATCH 3/3] Allow non-ASCII characters in passwords (#960837)

David Shea dshea at redhat.com
Fri May 9 20:19:22 UTC 2014


Warn about non-ASCII characters and how keymaps are important and
(maybe) not configurable at login, but let the user set a non-ASCII
password if they really want it.
---
 pyanaconda/constants.py              |  2 ++
 pyanaconda/ui/gui/spokes/password.py | 28 ++++++++++++++++++++++++++--
 pyanaconda/ui/gui/spokes/user.py     | 27 +++++++++++++++++++++++++--
 pyanaconda/ui/tui/spokes/__init__.py | 11 ++++++++++-
 pyanaconda/users.py                  | 11 +----------
 5 files changed, 64 insertions(+), 15 deletions(-)

diff --git a/pyanaconda/constants.py b/pyanaconda/constants.py
index f44ef7e..2e84838 100644
--- a/pyanaconda/constants.py
+++ b/pyanaconda/constants.py
@@ -149,6 +149,8 @@ PASSWORD_WEAK = N_("The password you have provided is weak. You will have to pre
 PASSWORD_WEAK_WITH_ERROR = N_("The password you have provided is weak: %s. You will have to press Done twice to confirm it.")
 PASSWORD_WEAK_CONFIRM = N_("You have provided a weak password. Press Done again to use anyway.")
 PASSWORD_WEAK_CONFIRM_WITH_ERROR = N_("You have provided a weak password: %s. Press Done again to use anyway.")
+PASSWORD_ASCII = N_("The password you have provided contains non-ASCII characters. You may not be able to switch between keyboard layouts to login. Press Done twice to continue.")
+PASSWORD_ASCII_CONFIRM = N_("The password you have provided contains non-ASCII characters. Press Done again to continue.")
 
 PASSWORD_STRENGTH_DESC = [N_("Empty"), N_("Weak"), N_("Fair"), N_("Good"), N_("Strong")]
 
diff --git a/pyanaconda/ui/gui/spokes/password.py b/pyanaconda/ui/gui/spokes/password.py
index 3855758..a12e817 100644
--- a/pyanaconda/ui/gui/spokes/password.py
+++ b/pyanaconda/ui/gui/spokes/password.py
@@ -32,7 +32,8 @@ from pyanaconda.ui.helpers import InputCheck
 
 from pyanaconda.constants import PASSWORD_EMPTY_ERROR, PASSWORD_CONFIRM_ERROR_GUI,\
         PASSWORD_STRENGTH_DESC, PASSWORD_WEAK, PASSWORD_WEAK_WITH_ERROR,\
-        PASSWORD_WEAK_CONFIRM, PASSWORD_WEAK_CONFIRM_WITH_ERROR
+        PASSWORD_WEAK_CONFIRM, PASSWORD_WEAK_CONFIRM_WITH_ERROR, PW_ASCII_CHARS, PASSWORD_ASCII,\
+        PASSWORD_ASCII_CONFIRM
 
 __all__ = ["PasswordSpoke"]
 
@@ -63,6 +64,7 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler)
         # - 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?
+        # - Does the password contain non-ASCII characters?
         # - Is there any data in the confirm box?
         self.add_check(self.pw, self._checkPasswordEmpty)
 
@@ -75,9 +77,10 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler)
         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
+        # Keep a reference for these checks, since they have to be manually run for the
         # click Done twice check.
         self._pwStrengthCheck = self.add_check(self.pw, self._checkPasswordStrength)
+        self._pwASCIICheck = self.add_check(self.pw, self._checkPasswordASCII)
 
         self.add_check(self.confirm, self._checkPasswordEmpty)
 
@@ -255,10 +258,31 @@ class PasswordSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler)
         else:
             return InputCheck.CHECK_OK
 
+    def _checkPasswordASCII(self, inputcheck):
+        """Set an error message if the password contains non-ASCII characters.
+
+           Like the password strength check, this check can be bypassed by
+           pressing Done twice.
+        """
+
+        # If Done has been clicked twice, waive the check
+        if self._waivePasswordClicks > 1:
+            return InputCheck.CHECK_OK
+
+        password = self.get_input(inputcheck.input_obj)
+        if password and any(char not in PW_ASCII_CHARS for char in password):
+            if self._waivePasswordClicks == 0:
+                return _(PASSWORD_ASCII)
+            else:
+                return _(PASSWORD_ASCII_CONFIRM)
+
+        return InputCheck.CHECK_OK
+
     def on_back_clicked(self, button):
         # Add a click and re-check the password strength
         self._waivePasswordClicks += 1
         self._pwStrengthCheck.update_check_status()
+        self._pwASCIICheck.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()):
diff --git a/pyanaconda/ui/gui/spokes/user.py b/pyanaconda/ui/gui/spokes/user.py
index dd40c68..037e622 100644
--- a/pyanaconda/ui/gui/spokes/user.py
+++ b/pyanaconda/ui/gui/spokes/user.py
@@ -38,7 +38,7 @@ 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_CONFIRM,\
-        PASSWORD_WEAK_CONFIRM_WITH_ERROR
+        PASSWORD_WEAK_CONFIRM_WITH_ERROR, PW_ASCII_CHARS, PASSWORD_ASCII, PASSWORD_ASCII_CONFIRM
 from pyanaconda.regexes import GECOS_VALID, USERNAME_VALID, GROUPNAME_VALID, GROUPLIST_FANCY_PARSE
 
 __all__ = ["UserSpoke", "AdvancedUserDialog"]
@@ -283,6 +283,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
         # - if a password is required, is one specified?
         # - 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 strong password is specified, does it contain non-ASCII data?
         # - if a password is required, is there any data in the confirm box?
         self.add_check(self.pw, self._checkPasswordEmpty)
 
@@ -295,9 +296,10 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
         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
+        # Keep a reference to these checks, since they have to be manually run for the
         # click Done twice check.
         self._pwStrengthCheck = self.add_check(self.pw, self._checkPasswordStrength)
+        self._pwASCIICheck = self.add_check(self.pw, self._checkPasswordASCII)
 
         self.add_check(self.confirm, self._checkPasswordEmpty)
 
@@ -562,6 +564,26 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
         else:
             return InputCheck.CHECK_OK
 
+    def _checkPasswordASCII(self, inputcheck):
+        """Set an error message if the password contains non-ASCII characters.
+
+           Like the password strength check, this check can be bypassed by
+           pressing Done twice.
+        """
+
+        # If Done has been clicked twice, waive the check
+        if self._waivePasswordClicks > 1:
+            return InputCheck.CHECK_OK
+
+        password = self.get_input(inputcheck.input_obj)
+        if password and any(char not in PW_ASCII_CHARS for char in password):
+            if self._waivePasswordClicks == 0:
+                return _(PASSWORD_ASCII)
+            else:
+                return _(PASSWORD_ASCII_CONFIRM)
+
+        return InputCheck.CHECK_OK
+
     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.
@@ -586,6 +608,7 @@ class UserSpoke(FirstbootSpokeMixIn, NormalSpoke, GUISpokeInputCheckHandler):
         # Add a click and re-check the password strength
         self._waivePasswordClicks += 1
         self._pwStrengthCheck.update_check_status()
+        self._pwASCIICheck.update_check_status()
 
         # If there is no user set, skip the checks
         if not self.username.get_text():
diff --git a/pyanaconda/ui/tui/spokes/__init__.py b/pyanaconda/ui/tui/spokes/__init__.py
index 75bc18c..21d726a 100644
--- a/pyanaconda/ui/tui/spokes/__init__.py
+++ b/pyanaconda/ui/tui/spokes/__init__.py
@@ -26,7 +26,7 @@ import re
 from collections import namedtuple
 from pyanaconda.iutil import setdeepattr, getdeepattr
 from pyanaconda.i18n import N_, _
-from pyanaconda.constants import PASSWORD_CONFIRM_ERROR_TUI
+from pyanaconda.constants import PASSWORD_CONFIRM_ERROR_TUI, PW_ASCII_CHARS
 
 __all__ = ["TUISpoke", "EditTUISpoke", "EditTUIDialog", "EditTUISpokeEntry",
            "StandaloneSpoke", "NormalTUISpoke"]
@@ -146,6 +146,15 @@ class EditTUIDialog(NormalTUISpoke):
                 if not question_window.answer:
                     return None
 
+            if any(char not in PW_ASCII_CHARS for char in pw):
+                error = _("You have provided a password containing non-ASCII characters.\n"
+                          "You may not be able to switch between keyboard layouts to login.\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:
+                    return None
+
             self.value = cryptPassword(pw)
             return None
         else:
diff --git a/pyanaconda/users.py b/pyanaconda/users.py
index ca2a0c8..5cb884b 100644
--- a/pyanaconda/users.py
+++ b/pyanaconda/users.py
@@ -29,8 +29,7 @@ import os.path
 from pyanaconda import iutil
 import pwquality
 from pyanaconda.iutil import strip_accents
-from pyanaconda.i18n import _
-from pyanaconda.constants import PASSWORD_MIN_LEN, PW_ASCII_CHARS
+from pyanaconda.constants import PASSWORD_MIN_LEN
 
 import logging
 log = logging.getLogger("anaconda")
@@ -162,14 +161,6 @@ def validatePassword(pw, user="root", settings=None):
             validatePassword.pwqsettings.minlen = PASSWORD_MIN_LEN
         settings = validatePassword.pwqsettings
 
-    for letter in pw:
-        if letter not in PW_ASCII_CHARS:
-            message = _("Requested password contains "
-                      "non-ASCII characters, which are "
-                      "not allowed.")
-            valid = False
-            break
-
     if valid:
         try:
             strength = settings.check(pw, None, user)
-- 
1.9.0



More information about the anaconda-patches mailing list