selftest: Split up password_lockout into tests with and without a call to sleep()
authorAndrew Bartlett <abartlet@samba.org>
Sun, 2 Sep 2018 06:03:06 +0000 (18:03 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Mon, 3 Sep 2018 04:14:55 +0000 (06:14 +0200)
This means we can have a long observation window for many of the tests and
so make them much more reliable.  Many of these cause frustrating flapping
failures in our CI systems.

Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Gary Lockyer <gary@catalyst.net.nz>
Autobuild-User(master): Andrew Bartlett <abartlet@samba.org>
Autobuild-Date(master): Mon Sep  3 06:14:55 CEST 2018 on sn-devel-144

source4/dsdb/tests/python/password_lockout.py

index be3e3fa53e20b9c685d369d4406a42f7532fe2eb..aceebd660723bfc722b4b469a0e6e52b61cffcb2 100755 (executable)
@@ -89,6 +89,42 @@ class PasswordTests(password_lockout_base.BasePasswordTestCase):
         self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds,
                                                  lockOutObservationWindow=self.lockout_observation_window)
 
+
+    def use_pso_lockout_settings(self, creds):
+
+        # create a PSO with the lockout settings the test cases normally expect
+        #
+        # Some test cases sleep() for self.account_lockout_duration
+        pso = PasswordSettings("lockout-PSO", self.ldb, lockout_attempts=3,
+                               lockout_duration=self.account_lockout_duration)
+        self.addCleanup(self.ldb.delete, pso.dn)
+
+        userdn = "cn=%s,cn=users,%s" % (creds.get_username(), self.base_dn)
+        pso.apply_to(userdn)
+
+        # update the global lockout settings to be wildly different to what
+        # the test cases normally expect
+        self.update_lockout_settings(threshold=10, duration=600,
+                                     observation_window=600)
+
+    def _reset_samr(self, res):
+
+        # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
+        samr_user = self._open_samr_user(res)
+        acb_info = self.samr.QueryUserInfo(samr_user, 16)
+        acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
+        self.samr.SetUserInfo(samr_user, 16, acb_info)
+        self.samr.Close(samr_user)
+
+
+class PasswordTestsWithoutSleep(PasswordTests):
+    def setUp(self):
+        # The tests in this class do not sleep, so we can have a
+        # longer window and not flap on slower hosts
+        self.account_lockout_duration = 30
+        self.lockout_observation_window = 30
+        super(PasswordTestsWithoutSleep, self).setUp()
+
     def _reset_ldap_lockoutTime(self, res):
         self.ldb.modify_ldif("""
 dn: """ + str(res[0].dn) + """
@@ -599,22 +635,130 @@ userPassword: thatsAcomplPASS2XYZ
                                                           "samr",
                                                           initial_lastlogon_relation='greater')
 
-    def use_pso_lockout_settings(self, creds):
+    def test_multiple_logon_krb5(self):
+        self._test_multiple_logon(self.lockout1krb5_creds)
 
-        # create a PSO with the lockout settings the test cases normally expect
-        #
-        # Some test cases sleep() for self.account_lockout_duration
-        pso = PasswordSettings("lockout-PSO", self.ldb, lockout_attempts=3,
-                               lockout_duration=self.account_lockout_duration)
-        self.addCleanup(self.ldb.delete, pso.dn)
+    def test_multiple_logon_ntlm(self):
+        self._test_multiple_logon(self.lockout1ntlm_creds)
 
-        userdn = "cn=%s,cn=users,%s" % (creds.get_username(), self.base_dn)
-        pso.apply_to(userdn)
+    def _test_samr_password_change(self, creds, other_creds, lockout_threshold=3):
+        """Tests user lockout by using bad password in SAMR password_change"""
 
-        # update the global lockout settings to be wildly different to what
-        # the test cases normally expect
-        self.update_lockout_settings(threshold=10, duration=600,
-                                     observation_window=600)
+        # create a connection for SAMR using another user's credentials
+        lp = self.get_loadparm()
+        net = Net(other_creds, lp, server=self.host)
+
+        # work out the initial account values for this user
+        username = creds.get_username()
+        userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=("greater", 0),
+                                  badPwdCountOnly=True)
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
+        logonCount = int(res[0]["logonCount"][0])
+        lastLogon = int(res[0]["lastLogon"][0])
+        lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
+
+        # prove we can change the user password (using the correct password)
+        new_password = "thatsAcomplPASS2"
+        net.change_password(newpassword=new_password.encode('utf-8'),
+                            username=username,
+                            oldpassword=creds.get_password())
+        creds.set_password(new_password)
+
+        # try entering 'x' many bad passwords in a row to lock the user out
+        new_password = "thatsAcomplPASS3"
+        for i in range(lockout_threshold):
+            badPwdCount = i + 1
+            try:
+                print("Trying bad password, attempt #%u" % badPwdCount)
+                net.change_password(newpassword=new_password.encode('utf-8'),
+                                    username=creds.get_username(),
+                                    oldpassword="bad-password")
+                self.fail("Invalid SAMR change_password accepted")
+            except NTSTATUSError as e:
+                enum = ctypes.c_uint32(e[0]).value
+                self.assertEquals(enum, ntstatus.NT_STATUS_WRONG_PASSWORD)
+
+            # check the status of the account is updated after each bad attempt
+            account_flags = 0
+            lockoutTime = None
+            if badPwdCount >= lockout_threshold:
+                account_flags = dsdb.UF_LOCKOUT
+                lockoutTime = ("greater", badPasswordTime)
+
+            res = self._check_account(userdn,
+                                      badPwdCount=badPwdCount,
+                                      badPasswordTime=("greater", badPasswordTime),
+                                      logonCount=logonCount,
+                                      lastLogon=lastLogon,
+                                      lastLogonTimestamp=lastLogonTimestamp,
+                                      lockoutTime=lockoutTime,
+                                      userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
+                                      msDSUserAccountControlComputed=account_flags)
+            badPasswordTime = int(res[0]["badPasswordTime"][0])
+
+        # the user is now locked out
+        lockoutTime = int(res[0]["lockoutTime"][0])
+
+        # check the user remains locked out regardless of whether they use a
+        # good or a bad password now
+        for password in (creds.get_password(), "bad-password"):
+            try:
+                print("Trying password %s" % password)
+                net.change_password(newpassword=new_password.encode('utf-8'),
+                                    username=creds.get_username(),
+                                    oldpassword=password)
+                self.fail("Invalid SAMR change_password accepted")
+            except NTSTATUSError as e:
+                enum = ctypes.c_uint32(e[0]).value
+                self.assertEquals(enum, ntstatus.NT_STATUS_ACCOUNT_LOCKED_OUT)
+
+            res = self._check_account(userdn,
+                                      badPwdCount=lockout_threshold,
+                                      badPasswordTime=badPasswordTime,
+                                      logonCount=logonCount,
+                                      lastLogon=lastLogon,
+                                      lastLogonTimestamp=lastLogonTimestamp,
+                                      lockoutTime=lockoutTime,
+                                      userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
+                                      msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
+
+        # reset the user account lockout
+        self._reset_samr(res)
+
+        # check bad password counts are reset
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
+                                  lockoutTime=0,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+
+        # check we can change the user password successfully now
+        net.change_password(newpassword=new_password.encode('utf-8'),
+                            username=username,
+                            oldpassword=creds.get_password())
+        creds.set_password(new_password)
+
+    def test_samr_change_password(self):
+        self._test_samr_password_change(self.lockout1ntlm_creds,
+                                        other_creds=self.lockout2ntlm_creds)
+
+    # same as above, but use a PSO to enforce the lockout
+    def test_pso_samr_change_password(self):
+        self.use_pso_lockout_settings(self.lockout1ntlm_creds)
+        self._test_samr_password_change(self.lockout1ntlm_creds,
+                                        other_creds=self.lockout2ntlm_creds)
+
+
+class PasswordTestsWithSleep(PasswordTests):
+    def setUp(self):
+        super(PasswordTestsWithSleep, self).setUp()
 
     def _test_unicodePwd_lockout_with_clear_change(self, creds, other_ldb,
                                                    initial_logoncount_relation=None):
@@ -1032,12 +1176,6 @@ unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
         self.use_pso_lockout_settings(self.lockout1ntlm_creds)
         self._test_login_lockout(self.lockout1ntlm_creds)
 
-    def test_multiple_logon_krb5(self):
-        self._test_multiple_logon(self.lockout1krb5_creds)
-
-    def test_multiple_logon_ntlm(self):
-        self._test_multiple_logon(self.lockout1ntlm_creds)
-
     def _testing_add_user(self, creds, lockOutObservationWindow=0):
         username = creds.get_username()
         userpass = creds.get_password()
@@ -1207,15 +1345,6 @@ userPassword: """ + userpass + """
                                   msDSUserAccountControlComputed=0)
         return ldb
 
-    def _reset_samr(self, res):
-
-        # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
-        samr_user = self._open_samr_user(res)
-        acb_info = self.samr.QueryUserInfo(samr_user, 16)
-        acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
-        self.samr.SetUserInfo(samr_user, 16, acb_info)
-        self.samr.Close(samr_user)
-
     def test_lockout_observation_window(self):
         lockout3krb5_creds = self.insta_creds(self.template_creds,
                                               username="lockout3krb5",
@@ -1242,120 +1371,6 @@ userPassword: """ + userpass + """
         self._testing_add_user(lockout4ntlm_creds,
                                lockOutObservationWindow=self.lockout_observation_window)
 
-    def _test_samr_password_change(self, creds, other_creds, lockout_threshold=3):
-        """Tests user lockout by using bad password in SAMR password_change"""
-
-        # create a connection for SAMR using another user's credentials
-        lp = self.get_loadparm()
-        net = Net(other_creds, lp, server=self.host)
-
-        # work out the initial account values for this user
-        username = creds.get_username()
-        userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
-        res = self._check_account(userdn,
-                                  badPwdCount=0,
-                                  badPasswordTime=("greater", 0),
-                                  badPwdCountOnly=True)
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
-        logonCount = int(res[0]["logonCount"][0])
-        lastLogon = int(res[0]["lastLogon"][0])
-        lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
-
-        # prove we can change the user password (using the correct password)
-        new_password = "thatsAcomplPASS2"
-        net.change_password(newpassword=new_password.encode('utf-8'),
-                            username=username,
-                            oldpassword=creds.get_password())
-        creds.set_password(new_password)
-
-        # try entering 'x' many bad passwords in a row to lock the user out
-        new_password = "thatsAcomplPASS3"
-        for i in range(lockout_threshold):
-            badPwdCount = i + 1
-            try:
-                print("Trying bad password, attempt #%u" % badPwdCount)
-                net.change_password(newpassword=new_password.encode('utf-8'),
-                                    username=creds.get_username(),
-                                    oldpassword="bad-password")
-                self.fail("Invalid SAMR change_password accepted")
-            except NTSTATUSError as e:
-                enum = ctypes.c_uint32(e[0]).value
-                self.assertEquals(enum, ntstatus.NT_STATUS_WRONG_PASSWORD)
-
-            # check the status of the account is updated after each bad attempt
-            account_flags = 0
-            lockoutTime = None
-            if badPwdCount >= lockout_threshold:
-                account_flags = dsdb.UF_LOCKOUT
-                lockoutTime = ("greater", badPasswordTime)
-
-            res = self._check_account(userdn,
-                                      badPwdCount=badPwdCount,
-                                      badPasswordTime=("greater", badPasswordTime),
-                                      logonCount=logonCount,
-                                      lastLogon=lastLogon,
-                                      lastLogonTimestamp=lastLogonTimestamp,
-                                      lockoutTime=lockoutTime,
-                                      userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
-                                      msDSUserAccountControlComputed=account_flags)
-            badPasswordTime = int(res[0]["badPasswordTime"][0])
-
-        # the user is now locked out
-        lockoutTime = int(res[0]["lockoutTime"][0])
-
-        # check the user remains locked out regardless of whether they use a
-        # good or a bad password now
-        for password in (creds.get_password(), "bad-password"):
-            try:
-                print("Trying password %s" % password)
-                net.change_password(newpassword=new_password.encode('utf-8'),
-                                    username=creds.get_username(),
-                                    oldpassword=password)
-                self.fail("Invalid SAMR change_password accepted")
-            except NTSTATUSError as e:
-                enum = ctypes.c_uint32(e[0]).value
-                self.assertEquals(enum, ntstatus.NT_STATUS_ACCOUNT_LOCKED_OUT)
-
-            res = self._check_account(userdn,
-                                      badPwdCount=lockout_threshold,
-                                      badPasswordTime=badPasswordTime,
-                                      logonCount=logonCount,
-                                      lastLogon=lastLogon,
-                                      lastLogonTimestamp=lastLogonTimestamp,
-                                      lockoutTime=lockoutTime,
-                                      userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
-                                      msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
-
-        # reset the user account lockout
-        self._reset_samr(res)
-
-        # check bad password counts are reset
-        res = self._check_account(userdn,
-                                  badPwdCount=0,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=logonCount,
-                                  lockoutTime=0,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-
-        # check we can change the user password successfully now
-        net.change_password(newpassword=new_password.encode('utf-8'),
-                            username=username,
-                            oldpassword=creds.get_password())
-        creds.set_password(new_password)
-
-    def test_samr_change_password(self):
-        self._test_samr_password_change(self.lockout1ntlm_creds,
-                                        other_creds=self.lockout2ntlm_creds)
-
-    # same as above, but use a PSO to enforce the lockout
-    def test_pso_samr_change_password(self):
-        self.use_pso_lockout_settings(self.lockout1ntlm_creds)
-        self._test_samr_password_change(self.lockout1ntlm_creds,
-                                        other_creds=self.lockout2ntlm_creds)
-
 
 host_url = "ldap://%s" % host