3 from samba.auth import system_session
4 from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS
5 from ldb import SCOPE_BASE, LdbError
6 from ldb import ERR_CONSTRAINT_VIOLATION
7 from ldb import ERR_INVALID_CREDENTIALS
8 from ldb import Message, MessageElement, Dn
9 from ldb import FLAG_MOD_REPLACE
10 from samba import gensec, dsdb
11 from samba.samdb import SamDB
13 from samba.tests import delete_force
14 from samba.dcerpc import security, samr
15 from samba.ndr import ndr_unpack
19 class BasePasswordTestCase(samba.tests.TestCase):
20 def _open_samr_user(self, res):
21 self.assertTrue("objectSid" in res[0])
23 (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split()
24 self.assertEquals(self.domain_sid, domain_sid)
26 return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid)
28 def _reset_samr(self, res):
30 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
31 samr_user = self._open_samr_user(res)
32 acb_info = self.samr.QueryUserInfo(samr_user, 16)
33 acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
34 self.samr.SetUserInfo(samr_user, 16, acb_info)
35 self.samr.Close(samr_user)
37 def _check_attribute(self, res, name, value):
39 self.assertTrue(name not in res[0],
40 msg="attr[%s]=%r on dn[%s]" %
41 (name, res[0], res[0].dn))
44 if isinstance(value, tuple):
53 self.assertFalse(name in res[0],
54 msg="attr[%s] not missing on dn[%s]" %
58 self.assertTrue(name in res[0],
59 msg="attr[%s] missing on dn[%s]" %
61 self.assertTrue(len(res[0][name]) == 1,
62 msg="attr[%s]=%r on dn[%s]" %
63 (name, res[0][name], res[0].dn))
66 print "%s = '%s'" % (name, res[0][name][0])
72 v = int(res[0][name][0])
74 msg = ("attr[%s]=[%s] != [%s] on dn[%s]\n"
75 "(diff %d; actual value is %s than expected)" %
76 (name, v, value, res[0].dn, v - value,
77 ('less' if v < value else 'greater')))
79 self.assertTrue(v == value, msg)
83 v = int(res[0][name][0])
84 self.assertTrue(v > int(value),
85 msg="attr[%s]=[%s] <= [%s] on dn[%s] (diff %d)" %
86 (name, v, int(value), res[0].dn, v - int(value)))
89 v = int(res[0][name][0])
90 self.assertTrue(v < int(value),
91 msg="attr[%s]=[%s] >= [%s] on dn[%s] (diff %d)" %
92 (name, v, int(value), res[0].dn, v - int(value)))
94 self.assertEqual(mode, not mode, "Invalid Mode[%s]" % mode)
96 def _check_account(self, dn,
101 lastLogonTimestamp=None,
103 userAccountControl=None,
104 msDSUserAccountControlComputed=None,
105 effective_bad_password_count=None,
109 print "\033[01;32m %s \033[00m\n" % msg
115 "lastLogonTimestamp",
118 "userAccountControl",
119 "msDS-User-Account-Control-Computed"
122 # in order to prevent some time resolution problems we sleep for
126 res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
127 self.assertTrue(len(res) == 1)
128 self._check_attribute(res, "badPwdCount", badPwdCount)
129 self._check_attribute(res, "badPasswordTime", badPasswordTime)
130 self._check_attribute(res, "logonCount", logonCount)
131 self._check_attribute(res, "lastLogon", lastLogon)
132 self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp)
133 self._check_attribute(res, "lockoutTime", lockoutTime)
134 self._check_attribute(res, "userAccountControl", userAccountControl)
135 self._check_attribute(res, "msDS-User-Account-Control-Computed",
136 msDSUserAccountControlComputed)
138 lastLogon = int(res[0]["lastLogon"][0])
139 logonCount = int(res[0]["logonCount"][0])
141 samr_user = self._open_samr_user(res)
142 uinfo3 = self.samr.QueryUserInfo(samr_user, 3)
143 uinfo5 = self.samr.QueryUserInfo(samr_user, 5)
144 uinfo16 = self.samr.QueryUserInfo(samr_user, 16)
145 uinfo21 = self.samr.QueryUserInfo(samr_user, 21)
146 self.samr.Close(samr_user)
148 expected_acb_info = 0
149 if userAccountControl & dsdb.UF_NORMAL_ACCOUNT:
150 expected_acb_info |= samr.ACB_NORMAL
151 if userAccountControl & dsdb.UF_ACCOUNTDISABLE:
152 expected_acb_info |= samr.ACB_DISABLED
153 if userAccountControl & dsdb.UF_PASSWD_NOTREQD:
154 expected_acb_info |= samr.ACB_PWNOTREQ
155 if msDSUserAccountControlComputed & dsdb.UF_LOCKOUT:
156 expected_acb_info |= samr.ACB_AUTOLOCK
157 if msDSUserAccountControlComputed & dsdb.UF_PASSWORD_EXPIRED:
158 expected_acb_info |= samr.ACB_PW_EXPIRED
160 expected_bad_password_count = 0
161 if badPwdCount is not None:
162 expected_bad_password_count = badPwdCount
163 if effective_bad_password_count is None:
164 effective_bad_password_count = expected_bad_password_count
166 self.assertEquals(uinfo3.acct_flags, expected_acb_info)
167 self.assertEquals(uinfo3.bad_password_count, expected_bad_password_count)
168 self.assertEquals(uinfo3.last_logon, lastLogon)
169 self.assertEquals(uinfo3.logon_count, logonCount)
171 self.assertEquals(uinfo5.acct_flags, expected_acb_info)
172 self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count)
173 self.assertEquals(uinfo5.last_logon, lastLogon)
174 self.assertEquals(uinfo5.logon_count, logonCount)
176 self.assertEquals(uinfo16.acct_flags, expected_acb_info)
178 self.assertEquals(uinfo21.acct_flags, expected_acb_info)
179 self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count)
180 self.assertEquals(uinfo21.last_logon, lastLogon)
181 self.assertEquals(uinfo21.logon_count, logonCount)
183 # check LDAP again and make sure the samr.QueryUserInfo
184 # doesn't have any impact.
185 res2 = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
186 self.assertEquals(res[0], res2[0])
188 # in order to prevent some time resolution problems we sleep for
193 def _readd_user(self, creds, lockOutObservationWindow=0):
194 username = creds.get_username()
195 userpass = creds.get_password()
196 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
198 delete_force(self.ldb, userdn)
201 "objectclass": "user",
202 "sAMAccountName": username})
204 self.addCleanup(delete_force, self.ldb, userdn)
206 # Sets the initial user password with a "special" password change
207 # I think that this internally is a password set operation and it can
208 # only be performed by someone which has password set privileges on the
209 # account (at least in s4 we do handle it like that).
210 self.ldb.modify_ldif("""
211 dn: """ + userdn + """
215 userPassword: """ + userpass + """
217 # Enables the user account
218 self.ldb.enable_account("(sAMAccountName=%s)" % username)
220 use_kerberos = creds.get_kerberos_state()
221 fail_creds = self.insta_creds(self.template_creds,
223 userpass=userpass+"X",
224 kerberos_state=use_kerberos)
226 # Fail once to get a badPasswordTime
228 ldb = SamDB(url=self.host_url, credentials=fail_creds, lp=self.lp)
230 except LdbError, (num, msg):
231 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
233 # Succeed to reset everything to 0
234 ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp)
238 def _testing_add_user(self, creds, lockOutObservationWindow=0):
239 username = creds.get_username()
240 userpass = creds.get_password()
241 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
243 use_kerberos = creds.get_kerberos_state()
244 if use_kerberos == MUST_USE_KERBEROS:
245 logoncount_relation = 'greater'
246 lastlogon_relation = 'greater'
248 logoncount_relation = 'equal'
249 if lockOutObservationWindow == 0:
250 lastlogon_relation = 'greater'
252 lastlogon_relation = 'equal'
254 delete_force(self.ldb, userdn)
257 "objectclass": "user",
258 "sAMAccountName": username})
260 self.addCleanup(delete_force, self.ldb, userdn)
262 res = self._check_account(userdn,
267 lastLogonTimestamp=('absent', None),
269 dsdb.UF_NORMAL_ACCOUNT |
270 dsdb.UF_ACCOUNTDISABLE |
271 dsdb.UF_PASSWD_NOTREQD,
272 msDSUserAccountControlComputed=
273 dsdb.UF_PASSWORD_EXPIRED)
275 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
276 # It doesn't create "lockoutTime" = 0.
277 self._reset_samr(res)
279 res = self._check_account(userdn,
284 lastLogonTimestamp=('absent', None),
286 dsdb.UF_NORMAL_ACCOUNT |
287 dsdb.UF_ACCOUNTDISABLE |
288 dsdb.UF_PASSWD_NOTREQD,
289 msDSUserAccountControlComputed=
290 dsdb.UF_PASSWORD_EXPIRED)
292 # Tests a password change when we don't have any password yet with a
295 self.ldb.modify_ldif("""
296 dn: """ + userdn + """
299 userPassword: noPassword
301 userPassword: thatsAcomplPASS2
304 except LdbError, (num, msg):
305 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
306 # Windows (2008 at least) seems to have some small bug here: it
307 # returns "0000056A" on longer (always wrong) previous passwords.
308 self.assertTrue('00000056' in msg, msg)
310 res = self._check_account(userdn,
312 badPasswordTime=("greater", 0),
315 lastLogonTimestamp=('absent', None),
317 dsdb.UF_NORMAL_ACCOUNT |
318 dsdb.UF_ACCOUNTDISABLE |
319 dsdb.UF_PASSWD_NOTREQD,
320 msDSUserAccountControlComputed=
321 dsdb.UF_PASSWORD_EXPIRED)
322 badPwdCount = int(res[0]["badPwdCount"][0])
323 badPasswordTime = int(res[0]["badPasswordTime"][0])
325 # Sets the initial user password with a "special" password change
326 # I think that this internally is a password set operation and it can
327 # only be performed by someone which has password set privileges on the
328 # account (at least in s4 we do handle it like that).
329 self.ldb.modify_ldif("""
330 dn: """ + userdn + """
334 userPassword: """ + userpass + """
337 res = self._check_account(userdn,
338 badPwdCount=badPwdCount,
339 badPasswordTime=badPasswordTime,
342 lastLogonTimestamp=('absent', None),
344 dsdb.UF_NORMAL_ACCOUNT |
345 dsdb.UF_ACCOUNTDISABLE |
346 dsdb.UF_PASSWD_NOTREQD,
347 msDSUserAccountControlComputed=0)
349 # Enables the user account
350 self.ldb.enable_account("(sAMAccountName=%s)" % username)
352 res = self._check_account(userdn,
353 badPwdCount=badPwdCount,
354 badPasswordTime=badPasswordTime,
357 lastLogonTimestamp=('absent', None),
359 dsdb.UF_NORMAL_ACCOUNT,
360 msDSUserAccountControlComputed=0)
361 if lockOutObservationWindow != 0:
362 time.sleep(lockOutObservationWindow + 1)
363 effective_bad_password_count = 0
365 effective_bad_password_count = badPwdCount
367 res = self._check_account(userdn,
368 badPwdCount=badPwdCount,
369 effective_bad_password_count=effective_bad_password_count,
370 badPasswordTime=badPasswordTime,
373 lastLogonTimestamp=('absent', None),
375 dsdb.UF_NORMAL_ACCOUNT,
376 msDSUserAccountControlComputed=0)
378 ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp)
380 if lockOutObservationWindow == 0:
382 effective_bad_password_count = 0
383 if use_kerberos == MUST_USE_KERBEROS:
385 effective_bad_password_count = 0
387 res = self._check_account(userdn,
388 badPwdCount=badPwdCount,
389 effective_bad_password_count=effective_bad_password_count,
390 badPasswordTime=badPasswordTime,
391 logonCount=(logoncount_relation, 0),
392 lastLogon=(lastlogon_relation, 0),
393 lastLogonTimestamp=('greater', badPasswordTime),
395 dsdb.UF_NORMAL_ACCOUNT,
396 msDSUserAccountControlComputed=0)
398 logonCount = int(res[0]["logonCount"][0])
399 lastLogon = int(res[0]["lastLogon"][0])
400 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
401 if lastlogon_relation == 'greater':
402 self.assertGreater(lastLogon, badPasswordTime)
403 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
405 res = self._check_account(userdn,
406 badPwdCount=badPwdCount,
407 effective_bad_password_count=effective_bad_password_count,
408 badPasswordTime=badPasswordTime,
409 logonCount=logonCount,
411 lastLogonTimestamp=lastLogonTimestamp,
413 dsdb.UF_NORMAL_ACCOUNT,
414 msDSUserAccountControlComputed=0)
417 def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS):
419 ldb = SamDB(url=url, credentials=creds, lp=lp)
420 self.fail("Login unexpectedly succeeded")
421 except LdbError, (num, msg):
422 if errno is not None:
423 self.assertEquals(num, errno, ("Login failed in the wrong way"
424 "(got err %d, expected %d)" %
428 super(BasePasswordTestCase, self).setUp()
430 self.global_creds.set_gensec_features(self.global_creds.get_gensec_features() |
433 self.template_creds = Credentials()
434 self.template_creds.set_username("testuser")
435 self.template_creds.set_password("thatsAcomplPASS1")
436 self.template_creds.set_domain(self.global_creds.get_domain())
437 self.template_creds.set_realm(self.global_creds.get_realm())
438 self.template_creds.set_workstation(self.global_creds.get_workstation())
439 self.template_creds.set_gensec_features(self.global_creds.get_gensec_features())
440 self.template_creds.set_kerberos_state(self.global_creds.get_kerberos_state())
443 # Gets back the basedn
444 base_dn = self.ldb.domain_dn()
446 # Gets back the configuration basedn
447 configuration_dn = self.ldb.get_config_basedn().get_linearized()
449 # Get the old "dSHeuristics" if it was set
450 dsheuristics = self.ldb.get_dsheuristics()
452 # Reset the "dSHeuristics" as they were before
453 self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
455 res = self.ldb.search(base_dn,
456 scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
458 if "lockoutDuration" in res[0]:
459 lockoutDuration = res[0]["lockoutDuration"][0]
463 if "lockoutObservationWindow" in res[0]:
464 lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
466 lockoutObservationWindow = 0
468 if "lockoutThreshold" in res[0]:
469 lockoutThreshold = res[0]["lockoutThreshold"][0]
473 self.addCleanup(self.ldb.modify_ldif, """
474 dn: """ + base_dn + """
476 replace: lockoutDuration
477 lockoutDuration: """ + str(lockoutDuration) + """
478 replace: lockoutObservationWindow
479 lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
480 replace: lockoutThreshold
481 lockoutThreshold: """ + str(lockoutThreshold) + """
485 m.dn = Dn(self.ldb, base_dn)
487 self.account_lockout_duration = 2
488 account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7))
490 m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
491 FLAG_MOD_REPLACE, "lockoutDuration")
493 account_lockout_threshold = 3
494 m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
495 FLAG_MOD_REPLACE, "lockoutThreshold")
497 self.lockout_observation_window = 2
498 lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7))
500 m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
501 FLAG_MOD_REPLACE, "lockOutObservationWindow")
505 # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
506 self.ldb.set_dsheuristics("000000001")
508 # Get the old "minPwdAge"
509 minPwdAge = self.ldb.get_minPwdAge()
511 # Reset the "minPwdAge" as it was before
512 self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
514 # Set it temporarely to "0"
515 self.ldb.set_minPwdAge("0")
517 self.base_dn = self.ldb.domain_dn()
519 self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
520 self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % self.host, self.lp, self.global_creds)
521 self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
522 self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
524 # (Re)adds the test user accounts
525 self.lockout1krb5_creds = self.insta_creds(self.template_creds,
526 username="lockout1krb5",
527 userpass="thatsAcomplPASS0",
528 kerberos_state=MUST_USE_KERBEROS)
529 self.lockout1krb5_ldb = self._readd_user(self.lockout1krb5_creds)
530 self.lockout2krb5_creds = self.insta_creds(self.template_creds,
531 username="lockout2krb5",
532 userpass="thatsAcomplPASS0",
533 kerberos_state=MUST_USE_KERBEROS)
534 self.lockout2krb5_ldb = self._readd_user(self.lockout2krb5_creds,
535 lockOutObservationWindow=self.lockout_observation_window)
536 self.lockout1ntlm_creds = self.insta_creds(self.template_creds,
537 username="lockout1ntlm",
538 userpass="thatsAcomplPASS0",
539 kerberos_state=DONT_USE_KERBEROS)
540 self.lockout1ntlm_ldb = self._readd_user(self.lockout1ntlm_creds)
541 self.lockout2ntlm_creds = self.insta_creds(self.template_creds,
542 username="lockout2ntlm",
543 userpass="thatsAcomplPASS0",
544 kerberos_state=DONT_USE_KERBEROS)
545 self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds,
546 lockOutObservationWindow=self.lockout_observation_window)
549 super(BasePasswordTestCase, self).tearDown()
551 def _test_login_lockout(self, creds):
552 username = creds.get_username()
553 userpass = creds.get_password()
554 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
556 use_kerberos = creds.get_kerberos_state()
557 # This unlocks by waiting for account_lockout_duration
558 if use_kerberos == MUST_USE_KERBEROS:
559 logoncount_relation = 'greater'
560 lastlogon_relation = 'greater'
561 print "Performs a lockout attempt against LDAP using Kerberos"
563 logoncount_relation = 'equal'
564 lastlogon_relation = 'equal'
565 print "Performs a lockout attempt against LDAP using NTLM"
567 # Change password on a connection as another user
568 res = self._check_account(userdn,
570 badPasswordTime=("greater", 0),
571 logonCount=(logoncount_relation, 0),
572 lastLogon=("greater", 0),
573 lastLogonTimestamp=("greater", 0),
575 dsdb.UF_NORMAL_ACCOUNT,
576 msDSUserAccountControlComputed=0)
577 badPasswordTime = int(res[0]["badPasswordTime"][0])
578 logonCount = int(res[0]["logonCount"][0])
579 lastLogon = int(res[0]["lastLogon"][0])
580 firstLogon = lastLogon
581 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
583 print lastLogonTimestamp
586 self.assertGreater(lastLogon, badPasswordTime)
587 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
589 # Open a second LDB connection with the user credentials. Use the
590 # command line credentials for informations like the domain, the realm
591 # and the workstation.
592 creds_lockout = self.insta_creds(creds)
595 creds_lockout.set_password("thatsAcomplPASS1x")
597 self.assertLoginFailure(self.host_url, creds_lockout, self.lp)
599 res = self._check_account(userdn,
601 badPasswordTime=("greater", badPasswordTime),
602 logonCount=logonCount,
604 lastLogonTimestamp=lastLogonTimestamp,
606 dsdb.UF_NORMAL_ACCOUNT,
607 msDSUserAccountControlComputed=0,
608 msg='lastlogontimestamp with wrong password')
609 badPasswordTime = int(res[0]["badPasswordTime"][0])
611 # Correct old password
612 creds_lockout.set_password(userpass)
614 ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
616 # lastLogonTimestamp should not change
617 # lastLogon increases if badPwdCount is non-zero (!)
618 res = self._check_account(userdn,
620 badPasswordTime=badPasswordTime,
621 logonCount=(logoncount_relation, logonCount),
622 lastLogon=('greater', lastLogon),
623 lastLogonTimestamp=lastLogonTimestamp,
625 dsdb.UF_NORMAL_ACCOUNT,
626 msDSUserAccountControlComputed=0,
627 msg='LLTimestamp is updated to lastlogon')
629 logonCount = int(res[0]["logonCount"][0])
630 lastLogon = int(res[0]["lastLogon"][0])
631 self.assertGreater(lastLogon, badPasswordTime)
632 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
635 creds_lockout.set_password("thatsAcomplPASS1x")
637 self.assertLoginFailure(self.host_url, creds_lockout, self.lp)
639 res = self._check_account(userdn,
641 badPasswordTime=("greater", badPasswordTime),
642 logonCount=logonCount,
644 lastLogonTimestamp=lastLogonTimestamp,
646 dsdb.UF_NORMAL_ACCOUNT,
647 msDSUserAccountControlComputed=0)
648 badPasswordTime = int(res[0]["badPasswordTime"][0])
651 creds_lockout.set_password("thatsAcomplPASS1x")
654 ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
657 except LdbError, (num, msg):
658 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
660 res = self._check_account(userdn,
662 badPasswordTime=("greater", badPasswordTime),
663 logonCount=logonCount,
665 lastLogonTimestamp=lastLogonTimestamp,
667 dsdb.UF_NORMAL_ACCOUNT,
668 msDSUserAccountControlComputed=0)
669 badPasswordTime = int(res[0]["badPasswordTime"][0])
671 print "two failed password change"
674 creds_lockout.set_password("thatsAcomplPASS1x")
677 ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
680 except LdbError, (num, msg):
681 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
683 res = self._check_account(userdn,
685 badPasswordTime=("greater", badPasswordTime),
686 logonCount=logonCount,
688 lastLogonTimestamp=lastLogonTimestamp,
689 lockoutTime=("greater", badPasswordTime),
691 dsdb.UF_NORMAL_ACCOUNT,
692 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
693 badPasswordTime = int(res[0]["badPasswordTime"][0])
694 lockoutTime = int(res[0]["lockoutTime"][0])
697 creds_lockout.set_password("thatsAcomplPASS1x")
699 ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
701 except LdbError, (num, msg):
702 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
704 res = self._check_account(userdn,
706 badPasswordTime=badPasswordTime,
707 logonCount=logonCount,
709 lastLogonTimestamp=lastLogonTimestamp,
710 lockoutTime=lockoutTime,
712 dsdb.UF_NORMAL_ACCOUNT,
713 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
716 creds_lockout.set_password("thatsAcomplPASS1x")
718 ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
720 except LdbError, (num, msg):
721 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
723 res = self._check_account(userdn,
725 badPasswordTime=badPasswordTime,
726 logonCount=logonCount,
728 lastLogonTimestamp=lastLogonTimestamp,
729 lockoutTime=lockoutTime,
731 dsdb.UF_NORMAL_ACCOUNT,
732 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
734 # The correct password, but we are locked out
735 creds_lockout.set_password(userpass)
737 ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
739 except LdbError, (num, msg):
740 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
742 res = self._check_account(userdn,
744 badPasswordTime=badPasswordTime,
745 logonCount=logonCount,
747 lastLogonTimestamp=lastLogonTimestamp,
748 lockoutTime=lockoutTime,
750 dsdb.UF_NORMAL_ACCOUNT,
751 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
753 # wait for the lockout to end
754 time.sleep(self.account_lockout_duration + 1)
755 print self.account_lockout_duration + 1
757 res = self._check_account(userdn,
758 badPwdCount=3, effective_bad_password_count=0,
759 badPasswordTime=badPasswordTime,
760 logonCount=logonCount,
761 lockoutTime=lockoutTime,
763 lastLogonTimestamp=lastLogonTimestamp,
765 dsdb.UF_NORMAL_ACCOUNT,
766 msDSUserAccountControlComputed=0)
768 # The correct password after letting the timeout expire
770 creds_lockout.set_password(userpass)
772 creds_lockout2 = self.insta_creds(creds_lockout)
774 ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout2, lp=self.lp)
777 res = self._check_account(userdn,
779 badPasswordTime=badPasswordTime,
780 logonCount=(logoncount_relation, logonCount),
781 lastLogon=(lastlogon_relation, lastLogon),
782 lastLogonTimestamp=lastLogonTimestamp,
785 dsdb.UF_NORMAL_ACCOUNT,
786 msDSUserAccountControlComputed=0,
787 msg="lastLogon is way off")
789 logonCount = int(res[0]["logonCount"][0])
790 lastLogon = int(res[0]["lastLogon"][0])
793 creds_lockout.set_password("thatsAcomplPASS1x")
795 ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
797 except LdbError, (num, msg):
798 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
800 res = self._check_account(userdn,
802 badPasswordTime=("greater", badPasswordTime),
803 logonCount=logonCount,
806 lastLogonTimestamp=lastLogonTimestamp,
808 dsdb.UF_NORMAL_ACCOUNT,
809 msDSUserAccountControlComputed=0)
810 badPasswordTime = int(res[0]["badPasswordTime"][0])
813 creds_lockout.set_password("thatsAcomplPASS1x")
815 ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
817 except LdbError, (num, msg):
818 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
820 res = self._check_account(userdn,
822 badPasswordTime=("greater", badPasswordTime),
823 logonCount=logonCount,
826 lastLogonTimestamp=lastLogonTimestamp,
828 dsdb.UF_NORMAL_ACCOUNT,
829 msDSUserAccountControlComputed=0)
830 badPasswordTime = int(res[0]["badPasswordTime"][0])
832 time.sleep(self.lockout_observation_window + 1)
834 res = self._check_account(userdn,
835 badPwdCount=2, effective_bad_password_count=0,
836 badPasswordTime=badPasswordTime,
837 logonCount=logonCount,
840 lastLogonTimestamp=lastLogonTimestamp,
842 dsdb.UF_NORMAL_ACCOUNT,
843 msDSUserAccountControlComputed=0)
846 creds_lockout.set_password("thatsAcomplPASS1x")
848 ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
850 except LdbError, (num, msg):
851 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
853 res = self._check_account(userdn,
855 badPasswordTime=("greater", badPasswordTime),
856 logonCount=logonCount,
859 lastLogonTimestamp=lastLogonTimestamp,
861 dsdb.UF_NORMAL_ACCOUNT,
862 msDSUserAccountControlComputed=0)
863 badPasswordTime = int(res[0]["badPasswordTime"][0])
865 # The correct password without letting the timeout expire
866 creds_lockout.set_password(userpass)
867 ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
869 res = self._check_account(userdn,
871 badPasswordTime=badPasswordTime,
872 logonCount=(logoncount_relation, logonCount),
874 lastLogon=("greater", lastLogon),
875 lastLogonTimestamp=lastLogonTimestamp,
877 dsdb.UF_NORMAL_ACCOUNT,
878 msDSUserAccountControlComputed=0)
880 def _test_multiple_logon(self, creds):
881 # Test the happy case in which a user logs on correctly, then
882 # logs on correctly again, so that the bad password and
883 # lockout times are both zero the second time. The lastlogon
884 # time should increase.
886 # Open a second LDB connection with the user credentials. Use the
887 # command line credentials for informations like the domain, the realm
888 # and the workstation.
889 username = creds.get_username()
890 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
892 use_kerberos = creds.get_kerberos_state()
893 if use_kerberos == MUST_USE_KERBEROS:
894 print "Testing multiple logon with Kerberos"
895 logoncount_relation = 'greater'
896 lastlogon_relation = 'greater'
898 print "Testing multiple logon with NTLM"
899 logoncount_relation = 'equal'
900 lastlogon_relation = 'equal'
902 SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp)
904 res = self._check_account(userdn,
906 badPasswordTime=("greater", 0),
907 logonCount=(logoncount_relation, 0),
908 lastLogon=("greater", 0),
909 lastLogonTimestamp=("greater", 0),
911 dsdb.UF_NORMAL_ACCOUNT,
912 msDSUserAccountControlComputed=0)
913 badPasswordTime = int(res[0]["badPasswordTime"][0])
914 logonCount = int(res[0]["logonCount"][0])
915 lastLogon = int(res[0]["lastLogon"][0])
916 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
917 firstLogon = lastLogon
918 print "last logon is %d" % lastLogon
919 self.assertGreater(lastLogon, badPasswordTime)
920 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
923 SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp)
925 res = self._check_account(userdn,
927 badPasswordTime=badPasswordTime,
928 logonCount=(logoncount_relation, logonCount),
929 lastLogon=(lastlogon_relation, lastLogon),
930 lastLogonTimestamp=lastLogonTimestamp,
932 dsdb.UF_NORMAL_ACCOUNT,
933 msDSUserAccountControlComputed=0,
934 msg=("second logon, firstlogon was %s" %
938 lastLogon = int(res[0]["lastLogon"][0])
942 SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp)
944 res = self._check_account(userdn,
946 badPasswordTime=badPasswordTime,
947 logonCount=(logoncount_relation, logonCount),
948 lastLogon=(lastlogon_relation, lastLogon),
949 lastLogonTimestamp=lastLogonTimestamp,
951 dsdb.UF_NORMAL_ACCOUNT,
952 msDSUserAccountControlComputed=0)