diff --git a/changelogs/fragments/122-implement-password-sanitation-before-hashing.yml b/changelogs/fragments/122-implement-password-sanitation-before-hashing.yml
new file mode 100644
index 00000000..89a64aa3
--- /dev/null
+++ b/changelogs/fragments/122-implement-password-sanitation-before-hashing.yml
@@ -0,0 +1,7 @@
+---
+bugfixes:
+ - system_access_users - Introduced password sanitization to fix parsing errors.
+ - system_access_users - Introduced password verification to fix passwords not being updated.
+
+minor_changes:
+ - system_access_users - Enhanced group removal handling
diff --git a/molecule/system_access_users/converge.yml b/molecule/system_access_users/converge.yml
index 2093e320..605ed2dd 100644
--- a/molecule/system_access_users/converge.yml
+++ b/molecule/system_access_users/converge.yml
@@ -7,13 +7,13 @@
ansible.builtin.debug:
msg: "test"
- # Test User minimum requirements
+ # Test User minimum requirements
- name: "Test User 1: Test minimum requirements User Creation"
puzzle.opnsense.system_access_users:
username: test_user_1
password: test_password_1
- # Test User minimum requirements disabled
+ # Test User minimum requirements disabled
- name: "Test User 2: Test disabled User Creation"
puzzle.opnsense.system_access_users:
username: test_user_2
@@ -21,14 +21,14 @@
full_name: "Test User 2: Test disabled User Creation"
disabled: True
- # Test User with Full Name
+ # Test User with Full Name
- name: "Test User 3: Test User Creation with Full Name"
puzzle.opnsense.system_access_users:
username: test_user_3
password: test_password_3
full_name: "Test User 3: Test User Creation with Full Name"
- # Test User with E-Mail
+ # Test User with E-Mail
- name: "Test User 4: Test User Creation with E-Mail"
puzzle.opnsense.system_access_users:
username: test_user_4
@@ -36,7 +36,7 @@
email: test_user_4@test.ch
full_name: "Test User 4: Test User Creation with E-Mail"
- # Test User with Comment
+ # Test User with Comment
- name: "Test User 5: Test User Creation with Comment"
puzzle.opnsense.system_access_users:
username: test_user_5
@@ -44,7 +44,7 @@
comment: Test User 5 Comment
full_name: "Test User 5: Test User Creation with Comment"
- # Test User with Preferred landing page
+ # Test User with Preferred landing page
- name: "Test User 6: Test User Creation with Preferred landing page"
puzzle.opnsense.system_access_users:
username: test_user_6
@@ -52,7 +52,7 @@
landing_page: /ui/ipsec/sessions
full_name: "Test User 6: Test User Creation with Preferred landing page"
- # Test User with nologin shell
+ # Test User with nologin shell
- name: "Test User 7: Test User Creation with nologin shell"
puzzle.opnsense.system_access_users:
username: test_user_7
@@ -60,7 +60,7 @@
shell: /sbin/nologin
full_name: "Test User 7: Test User Creation with nologin shell"
- # Test User with csh shell
+ # Test User with csh shell
- name: "Test User 8: Test User Creation with csh shell"
puzzle.opnsense.system_access_users:
username: test_user_8
@@ -68,7 +68,7 @@
shell: /bin/csh
full_name: "Test User 8: Test User Creation with csh shell"
- # Test User with sh shell
+ # Test User with sh shell
- name: "Test User 9: Test User Creation with sh shell"
puzzle.opnsense.system_access_users:
username: test_user_9
@@ -76,7 +76,7 @@
shell: /bin/sh
full_name: "Test User 9: Test User Creation with sh shell"
- # Test User with tcsh shell
+ # Test User with tcsh shell
- name: "Test User 10: Test User Creation with tcsh shell"
puzzle.opnsense.system_access_users:
username: test_user_10
@@ -84,7 +84,7 @@
shell: /bin/tcsh
full_name: "Test User 10: Test User Creation with tcsh shell"
- # Test User with Expiration date
+ # Test User with Expiration date
- name: "Test User 11: Test User Creation with Expiration date"
puzzle.opnsense.system_access_users:
username: test_user_11
@@ -92,7 +92,7 @@
expires: 02/27/2024
full_name: "Test User 11: Test User Creation with Expiration date"
- # Test User with group as string
+ # Test User with group as string
- name: "Test User 12: Test User Creation with group as string"
puzzle.opnsense.system_access_users:
username: test_user_12
@@ -100,34 +100,34 @@
full_name: "Test User 12: Test User Creation with group as string"
groups: admins
- # Test User with group as list
+ # Test User with group as list
- name: "Test User 13: Test User Creation with group as list"
puzzle.opnsense.system_access_users:
username: test_user_13
password: test_password_13
full_name: "Test User 13: Test User Creation with group as list"
groups:
- - admins
+ - admins
- # Test User with not existing group as list
+ # Test User with not existing group as list
- name: "Test User 14: Test User Creation with not existing group as list"
puzzle.opnsense.system_access_users:
username: test_user_14
password: test_password_14
full_name: "Test User 14: Test User Creation with not existing group as list"
groups:
- - test
+ - test
register: test_user_14_result
ignore_errors: yes
- name: "Verify that the user creation failed due to non-existing group"
ansible.builtin.assert:
that:
- - test_user_14_result is failed
+ - test_user_14_result is failed
fail_msg: "User creation should fail due to non-existing group"
success_msg: "User creation failed as expected due to non-existing group"
- # Test User with empty otp_seed
+ # Test User with empty otp_seed
- name: "Test User 15: Test User Creation with empty otp_seed"
puzzle.opnsense.system_access_users:
username: test_user_15
@@ -135,7 +135,7 @@
otp_seed: ""
full_name: "Test User 15: Test User Creation with empty otp_seed"
- # Test User with otp_seed
+ # Test User with otp_seed
- name: "Test User 16: Test User Creation with otp_seed"
puzzle.opnsense.system_access_users:
username: test_user_16
@@ -143,7 +143,7 @@
otp_seed: test_seed
full_name: "Test User 16: Test User Creation with otp_seed"
- # Test User with empty authorizedkeys
+ # Test User with empty authorizedkeys
- name: "Test User 17: Test User Creation with empty authorizedkeys"
puzzle.opnsense.system_access_users:
username: test_user_17
@@ -151,21 +151,21 @@
authorizedkeys: ""
full_name: "Test User 17: Test User Creation with empty authorizedkeys"
- # Test User with authorizedkeys
+ # Test User with authorizedkeys
- name: "Test User 18: Test User Creation with authorizedkeys"
puzzle.opnsense.system_access_users:
- username: test_user_18
- password: test_password_18
- authorizedkeys: test_authorized_key
- full_name: "Test User 18: Test User Creation with authorizedkeys"
+ username: test_user_18
+ password: test_password_18
+ authorizedkeys: test_authorized_key
+ full_name: "Test User 18: Test User Creation with authorizedkeys"
- # Test User with empty api_keys
+ # Test User with empty api_keys
- name: "Test User 19: Test User Creation with empty api_keys"
puzzle.opnsense.system_access_users:
- username: test_user_19
- password: test_password_19
- apikeys: ""
- full_name: "Test User 19: Test User Creation with empty api_keys"
+ username: test_user_19
+ password: test_password_19
+ apikeys: ""
+ full_name: "Test User 19: Test User Creation with empty api_keys"
register: api_keys_result
- name: Return the created apikeys and secret of Test User 19
@@ -175,30 +175,30 @@
- "'generated_apikeys' in api_keys_result"
- api_keys_result.generated_apikeys | length > 0
- # Test User with too short api_keys
+ # Test User with too short api_keys
- name: "Test User 20: Test User Creation with too short api_keys"
puzzle.opnsense.system_access_users:
- username: test_user_20
- password: test_password_20
- apikeys: "TEST_API_KEY"
- full_name: "Test User 20: Test User Creation with too short api_keys"
+ username: test_user_20
+ password: test_password_20
+ apikeys: "TEST_API_KEY"
+ full_name: "Test User 20: Test User Creation with too short api_keys"
register: test_user_20_result
ignore_errors: yes
- name: "Verify that the user creation failed due to too short api key"
ansible.builtin.assert:
that:
- - test_user_20_result is failed
+ - test_user_20_result is failed
fail_msg: "The API key: TEST_API_KEY is not a valid string. Must be >= 80 characters."
success_msg: "The API key: TEST_API_KEY is not a valid string. Must be >= 80 characters."
- # Test User with valid api_keys
+ # Test User with valid api_keys
- name: "Test User 21: Test User Creation with valid api_keys"
puzzle.opnsense.system_access_users:
- username: test_user_21
- password: test_password_21
- apikeys: "TEST_API_KEY_WITH_RANDOM_CHARS_UNTIL_80_zo5Y3bUpOQFfbQnAOB6GqbHsPAP9Jqbjofnqu9xc"
- full_name: "Test User 21: Test User Creation with valid api_keys"
+ username: test_user_21
+ password: test_password_21
+ apikeys: "TEST_API_KEY_WITH_RANDOM_CHARS_UNTIL_80_zo5Y3bUpOQFfbQnAOB6GqbHsPAP9Jqbjofnqu9xc"
+ full_name: "Test User 21: Test User Creation with valid api_keys"
register: api_keys_result
- name: Return the created apikeys and secret of Test User 21
@@ -206,4 +206,36 @@
msg: "The following api_keys were created {{ api_keys_result.generated_apikeys }}"
when:
- "'generated_apikeys' in api_keys_result"
- - api_keys_result.generated_apikeys | length > 0
\ No newline at end of file
+ - api_keys_result.generated_apikeys | length > 0
+
+ # Test User password escaping
+ - name: "Test User 22: Test password escaping"
+ puzzle.opnsense.system_access_users:
+ username: test_user_22
+ password: test_password_22\
+ shell: /bin/sh
+ groups:
+ - admins
+
+ # Test User password escaping
+ - name: "Test User 23: Test password escaping"
+ puzzle.opnsense.system_access_users:
+ username: test_user_23
+ password: test_password_23'
+ shell: /bin/sh
+ groups:
+ - admins
+
+ # we have no alternative way to compare the values
+ # other than getting them from the config
+ # see https://github.com/opnsense/core/blob/24.1/src/opnsense/scripts/syslog/log_archive#L36
+ - name: Get current config
+ ansible.builtin.slurp:
+ src: /conf/config.xml
+ register: current_config
+
+ - name: Test that no error message is in config
+ ansible.builtin.assert:
+ that:
+ - "'syntax error, unexpected identifier \"cost\", expecting \")\" in Command line code on line 1' not in (current_config.content | b64decode | string)"
+ - "'syntax error, unexpected single-quoted string \",PASSWORD_BCRYPT,[ \", expecting \")\" in Command line code on line 1' not in (current_config.content | b64decode | string)"
diff --git a/plugins/module_utils/system_access_users_utils.py b/plugins/module_utils/system_access_users_utils.py
index 33e0de1e..7d844e59 100644
--- a/plugins/module_utils/system_access_users_utils.py
+++ b/plugins/module_utils/system_access_users_utils.py
@@ -63,6 +63,46 @@ class OPNSenseCryptReturnError(Exception):
"""
+class OPNSensePasswordVerifyReturnError(Exception):
+ """
+ Exception raised when the return value of the instance is not what is expected
+ """
+
+
+def password_verify(existing_user_password: str, password: Optional[str]) -> bool:
+ """
+ Verify if provided password matches the stored password using OPNsense's PHP command.
+
+ Args:
+ existing_user_password (str): The hashed password stored in the XML config.
+ password (str): The plaintext password to verify.
+
+ Returns:
+ bool: True if passwords match, False otherwise.
+
+ Raises:
+ OPNSensePasswordVerifyReturnError: If an error occurs during verification.
+ """
+
+ if password is None:
+ return False
+
+ # check if current password matches hash
+ password_matches = opnsense_utils.run_command(
+ php_requirements=[],
+ command=f"password_verify('{password}','{existing_user_password}');",
+ )
+
+ if password_matches.get("stderr"):
+ raise OPNSensePasswordVerifyReturnError("error encounterd verifying password")
+
+ # if return code of password_matches not equals 1, it's a match
+ if password_matches.get("stdout") != "1":
+ return True
+
+ return False
+
+
@dataclass
class Group:
"""
@@ -175,7 +215,7 @@ class User:
Args:
name (str): The username of the user.
- password (str): The user's password.
+ password (Optional[str]): The user's password.
scope (Optional[str]): The scope of the user, default is "User".
descr (Optional[str]): A description of the user, if available.
ipsecpsk (Optional[str]): IPsec pre-shared key, if applicable.
@@ -210,7 +250,7 @@ class User:
"""
name: str
- password: str
+ password: Optional[str] = None
scope: Optional[str] = "User"
descr: Optional[str] = None
ipsecpsk: Optional[str] = None
@@ -245,10 +285,22 @@ def __eq__(self, other) -> bool:
if not isinstance(other, User):
return False
+ if not hasattr(self, "password") or not hasattr(other, "password"):
+ return False
+
for _field in fields(self):
- if _field.name not in ["password", "uid", "otp_seed", "apikeys"]:
- if getattr(self, _field.name) != getattr(other, _field.name):
- return False
+ if _field.name in ["uid", "otp_seed", "apikeys"]:
+ continue
+
+ if _field.name == "password" and not password_verify(
+ existing_user_password=getattr(other, _field.name),
+ password=self.password,
+ ):
+ return False
+
+ # if value is not equal return False
+ if getattr(self, _field.name) != getattr(other, _field.name):
+ return False
return True
@@ -383,7 +435,6 @@ def to_etree(self) -> Element:
user_dict: dict = asdict(self)
for user_key, user_val in user_dict.copy().items():
-
if user_val is None and user_key in [
"expires",
"ipsecpsk",
@@ -690,6 +741,10 @@ def _update_user_groups(self, user: User, existing_user: Optional[User] = None):
for existing_group in self._groups:
if existing_group.check_if_user_in_group(target_user):
existing_group.remove_user(target_user)
+ if target_user.groupname:
+ target_user.groupname.remove(existing_group.name)
+ if not target_user.groupname:
+ target_user.groupname = None
return # Exit the method after removing the user from all groups.
# Convert groupname to a list if it's not already.
@@ -727,10 +782,13 @@ def set_user_password(self, user: User) -> None:
"configure_params"
]
+ # sanitize and escape password
+ escaped_password = user.password.replace("\\", "\\\\").replace("'", "\\'")
+
# format parameters
formatted_params = [
(
- param.replace("'password'", f"'{user.password}'")
+ param.replace("'password'", f"'{escaped_password}'")
if "password" in param
else param
)
@@ -780,28 +838,37 @@ def add_or_update(self, user: User) -> None:
)
next_uid: Element = self.get("uid")
- # since the current password of an user cannot not be compared with the new one,
- # we're setting the password anyways
- self.set_user_password(user)
-
if existing_user:
+ if not password_verify(
+ existing_user_password=existing_user.password, password=user.password
+ ):
+ self.set_user_password(user)
+
+ # since we don't want the clear-type password to be set,
+ # and it is clear a update is not needed, we remove it from the update
+ if "password" in user.__dict__:
+ del user.__dict__["password"]
+
# Update groups if needed
self._update_user_groups(user, existing_user)
# Update existing user's attributes
existing_user.__dict__.update(user.__dict__)
- else:
- # Assign UID if not set
- if not user.uid:
- user.uid = next_uid.text
- # Increase the next_uid
- self.set(value=str(int(next_uid.text) + 1), setting="uid")
-
- if user.groupname:
- # Update groups for the new user
- self._update_user_groups(user)
- # Add the new user
- self._users.append(user)
+
+ return
+
+ self.set_user_password(user)
+ # Assign UID if not set
+ if not user.uid:
+ user.uid = next_uid.text
+ # Increase the next_uid
+ self.set(value=str(int(next_uid.text) + 1), setting="uid")
+
+ if user.groupname:
+ # Update groups for the new user
+ self._update_user_groups(user)
+ # Add the new user
+ self._users.append(user)
def delete(self, user: User) -> None:
"""
diff --git a/tests/unit/plugins/module_utils/test_system_access_users_utils.py b/tests/unit/plugins/module_utils/test_system_access_users_utils.py
index eb363210..539f30b8 100644
--- a/tests/unit/plugins/module_utils/test_system_access_users_utils.py
+++ b/tests/unit/plugins/module_utils/test_system_access_users_utils.py
@@ -15,8 +15,10 @@
Group,
User,
UserSet,
+ password_verify,
OPNSenseGroupNotFoundError,
OPNSenseCryptReturnError,
+ OPNSensePasswordVerifyReturnError,
)
from ansible_collections.puzzle.opnsense.plugins.module_utils.module_index import (
VERSION_MAP,
@@ -87,6 +89,20 @@
/bin/sh
1001
+
+ test_user_23
+ $2y$11$FGohY592rylJdDw5vTaxNubYHwh9326Eb7gtdY4GRbXrViGsPEykq
+ User
+ [ ANSIBLE ]
+
+
+ /bin/sh
+ 2021
+ [ ANSIBLE ]
+
+
+ test_group
+
admins
System Administrators
@@ -108,6 +124,7 @@
system
1000
2004
+ 2021
2000
page-all
@@ -234,17 +251,23 @@ def test_user_from_ansible_module_params_simple(sample_config_path):
"ansible_collections.puzzle.opnsense.plugins.module_utils.version_utils.get_opnsense_version",
return_value="OPNsense Test",
)
+@patch(
+ "ansible_collections.puzzle.opnsense.plugins.module_utils.system_access_users_utils.password_verify",
+ return_value=True,
+)
@patch.dict(in_dict=VERSION_MAP, values=TEST_VERSION_MAP, clear=True)
-def test_user_set_load_simple_user(mocked_version_utils: MagicMock, sample_config_path):
+def test_user_set_load_simple_user(
+ mocked_version_utils: MagicMock, mock_password_verify: MagicMock, sample_config_path
+):
with UserSet(sample_config_path) as user_set:
- assert len(user_set._users) == 2
+ assert len(user_set._users) == 3
user_set.save()
def test_group_from_xml():
test_etree_opnsense: Element = ElementTree.fromstring(TEST_XML)
- test_etree_group: Element = list(list(test_etree_opnsense)[0])[4]
+ test_etree_group: Element = list(list(test_etree_opnsense)[0])[5]
test_group: Group = Group.from_xml(test_etree_group)
assert test_group.name == "admins"
@@ -271,9 +294,16 @@ def test_group_from_xml():
"ansible_collections.puzzle.opnsense.plugins.module_utils.system_access_users_utils.UserSet.set_user_password",
return_value="$2y$10$1BvUdvwM.a.dJACwfeNfAOgNT6Cqc4cKZ2F6byyvY8hIK9I8fn36O",
)
+@patch(
+ "ansible_collections.puzzle.opnsense.plugins.module_utils.system_access_users_utils.password_verify",
+ return_value=True,
+)
@patch.dict(in_dict=VERSION_MAP, values=TEST_VERSION_MAP, clear=True)
def test_user_set_add_group(
- mocked_version_utils: MagicMock, mock_set_password: MagicMock, sample_config_path
+ mocked_version_utils: MagicMock,
+ mock_set_password: MagicMock,
+ mock_password_verify: MagicMock,
+ sample_config_path,
):
with UserSet(sample_config_path) as user_set:
test_user: User = user_set.find(name="vagrant")
@@ -287,7 +317,6 @@ def test_user_set_add_group(
with UserSet(sample_config_path) as new_user_set:
new_test_user: User = new_user_set.find(name="vagrant")
- # group: Group = new_user_set
assert new_test_user.groupname == ["admins"]
assert "1000" in new_user_set._groups[0].member
@@ -328,9 +357,16 @@ def test_user_from_ansible_module_params_with_group(sample_config_path):
"ansible_collections.puzzle.opnsense.plugins.module_utils.system_access_users_utils.UserSet.set_user_password",
return_value="$2y$10$1BvUdvwM.a.dJACwfeNfAOgNT6Cqc4cKZ2F6byyvY8hIK9I8fn36O",
)
+@patch(
+ "ansible_collections.puzzle.opnsense.plugins.module_utils.system_access_users_utils.password_verify",
+ return_value=True,
+)
@patch.dict(in_dict=VERSION_MAP, values=TEST_VERSION_MAP, clear=True)
def test_user_from_ansible_module_params_with_group_as_string(
- mock_set_password, mock_get_version, sample_config_path
+ mock_set_password,
+ mock_get_version,
+ mock_password_verify: MagicMock,
+ sample_config_path,
):
test_params = {
"username": "test",
@@ -368,9 +404,16 @@ def test_user_from_ansible_module_params_with_group_as_string(
"ansible_collections.puzzle.opnsense.plugins.module_utils.system_access_users_utils.UserSet.set_user_password",
return_value="$2y$10$1BvUdvwM.a.dJACwfeNfAOgNT6Cqc4cKZ2F6byyvY8hIK9I8fn36O",
)
+@patch(
+ "ansible_collections.puzzle.opnsense.plugins.module_utils.system_access_users_utils.password_verify",
+ return_value=True,
+)
@patch.dict(in_dict=VERSION_MAP, values=TEST_VERSION_MAP, clear=True)
def test_user_from_ansible_module_params_with_multiple_groups_as_list(
- mock_set_password, mock_get_version, sample_config_path
+ mock_set_password,
+ mock_get_version,
+ mock_password_verify: MagicMock,
+ sample_config_path,
):
test_params = {
"username": "test",
@@ -411,9 +454,16 @@ def test_user_from_ansible_module_params_with_multiple_groups_as_list(
"ansible_collections.puzzle.opnsense.plugins.module_utils.system_access_users_utils.UserSet.set_user_password",
return_value="$2y$10$1BvUdvwM.a.dJACwfeNfAOgNT6Cqc4cKZ2F6byyvY8hIK9I8fn36O",
)
+@patch(
+ "ansible_collections.puzzle.opnsense.plugins.module_utils.system_access_users_utils.password_verify",
+ return_value=True,
+)
@patch.dict(in_dict=VERSION_MAP, values=TEST_VERSION_MAP, clear=True)
def test_user_from_ansible_module_params_with_no_groups(
- mock_set_password, mock_get_version, sample_config_path
+ mock_set_password,
+ mock_get_version,
+ mock_password_verify: MagicMock,
+ sample_config_path,
):
test_params = {
"username": "test",
@@ -448,9 +498,16 @@ def test_user_from_ansible_module_params_with_no_groups(
"ansible_collections.puzzle.opnsense.plugins.module_utils.system_access_users_utils.UserSet.set_user_password",
return_value="$2y$10$1BvUdvwM.a.dJACwfeNfAOgNT6Cqc4cKZ2F6byyvY8hIK9I8fn36O",
)
+@patch(
+ "ansible_collections.puzzle.opnsense.plugins.module_utils.system_access_users_utils.password_verify",
+ return_value=True,
+)
@patch.dict(in_dict=VERSION_MAP, values=TEST_VERSION_MAP, clear=True)
def test_user_from_ansible_module_params_with_not_existing_group(
- mock_set_password, mock_get_version, sample_config_path
+ mock_set_password,
+ mock_get_version,
+ mock_password_verify: MagicMock,
+ sample_config_path,
):
test_params = {
"username": "test",
@@ -515,17 +572,24 @@ def test_user_from_ansible_module_params_with_authorizedkeys(
"ansible_collections.puzzle.opnsense.plugins.module_utils.system_access_users_utils.UserSet.set_user_password",
return_value="$2y$10$1BvUdvwM.a.dJACwfeNfAOgNT6Cqc4cKZ2F6byyvY8hIK9I8fn36O",
)
+@patch(
+ "ansible_collections.puzzle.opnsense.plugins.module_utils.system_access_users_utils.password_verify",
+ return_value=True,
+)
@patch.dict(in_dict=VERSION_MAP, values=TEST_VERSION_MAP, clear=True)
def test_user_from_ansible_module_params_single_group_removal(
- mock_set_password, mock_get_version, sample_config_path
+ mock_set_password,
+ mock_get_version,
+ mock_password_verify: MagicMock,
+ sample_config_path,
):
test_params = {
- "username": "vagrant",
- "password": "vagrant",
+ "username": "test_user_23",
+ "password": "test_password_23",
"scope": "user",
- "full_name": "vagrant box management",
+ "full_name": "[ ANSIBLE ]",
"shell": "/bin/sh",
- "uid": "1000",
+ "uid": "2021",
}
with UserSet(sample_config_path) as user_set:
@@ -538,10 +602,12 @@ def test_user_from_ansible_module_params_single_group_removal(
with UserSet(sample_config_path) as new_user_set:
all_groups = new_user_set._load_groups()
+ test_user: User = user_set.find(name="test_user_23")
- admin_group = all_groups[0]
+ test_group = all_groups[1]
- assert "1000" not in admin_group.member
+ assert "2021" not in test_group.member
+ assert not test_user.groupname
new_user_set.save()
@@ -554,9 +620,16 @@ def test_user_from_ansible_module_params_single_group_removal(
"ansible_collections.puzzle.opnsense.plugins.module_utils.system_access_users_utils.UserSet.set_user_password",
return_value="$2y$10$1BvUdvwM.a.dJACwfeNfAOgNT6Cqc4cKZ2F6byyvY8hIK9I8fn36O",
)
+@patch(
+ "ansible_collections.puzzle.opnsense.plugins.module_utils.system_access_users_utils.password_verify",
+ return_value=True,
+)
@patch.dict(in_dict=VERSION_MAP, values=TEST_VERSION_MAP, clear=True)
def test_user_from_ansible_module_params_multiple_group_removal(
- mock_set_password, mock_get_version, sample_config_path
+ mock_set_password,
+ mock_get_version,
+ mock_password_verify: MagicMock,
+ sample_config_path,
):
test_params = {
"username": "vagrant",
@@ -631,3 +704,68 @@ def test_generate_hashed_secret_error_in_crypt(mock_run_function):
user._generate_hashed_secret("password123")
assert "error encounterd while creating secret" in str(excinfo.value)
+
+
+@patch(
+ "ansible_collections.puzzle.opnsense.plugins.module_utils.opnsense_utils.run_command"
+)
+def test_password_verify_returns_true_on_match(mock_run_command: MagicMock):
+
+ # Mock the return value of the run_command to simulate a password match
+ mock_run_command.return_value = {
+ "stdout": "",
+ "stderr": None,
+ }
+
+ # Call the function with test data
+ test_password_matches = password_verify(
+ password="test_password_1",
+ existing_user_password="$2y$11$pSYTZcD0o23JSfksEekwKOnWM1o3Ih9vp7OOQN.v35E1rag49cEc6",
+ )
+
+ # Assert that the function returns True for a password match
+ assert test_password_matches
+
+
+@patch(
+ "ansible_collections.puzzle.opnsense.plugins.module_utils.opnsense_utils.run_command"
+)
+def test_password_verify_returns_false_on_difference(mock_run_command: MagicMock):
+
+ # Mock the return value of the run_command to simulate a password match
+ mock_run_command.return_value = {
+ "stdout": "1",
+ "stderr": None,
+ }
+
+ # Call the function with test data
+ test_password_matches = password_verify(
+ password="test_password_1",
+ existing_user_password="$2y$11$pSYTZcD0o23JSfksEe1231345h9vp7OOQN.v35E1rag49cEc6",
+ )
+
+ # Assert that the function returns True for a password match
+ assert not test_password_matches
+
+
+@patch(
+ "ansible_collections.puzzle.opnsense.plugins.module_utils.opnsense_utils.run_command"
+)
+def test_password_verify_returns_OPNSensePasswordVerifyReturnError(
+ mock_run_command: MagicMock,
+):
+
+ # Mock the return value of the run_command to simulate a password match
+ mock_run_command.return_value = {
+ "stdout": None,
+ "stderr": "this an error",
+ }
+
+ with pytest.raises(OPNSensePasswordVerifyReturnError) as excinfo:
+ # Call the function with test data
+ test_password_matches = password_verify(
+ password="test_password_1",
+ existing_user_password="$2y$11$pSYTZcD0o23JSfksEekwKOnWM1o3Ih9vp7OOQN.v35E1rag49cEc6",
+ )
+
+ assert "error encounterd verifying passwor" in str(excinfo.value)