Skip to content

Commit

Permalink
Add "class" to password_policies
Browse files Browse the repository at this point in the history
For example:

    Password    Class
    1234        1
    1abc        2
    1aBC        3
    1aB!        4

Signed-off-by: Rong Tao <[email protected]>
  • Loading branch information
Rtoax committed Nov 23, 2023
1 parent 149f6fd commit 86adf0e
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 4 deletions.
7 changes: 4 additions & 3 deletions data/anaconda.conf
Original file line number Diff line number Diff line change
Expand Up @@ -291,13 +291,14 @@ can_change_users = False
#
# quality <NUMBER> The minimum quality score (see libpwquality).
# length <NUMBER> The minimum length of the password.
# class <NUMBER> The minimum class of the password characters.
# empty Allow an empty password.
# strict Require the minimum quality.
#
password_policies =
root (quality 1, length 6)
user (quality 1, length 6, empty)
luks (quality 1, length 6)
root (quality 1, length 6, class 1)
user (quality 1, length 6, class 1, empty)
luks (quality 1, length 6, class 1)

[License]
# A path to EULA (if any)
Expand Down
6 changes: 5 additions & 1 deletion pyanaconda/core/configuration/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def password_policies(self):
name The name of the policy.
quality The minimum quality score (see libpwquality).
length The minimum length of the password.
class The minimum class of the password characters.
empty Allow an empty password.
strict Require the minimum quality.
Expand All @@ -98,7 +99,7 @@ def _convert_policy_line(cls, line):
if not value and name in ("strict", "empty"):
# Handle a boolean attribute.
attrs[name] = True
elif value and name in ("length", "quality"):
elif value and name in ("length", "class", "quality"):
# Handle an integer attribute.
attrs[name] = int(value)
else:
Expand All @@ -119,5 +120,8 @@ def _validate_policy_attributes(attrs):
if "length" not in attrs:
raise ValueError("The minimal length is not specified.")

if "class" not in attrs:
raise ValueError("The minimal class is not specified.")

if "quality" not in attrs:
raise ValueError("The minimal quality is not specified.")
5 changes: 5 additions & 0 deletions pyanaconda/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ class SecretType(Enum):
SecretType.PASSWORD : N_("The password is too short"),
SecretType.PASSPHRASE : N_("The passphrase is too short")
}
SECRET_TOO_FEW_CLASS = {
SecretType.PASSWORD : N_("The password need more character types, upper/lower letters, digits or special character"),
SecretType.PASSPHRASE : N_("The password need more character types, upper/lower letters, digits or special character")
}
SECRET_WEAK = {
SecretType.PASSWORD : N_("The password you have provided is weak."),
SecretType.PASSPHRASE : N_("The passphrase you have provided is weak.")
Expand All @@ -216,6 +220,7 @@ class SecretType(Enum):
class SecretStatus(Enum):
EMPTY = N_("Empty")
TOO_SHORT = N_("Too short")
TOO_FEW_CLASS = N_("Class less")
WEAK = N_("Weak")
FAIR = N_("Fair")
GOOD = N_("Good")
Expand Down
8 changes: 8 additions & 0 deletions pyanaconda/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,14 @@ def restorecon(paths, root, skip_nonexistent=False):
return True


def get_password_character_class(password):
lower = bool(re.search('[a-z]', password))
upper = bool(re.search('[A-Z]', password))
digit = bool(re.search('[0-9]', password))
special = bool(re.search(r'[?![\]{}*/\\<>":+\-@$%^&()=_#~,\';.`]', password))
return lower + upper + digit + special


def get_image_packages_info(max_string_chars=0):
"""List of strings containing versions of installer image packages.
Expand Down
25 changes: 25 additions & 0 deletions pyanaconda/input_checking.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from pyanaconda.core.kernel import kernel_arguments
from pyanaconda.core import constants, regexes
from pyanaconda.core import users
from pyanaconda.core.util import get_password_character_class
from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.modules.common.constants.objects import USER_INTERFACE
from pyanaconda.modules.common.constants.services import RUNTIME
Expand Down Expand Up @@ -244,6 +245,8 @@ def __init__(self):
self.password_quality_changed = Signal()
self._length_ok = False
self.length_ok_changed = Signal()
self._class_ok = False
self.class_ok_changed = Signal()

@property
def password_score(self):
Expand Down Expand Up @@ -311,6 +314,20 @@ def length_ok(self, value):
self._length_ok = value
self.length_ok_changed.emit(value)

@property
def class_ok(self):
"""Reports if the password has enough class of characters type.
:returns: if the password has enough class of characters type
:rtype: bool
"""
return self._class_ok

@class_ok.setter
def class_ok(self, value):
self._class_ok = value
self.class_ok_changed.emit(value)


class InputCheck(object):
"""Input checking base class."""
Expand Down Expand Up @@ -379,6 +396,7 @@ def run(self, check_request):
"""

length_ok = False
class_ok = False
error_message = ""
pw_quality = 0
try:
Expand All @@ -394,8 +412,10 @@ def run(self, check_request):
if check_request.policy.allow_empty and not check_request.password:
# if we are OK with empty passwords, then empty passwords are also fine length wise
length_ok = True
class_ok = True
else:
length_ok = len(check_request.password) >= check_request.policy.min_length
class_ok = get_password_character_class(check_request.password) >= check_request.policy.min_class

if not check_request.password:
if check_request.policy.allow_empty:
Expand All @@ -411,6 +431,10 @@ def run(self, check_request):
# This is because the error messages returned by libpwquality
# for short passwords don't make much sense.
error_message = _(constants.SECRET_TOO_SHORT[check_request.secret_type])
elif not class_ok:
pw_score = 0
status_text = _(constants.SecretStatus.TOO_FEW_CLASS.value)
error_message = _(constants.SECRET_TOO_FEW_CLASS[check_request.secret_type])
elif error_message:
pw_score = 1
status_text = _(constants.SecretStatus.WEAK.value)
Expand All @@ -436,6 +460,7 @@ def run(self, check_request):
self.result.password_quality = pw_quality # pylint: disable=attribute-defined-outside-init
self.result.error_message = error_message # pylint: disable=attribute-defined-outside-init
self.result.length_ok = length_ok # pylint: disable=attribute-defined-outside-init
self.result.class_ok = class_ok # pylint: disable=attribute-defined-outside-init


class PasswordFIPSCheck(InputCheck):
Expand Down
14 changes: 14 additions & 0 deletions pyanaconda/modules/common/structures/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class PasswordPolicy(DBusData):
def __init__(self):
self._min_quality = UInt16(0)
self._min_length = UInt16(0)
self._min_class = UInt16(0)
self._allow_empty = True
self._is_strict = False

Expand Down Expand Up @@ -60,6 +61,18 @@ def min_length(self) -> UInt16:
def min_length(self, value):
self._min_length = UInt16(value)

@property
def min_class(self) -> UInt16:
"""The minimum class of the password characters.
:return: a number of type of password characters
"""
return self._min_class

@min_class.setter
def min_class(self, value):
self._min_class = UInt16(value)

@property
def allow_empty(self) -> Bool:
"""Should an empty password be allowed?
Expand Down Expand Up @@ -99,6 +112,7 @@ def from_defaults(cls, policy_name):

policy.min_quality = attrs.get("quality")
policy.min_length = attrs.get("length")
policy.min_class = attrs.get("class")
policy.allow_empty = attrs.get("empty", False)
policy.is_strict = attrs.get("strict", False)
break
Expand Down

0 comments on commit 86adf0e

Please sign in to comment.