From a0156fe70ffcc978bc2f702d13672798226a1e67 Mon Sep 17 00:00:00 2001 From: Dan Lavu Date: Fri, 29 Nov 2024 01:04:50 -0500 Subject: [PATCH] roles: added password policy utilities to ad role --- sssd_test_framework/roles/ad.py | 125 +++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/sssd_test_framework/roles/ad.py b/sssd_test_framework/roles/ad.py index 11c5b3e..60a03e5 100644 --- a/sssd_test_framework/roles/ad.py +++ b/sssd_test_framework/roles/ad.py @@ -9,7 +9,7 @@ from pytest_mh.conn import ProcessResult from ..hosts.ad import ADHost -from ..misc import attrs_include_value, attrs_parse, attrs_to_hash +from ..misc import attrs_include_value, attrs_parse, attrs_to_hash, seconds_to_timespan from .base import BaseObject, BaseWindowsRole, DeleteAttribute from .ldap import LDAPNetgroupMember from .nfs import NFSExport @@ -358,6 +358,30 @@ def test_ad__gpo_is_set_to_enforcing(client: Client, ad: AD): """ return GPO(self, name) + @property + def password(self) -> ADPasswordPolicy: + """ + Domain password policy management. + + .. code-block:: python + :caption: Example usage + + @pytest.mark.topology(KnownTopology.AD + def test_example(client: Client, ad: AD): + # Enable password complexity + ad.password.complexity(enable=True) + + # Set 3 login attempts and 30 lockout duration + ad.password.lockout(attempts=3, duration=30) + + # Set password length requirement to 12 characters + ad.password.requirement(length=12) + + # Set password max age to 30 seconds + ad.password.age(max=30 + """ + return ADPasswordPolicy(self) + def sudorule(self, name: str, basedn: ADObject | str | None = "ou=sudoers") -> ADSudoRule: """ Get sudo rule object. @@ -911,6 +935,18 @@ def expire(self, expiration: str = "19700101000000") -> ADUser: return self + @property + def password_change_at_logon(self) -> ADUser: + """ + Force user to change password next logon. + + :return: Self. + :rtype: ADUser + """ + self.role.host.conn.run(f"Set-ADUser -Identity {self.name} -ChangePasswordAtLogon:$true") + + return self + def passkey_add(self, passkey_mapping: str) -> ADUser: """ Add passkey mapping to the user. @@ -2009,4 +2045,91 @@ def policy(self, logon_rights: dict[str, list[ADObject]], cfg: dict[str, Any] | return self +class ADPasswordPolicy(BaseObject[ADHost, AD]): + """ + Password policy management. + """ + + def __init__(self, role: AD): + """ + :param role: AD host object. + :type role: ADHost + """ + super().__init__(role) + + def complexity(self, enable: bool) -> ADPasswordPolicy: + """ + Enable or disable password complexity. + + :param enable: Enable or disable password complexity. + :type enable: bool + :return ADPasswordPolicy object. + :rtype: ADPasswordPolicy + """ + args: CLIBuilderArgs = { + "Identity": (self.cli.option.VALUE, self.role.domain), + "Complexity": (self.cli.option.SWITCH, enable), + } + self.role.host.conn.run(self.cli.command("Set-ADDefaultDomainPasswordPolicy", args)) + + return self + + def lockout(self, duration: int, attempts: int) -> ADPasswordPolicy: + """ + Set lockout duration and login attempts. + + :param duration: Duration of lockout in seconds. + :type duration: int + :param attempts: Number of login attempts. + :type attempts: int + :return: ADPasswordPolicy object. + :rtype: ADPasswordPolicy + """ + args: CLIBuilderArgs = { + "Identity": (self.cli.option.VALUE, self.role.domain), + "LockoutDuration": (self.cli.option.VALUE, seconds_to_timespan(duration)), + "LockoutThreshold": (self.cli.option.VALUE, str(attempts)), + } + self.role.host.conn.run(self.cli.command("Set-ADDefaultDomainPasswordPolicy", args)) + + return self + + def age(self, minimum: int, maximum: int) -> ADPasswordPolicy: + """ + Set maximum and minimum password age. + + :param minimum: Minimum password age in seconds. + :type minimum: int + :param maximum: Maximum password age in seconds. + :type maximum: int + :return: ADPasswordPolicy object. + :rtype: ADPasswordPolicy + """ + args: CLIBuilderArgs = { + "Identity": (self.cli.option.VALUE, self.role.domain), + "MinPasswordAge": (self.cli.option.VALUE, seconds_to_timespan(minimum)), + "MaxPasswordAge": (self.cli.option.VALUE, seconds_to_timespan(maximum)), + } + self.role.host.conn.run(self.cli.command("Set-ADDefaultDomainPasswordPolicy", args)) + + return self + + def requirements(self, length: int) -> ADPasswordPolicy: + """ + Set password requirements, like length. + + :param length: Required password character count. + :type length: int + :return: ADPasswordPolicy object. + :rtype: ADPasswordPolicy + """ + args: CLIBuilderArgs = { + "Identity": (self.cli.option.VALUE, self.role.domain), + "MinPasswordLength": (self.cli.option.VALUE, str(length)), + } + self.role.host.conn.run(self.cli.command("Set-ADDefaultDomainPasswordPolicy", args)) + + return self + + ADNetgroupMember: TypeAlias = LDAPNetgroupMember[ADUser, ADNetgroup]