password_lockout: Factor out a base testcase
authorGarming Sam <garming@catalyst.net.nz>
Thu, 6 Apr 2017 03:53:25 +0000 (15:53 +1200)
committerGarming Sam <garming@samba.org>
Thu, 13 Apr 2017 05:29:17 +0000 (07:29 +0200)
This allows it to be used for the RODC testing.

Signed-off-by: Garming Sam <garming@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
source4/dsdb/tests/python/password_lockout.py
source4/dsdb/tests/python/password_lockout_base.py [new file with mode: 0644]

index da319db97398db7ab52cef3db7ac38de68b5b987..2e345d9db710b043e55fc3802be6b0ae36702e1f 100755 (executable)
@@ -53,908 +53,22 @@ host = args[0]
 lp = sambaopts.get_loadparm()
 global_creds = credopts.get_credentials(lp)
 
+import password_lockout_base
+
 #
 # Tests start here
 #
 
-class BasePasswordTestCase(samba.tests.TestCase):
-    def _open_samr_user(self, res):
-        self.assertTrue("objectSid" in res[0])
-
-        (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split()
-        self.assertEquals(self.domain_sid, domain_sid)
-
-        return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid)
-
-    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 _check_attribute(self, res, name, value):
-        if value is None:
-            self.assertTrue(name not in res[0],
-                            msg="attr[%s]=%r on dn[%s]" %
-                            (name, res[0], res[0].dn))
-            return
-
-        if isinstance(value, tuple):
-            (mode, value) = value
-        else:
-            mode = "equal"
-
-        if mode == "ignore":
-            return
-
-        if mode == "absent":
-            self.assertFalse(name in res[0],
-                            msg="attr[%s] not missing on dn[%s]" %
-                            (name, res[0].dn))
-            return
-
-        self.assertTrue(name in res[0],
-                        msg="attr[%s] missing on dn[%s]" %
-                        (name, res[0].dn))
-        self.assertTrue(len(res[0][name]) == 1,
-                        msg="attr[%s]=%r on dn[%s]" %
-                        (name, res[0][name], res[0].dn))
-
-
-        print  "%s = '%s'" % (name, res[0][name][0])
-
-        if mode == "present":
-            return
-
-        if mode == "equal":
-            v = int(res[0][name][0])
-            value = int(value)
-            msg = ("attr[%s]=[%s] != [%s] on dn[%s]\n"
-                   "(diff %d; actual value is %s than expected)"  %
-                   (name, v, value, res[0].dn, v - value,
-                    ('less' if v < value else 'greater')))
-
-            self.assertTrue(v == value, msg)
-            return
-
-        if mode == "greater":
-            v = int(res[0][name][0])
-            self.assertTrue(v > int(value),
-                            msg="attr[%s]=[%s] <= [%s] on dn[%s] (diff %d)" %
-                            (name, v, int(value), res[0].dn, v - int(value)))
-            return
-        if mode == "less":
-            v = int(res[0][name][0])
-            self.assertTrue(v < int(value),
-                            msg="attr[%s]=[%s] >= [%s] on dn[%s] (diff %d)" %
-                            (name, v, int(value), res[0].dn, v - int(value)))
-            return
-        self.assertEqual(mode, not mode, "Invalid Mode[%s]" % mode)
-
-    def _check_account(self, dn,
-                       badPwdCount=None,
-                       badPasswordTime=None,
-                       logonCount=None,
-                       lastLogon=None,
-                       lastLogonTimestamp=None,
-                       lockoutTime=None,
-                       userAccountControl=None,
-                       msDSUserAccountControlComputed=None,
-                       effective_bad_password_count=None,
-                       msg=None):
-        print '-=' * 36
-        if msg is not None:
-            print  "\033[01;32m %s \033[00m\n" % msg
-        attrs = [
-           "objectSid",
-           "badPwdCount",
-           "badPasswordTime",
-           "lastLogon",
-           "lastLogonTimestamp",
-           "logonCount",
-           "lockoutTime",
-           "userAccountControl",
-           "msDS-User-Account-Control-Computed"
-        ]
-
-        # in order to prevent some time resolution problems we sleep for
-        # 10 micro second
-        time.sleep(0.01)
-
-        res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
-        self.assertTrue(len(res) == 1)
-        self._check_attribute(res, "badPwdCount", badPwdCount)
-        self._check_attribute(res, "badPasswordTime", badPasswordTime)
-        self._check_attribute(res, "logonCount", logonCount)
-        self._check_attribute(res, "lastLogon", lastLogon)
-        self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp)
-        self._check_attribute(res, "lockoutTime", lockoutTime)
-        self._check_attribute(res, "userAccountControl", userAccountControl)
-        self._check_attribute(res, "msDS-User-Account-Control-Computed",
-                              msDSUserAccountControlComputed)
-
-        lastLogon = int(res[0]["lastLogon"][0])
-        logonCount = int(res[0]["logonCount"][0])
-
-        samr_user = self._open_samr_user(res)
-        uinfo3 = self.samr.QueryUserInfo(samr_user, 3)
-        uinfo5 = self.samr.QueryUserInfo(samr_user, 5)
-        uinfo16 = self.samr.QueryUserInfo(samr_user, 16)
-        uinfo21 = self.samr.QueryUserInfo(samr_user, 21)
-        self.samr.Close(samr_user)
-
-        expected_acb_info = 0
-        if userAccountControl & dsdb.UF_NORMAL_ACCOUNT:
-            expected_acb_info |= samr.ACB_NORMAL
-        if userAccountControl & dsdb.UF_ACCOUNTDISABLE:
-            expected_acb_info |= samr.ACB_DISABLED
-        if userAccountControl & dsdb.UF_PASSWD_NOTREQD:
-            expected_acb_info |= samr.ACB_PWNOTREQ
-        if msDSUserAccountControlComputed & dsdb.UF_LOCKOUT:
-            expected_acb_info |= samr.ACB_AUTOLOCK
-        if msDSUserAccountControlComputed & dsdb.UF_PASSWORD_EXPIRED:
-            expected_acb_info |= samr.ACB_PW_EXPIRED
-
-        expected_bad_password_count = 0
-        if badPwdCount is not None:
-            expected_bad_password_count = badPwdCount
-        if effective_bad_password_count is None:
-            effective_bad_password_count = expected_bad_password_count
-
-        self.assertEquals(uinfo3.acct_flags, expected_acb_info)
-        self.assertEquals(uinfo3.bad_password_count, expected_bad_password_count)
-        self.assertEquals(uinfo3.last_logon, lastLogon)
-        self.assertEquals(uinfo3.logon_count, logonCount)
-
-        self.assertEquals(uinfo5.acct_flags, expected_acb_info)
-        self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count)
-        self.assertEquals(uinfo5.last_logon, lastLogon)
-        self.assertEquals(uinfo5.logon_count, logonCount)
-
-        self.assertEquals(uinfo16.acct_flags, expected_acb_info)
-
-        self.assertEquals(uinfo21.acct_flags, expected_acb_info)
-        self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count)
-        self.assertEquals(uinfo21.last_logon, lastLogon)
-        self.assertEquals(uinfo21.logon_count, logonCount)
-
-        # check LDAP again and make sure the samr.QueryUserInfo
-        # doesn't have any impact.
-        res2 = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
-        self.assertEquals(res[0], res2[0])
-
-        # in order to prevent some time resolution problems we sleep for
-        # 10 micro second
-        time.sleep(0.01)
-        return res
-
-    def _readd_user(self, creds, lockOutObservationWindow=0):
-        username = creds.get_username()
-        userpass = creds.get_password()
-        userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
-
-        use_kerberos = creds.get_kerberos_state()
-        if use_kerberos == MUST_USE_KERBEROS:
-            logoncount_relation = 'greater'
-            lastlogon_relation = 'greater'
-        else:
-            logoncount_relation = 'equal'
-            if lockOutObservationWindow == 0:
-                lastlogon_relation = 'greater'
-            else:
-                lastlogon_relation = 'equal'
-
-        delete_force(self.ldb, userdn)
-        self.ldb.add({
-             "dn": userdn,
-             "objectclass": "user",
-             "sAMAccountName": username})
-
-        self.addCleanup(delete_force, self.ldb, userdn)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=0,
-                                  badPasswordTime=0,
-                                  logonCount=0,
-                                  lastLogon=0,
-                                  lastLogonTimestamp=('absent', None),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT |
-                                    dsdb.UF_ACCOUNTDISABLE |
-                                    dsdb.UF_PASSWD_NOTREQD,
-                                  msDSUserAccountControlComputed=
-                                    dsdb.UF_PASSWORD_EXPIRED)
-
-        # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
-        # It doesn't create "lockoutTime" = 0.
-        self._reset_samr(res)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=0,
-                                  badPasswordTime=0,
-                                  logonCount=0,
-                                  lastLogon=0,
-                                  lastLogonTimestamp=('absent', None),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT |
-                                    dsdb.UF_ACCOUNTDISABLE |
-                                    dsdb.UF_PASSWD_NOTREQD,
-                                  msDSUserAccountControlComputed=
-                                    dsdb.UF_PASSWORD_EXPIRED)
-
-        # Tests a password change when we don't have any password yet with a
-        # wrong old password
-        try:
-            self.ldb.modify_ldif("""
-dn: """ + userdn + """
-changetype: modify
-delete: userPassword
-userPassword: noPassword
-add: userPassword
-userPassword: thatsAcomplPASS2
-""")
-            self.fail()
-        except LdbError, (num, msg):
-            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
-            # Windows (2008 at least) seems to have some small bug here: it
-            # returns "0000056A" on longer (always wrong) previous passwords.
-            self.assertTrue('00000056' in msg, msg)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=1,
-                                  badPasswordTime=("greater", 0),
-                                  logonCount=0,
-                                  lastLogon=0,
-                                  lastLogonTimestamp=('absent', None),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT |
-                                    dsdb.UF_ACCOUNTDISABLE |
-                                    dsdb.UF_PASSWD_NOTREQD,
-                                  msDSUserAccountControlComputed=
-                                    dsdb.UF_PASSWORD_EXPIRED)
-        badPwdCount = int(res[0]["badPwdCount"][0])
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
-
-        # Sets the initial user password with a "special" password change
-        # I think that this internally is a password set operation and it can
-        # only be performed by someone which has password set privileges on the
-        # account (at least in s4 we do handle it like that).
-        self.ldb.modify_ldif("""
-dn: """ + userdn + """
-changetype: modify
-delete: userPassword
-add: userPassword
-userPassword: """ + userpass + """
-""")
-
-        res = self._check_account(userdn,
-                                  badPwdCount=badPwdCount,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=0,
-                                  lastLogon=0,
-                                  lastLogonTimestamp=('absent', None),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT |
-                                    dsdb.UF_ACCOUNTDISABLE |
-                                    dsdb.UF_PASSWD_NOTREQD,
-                                  msDSUserAccountControlComputed=0)
-
-        # Enables the user account
-        self.ldb.enable_account("(sAMAccountName=%s)" % username)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=badPwdCount,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=0,
-                                  lastLogon=0,
-                                  lastLogonTimestamp=('absent', None),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-        if lockOutObservationWindow != 0:
-            time.sleep(lockOutObservationWindow + 1)
-            effective_bad_password_count = 0
-        else:
-            effective_bad_password_count = badPwdCount
-
-        res = self._check_account(userdn,
-                                  badPwdCount=badPwdCount,
-                                  effective_bad_password_count=effective_bad_password_count,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=0,
-                                  lastLogon=0,
-                                  lastLogonTimestamp=('absent', None),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-
-        ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp)
-
-        if lockOutObservationWindow == 0:
-            badPwdCount = 0
-            effective_bad_password_count = 0
-        if use_kerberos == MUST_USE_KERBEROS:
-            badPwdCount = 0
-            effective_bad_password_count = 0
-
-        res = self._check_account(userdn,
-                                  badPwdCount=badPwdCount,
-                                  effective_bad_password_count=effective_bad_password_count,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=(logoncount_relation, 0),
-                                  lastLogon=(lastlogon_relation, 0),
-                                  lastLogonTimestamp=('greater', badPasswordTime),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-
-        logonCount = int(res[0]["logonCount"][0])
-        lastLogon = int(res[0]["lastLogon"][0])
-        lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
-        if lastlogon_relation == 'greater':
-            self.assertGreater(lastLogon, badPasswordTime)
-            self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=badPwdCount,
-                                  effective_bad_password_count=effective_bad_password_count,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=logonCount,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-        return ldb
-
-    def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS):
-        try:
-            ldb = SamDB(url=url, credentials=creds, lp=lp)
-            self.fail("Login unexpectedly succeeded")
-        except LdbError, (num, msg):
-            if errno is not None:
-                self.assertEquals(num, errno, ("Login failed in the wrong way"
-                                               "(got err %d, expected %d)" %
-                                               (num, errno)))
-
+class PasswordTests(password_lockout_base.BasePasswordTestCase):
     def setUp(self):
         self.host = host
         self.host_url = host_url
         self.lp = lp
         self.global_creds = global_creds
