The branch, master has been updated via 569937b tests: Add tests for samba-tool passwordsettings commands via 7255e0c netcmd: Split 'domain passwordsettings' into a super-command via 0da9dbb netcmd: Small tweak to retrieving pwdProperties via 8a105af dsdb: Split out construct_generic_token_groups() so we can reuse it via fcdb935 dsdb: Use attribute-name parameter for error message via 823dec9 tests: Add a test case for msDS-PasswordReversibleEncryptionEnabled via 17d8d47 tests: Add test for password-lockout via SAMR RPC via f94f472 tests: Add PSO test case to existing password_lockout tests via f5d67c1 tests: Add comments to help explain password_lockout tests via 78ebfcf tests: Add tests for Password Settings Objects via d0a9e19 tests: Split out setUp code into separate function for reuse via 5974289 tests: Move repeated code into a helper function from b07b4e4 loadparm: Remove unused realm_original
https://git.samba.org/?p=samba.git;a=shortlog;h=master - Log ----------------------------------------------------------------- commit 569937b8008ea8b64b4e3ead61ebc97c6c41f6b6 Author: Tim Beale <timbe...@catalyst.net.nz> Date: Thu May 10 16:22:06 2018 +1200 tests: Add tests for samba-tool passwordsettings commands I've added a test case for 'samba-tool domain passwordsettings set/show' to prove I haven't broken it. It's behaviour shouldn't have changed, but there was no test for it previously. We'll extend these tests in the very near future, when we add samba-tool support for managing PSOs. The base samba_tool test's runsubcmd() only handled commands with exactly one sub-command, i.e. it would handle the command 'samba-tool domain passwordsettings' OK, but not 'samba-tool domain passwordsettings set' (The command still seemed to run OK, but you wouldn't get the output/err back correctly). A new runsublevelcmd() function now handles a varying number of sub-commands. Reviewed-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Garming Sam <garm...@catalyst.net.nz> Signed-off-by: Tim Beale <timbe...@catalyst.net.nz> Autobuild-User(master): Garming Sam <garm...@samba.org> Autobuild-Date(master): Fri May 11 09:06:10 CEST 2018 on sn-devel-144 commit 7255e0ced33826d1e528c3e465105e7e194eb36e Author: Tim Beale <timbe...@catalyst.net.nz> Date: Thu May 3 12:12:04 2018 +1200 netcmd: Split 'domain passwordsettings' into a super-command The show and set options are not really related to each other at all, so it makes sense to split the code into 2 separate commands. We also want to add separate sub-commands for PSOs in a subsequent patch. Because of the way the sub-command was implemented previously, it meant that you could specify other command-line options before the 'set' or 'show' keyword, and the command would still be accepted. However, now that it's a super-command 'set'/'show' needs to be specified before any additional arguments, so we need to update the test code to reflect this. Reviewed-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Garming Sam <garm...@catalyst.net.nz> Signed-off-by: Tim Beale <timbe...@catalyst.net.nz> commit 0da9dbbf5a762f0953f1860b8b04810d33557094 Author: Tim Beale <timbe...@catalyst.net.nz> Date: Thu May 3 11:48:21 2018 +1200 netcmd: Small tweak to retrieving pwdProperties Currently the 'samba-tool domain passwordsettings' command shares a 'set' and 'show' option, but there is very little common code between the two. The only variable that's shared is pwd_props, but there's a separate API we can use to get this. This allows us to split the command into a super-command in a subsequent patch. Fixed up erroneous comments while I'm at it. Reviewed-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Garming Sam <garm...@catalyst.net.nz> Signed-off-by: Tim Beale <timbe...@catalyst.net.nz> commit 8a105af76c3d432505e0a503cd32feb3808df59a Author: Tim Beale <timbe...@catalyst.net.nz> Date: Thu Apr 5 10:51:42 2018 +1200 dsdb: Split out construct_generic_token_groups() so we can reuse it construct_generic_token_groups() currently works out the entire group membership for a user, including the primaryGroupID. We want to do the exact same thing for the msDS-ResultantPSO constructed attribute. However, construct_generic_token_groups() currently adds the resulting SIDs to the LDB search result, which we don't want to do for msDS-ResultantPSO. This patch splits the bulk of the group SID calculation work out into a separate function that we can reuse for msDS-ResultantPSO. basically this is just a straight move of the existing code. The only real change is the TALLOC_CTX is renamed (tmp_ctx --> mem_ctx) and now passed into the new function (so freeing it if an error conditions is hit is now done in the caller). Reviewed-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Garming Sam <garm...@catalyst.net.nz> Signed-off-by: Tim Beale <timbe...@catalyst.net.nz> commit fcdb935e3749cab5bc7dcbb83414032c683640ef Author: Tim Beale <timbe...@catalyst.net.nz> Date: Thu Apr 5 10:40:03 2018 +1200 dsdb: Use attribute-name parameter for error message We'll reuse this code for working out the msDS-ResultantPSO, so references to 'tokenGroups' in error messages would be misleading. Reviewed-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Garming Sam <garm...@catalyst.net.nz> Signed-off-by: Tim Beale <timbe...@catalyst.net.nz> commit 823dec9d166b3fbe2caacdb699173601603c1101 Author: Tim Beale <timbe...@catalyst.net.nz> Date: Mon May 7 17:33:51 2018 +1200 tests: Add a test case for msDS-PasswordReversibleEncryptionEnabled Add a test for the 'msDS-PasswordReversibleEncryptionEnabled' attribute on the PSO. The Effective-PasswordReversibleEncryptionEnabled is based on the PSO setting (if one applies) or else the DOMAIN_PASSWORD_STORE_CLEARTEXT bit for the domain's pwdProperties. This indicates whether the user's cleartext password is to be stored in the supplementalCredentials attribute (as 'Primary:CLEARTEXT'). The password_hash tests already text the cleartext behaviour, so I've added an additional test case for PSOs. Note that supplementary- credential information is not returned over LDAP (the password_hash test uses a local LDB connection), so it made more sense to extend the password_hash tests than to check this behaviour as part of the PSO tests (i.e. rather than in password_settings.py). Reviewed-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Garming Sam <garm...@catalyst.net.nz> Signed-off-by: Tim Beale <timbe...@catalyst.net.nz> commit 17d8d475e5376a3c1a313c99cd988b0d1180c5e2 Author: Tim Beale <timbe...@catalyst.net.nz> Date: Fri Apr 20 12:50:00 2018 +1200 tests: Add test for password-lockout via SAMR RPC The existing password_lockout tests didn't check for changing the password via the SAMR password_change RPC. This patch adds a test-case for this, using the default domain lockout settings (which passes), and then repeats the same test using a PSO (which fails). Reviewed-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Garming Sam <garm...@catalyst.net.nz> Signed-off-by: Tim Beale <timbe...@catalyst.net.nz> commit f94f472830d684683f2769d83ad1205720a3029d Author: Tim Beale <timbe...@catalyst.net.nz> Date: Mon Mar 19 12:56:14 2018 +1300 tests: Add PSO test case to existing password_lockout tests This checks that the lockout settings of the PSO take effect when one is applied to a user. Import the password_settings code to create/apply a PSO with the same lockout settings that the test cases normally use. Then update the global settings so that the default lockout settings are wildly different (i.e. so the test fails if the default lockout settings get used instead of the PSO's). As the password-lockout tests are quite slow, I've selected test cases that should provide sufficient PSO coverage (rather than repeat every single password-lockout test case in its entirety). Reviewed-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Garming Sam <garm...@catalyst.net.nz> Signed-off-by: Tim Beale <timbe...@catalyst.net.nz> commit f5d67c10c02b9f5023cb58914cbb1af193846178 Author: Tim Beale <timbe...@catalyst.net.nz> Date: Wed Apr 11 12:40:59 2018 +1200 tests: Add comments to help explain password_lockout tests Reviewed-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Garming Sam <garm...@catalyst.net.nz> Signed-off-by: Tim Beale <timbe...@catalyst.net.nz> commit 78ebfcfa862ba05d18305be07192837681c841cb Author: Tim Beale <timbe...@catalyst.net.nz> Date: Mon Mar 12 15:22:24 2018 +1300 tests: Add tests for Password Settings Objects a.k.a Fine-Grained Password Policies These tests currently all run and pass gainst Windows, but fail against Samba. (Actually, the permissions test case passes against Samba, presumably because it's enforced by the Schema permissions). Two helper classes have been added: - PasswordSettings: creates a PSO object and tracks its values. - TestUser: creates a user and tracks its password history This allows other existing tests (e.g. password_lockout, password_hash) to easily be extended to also cover PSOs. Most test cases use assert_PSO_applied(), which asserts: - the correct msDS-ResultantPSO attribute is returned - the PSO's min-password-length, complexity, and password-history settings are correctly enforced (this has been temporarily been hobbled until the basic constructed-attribute support is working). Reviewed-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Garming Sam <garm...@catalyst.net.nz> Signed-off-by: Tim Beale <timbe...@catalyst.net.nz> commit d0a9e19114cfdaa9cd083e46d51f9e5de9e907e5 Author: Tim Beale <timbe...@catalyst.net.nz> Date: Fri May 11 11:03:03 2018 +1200 tests: Split out setUp code into separate function for reuse Any test that wants to change a password has to set the dSHeuristics and minPwdAge first in order for the password change to work. The code that does this is duplicated in several tests. This patch splits it out into a static method so that the code can be reused rather than duplicated. Reviewed-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Garming Sam <garm...@catalyst.net.nz> Signed-off-by: Tim Beale <timbe...@catalyst.net.nz> commit 597428943b1b77267243dc69ecea6fda8dfc3163 Author: Tim Beale <timbe...@catalyst.net.nz> Date: Thu Mar 15 12:44:30 2018 +1300 tests: Move repeated code into a helper function Several tests hang all the objects they create off a unique OU. Having a common OU makes cleanup easier, and having a unique OU (i.e. adding some randomness) helps protect against one-off test failures (Replication between testenvs is happening in the background. Occasionally, when a test finishes on one testenv and moves onto the next testenv, that testenv may have received the replicated test objects from the first testenv, but has not received their deletion yet). Rather than copy-n-pasting this code yet again, split it out into a helper function. Reviewed-by: Andrew Bartlett <abart...@samba.org> Reviewed-by: Garming Sam <garm...@catalyst.net.nz> Signed-off-by: Tim Beale <timbe...@catalyst.net.nz> ----------------------------------------------------------------------- Summary of changes: python/samba/netcmd/domain.py | 355 ++++----- python/samba/tests/__init__.py | 13 + python/samba/tests/auth_log_pass_change.py | 20 +- python/samba/tests/password_hash.py | 40 +- python/samba/tests/password_hash_gpgme.py | 67 +- python/samba/tests/password_test.py | 60 ++ python/samba/tests/pso.py | 271 +++++++ python/samba/tests/samba_tool/base.py | 17 + python/samba/tests/samba_tool/passwordsettings.py | 70 ++ selftest/knownfail.d/password_hash_gpgme | 2 + selftest/knownfail.d/password_lockout | 6 + selftest/knownfail.d/password_settings | 9 + source4/dsdb/samdb/ldb_modules/operational.c | 104 +-- source4/dsdb/tests/python/acl.py | 16 +- source4/dsdb/tests/python/password_lockout.py | 175 +++++ source4/dsdb/tests/python/password_lockout_base.py | 60 +- source4/dsdb/tests/python/password_settings.py | 802 +++++++++++++++++++++ source4/dsdb/tests/python/passwords.py | 22 +- source4/dsdb/tests/python/tombstone_reanimation.py | 16 +- source4/selftest/tests.py | 6 + source4/setup/tests/blackbox_setpassword.sh | 2 +- source4/torture/drs/python/getncchanges.py | 9 +- source4/torture/drs/python/link_conflicts.py | 9 +- source4/torture/drs/python/repl_rodc.py | 10 +- testprogs/blackbox/test_kinit_heimdal.sh | 4 +- testprogs/blackbox/test_kinit_mit.sh | 4 +- testprogs/blackbox/test_kpasswd_heimdal.sh | 4 +- testprogs/blackbox/test_kpasswd_mit.sh | 4 +- testprogs/blackbox/test_password_settings.sh | 10 +- 29 files changed, 1814 insertions(+), 373 deletions(-) create mode 100644 python/samba/tests/password_test.py create mode 100644 python/samba/tests/pso.py create mode 100644 python/samba/tests/samba_tool/passwordsettings.py create mode 100644 selftest/knownfail.d/password_hash_gpgme create mode 100644 selftest/knownfail.d/password_lockout create mode 100644 selftest/knownfail.d/password_settings create mode 100644 source4/dsdb/tests/python/password_settings.py Changeset truncated at 500 lines: diff --git a/python/samba/netcmd/domain.py b/python/samba/netcmd/domain.py index da61711..cb2b1cc 100644 --- a/python/samba/netcmd/domain.py +++ b/python/samba/netcmd/domain.py @@ -1263,8 +1263,74 @@ class cmd_domain_level(Command): else: raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand) +class cmd_domain_passwordsettings_show(Command): + """Display current password settings for the domain.""" -class cmd_domain_passwordsettings(Command): + synopsis = "%prog [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", type=str, + metavar="URL", dest="H"), + ] + + def run(self, H=None, credopts=None, sambaopts=None, versionopts=None): + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp) + + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + + domain_dn = samdb.domain_dn() + res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE, + attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength", + "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold", + "lockOutObservationWindow"]) + assert(len(res) == 1) + try: + pwd_props = int(res[0]["pwdProperties"][0]) + pwd_hist_len = int(res[0]["pwdHistoryLength"][0]) + cur_min_pwd_len = int(res[0]["minPwdLength"][0]) + # ticks -> days + cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24)) + if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000: + cur_max_pwd_age = 0 + else: + cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24)) + cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0]) + # ticks -> mins + if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000: + cur_account_lockout_duration = 0 + else: + cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60) + cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60) + except Exception as e: + raise CommandError("Could not retrieve password properties!", e) + + self.message("Password informations for domain '%s'" % domain_dn) + self.message("") + if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0: + self.message("Password complexity: on") + else: + self.message("Password complexity: off") + if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0: + self.message("Store plaintext passwords: on") + else: + self.message("Store plaintext passwords: off") + self.message("Password history length: %d" % pwd_hist_len) + self.message("Minimum password length: %d" % cur_min_pwd_len) + self.message("Minimum password age (days): %d" % cur_min_pwd_age) + self.message("Maximum password age (days): %d" % cur_max_pwd_age) + self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration) + self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold) + self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after) + +class cmd_domain_passwordsettings_set(Command): """Set password settings. Password complexity, password lockout policy, history length, @@ -1274,7 +1340,7 @@ class cmd_domain_passwordsettings(Command): Use against a Windows DC is possible, but group policy will override it. """ - synopsis = "%prog (show|set <options>) [options]" + synopsis = "%prog <options> [options]" takes_optiongroups = { "sambaopts": options.SambaOptions, @@ -1306,9 +1372,7 @@ class cmd_domain_passwordsettings(Command): help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str), ] - takes_args = ["subcommand"] - - def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None, + def run(self, H=None, min_pwd_age=None, max_pwd_age=None, quiet=False, complexity=None, store_plaintext=None, history_length=None, min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None, reset_account_lockout_after=None, credopts=None, sambaopts=None, @@ -1320,194 +1384,155 @@ class cmd_domain_passwordsettings(Command): credentials=creds, lp=lp) domain_dn = samdb.domain_dn() - res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE, - attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength", - "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold", - "lockOutObservationWindow"]) - assert(len(res) == 1) - try: - pwd_props = int(res[0]["pwdProperties"][0]) - pwd_hist_len = int(res[0]["pwdHistoryLength"][0]) - cur_min_pwd_len = int(res[0]["minPwdLength"][0]) - # ticks -> days - cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24)) - if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000: - cur_max_pwd_age = 0 + msgs = [] + m = ldb.Message() + m.dn = ldb.Dn(samdb, domain_dn) + pwd_props = int(samdb.get_pwdProperties()) + + if complexity is not None: + if complexity == "on" or complexity == "default": + pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX + msgs.append("Password complexity activated!") + elif complexity == "off": + pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX) + msgs.append("Password complexity deactivated!") + + if store_plaintext is not None: + if store_plaintext == "on" or store_plaintext == "default": + pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT + msgs.append("Plaintext password storage for changed passwords activated!") + elif store_plaintext == "off": + pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT) + msgs.append("Plaintext password storage for changed passwords deactivated!") + + if complexity is not None or store_plaintext is not None: + m["pwdProperties"] = ldb.MessageElement(str(pwd_props), + ldb.FLAG_MOD_REPLACE, "pwdProperties") + + if history_length is not None: + if history_length == "default": + pwd_hist_len = 24 else: - cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24)) - cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0]) - # ticks -> mins - if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000: - cur_account_lockout_duration = 0 - else: - cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60) - cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60) - except Exception as e: - raise CommandError("Could not retrieve password properties!", e) + pwd_hist_len = int(history_length) - if subcommand == "show": - self.message("Password informations for domain '%s'" % domain_dn) - self.message("") - if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0: - self.message("Password complexity: on") - else: - self.message("Password complexity: off") - if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0: - self.message("Store plaintext passwords: on") - else: - self.message("Store plaintext passwords: off") - self.message("Password history length: %d" % pwd_hist_len) - self.message("Minimum password length: %d" % cur_min_pwd_len) - self.message("Minimum password age (days): %d" % cur_min_pwd_age) - self.message("Maximum password age (days): %d" % cur_max_pwd_age) - self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration) - self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold) - self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after) - elif subcommand == "set": - msgs = [] - m = ldb.Message() - m.dn = ldb.Dn(samdb, domain_dn) - - if complexity is not None: - if complexity == "on" or complexity == "default": - pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX - msgs.append("Password complexity activated!") - elif complexity == "off": - pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX) - msgs.append("Password complexity deactivated!") - - if store_plaintext is not None: - if store_plaintext == "on" or store_plaintext == "default": - pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT - msgs.append("Plaintext password storage for changed passwords activated!") - elif store_plaintext == "off": - pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT) - msgs.append("Plaintext password storage for changed passwords deactivated!") - - if complexity is not None or store_plaintext is not None: - m["pwdProperties"] = ldb.MessageElement(str(pwd_props), - ldb.FLAG_MOD_REPLACE, "pwdProperties") - - if history_length is not None: - if history_length == "default": - pwd_hist_len = 24 - else: - pwd_hist_len = int(history_length) + if pwd_hist_len < 0 or pwd_hist_len > 24: + raise CommandError("Password history length must be in the range of 0 to 24!") - if pwd_hist_len < 0 or pwd_hist_len > 24: - raise CommandError("Password history length must be in the range of 0 to 24!") + m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len), + ldb.FLAG_MOD_REPLACE, "pwdHistoryLength") + msgs.append("Password history length changed!") - m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len), - ldb.FLAG_MOD_REPLACE, "pwdHistoryLength") - msgs.append("Password history length changed!") + if min_pwd_length is not None: + if min_pwd_length == "default": + min_pwd_len = 7 + else: + min_pwd_len = int(min_pwd_length) - if min_pwd_length is not None: - if min_pwd_length == "default": - min_pwd_len = 7 - else: - min_pwd_len = int(min_pwd_length) + if min_pwd_len < 0 or min_pwd_len > 14: + raise CommandError("Minimum password length must be in the range of 0 to 14!") - if min_pwd_len < 0 or min_pwd_len > 14: - raise CommandError("Minimum password length must be in the range of 0 to 14!") + m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len), + ldb.FLAG_MOD_REPLACE, "minPwdLength") + msgs.append("Minimum password length changed!") - m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len), - ldb.FLAG_MOD_REPLACE, "minPwdLength") - msgs.append("Minimum password length changed!") + if min_pwd_age is not None: + if min_pwd_age == "default": + min_pwd_age = 1 + else: + min_pwd_age = int(min_pwd_age) - if min_pwd_age is not None: - if min_pwd_age == "default": - min_pwd_age = 1 - else: - min_pwd_age = int(min_pwd_age) + if min_pwd_age < 0 or min_pwd_age > 998: + raise CommandError("Minimum password age must be in the range of 0 to 998!") - if min_pwd_age < 0 or min_pwd_age > 998: - raise CommandError("Minimum password age must be in the range of 0 to 998!") + # days -> ticks + min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7)) - # days -> ticks - min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7)) + m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks), + ldb.FLAG_MOD_REPLACE, "minPwdAge") + msgs.append("Minimum password age changed!") - m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks), - ldb.FLAG_MOD_REPLACE, "minPwdAge") - msgs.append("Minimum password age changed!") + if max_pwd_age is not None: + if max_pwd_age == "default": + max_pwd_age = 43 + else: + max_pwd_age = int(max_pwd_age) - if max_pwd_age is not None: - if max_pwd_age == "default": - max_pwd_age = 43 - else: - max_pwd_age = int(max_pwd_age) + if max_pwd_age < 0 or max_pwd_age > 999: + raise CommandError("Maximum password age must be in the range of 0 to 999!") - if max_pwd_age < 0 or max_pwd_age > 999: - raise CommandError("Maximum password age must be in the range of 0 to 999!") + # days -> ticks + if max_pwd_age == 0: + max_pwd_age_ticks = -0x8000000000000000 + else: + max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7)) - # days -> ticks - if max_pwd_age == 0: - max_pwd_age_ticks = -0x8000000000000000 - else: - max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7)) + m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks), + ldb.FLAG_MOD_REPLACE, "maxPwdAge") + msgs.append("Maximum password age changed!") - m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks), - ldb.FLAG_MOD_REPLACE, "maxPwdAge") - msgs.append("Maximum password age changed!") + if account_lockout_duration is not None: + if account_lockout_duration == "default": + account_lockout_duration = 30 + else: + account_lockout_duration = int(account_lockout_duration) - if account_lockout_duration is not None: - if account_lockout_duration == "default": - account_lockout_duration = 30 - else: - account_lockout_duration = int(account_lockout_duration) + if account_lockout_duration < 0 or account_lockout_duration > 99999: + raise CommandError("Maximum password age must be in the range of 0 to 99999!") - if account_lockout_duration < 0 or account_lockout_duration > 99999: - raise CommandError("Maximum password age must be in the range of 0 to 99999!") + # minutes -> ticks + if account_lockout_duration == 0: + account_lockout_duration_ticks = -0x8000000000000000 + else: + account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7)) - # days -> ticks - if account_lockout_duration == 0: - account_lockout_duration_ticks = -0x8000000000000000 - else: - account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7)) + m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks), + ldb.FLAG_MOD_REPLACE, "lockoutDuration") + msgs.append("Account lockout duration changed!") - m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks), - ldb.FLAG_MOD_REPLACE, "lockoutDuration") - msgs.append("Account lockout duration changed!") + if account_lockout_threshold is not None: + if account_lockout_threshold == "default": + account_lockout_threshold = 0 + else: + account_lockout_threshold = int(account_lockout_threshold) - if account_lockout_threshold is not None: - if account_lockout_threshold == "default": - account_lockout_threshold = 0 - else: - account_lockout_threshold = int(account_lockout_threshold) + m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold), + ldb.FLAG_MOD_REPLACE, "lockoutThreshold") + msgs.append("Account lockout threshold changed!") - m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold), - ldb.FLAG_MOD_REPLACE, "lockoutThreshold") - msgs.append("Account lockout threshold changed!") + if reset_account_lockout_after is not None: + if reset_account_lockout_after == "default": + reset_account_lockout_after = 30 + else: + reset_account_lockout_after = int(reset_account_lockout_after) - if reset_account_lockout_after is not None: - if reset_account_lockout_after == "default": - reset_account_lockout_after = 30 - else: - reset_account_lockout_after = int(reset_account_lockout_after) + if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999: + raise CommandError("Maximum password age must be in the range of 0 to 99999!") - if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999: - raise CommandError("Maximum password age must be in the range of 0 to 99999!") + # minutes -> ticks + if reset_account_lockout_after == 0: + reset_account_lockout_after_ticks = -0x8000000000000000 + else: + reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7)) - # days -> ticks - if reset_account_lockout_after == 0: - reset_account_lockout_after_ticks = -0x8000000000000000 - else: - reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7)) + m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks), + ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow") + msgs.append("Duration to reset account lockout after changed!") - m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks), - ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow") - msgs.append("Duration to reset account lockout after changed!") + if max_pwd_age > 0 and min_pwd_age >= max_pwd_age: + raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age)) - if max_pwd_age > 0 and min_pwd_age >= max_pwd_age: - raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age)) + if len(m) == 0: + raise CommandError("You must specify at least one option to set. Try --help") + samdb.modify(m) + msgs.append("All changes applied successfully!") + self.message("\n".join(msgs)) - if len(m) == 0: - raise CommandError("You must specify at least one option to set. Try --help") - samdb.modify(m) - msgs.append("All changes applied successfully!") - self.message("\n".join(msgs)) - else: - raise CommandError("Wrong argument '%s'!" % subcommand) +class cmd_domain_passwordsettings(SuperCommand): + """Manage password policy settings.""" + subcommands = {} + subcommands["show"] = cmd_domain_passwordsettings_show() + subcommands["set"] = cmd_domain_passwordsettings_set() class cmd_domain_classicupgrade(Command): """Upgrade from Samba classic (NT4-like) database to Samba AD DC database. diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py index bc8c185..61036b5 100644 --- a/python/samba/tests/__init__.py +++ b/python/samba/tests/__init__.py @@ -36,6 +36,7 @@ import re import samba.auth import samba.dcerpc.base from samba.compat import PY3, text_type +from random import randint if not PY3: # Py2 only from samba.samdb import SamDB @@ -475,3 +476,15 @@ def delete_force(samdb, dn, **kwargs): except ldb.LdbError as error: (num, errstr) = error.args assert num == ldb.ERR_NO_SUCH_OBJECT, "ldb.delete() failed: %s" % errstr + +def create_test_ou(samdb, name): + """Creates a unique OU for the test""" + + # Add some randomness to the test OU. Replication between the testenvs is + # constantly happening in the background. Deletion of the last test's + # objects can be slow to replicate out. So the OU created by a previous + # testenv may still exist at the point that tests start on another testenv. + rand = randint(1, 10000000) + dn = "OU=%s%d,%s" %(name, rand, samdb.get_default_basedn()) + samdb.add({ "dn": dn, "objectclass": "organizationalUnit"}) + return dn diff --git a/python/samba/tests/auth_log_pass_change.py b/python/samba/tests/auth_log_pass_change.py index 8890694..1bbb0ea 100644 --- a/python/samba/tests/auth_log_pass_change.py +++ b/python/samba/tests/auth_log_pass_change.py @@ -29,6 +29,7 @@ from samba.net import Net import samba from subprocess import call from ldb import LdbError +from samba.tests.password_test import PasswordCommon USER_NAME = "authlogtestuser" USER_PASS = samba.generate_random_password(32, 32) @@ -53,26 +54,11 @@ class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase): base_dn = self.ldb.domain_dn() print("base_dn %s" % base_dn) - # Get the old "dSHeuristics" if it was set - dsheuristics = self.ldb.get_dsheuristics() + # permit password changes during this test + PasswordCommon.allow_password_changes(self, self.ldb) - # Set the "dSHeuristics" to activate the correct "userPassword" - # behaviour - self.ldb.set_dsheuristics("000000001") - - # Reset the "dSHeuristics" as they were before - self.addCleanup(self.ldb.set_dsheuristics, dsheuristics) - - # Get the old "minPwdAge" - minPwdAge = self.ldb.get_minPwdAge() - - # Set it temporarily to "0" - self.ldb.set_minPwdAge("0") self.base_dn = self.ldb.domain_dn() - # Reset the "minPwdAge" as it was before - self.addCleanup(self.ldb.set_minPwdAge, minPwdAge) - # (Re)adds the test user USER_NAME with password USER_PASS delete_force(self.ldb, "cn=" + USER_NAME + ",cn=users," + self.base_dn) self.ldb.add({ diff --git a/python/samba/tests/password_hash.py b/python/samba/tests/password_hash.py index a3a74aa..4c4dbcf 100644 --- a/python/samba/tests/password_hash.py +++ b/python/samba/tests/password_hash.py @@ -29,6 +29,7 @@ from samba.dcerpc import drsblobs from samba.dcerpc.samr import DOMAIN_PASSWORD_STORE_CLEARTEXT from samba.dsdb import UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED from samba.tests import delete_force +from samba.tests.password_test import PasswordCommon import ldb import samba import binascii @@ -69,6 +70,17 @@ class PassWordHashTests(TestCase): self.lp = samba.tests.env_loadparm() super(PassWordHashTests, self).setUp() -- Samba Shared Repository