Skip to content

Commit

Permalink
delete new function rand_str_minclass, optimize rand_user_password
Browse files Browse the repository at this point in the history
Co-authored-by: shixuantong <shixuantong1@huawei.com>
Co-authored-by: James Falcon <james.falcon@canonical.com>
  • Loading branch information
xiaoge1001 and TheRealFalcon committed Oct 17, 2024
1 parent 89dbe50 commit 72cf348
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 91 deletions.
33 changes: 28 additions & 5 deletions cloudinit/config/cc_set_passwords.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
"""Set Passwords: Set user passwords and enable/disable SSH password auth"""

import logging
import random
import re
from string import ascii_letters, digits
import string
from typing import List

from cloudinit import features, lifecycle, subp, util
Expand All @@ -30,9 +31,6 @@

LOG = logging.getLogger(__name__)

# We are removing certain 'painful' letters/numbers
PW_SET = "".join([x for x in ascii_letters + digits if x not in "loLOI01"])


def get_users_by_type(users_list: list, pw_type: str) -> list:
"""either password or type: RANDOM is required, user is always required"""
Expand Down Expand Up @@ -248,4 +246,29 @@ def handle(name: str, cfg: Config, cloud: Cloud, args: list) -> None:


def rand_user_password(pwlen=20):
return util.rand_str_minclass(pwlen, minclass=3, select_from=PW_SET)
if pwlen < 4:
raise ValueError("Password length must be at least 4 characters.")

# There are often restrictions on the minimum number of character
# classes required in a password, so ensure we at least one character
# from each class.
res_rand_list = [
random.choice(string.digits),
random.choice(string.ascii_lowercase),
random.choice(string.ascii_uppercase),
random.choice(string.punctuation),
]

res_rand_list.extend(
list(
util.rand_str(
pwlen - len(res_rand_list),
select_from=string.digits
+ string.ascii_lowercase
+ string.ascii_uppercase
+ string.punctuation,
)
)
)
random.shuffle(res_rand_list)
return "".join(res_rand_list)
25 changes: 0 additions & 25 deletions cloudinit/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,31 +299,6 @@ def rand_str(strlen=32, select_from=None):
return "".join([r.choice(select_from) for _x in range(strlen)])


def rand_str_minclass(strlen=32, minclass=3, select_from=None):
if strlen <= 0:
strlen = 32
LOG.warning("strlen <= 0, use default value(32)")

if minclass <= 0:
minclass = 3
LOG.warning("minclass <= 0, use default value(3)")

res_rand_list = [
random.choice(string.digits),
random.choice(string.ascii_lowercase),
random.choice(string.ascii_uppercase),
]
if minclass > 3:
res_rand_list.append(random.choice(string.punctuation))
if select_from:
select_from += string.punctuation

res_rand_list = random.sample(res_rand_list, min(minclass, strlen, 4))
res_rand_list.extend(list(rand_str(strlen - minclass, select_from)))
random.shuffle(res_rand_list)
return "".join(res_rand_list)


def rand_dict_key(dictionary, postfix=None):
if not postfix:
postfix = ""
Expand Down
35 changes: 35 additions & 0 deletions tests/unittests/config/test_cc_set_passwords.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import copy
import logging
import string
from unittest import mock

import pytest
Expand Down Expand Up @@ -559,6 +560,40 @@ def test_expire_old_behavior(self, cfg, mocker, caplog):
assert "Expired passwords" not in caplog.text


class TestRandUserPassword:
def _get_str_class_num(self, str):
return sum(
[
any(c.islower() for c in str),
any(c.isupper() for c in str),
any(c.isupper() for c in str),
any(c in string.punctuation for c in str),
]
)

@pytest.mark.parametrize(
"strlen, expected_result",
[
(1, ValueError),
(2, ValueError),
(3, ValueError),
(4, 4),
(5, 4),
(5, 4),
(6, 4),
(20, 4),
],
)
def test_get_str_class_num(self, strlen, expected_result):
if expected_result is ValueError:
with pytest.raises(expected_result):
setpass.rand_user_password(strlen)
else:
rand_password = setpass.rand_user_password(strlen)
assert len(rand_password) == strlen
assert self._get_str_class_num(rand_password) == expected_result


class TestSetPasswordsSchema:
@pytest.mark.parametrize(
"config, expectation",
Expand Down
61 changes: 0 additions & 61 deletions tests/unittests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -3369,64 +3369,3 @@ def test_logexc_with_log_level(self, caplog, log_level):
),
("tests.unittests.test_util", logging.DEBUG, "an error occurred"),
]


class TestRandStr:
def _get_str_class_num(self, str):
import string

str_class_num = 0
if any(c.islower() for c in str):
str_class_num += 1
if any(c.isupper() for c in str):
str_class_num += 1
if any(c.isdigit() for c in str):
str_class_num += 1
if any(c in string.punctuation for c in str):
str_class_num += 1
return str_class_num

@pytest.mark.parametrize(
"strlen, param_minclass, expected_minclass, expected_maxclass",
[
(1, 1, 1, 1),
(1, 2, 1, 1),
(1, 3, 1, 1),
(1, 4, 1, 1),
(2, 1, 1, 2),
(2, 2, 2, 2),
(2, 3, 2, 2),
(2, 4, 2, 2),
(3, 1, 1, 3),
(3, 2, 2, 3),
(3, 3, 3, 3),
(3, 4, 3, 3),
(4, 1, 1, 3),
(4, 2, 2, 3),
(4, 3, 3, 3),
(4, 4, 4, 4),
(5, 1, 1, 3),
(5, 2, 2, 3),
(5, 3, 3, 3),
(5, 4, 4, 4),
(5, 5, 4, 4),
(6, 1, 1, 3),
(6, 2, 2, 3),
(6, 3, 3, 3),
(6, 4, 4, 4),
(6, 5, 4, 4),
(6, 6, 4, 4),
(20, 1, 1, 3),
(20, 2, 2, 3),
(20, 3, 3, 3),
(20, 4, 4, 4),
(20, 5, 4, 4),
],
)
def test_get_str_class_num(
self, strlen, param_minclass, expected_minclass, expected_maxclass
):
res_rand_str = util.rand_str_minclass(strlen, param_minclass)
actual_class = self._get_str_class_num(res_rand_str)
assert actual_class >= expected_minclass
assert actual_class <= expected_maxclass

0 comments on commit 72cf348

Please sign in to comment.