-        super(BasePasswordTestCase, self).setUp()
-
-        self.global_creds.set_gensec_features(self.global_creds.get_gensec_features() |
-                                              gensec.FEATURE_SEAL)
-
-        self.template_creds = Credentials()
-        self.template_creds.set_username("testuser")
-        self.template_creds.set_password("thatsAcomplPASS1")
-        self.template_creds.set_domain(self.global_creds.get_domain())
-        self.template_creds.set_realm(self.global_creds.get_realm())
-        self.template_creds.set_workstation(self.global_creds.get_workstation())
-        self.template_creds.set_gensec_features(self.global_creds.get_gensec_features())
-        self.template_creds.set_kerberos_state(self.global_creds.get_kerberos_state())
-
-        self.ldb = SamDB(url=self.host_url, session_info=system_session(lp),
-                         credentials=global_creds, lp=lp)
-
-        # Gets back the basedn
-        base_dn = self.ldb.domain_dn()
-
-        # Gets back the configuration basedn
-        configuration_dn = self.ldb.get_config_basedn().get_linearized()
-
-        # Get the old "dSHeuristics" if it was set
-        dsheuristics = self.ldb.get_dsheuristics()
-
-        # Reset the "dSHeuristics" as they were before
-        self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
-
-        res = self.ldb.search(base_dn,
-                         scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
-
-        if "lockoutDuration" in res[0]:
-            lockoutDuration = res[0]["lockoutDuration"][0]
-        else:
-            lockoutDuration = 0
-
-        if "lockoutObservationWindow" in res[0]:
-            lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
-        else:
-            lockoutObservationWindow = 0
-
-        if "lockoutThreshold" in res[0]:
-            lockoutThreshold = res[0]["lockoutThreshold"][0]
-        else:
-            lockoutTreshold = 0
-
-        self.addCleanup(self.ldb.modify_ldif, """
-dn: """ + base_dn + """
-changetype: modify
-replace: lockoutDuration
-lockoutDuration: """ + str(lockoutDuration) + """
-replace: lockoutObservationWindow
-lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
-replace: lockoutThreshold
-lockoutThreshold: """ + str(lockoutThreshold) + """
-""")
-
-        m = Message()
-        m.dn = Dn(self.ldb, base_dn)
-
-        self.account_lockout_duration = 2
-        account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7))
-
-        m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
-                                              FLAG_MOD_REPLACE, "lockoutDuration")
-
-        account_lockout_threshold = 3
-        m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
-                                               FLAG_MOD_REPLACE, "lockoutThreshold")
-
-        self.lockout_observation_window = 2
-        lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7))
-
-        m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
-                                                       FLAG_MOD_REPLACE, "lockOutObservationWindow")
-
-        self.ldb.modify(m)
-
-        # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
-        self.ldb.set_dsheuristics("000000001")
-
-        # Get the old "minPwdAge"
-        minPwdAge = self.ldb.get_minPwdAge()
-
-        # Reset the "minPwdAge" as it was before
-        self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
-
-        # Set it temporarely to "0"
-        self.ldb.set_minPwdAge("0")
-
-        self.base_dn = self.ldb.domain_dn()
-
-        self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
-        self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % self.host, self.lp, self.global_creds)
-        self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
-        self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
-
-        # (Re)adds the test user accounts
-        self.lockout1krb5_creds = self.insta_creds(self.template_creds,
-                                                   username="lockout1krb5",
-                                                   userpass="thatsAcomplPASS0",
-                                                   kerberos_state=MUST_USE_KERBEROS)
-        self.lockout1krb5_ldb = self._readd_user(self.lockout1krb5_creds)
-        self.lockout2krb5_creds = self.insta_creds(self.template_creds,
-                                                   username="lockout2krb5",
-                                                   userpass="thatsAcomplPASS0",
-                                                   kerberos_state=MUST_USE_KERBEROS)
-        self.lockout2krb5_ldb = self._readd_user(self.lockout2krb5_creds,
-                                                 lockOutObservationWindow=self.lockout_observation_window)
-        self.lockout1ntlm_creds = self.insta_creds(self.template_creds,
-                                                   username="lockout1ntlm",
-                                                   userpass="thatsAcomplPASS0",
-                                                   kerberos_state=DONT_USE_KERBEROS)
-        self.lockout1ntlm_ldb = self._readd_user(self.lockout1ntlm_creds)
-        self.lockout2ntlm_creds = self.insta_creds(self.template_creds,
-                                                   username="lockout2ntlm",
-                                                   userpass="thatsAcomplPASS0",
-                                                   kerberos_state=DONT_USE_KERBEROS)
-        self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds,
-                                                 lockOutObservationWindow=self.lockout_observation_window)
-
-    def tearDown(self):
-        super(BasePasswordTestCase, self).tearDown()
-
-    def _test_login_lockout(self, creds):
-        username = creds.get_username()
-        userpass = creds.get_password()
-        userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
-
-        use_kerberos = creds.get_kerberos_state()
-        # This unlocks by waiting for account_lockout_duration
-        if use_kerberos == MUST_USE_KERBEROS:
-            logoncount_relation = 'greater'
-            lastlogon_relation = 'greater'
-            print "Performs a lockout attempt against LDAP using Kerberos"
-        else:
-            logoncount_relation = 'equal'
-            lastlogon_relation = 'equal'
-            print "Performs a lockout attempt against LDAP using NTLM"
-
-        # Change password on a connection as another user
-        res = self._check_account(userdn,
-                                  badPwdCount=0,
-                                  badPasswordTime=("greater", 0),
-                                  logonCount=(logoncount_relation, 0),
-                                  lastLogon=("greater", 0),
-                                  lastLogonTimestamp=("greater", 0),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
-        logonCount = int(res[0]["logonCount"][0])
-        lastLogon = int(res[0]["lastLogon"][0])
-        firstLogon = lastLogon
-        lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
-        print firstLogon
-        print lastLogonTimestamp
-
-
-        self.assertGreater(lastLogon, badPasswordTime)
-        self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
-
-        # Open a second LDB connection with the user credentials. Use the
-        # command line credentials for informations like the domain, the realm
-        # and the workstation.
-        creds_lockout = self.insta_creds(creds)
-
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
-
-        self.assertLoginFailure(self.host_url, creds_lockout, self.lp)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=1,
-                                  badPasswordTime=("greater", badPasswordTime),
-                                  logonCount=logonCount,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0,
-                                  msg='lastlogontimestamp with wrong password')
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
-
-        # Correct old password
-        creds_lockout.set_password(userpass)
-
-        ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
-
-        # lastLogonTimestamp should not change
-        # lastLogon increases if badPwdCount is non-zero (!)
-        res = self._check_account(userdn,
-                                  badPwdCount=0,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=(logoncount_relation, logonCount),
-                                  lastLogon=('greater', lastLogon),
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0,
-                                  msg='LLTimestamp is updated to lastlogon')
-
-        logonCount = int(res[0]["logonCount"][0])
-        lastLogon = int(res[0]["lastLogon"][0])
-        self.assertGreater(lastLogon, badPasswordTime)
-        self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
-
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
-
-        self.assertLoginFailure(self.host_url, creds_lockout, self.lp)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=1,
-                                  badPasswordTime=("greater", badPasswordTime),
-                                  logonCount=logonCount,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
-
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
-
-        try:
-            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
-            self.fail()
-
-        except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=2,
-                                  badPasswordTime=("greater", badPasswordTime),
-                                  logonCount=logonCount,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
-
-        print "two failed password change"
-
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
-
-        try:
-            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
-            self.fail()
-
-        except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=3,
-                                  badPasswordTime=("greater", badPasswordTime),
-                                  logonCount=logonCount,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  lockoutTime=("greater", badPasswordTime),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
-        lockoutTime = int(res[0]["lockoutTime"][0])
-
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
-        try:
-            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
-            self.fail()
-        except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=3,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=logonCount,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  lockoutTime=lockoutTime,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
-
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
-        try:
-            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
-            self.fail()
-        except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=3,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=logonCount,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  lockoutTime=lockoutTime,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
-
-        # The correct password, but we are locked out
-        creds_lockout.set_password(userpass)
-        try:
-            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
-            self.fail()
-        except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=3,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=logonCount,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  lockoutTime=lockoutTime,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
-
-        # wait for the lockout to end
-        time.sleep(self.account_lockout_duration + 1)
-        print self.account_lockout_duration + 1
-
-        res = self._check_account(userdn,
-                                  badPwdCount=3, effective_bad_password_count=0,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=logonCount,
-                                  lockoutTime=lockoutTime,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-
-        # The correct password after letting the timeout expire
-
-        creds_lockout.set_password(userpass)
-
-        creds_lockout2 = self.insta_creds(creds_lockout)
-
-        ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout2, lp=self.lp)
-        time.sleep(3)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=0,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=(logoncount_relation, logonCount),
-                                  lastLogon=(lastlogon_relation, lastLogon),
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  lockoutTime=0,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0,
-                                  msg="lastLogon is way off")
-
-        logonCount = int(res[0]["logonCount"][0])
-        lastLogon = int(res[0]["lastLogon"][0])
-
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
-        try:
-            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
-            self.fail()
-        except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=1,
-                                  badPasswordTime=("greater", badPasswordTime),
-                                  logonCount=logonCount,
-                                  lockoutTime=0,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
-
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
-        try:
-            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
-            self.fail()
-        except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=2,
-                                  badPasswordTime=("greater", badPasswordTime),
-                                  logonCount=logonCount,
-                                  lockoutTime=0,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
-
-        time.sleep(self.lockout_observation_window + 1)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=2, effective_bad_password_count=0,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=logonCount,
-                                  lockoutTime=0,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-
-        # The wrong password
-        creds_lockout.set_password("thatsAcomplPASS1x")
-        try:
-            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
-            self.fail()
-        except LdbError, (num, msg):
-            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=1,
-                                  badPasswordTime=("greater", badPasswordTime),
-                                  logonCount=logonCount,
-                                  lockoutTime=0,
-                                  lastLogon=lastLogon,
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
-
-        # The correct password without letting the timeout expire
-        creds_lockout.set_password(userpass)
-        ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=0,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=(logoncount_relation, logonCount),
-                                  lockoutTime=0,
-                                  lastLogon=("greater", lastLogon),
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-
-    def _test_multiple_logon(self, creds):
-        # Test the happy case in which a user logs on correctly, then
-        # logs on correctly again, so that the bad password and
-        # lockout times are both zero the second time. The lastlogon
-        # time should increase.
-
-        # Open a second LDB connection with the user credentials. Use the
-        # command line credentials for informations like the domain, the realm
-        # and the workstation.
-        username = creds.get_username()
-        userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
-
-        use_kerberos = creds.get_kerberos_state()
-        if use_kerberos == MUST_USE_KERBEROS:
-            print "Testing multiple logon with Kerberos"
-            logoncount_relation = 'greater'
-            lastlogon_relation = 'greater'
-        else:
-            print "Testing multiple logon with NTLM"
-            logoncount_relation = 'equal'
-            lastlogon_relation = 'equal'
-
-        SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=0,
-                                  badPasswordTime=("greater", 0),
-                                  logonCount=(logoncount_relation, 0),
-                                  lastLogon=("greater", 0),
-                                  lastLogonTimestamp=("greater", 0),
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-        badPasswordTime = int(res[0]["badPasswordTime"][0])
-        logonCount = int(res[0]["logonCount"][0])
-        lastLogon = int(res[0]["lastLogon"][0])
-        lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
-        firstLogon = lastLogon
-        print "last logon is %d" % lastLogon
-        self.assertGreater(lastLogon, badPasswordTime)
-        self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
-
-        time.sleep(1)
-        SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=0,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=(logoncount_relation, logonCount),
-                                  lastLogon=(lastlogon_relation, lastLogon),
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  userAccountControl=
-                                  dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0,
-                                  msg=("second logon, firstlogon was %s" %
-                                       firstLogon))
-
-
-        lastLogon = int(res[0]["lastLogon"][0])
-
-        time.sleep(1)
-
-        SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp)
-
-        res = self._check_account(userdn,
-                                  badPwdCount=0,
-                                  badPasswordTime=badPasswordTime,
-                                  logonCount=(logoncount_relation, logonCount),
-                                  lastLogon=(lastlogon_relation, lastLogon),
-                                  lastLogonTimestamp=lastLogonTimestamp,
-                                  userAccountControl=
-                                    dsdb.UF_NORMAL_ACCOUNT,
-                                  msDSUserAccountControlComputed=0)
-
+        self.ldb = SamDB(url=self.host_url, session_info=system_session(self.lp),
+                         credentials=self.global_creds, lp=self.lp)
+        super(PasswordTests, self).setUp()
 
-class PasswordTests(BasePasswordTestCase):
     def _reset_ldap_lockoutTime(self, res):
         self.ldb.modify_ldif("""
 dn: """ + str(res[0].dn) + """
diff --git a/source4/dsdb/tests/python/password_lockout_base.py b/source4/dsdb/tests/python/password_lockout_base.py
new file mode 100644 (file)
index 0000000..1b14979
--- /dev/null
@@ -0,0 +1,907 @@
+import samba
+
+from samba.auth import system_session
+from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS
+from ldb import SCOPE_BASE, LdbError
+from ldb import ERR_CONSTRAINT_VIOLATION
+from ldb import ERR_INVALID_CREDENTIALS
+from ldb import Message, MessageElement, Dn
+from ldb import FLAG_MOD_REPLACE
+from samba import gensec, dsdb
+from samba.samdb import SamDB
+import samba.tests
+from samba.tests import delete_force
+from samba.dcerpc import security, samr
+from samba.ndr import ndr_unpack
+
+import time
+
+class BasePasswordTestCase(samba.tests.TestCase):
+    def _open_samr_user(self, res):
+        self.assertTrue("objectSid" in res[0])
+
+        (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split()
+        self.assertEquals(self.domain_sid, domain_sid)
+
+        return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid)
+
+    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 _check_attribute(self, res, name, value):
+        if value is None:
+            self.assertTrue(name not in res[0],
+                            msg="attr[%s]=%r on dn[%s]" %
+                            (name, res[0], res[0].dn))
+            return
+
+        if isinstance(value, tuple):
+            (mode, value) = value
+        else:
+            mode = "equal"
+
+        if mode == "ignore":
+            return
+
+        if mode == "absent":
+            self.assertFalse(name in res[0],
+                            msg="attr[%s] not missing on dn[%s]" %
+                            (name, res[0].dn))
+            return
+
+        self.assertTrue(name in res[0],
+                        msg="attr[%s] missing on dn[%s]" %
+                        (name, res[0].dn))
+        self.assertTrue(len(res[0][name]) == 1,
+                        msg="attr[%s]=%r on dn[%s]" %
+                        (name, res[0][name], res[0].dn))
+
+
+        print  "%s = '%s'" % (name, res[0][name][0])
+
+        if mode == "present":
+            return
+
+        if mode == "equal":
+            v = int(res[0][name][0])
+            value = int(value)
+            msg = ("attr[%s]=[%s] != [%s] on dn[%s]\n"
+                   "(diff %d; actual value is %s than expected)"  %
+                   (name, v, value, res[0].dn, v - value,
+                    ('less' if v < value else 'greater')))
+
+            self.assertTrue(v == value, msg)
+            return
+
+        if mode == "greater":
+            v = int(res[0][name][0])
+            self.assertTrue(v > int(value),
+                            msg="attr[%s]=[%s] <= [%s] on dn[%s] (diff %d)" %
+                            (name, v, int(value), res[0].dn, v - int(value)))
+            return
+        if mode == "less":
+            v = int(res[0][name][0])
+            self.assertTrue(v < int(value),
+                            msg="attr[%s]=[%s] >= [%s] on dn[%s] (diff %d)" %
+                            (name, v, int(value), res[0].dn, v - int(value)))
+            return
+        self.assertEqual(mode, not mode, "Invalid Mode[%s]" % mode)
+
+    def _check_account(self, dn,
+                       badPwdCount=None,
+                       badPasswordTime=None,
+                       logonCount=None,
+                       lastLogon=None,
+                       lastLogonTimestamp=None,
+                       lockoutTime=None,
+                       userAccountControl=None,
+                       msDSUserAccountControlComputed=None,
+                       effective_bad_password_count=None,
+                       msg=None):
+        print '-=' * 36
+        if msg is not None:
+            print  "\033[01;32m %s \033[00m\n" % msg
+        attrs = [
+           "objectSid",
+           "badPwdCount",
+           "badPasswordTime",
+           "lastLogon",
+           "lastLogonTimestamp",
+           "logonCount",
+           "lockoutTime",
+           "userAccountControl",
+           "msDS-User-Account-Control-Computed"
+        ]
+
+        # in order to prevent some time resolution problems we sleep for
+        # 10 micro second
+        time.sleep(0.01)
+
+        res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+        self.assertTrue(len(res) == 1)
+        self._check_attribute(res, "badPwdCount", badPwdCount)
+        self._check_attribute(res, "badPasswordTime", badPasswordTime)
+        self._check_attribute(res, "logonCount", logonCount)
+        self._check_attribute(res, "lastLogon", lastLogon)
+        self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp)
+        self._check_attribute(res, "lockoutTime", lockoutTime)
+        self._check_attribute(res, "userAccountControl", userAccountControl)
+        self._check_attribute(res, "msDS-User-Account-Control-Computed",
+                              msDSUserAccountControlComputed)
+
+        lastLogon = int(res[0]["lastLogon"][0])
+        logonCount = int(res[0]["logonCount"][0])
+
+        samr_user = self._open_samr_user(res)
+        uinfo3 = self.samr.QueryUserInfo(samr_user, 3)
+        uinfo5 = self.samr.QueryUserInfo(samr_user, 5)
+        uinfo16 = self.samr.QueryUserInfo(samr_user, 16)
+        uinfo21 = self.samr.QueryUserInfo(samr_user, 21)
+        self.samr.Close(samr_user)
+
+        expected_acb_info = 0
+        if userAccountControl & dsdb.UF_NORMAL_ACCOUNT:
+            expected_acb_info |= samr.ACB_NORMAL
+        if userAccountControl & dsdb.UF_ACCOUNTDISABLE:
+            expected_acb_info |= samr.ACB_DISABLED
+        if userAccountControl & dsdb.UF_PASSWD_NOTREQD:
+            expected_acb_info |= samr.ACB_PWNOTREQ
+        if msDSUserAccountControlComputed & dsdb.UF_LOCKOUT:
+            expected_acb_info |= samr.ACB_AUTOLOCK
+        if msDSUserAccountControlComputed & dsdb.UF_PASSWORD_EXPIRED:
+            expected_acb_info |= samr.ACB_PW_EXPIRED
+
+        expected_bad_password_count = 0
+        if badPwdCount is not None:
+            expected_bad_password_count = badPwdCount
+        if effective_bad_password_count is None:
+            effective_bad_password_count = expected_bad_password_count
+
+        self.assertEquals(uinfo3.acct_flags, expected_acb_info)
+        self.assertEquals(uinfo3.bad_password_count, expected_bad_password_count)
+        self.assertEquals(uinfo3.last_logon, lastLogon)
+        self.assertEquals(uinfo3.logon_count, logonCount)
+
+        self.assertEquals(uinfo5.acct_flags, expected_acb_info)
+        self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count)
+        self.assertEquals(uinfo5.last_logon, lastLogon)
+        self.assertEquals(uinfo5.logon_count, logonCount)
+
+        self.assertEquals(uinfo16.acct_flags, expected_acb_info)
+
+        self.assertEquals(uinfo21.acct_flags, expected_acb_info)
+        self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count)
+        self.assertEquals(uinfo21.last_logon, lastLogon)
+        self.assertEquals(uinfo21.logon_count, logonCount)
+
+        # check LDAP again and make sure the samr.QueryUserInfo
+        # doesn't have any impact.
+        res2 = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+        self.assertEquals(res[0], res2[0])
+
+        # in order to prevent some time resolution problems we sleep for
+        # 10 micro second
+        time.sleep(0.01)
+        return res
+
+    def _readd_user(self, creds, lockOutObservationWindow=0):
+        username = creds.get_username()
+        userpass = creds.get_password()
+        userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
+
+        use_kerberos = creds.get_kerberos_state()
+        if use_kerberos == MUST_USE_KERBEROS:
+            logoncount_relation = 'greater'
+            lastlogon_relation = 'greater'
+        else:
+            logoncount_relation = 'equal'
+            if lockOutObservationWindow == 0:
+                lastlogon_relation = 'greater'
+            else:
+                lastlogon_relation = 'equal'
+
+        delete_force(self.ldb, userdn)
+        self.ldb.add({
+             "dn": userdn,
+             "objectclass": "user",
+             "sAMAccountName": username})
+
+        self.addCleanup(delete_force, self.ldb, userdn)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=0,
+                                  logonCount=0,
+                                  lastLogon=0,
+                                  lastLogonTimestamp=('absent', None),
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT |
+                                    dsdb.UF_ACCOUNTDISABLE |
+                                    dsdb.UF_PASSWD_NOTREQD,
+                                  msDSUserAccountControlComputed=
+                                    dsdb.UF_PASSWORD_EXPIRED)
+
+        # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
+        # It doesn't create "lockoutTime" = 0.
+        self._reset_samr(res)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=0,
+                                  logonCount=0,
+                                  lastLogon=0,
+                                  lastLogonTimestamp=('absent', None),
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT |
+                                    dsdb.UF_ACCOUNTDISABLE |
+                                    dsdb.UF_PASSWD_NOTREQD,
+                                  msDSUserAccountControlComputed=
+                                    dsdb.UF_PASSWORD_EXPIRED)
+
+        # Tests a password change when we don't have any password yet with a
+        # wrong old password
+        try:
+            self.ldb.modify_ldif("""
+dn: """ + userdn + """
+changetype: modify
+delete: userPassword
+userPassword: noPassword
+add: userPassword
+userPassword: thatsAcomplPASS2
+""")
+            self.fail()
+        except LdbError, (num, msg):
+            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+            # Windows (2008 at least) seems to have some small bug here: it
+            # returns "0000056A" on longer (always wrong) previous passwords.
+            self.assertTrue('00000056' in msg, msg)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=1,
+                                  badPasswordTime=("greater", 0),
+                                  logonCount=0,
+                                  lastLogon=0,
+                                  lastLogonTimestamp=('absent', None),
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT |
+                                    dsdb.UF_ACCOUNTDISABLE |
+                                    dsdb.UF_PASSWD_NOTREQD,
+                                  msDSUserAccountControlComputed=
+                                    dsdb.UF_PASSWORD_EXPIRED)
+        badPwdCount = int(res[0]["badPwdCount"][0])
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
+
+        # Sets the initial user password with a "special" password change
+        # I think that this internally is a password set operation and it can
+        # only be performed by someone which has password set privileges on the
+        # account (at least in s4 we do handle it like that).
+        self.ldb.modify_ldif("""
+dn: """ + userdn + """
+changetype: modify
+delete: userPassword
+add: userPassword
+userPassword: """ + userpass + """
+""")
+
+        res = self._check_account(userdn,
+                                  badPwdCount=badPwdCount,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=0,
+                                  lastLogon=0,
+                                  lastLogonTimestamp=('absent', None),
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT |
+                                    dsdb.UF_ACCOUNTDISABLE |
+                                    dsdb.UF_PASSWD_NOTREQD,
+                                  msDSUserAccountControlComputed=0)
+
+        # Enables the user account
+        self.ldb.enable_account("(sAMAccountName=%s)" % username)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=badPwdCount,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=0,
+                                  lastLogon=0,
+                                  lastLogonTimestamp=('absent', None),
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+        if lockOutObservationWindow != 0:
+            time.sleep(lockOutObservationWindow + 1)
+            effective_bad_password_count = 0
+        else:
+            effective_bad_password_count = badPwdCount
+
+        res = self._check_account(userdn,
+                                  badPwdCount=badPwdCount,
+                                  effective_bad_password_count=effective_bad_password_count,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=0,
+                                  lastLogon=0,
+                                  lastLogonTimestamp=('absent', None),
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+
+        ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp)
+
+        if lockOutObservationWindow == 0:
+            badPwdCount = 0
+            effective_bad_password_count = 0
+        if use_kerberos == MUST_USE_KERBEROS:
+            badPwdCount = 0
+            effective_bad_password_count = 0
+
+        res = self._check_account(userdn,
+                                  badPwdCount=badPwdCount,
+                                  effective_bad_password_count=effective_bad_password_count,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=(logoncount_relation, 0),
+                                  lastLogon=(lastlogon_relation, 0),
+                                  lastLogonTimestamp=('greater', badPasswordTime),
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+
+        logonCount = int(res[0]["logonCount"][0])
+        lastLogon = int(res[0]["lastLogon"][0])
+        lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
+        if lastlogon_relation == 'greater':
+            self.assertGreater(lastLogon, badPasswordTime)
+            self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=badPwdCount,
+                                  effective_bad_password_count=effective_bad_password_count,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+        return ldb
+
+    def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS):
+        try:
+            ldb = SamDB(url=url, credentials=creds, lp=lp)
+            self.fail("Login unexpectedly succeeded")
+        except LdbError, (num, msg):
+            if errno is not None:
+                self.assertEquals(num, errno, ("Login failed in the wrong way"
+                                               "(got err %d, expected %d)" %
+                                               (num, errno)))
+
+    def setUp(self):
+        super(BasePasswordTestCase, self).setUp()
+
+        self.global_creds.set_gensec_features(self.global_creds.get_gensec_features() |
+                                              gensec.FEATURE_SEAL)
+
+        self.template_creds = Credentials()
+        self.template_creds.set_username("testuser")
+        self.template_creds.set_password("thatsAcomplPASS1")
+        self.template_creds.set_domain(self.global_creds.get_domain())
+        self.template_creds.set_realm(self.global_creds.get_realm())
+        self.template_creds.set_workstation(self.global_creds.get_workstation())
+        self.template_creds.set_gensec_features(self.global_creds.get_gensec_features())
+        self.template_creds.set_kerberos_state(self.global_creds.get_kerberos_state())
+
+
+        # Gets back the basedn
+        base_dn = self.ldb.domain_dn()
+
+        # Gets back the configuration basedn
+        configuration_dn = self.ldb.get_config_basedn().get_linearized()
+
+        # Get the old "dSHeuristics" if it was set
+        dsheuristics = self.ldb.get_dsheuristics()
+
+        # Reset the "dSHeuristics" as they were before
+        self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
+
+        res = self.ldb.search(base_dn,
+                         scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
+
+        if "lockoutDuration" in res[0]:
+            lockoutDuration = res[0]["lockoutDuration"][0]
+        else:
+            lockoutDuration = 0
+
+        if "lockoutObservationWindow" in res[0]:
+            lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
+        else:
+            lockoutObservationWindow = 0
+
+        if "lockoutThreshold" in res[0]:
+            lockoutThreshold = res[0]["lockoutThreshold"][0]
+        else:
+            lockoutTreshold = 0
+
+        self.addCleanup(self.ldb.modify_ldif, """
+dn: """ + base_dn + """
+changetype: modify
+replace: lockoutDuration
+lockoutDuration: """ + str(lockoutDuration) + """
+replace: lockoutObservationWindow
+lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
+replace: lockoutThreshold
+lockoutThreshold: """ + str(lockoutThreshold) + """
+""")
+
+        m = Message()
+        m.dn = Dn(self.ldb, base_dn)
+
+        self.account_lockout_duration = 2
+        account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7))
+
+        m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
+                                              FLAG_MOD_REPLACE, "lockoutDuration")
+
+        account_lockout_threshold = 3
+        m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
+                                               FLAG_MOD_REPLACE, "lockoutThreshold")
+
+        self.lockout_observation_window = 2
+        lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7))
+
+        m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
+                                                       FLAG_MOD_REPLACE, "lockOutObservationWindow")
+
+        self.ldb.modify(m)
+
+        # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
+        self.ldb.set_dsheuristics("000000001")
+
+        # Get the old "minPwdAge"
+        minPwdAge = self.ldb.get_minPwdAge()
+
+        # Reset the "minPwdAge" as it was before
+        self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
+
+        # Set it temporarely to "0"
+        self.ldb.set_minPwdAge("0")
+
+        self.base_dn = self.ldb.domain_dn()
+
+        self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
+        self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % self.host, self.lp, self.global_creds)
+        self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
+        self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
+
+        # (Re)adds the test user accounts
+        self.lockout1krb5_creds = self.insta_creds(self.template_creds,
+                                                   username="lockout1krb5",
+                                                   userpass="thatsAcomplPASS0",
+                                                   kerberos_state=MUST_USE_KERBEROS)
+        self.lockout1krb5_ldb = self._readd_user(self.lockout1krb5_creds)
+        self.lockout2krb5_creds = self.insta_creds(self.template_creds,
+                                                   username="lockout2krb5",
+                                                   userpass="thatsAcomplPASS0",
+                                                   kerberos_state=MUST_USE_KERBEROS)
+        self.lockout2krb5_ldb = self._readd_user(self.lockout2krb5_creds,
+                                                 lockOutObservationWindow=self.lockout_observation_window)
+        self.lockout1ntlm_creds = self.insta_creds(self.template_creds,
+                                                   username="lockout1ntlm",
+                                                   userpass="thatsAcomplPASS0",
+                                                   kerberos_state=DONT_USE_KERBEROS)
+        self.lockout1ntlm_ldb = self._readd_user(self.lockout1ntlm_creds)
+        self.lockout2ntlm_creds = self.insta_creds(self.template_creds,
+                                                   username="lockout2ntlm",
+                                                   userpass="thatsAcomplPASS0",
+                                                   kerberos_state=DONT_USE_KERBEROS)
+        self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds,
+                                                 lockOutObservationWindow=self.lockout_observation_window)
+
+    def tearDown(self):
+        super(BasePasswordTestCase, self).tearDown()
+
+    def _test_login_lockout(self, creds):
+        username = creds.get_username()
+        userpass = creds.get_password()
+        userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
+
+        use_kerberos = creds.get_kerberos_state()
+        # This unlocks by waiting for account_lockout_duration
+        if use_kerberos == MUST_USE_KERBEROS:
+            logoncount_relation = 'greater'
+            lastlogon_relation = 'greater'
+            print "Performs a lockout attempt against LDAP using Kerberos"
+        else:
+            logoncount_relation = 'equal'
+            lastlogon_relation = 'equal'
+            print "Performs a lockout attempt against LDAP using NTLM"
+
+        # Change password on a connection as another user
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=("greater", 0),
+                                  logonCount=(logoncount_relation, 0),
+                                  lastLogon=("greater", 0),
+                                  lastLogonTimestamp=("greater", 0),
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
+        logonCount = int(res[0]["logonCount"][0])
+        lastLogon = int(res[0]["lastLogon"][0])
+        firstLogon = lastLogon
+        lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
+        print firstLogon
+        print lastLogonTimestamp
+
+
+        self.assertGreater(lastLogon, badPasswordTime)
+        self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
+
+        # Open a second LDB connection with the user credentials. Use the
+        # command line credentials for informations like the domain, the realm
+        # and the workstation.
+        creds_lockout = self.insta_creds(creds)
+
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
+
+        self.assertLoginFailure(self.host_url, creds_lockout, self.lp)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=1,
+                                  badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0,
+                                  msg='lastlogontimestamp with wrong password')
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
+
+        # Correct old password
+        creds_lockout.set_password(userpass)
+
+        ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
+
+        # lastLogonTimestamp should not change
+        # lastLogon increases if badPwdCount is non-zero (!)
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=(logoncount_relation, logonCount),
+                                  lastLogon=('greater', lastLogon),
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0,
+                                  msg='LLTimestamp is updated to lastlogon')
+
+        logonCount = int(res[0]["logonCount"][0])
+        lastLogon = int(res[0]["lastLogon"][0])
+        self.assertGreater(lastLogon, badPasswordTime)
+        self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
+
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
+
+        self.assertLoginFailure(self.host_url, creds_lockout, self.lp)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=1,
+                                  badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
+
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
+
+        try:
+            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
+            self.fail()
+
+        except LdbError, (num, msg):
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=2,
+                                  badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
+
+        print "two failed password change"
+
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
+
+        try:
+            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
+            self.fail()
+
+        except LdbError, (num, msg):
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=3,
+                                  badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=("greater", badPasswordTime),
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
+        lockoutTime = int(res[0]["lockoutTime"][0])
+
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
+        try:
+            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
+            self.fail()
+        except LdbError, (num, msg):
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=3,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=lockoutTime,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
+
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
+        try:
+            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
+            self.fail()
+        except LdbError, (num, msg):
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=3,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=lockoutTime,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
+
+        # The correct password, but we are locked out
+        creds_lockout.set_password(userpass)
+        try:
+            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
+            self.fail()
+        except LdbError, (num, msg):
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=3,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=lockoutTime,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
+
+        # wait for the lockout to end
+        time.sleep(self.account_lockout_duration + 1)
+        print self.account_lockout_duration + 1
+
+        res = self._check_account(userdn,
+                                  badPwdCount=3, effective_bad_password_count=0,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
+                                  lockoutTime=lockoutTime,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+
+        # The correct password after letting the timeout expire
+
+        creds_lockout.set_password(userpass)
+
+        creds_lockout2 = self.insta_creds(creds_lockout)
+
+        ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout2, lp=self.lp)
+        time.sleep(3)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=(logoncount_relation, logonCount),
+                                  lastLogon=(lastlogon_relation, lastLogon),
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  lockoutTime=0,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0,
+                                  msg="lastLogon is way off")
+
+        logonCount = int(res[0]["logonCount"][0])
+        lastLogon = int(res[0]["lastLogon"][0])
+
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
+        try:
+            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
+            self.fail()
+        except LdbError, (num, msg):
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=1,
+                                  badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
+                                  lockoutTime=0,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
+
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
+        try:
+            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
+            self.fail()
+        except LdbError, (num, msg):
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=2,
+                                  badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
+                                  lockoutTime=0,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
+
+        time.sleep(self.lockout_observation_window + 1)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=2, effective_bad_password_count=0,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=logonCount,
+                                  lockoutTime=0,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+
+        # The wrong password
+        creds_lockout.set_password("thatsAcomplPASS1x")
+        try:
+            ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
+            self.fail()
+        except LdbError, (num, msg):
+            self.assertEquals(num, ERR_INVALID_CREDENTIALS)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=1,
+                                  badPasswordTime=("greater", badPasswordTime),
+                                  logonCount=logonCount,
+                                  lockoutTime=0,
+                                  lastLogon=lastLogon,
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
+
+        # The correct password without letting the timeout expire
+        creds_lockout.set_password(userpass)
+        ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=(logoncount_relation, logonCount),
+                                  lockoutTime=0,
+                                  lastLogon=("greater", lastLogon),
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+
+    def _test_multiple_logon(self, creds):
+        # Test the happy case in which a user logs on correctly, then
+        # logs on correctly again, so that the bad password and
+        # lockout times are both zero the second time. The lastlogon
+        # time should increase.
+
+        # Open a second LDB connection with the user credentials. Use the
+        # command line credentials for informations like the domain, the realm
+        # and the workstation.
+        username = creds.get_username()
+        userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
+
+        use_kerberos = creds.get_kerberos_state()
+        if use_kerberos == MUST_USE_KERBEROS:
+            print "Testing multiple logon with Kerberos"
+            logoncount_relation = 'greater'
+            lastlogon_relation = 'greater'
+        else:
+            print "Testing multiple logon with NTLM"
+            logoncount_relation = 'equal'
+            lastlogon_relation = 'equal'
+
+        SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=("greater", 0),
+                                  logonCount=(logoncount_relation, 0),
+                                  lastLogon=("greater", 0),
+                                  lastLogonTimestamp=("greater", 0),
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)
+        badPasswordTime = int(res[0]["badPasswordTime"][0])
+        logonCount = int(res[0]["logonCount"][0])
+        lastLogon = int(res[0]["lastLogon"][0])
+        lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
+        firstLogon = lastLogon
+        print "last logon is %d" % lastLogon
+        self.assertGreater(lastLogon, badPasswordTime)
+        self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
+
+        time.sleep(1)
+        SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=(logoncount_relation, logonCount),
+                                  lastLogon=(lastlogon_relation, lastLogon),
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                  dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0,
+                                  msg=("second logon, firstlogon was %s" %
+                                       firstLogon))
+
+
+        lastLogon = int(res[0]["lastLogon"][0])
+
+        time.sleep(1)
+
+        SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp)
+
+        res = self._check_account(userdn,
+                                  badPwdCount=0,
+                                  badPasswordTime=badPasswordTime,
+                                  logonCount=(logoncount_relation, logonCount),
+                                  lastLogon=(lastlogon_relation, lastLogon),
+                                  lastLogonTimestamp=lastLogonTimestamp,
+                                  userAccountControl=
+                                    dsdb.UF_NORMAL_ACCOUNT,
+                                  msDSUserAccountControlComputed=0)