2 # -*- coding: utf-8 -*-
3 # This tests the password lockout behavior for AD implementations
5 # Copyright Matthias Dieter Wallnoefer 2010
6 # Copyright Andrew Bartlett 2013
7 # Copyright Stefan Metzmacher 2014
15 sys.path.insert(0, "bin/python")
18 from samba.tests.subunitrun import TestProgram, SubunitOptions
20 import samba.getopt as options
22 from samba.auth import system_session
23 from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS
24 from ldb import SCOPE_BASE, LdbError
25 from ldb import ERR_CONSTRAINT_VIOLATION
26 from ldb import ERR_INVALID_CREDENTIALS
27 from ldb import Message, MessageElement, Dn
28 from ldb import FLAG_MOD_REPLACE
29 from samba import gensec, dsdb
30 from samba.samdb import SamDB
32 from samba.tests import delete_force
33 from samba.dcerpc import security, samr
34 from samba.ndr import ndr_unpack
36 parser = optparse.OptionParser("password_lockout.py [options] <host>")
37 sambaopts = options.SambaOptions(parser)
38 parser.add_option_group(sambaopts)
39 parser.add_option_group(options.VersionOptions(parser))
40 # use command line creds if available
41 credopts = options.CredentialsOptions(parser)
42 parser.add_option_group(credopts)
43 subunitopts = SubunitOptions(parser)
44 parser.add_option_group(subunitopts)
45 opts, args = parser.parse_args()
53 lp = sambaopts.get_loadparm()
54 global_creds = credopts.get_credentials(lp)
56 # Force an encrypted connection
57 global_creds.set_gensec_features(global_creds.get_gensec_features() |
60 template_creds = Credentials()
61 template_creds.set_username("testuser")
62 template_creds.set_password("thatsAcomplPASS1")
63 template_creds.set_domain(global_creds.get_domain())
64 template_creds.set_realm(global_creds.get_realm())
65 template_creds.set_workstation(global_creds.get_workstation())
66 template_creds.set_gensec_features(global_creds.get_gensec_features())
67 template_creds.set_kerberos_state(global_creds.get_kerberos_state())
73 class PasswordTests(samba.tests.TestCase):
75 def _open_samr_user(self, res):
76 self.assertTrue("objectSid" in res[0])
78 (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split()
79 self.assertEquals(self.domain_sid, domain_sid)
81 return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid)
83 def _reset_samr(self, res):
85 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
86 samr_user = self._open_samr_user(res)
87 acb_info = self.samr.QueryUserInfo(samr_user, 16)
88 acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
89 self.samr.SetUserInfo(samr_user, 16, acb_info)
90 self.samr.Close(samr_user)
92 def _reset_ldap_lockoutTime(self, res):
93 self.ldb.modify_ldif("""
94 dn: """ + str(res[0].dn) + """
100 def _reset_ldap_userAccountControl(self, res):
101 self.assertTrue("userAccountControl" in res[0])
102 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
104 uac = int(res[0]["userAccountControl"][0])
105 uacc = int(res[0]["msDS-User-Account-Control-Computed"][0])
108 uac = uac & ~dsdb.UF_LOCKOUT
110 self.ldb.modify_ldif("""
111 dn: """ + str(res[0].dn) + """
113 replace: userAccountControl
114 userAccountControl: %d
117 def _reset_by_method(self, res, method):
118 if method is "ldap_userAccountControl":
119 self._reset_ldap_userAccountControl(res)
120 elif method is "ldap_lockoutTime":
121 self._reset_ldap_lockoutTime(res)
122 elif method is "samr":
123 self._reset_samr(res)
125 self.assertTrue(False, msg="Invalid reset method[%s]" % method)
127 def _check_attribute(self, res, name, value):
129 self.assertTrue(name not in res[0],
130 msg="attr[%s]=%r on dn[%s]" %
131 (name, res[0], res[0].dn))
134 if isinstance(value, tuple):
135 (mode, value) = value
143 self.assertFalse(name in res[0],
144 msg="attr[%s] not missing on dn[%s]" %
148 self.assertTrue(name in res[0],
149 msg="attr[%s] missing on dn[%s]" %
151 self.assertTrue(len(res[0][name]) == 1,
152 msg="attr[%s]=%r on dn[%s]" %
153 (name, res[0][name], res[0].dn))
156 print "%s = '%s'" % (name, res[0][name][0])
158 if mode == "present":
162 v = int(res[0][name][0])
164 msg = ("attr[%s]=[%s] != [%s] on dn[%s]\n"
165 "(diff %d; actual value is %s than expected)" %
166 (name, v, value, res[0].dn, v - value,
167 ('less' if v < value else 'greater')))
169 self.assertTrue(v == value, msg)
172 if mode == "greater":
173 v = int(res[0][name][0])
174 self.assertTrue(v > int(value),
175 msg="attr[%s]=[%s] <= [%s] on dn[%s] (diff %d)" %
176 (name, v, int(value), res[0].dn, v - int(value)))
179 v = int(res[0][name][0])
180 self.assertTrue(v < int(value),
181 msg="attr[%s]=[%s] >= [%s] on dn[%s] (diff %d)" %
182 (name, v, int(value), res[0].dn, v - int(value)))
184 self.assertEqual(mode, not mode, "Invalid Mode[%s]" % mode)
186 def _check_account(self, dn,
188 badPasswordTime=None,
191 lastLogonTimestamp=None,
193 userAccountControl=None,
194 msDSUserAccountControlComputed=None,
195 effective_bad_password_count=None,
199 print "\033[01;32m %s \033[00m\n" % msg
205 "lastLogonTimestamp",
208 "userAccountControl",
209 "msDS-User-Account-Control-Computed"
212 # in order to prevent some time resolution problems we sleep for
216 res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
217 self.assertTrue(len(res) == 1)
218 self._check_attribute(res, "badPwdCount", badPwdCount)
219 self._check_attribute(res, "badPasswordTime", badPasswordTime)
220 self._check_attribute(res, "logonCount", logonCount)
221 self._check_attribute(res, "lastLogon", lastLogon)
222 self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp)
223 self._check_attribute(res, "lockoutTime", lockoutTime)
224 self._check_attribute(res, "userAccountControl", userAccountControl)
225 self._check_attribute(res, "msDS-User-Account-Control-Computed",
226 msDSUserAccountControlComputed)
228 lastLogon = int(res[0]["lastLogon"][0])
229 logonCount = int(res[0]["logonCount"][0])
231 samr_user = self._open_samr_user(res)
232 uinfo3 = self.samr.QueryUserInfo(samr_user, 3)
233 uinfo5 = self.samr.QueryUserInfo(samr_user, 5)
234 uinfo16 = self.samr.QueryUserInfo(samr_user, 16)
235 uinfo21 = self.samr.QueryUserInfo(samr_user, 21)
236 self.samr.Close(samr_user)
238 expected_acb_info = 0
239 if userAccountControl & dsdb.UF_NORMAL_ACCOUNT:
240 expected_acb_info |= samr.ACB_NORMAL
241 if userAccountControl & dsdb.UF_ACCOUNTDISABLE:
242 expected_acb_info |= samr.ACB_DISABLED
243 if userAccountControl & dsdb.UF_PASSWD_NOTREQD:
244 expected_acb_info |= samr.ACB_PWNOTREQ
245 if msDSUserAccountControlComputed & dsdb.UF_LOCKOUT:
246 expected_acb_info |= samr.ACB_AUTOLOCK
247 if msDSUserAccountControlComputed & dsdb.UF_PASSWORD_EXPIRED:
248 expected_acb_info |= samr.ACB_PW_EXPIRED
250 expected_bad_password_count = 0
251 if badPwdCount is not None:
252 expected_bad_password_count = badPwdCount
253 if effective_bad_password_count is None:
254 effective_bad_password_count = expected_bad_password_count
256 self.assertEquals(uinfo3.acct_flags, expected_acb_info)
257 self.assertEquals(uinfo3.bad_password_count, expected_bad_password_count)
258 self.assertEquals(uinfo3.last_logon, lastLogon)
259 self.assertEquals(uinfo3.logon_count, logonCount)
261 self.assertEquals(uinfo5.acct_flags, expected_acb_info)
262 self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count)
263 self.assertEquals(uinfo5.last_logon, lastLogon)
264 self.assertEquals(uinfo5.logon_count, logonCount)
266 self.assertEquals(uinfo16.acct_flags, expected_acb_info)
268 self.assertEquals(uinfo21.acct_flags, expected_acb_info)
269 self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count)
270 self.assertEquals(uinfo21.last_logon, lastLogon)
271 self.assertEquals(uinfo21.logon_count, logonCount)
273 # check LDAP again and make sure the samr.QueryUserInfo
274 # doesn't have any impact.
275 res2 = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
276 self.assertEquals(res[0], res2[0])
278 # in order to prevent some time resolution problems we sleep for
283 def _readd_user(self, creds, lockOutObservationWindow=0):
284 username = creds.get_username()
285 userpass = creds.get_password()
286 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
288 use_kerberos = creds.get_kerberos_state()
289 if use_kerberos == MUST_USE_KERBEROS:
290 logoncount_relation = 'greater'
291 lastlogon_relation = 'greater'
293 logoncount_relation = 'equal'
294 if lockOutObservationWindow == 0:
295 lastlogon_relation = 'greater'
297 lastlogon_relation = 'equal'
299 delete_force(self.ldb, userdn)
302 "objectclass": "user",
303 "sAMAccountName": username})
305 self.addCleanup(delete_force, self.ldb, userdn)
307 res = self._check_account(userdn,
312 lastLogonTimestamp=('absent', None),
314 dsdb.UF_NORMAL_ACCOUNT |
315 dsdb.UF_ACCOUNTDISABLE |
316 dsdb.UF_PASSWD_NOTREQD,
317 msDSUserAccountControlComputed=
318 dsdb.UF_PASSWORD_EXPIRED)
320 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
321 # It doesn't create "lockoutTime" = 0.
322 self._reset_samr(res)
324 res = self._check_account(userdn,
329 lastLogonTimestamp=('absent', None),
331 dsdb.UF_NORMAL_ACCOUNT |
332 dsdb.UF_ACCOUNTDISABLE |
333 dsdb.UF_PASSWD_NOTREQD,
334 msDSUserAccountControlComputed=
335 dsdb.UF_PASSWORD_EXPIRED)
337 # Tests a password change when we don't have any password yet with a
340 self.ldb.modify_ldif("""
341 dn: """ + userdn + """
344 userPassword: noPassword
346 userPassword: thatsAcomplPASS2
349 except LdbError, (num, msg):
350 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
351 # Windows (2008 at least) seems to have some small bug here: it
352 # returns "0000056A" on longer (always wrong) previous passwords.
353 self.assertTrue('00000056' in msg, msg)
355 res = self._check_account(userdn,
357 badPasswordTime=("greater", 0),
360 lastLogonTimestamp=('absent', None),
362 dsdb.UF_NORMAL_ACCOUNT |
363 dsdb.UF_ACCOUNTDISABLE |
364 dsdb.UF_PASSWD_NOTREQD,
365 msDSUserAccountControlComputed=
366 dsdb.UF_PASSWORD_EXPIRED)
367 badPwdCount = int(res[0]["badPwdCount"][0])
368 badPasswordTime = int(res[0]["badPasswordTime"][0])
370 # Sets the initial user password with a "special" password change
371 # I think that this internally is a password set operation and it can
372 # only be performed by someone which has password set privileges on the
373 # account (at least in s4 we do handle it like that).
374 self.ldb.modify_ldif("""
375 dn: """ + userdn + """
379 userPassword: """ + userpass + """
382 res = self._check_account(userdn,
383 badPwdCount=badPwdCount,
384 badPasswordTime=badPasswordTime,
387 lastLogonTimestamp=('absent', None),
389 dsdb.UF_NORMAL_ACCOUNT |
390 dsdb.UF_ACCOUNTDISABLE |
391 dsdb.UF_PASSWD_NOTREQD,
392 msDSUserAccountControlComputed=0)
394 # Enables the user account
395 self.ldb.enable_account("(sAMAccountName=%s)" % username)
397 res = self._check_account(userdn,
398 badPwdCount=badPwdCount,
399 badPasswordTime=badPasswordTime,
402 lastLogonTimestamp=('absent', None),
404 dsdb.UF_NORMAL_ACCOUNT,
405 msDSUserAccountControlComputed=0)
406 if lockOutObservationWindow != 0:
407 time.sleep(lockOutObservationWindow + 1)
408 effective_bad_password_count = 0
410 effective_bad_password_count = badPwdCount
412 res = self._check_account(userdn,
413 badPwdCount=badPwdCount,
414 effective_bad_password_count=effective_bad_password_count,
415 badPasswordTime=badPasswordTime,
418 lastLogonTimestamp=('absent', None),
420 dsdb.UF_NORMAL_ACCOUNT,
421 msDSUserAccountControlComputed=0)
423 ldb = SamDB(url=host_url, credentials=creds, lp=lp)
425 if lockOutObservationWindow == 0:
427 effective_bad_password_count = 0
428 if use_kerberos == MUST_USE_KERBEROS:
430 effective_bad_password_count = 0
432 res = self._check_account(userdn,
433 badPwdCount=badPwdCount,
434 effective_bad_password_count=effective_bad_password_count,
435 badPasswordTime=badPasswordTime,
436 logonCount=(logoncount_relation, 0),
437 lastLogon=(lastlogon_relation, 0),
438 lastLogonTimestamp=('greater', badPasswordTime),
440 dsdb.UF_NORMAL_ACCOUNT,
441 msDSUserAccountControlComputed=0)
443 logonCount = int(res[0]["logonCount"][0])
444 lastLogon = int(res[0]["lastLogon"][0])
445 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
446 if lastlogon_relation == 'greater':
447 self.assertGreater(lastLogon, badPasswordTime)
448 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
450 res = self._check_account(userdn,
451 badPwdCount=badPwdCount,
452 effective_bad_password_count=effective_bad_password_count,
453 badPasswordTime=badPasswordTime,
454 logonCount=logonCount,
456 lastLogonTimestamp=lastLogonTimestamp,
458 dsdb.UF_NORMAL_ACCOUNT,
459 msDSUserAccountControlComputed=0)
462 def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS):
464 ldb = SamDB(url=url, credentials=creds, lp=lp)
465 self.fail("Login unexpectedly succeeded")
466 except LdbError, (num, msg):
467 if errno is not None:
468 self.assertEquals(num, errno, ("Login failed in the wrong way"
469 "(got err %d, expected %d)" %
473 super(PasswordTests, self).setUp()
475 self.ldb = SamDB(url=host_url, session_info=system_session(lp),
476 credentials=global_creds, lp=lp)
478 # Gets back the basedn
479 base_dn = self.ldb.domain_dn()
481 # Gets back the configuration basedn
482 configuration_dn = self.ldb.get_config_basedn().get_linearized()
484 # Get the old "dSHeuristics" if it was set
485 dsheuristics = self.ldb.get_dsheuristics()
487 # Reset the "dSHeuristics" as they were before
488 self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
490 res = self.ldb.search(base_dn,
491 scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
493 if "lockoutDuration" in res[0]:
494 lockoutDuration = res[0]["lockoutDuration"][0]
498 if "lockoutObservationWindow" in res[0]:
499 lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
501 lockoutObservationWindow = 0
503 if "lockoutThreshold" in res[0]:
504 lockoutThreshold = res[0]["lockoutThreshold"][0]
508 self.addCleanup(self.ldb.modify_ldif, """
509 dn: """ + base_dn + """
511 replace: lockoutDuration
512 lockoutDuration: """ + str(lockoutDuration) + """
513 replace: lockoutObservationWindow
514 lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
515 replace: lockoutThreshold
516 lockoutThreshold: """ + str(lockoutThreshold) + """
520 m.dn = Dn(self.ldb, base_dn)
522 self.account_lockout_duration = 2
523 account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7))
525 m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
526 FLAG_MOD_REPLACE, "lockoutDuration")
528 account_lockout_threshold = 3
529 m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
530 FLAG_MOD_REPLACE, "lockoutThreshold")
532 self.lockout_observation_window = 2
533 lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7))
535 m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
536 FLAG_MOD_REPLACE, "lockOutObservationWindow")
540 # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
541 self.ldb.set_dsheuristics("000000001")
543 # Get the old "minPwdAge"
544 minPwdAge = self.ldb.get_minPwdAge()
546 # Reset the "minPwdAge" as it was before
547 self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
549 # Set it temporarely to "0"
550 self.ldb.set_minPwdAge("0")
552 self.base_dn = self.ldb.domain_dn()
554 self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
555 self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % host, lp, global_creds)
556 self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
557 self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
559 # (Re)adds the test user accounts
560 self.lockout1krb5_creds = self.insta_creds(template_creds,
561 username="lockout1krb5",
562 userpass="thatsAcomplPASS0",
563 kerberos_state=MUST_USE_KERBEROS)
564 self.lockout1krb5_ldb = self._readd_user(self.lockout1krb5_creds)
565 self.lockout2krb5_creds = self.insta_creds(template_creds,
566 username="lockout2krb5",
567 userpass="thatsAcomplPASS0",
568 kerberos_state=MUST_USE_KERBEROS)
569 self.lockout2krb5_ldb = self._readd_user(self.lockout2krb5_creds,
570 lockOutObservationWindow=self.lockout_observation_window)
571 self.lockout1ntlm_creds = self.insta_creds(template_creds,
572 username="lockout1ntlm",
573 userpass="thatsAcomplPASS0",
574 kerberos_state=DONT_USE_KERBEROS)
575 self.lockout1ntlm_ldb = self._readd_user(self.lockout1ntlm_creds)
576 self.lockout2ntlm_creds = self.insta_creds(template_creds,
577 username="lockout2ntlm",
578 userpass="thatsAcomplPASS0",
579 kerberos_state=DONT_USE_KERBEROS)
580 self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds,
581 lockOutObservationWindow=self.lockout_observation_window)
583 def _test_userPassword_lockout_with_clear_change(self, creds, other_ldb, method,
584 initial_lastlogon_relation=None):
585 # Notice: This works only against Windows if "dSHeuristics" has been set
587 username = creds.get_username()
588 userpass = creds.get_password()
589 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
591 use_kerberos = creds.get_kerberos_state()
592 if use_kerberos == MUST_USE_KERBEROS:
593 logoncount_relation = 'greater'
594 lastlogon_relation = 'greater'
595 print "Performs a password cleartext change operation on 'userPassword' using Kerberos"
597 logoncount_relation = 'equal'
598 lastlogon_relation = 'equal'
599 print "Performs a password cleartext change operation on 'userPassword' using NTLMSSP"
601 if initial_lastlogon_relation is not None:
602 lastlogon_relation = initial_lastlogon_relation
604 res = self._check_account(userdn,
606 badPasswordTime=("greater", 0),
607 logonCount=(logoncount_relation, 0),
608 lastLogon=(lastlogon_relation, 0),
609 lastLogonTimestamp=('greater', 0),
611 dsdb.UF_NORMAL_ACCOUNT,
612 msDSUserAccountControlComputed=0)
613 badPasswordTime = int(res[0]["badPasswordTime"][0])
614 logonCount = int(res[0]["logonCount"][0])
615 lastLogon = int(res[0]["lastLogon"][0])
616 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
617 if lastlogon_relation == 'greater':
618 self.assertGreater(lastLogon, badPasswordTime)
619 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
621 # Change password on a connection as another user
625 other_ldb.modify_ldif("""
626 dn: """ + userdn + """
629 userPassword: thatsAcomplPASS1x
631 userPassword: thatsAcomplPASS2
634 except LdbError, (num, msg):
635 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
636 self.assertTrue('00000056' in msg, msg)
638 res = self._check_account(userdn,
640 badPasswordTime=("greater", badPasswordTime),
641 logonCount=logonCount,
643 lastLogonTimestamp=lastLogonTimestamp,
645 dsdb.UF_NORMAL_ACCOUNT,
646 msDSUserAccountControlComputed=0)
647 badPasswordTime = int(res[0]["badPasswordTime"][0])
649 # Correct old password
650 other_ldb.modify_ldif("""
651 dn: """ + userdn + """
654 userPassword: """ + userpass + """
656 userPassword: thatsAcomplPASS2
659 res = self._check_account(userdn,
661 badPasswordTime=badPasswordTime,
662 logonCount=logonCount,
664 lastLogonTimestamp=lastLogonTimestamp,
666 dsdb.UF_NORMAL_ACCOUNT,
667 msDSUserAccountControlComputed=0)
671 other_ldb.modify_ldif("""
672 dn: """ + userdn + """
675 userPassword: thatsAcomplPASS1x
677 userPassword: thatsAcomplPASS2
680 except LdbError, (num, msg):
681 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
682 self.assertTrue('00000056' in msg, msg)
684 res = self._check_account(userdn,
686 badPasswordTime=("greater", badPasswordTime),
687 logonCount=logonCount,
689 lastLogonTimestamp=lastLogonTimestamp,
691 dsdb.UF_NORMAL_ACCOUNT,
692 msDSUserAccountControlComputed=0)
693 badPasswordTime = int(res[0]["badPasswordTime"][0])
695 print "two failed password change"
699 other_ldb.modify_ldif("""
700 dn: """ + userdn + """
703 userPassword: thatsAcomplPASS1x
705 userPassword: thatsAcomplPASS2
708 except LdbError, (num, msg):
709 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
710 self.assertTrue('00000056' in msg, msg)
712 res = self._check_account(userdn,
714 badPasswordTime=("greater", badPasswordTime),
715 logonCount=logonCount,
717 lastLogonTimestamp=lastLogonTimestamp,
718 lockoutTime=("greater", badPasswordTime),
720 dsdb.UF_NORMAL_ACCOUNT,
721 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
722 badPasswordTime = int(res[0]["badPasswordTime"][0])
723 lockoutTime = int(res[0]["lockoutTime"][0])
727 other_ldb.modify_ldif("""
728 dn: """ + userdn + """
731 userPassword: thatsAcomplPASS1x
733 userPassword: thatsAcomplPASS2
736 except LdbError, (num, msg):
737 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
738 self.assertTrue('00000775' in msg, msg)
740 res = self._check_account(userdn,
742 badPasswordTime=badPasswordTime,
743 logonCount=logonCount,
745 lastLogonTimestamp=lastLogonTimestamp,
746 lockoutTime=lockoutTime,
748 dsdb.UF_NORMAL_ACCOUNT,
749 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
753 other_ldb.modify_ldif("""
754 dn: """ + userdn + """
757 userPassword: thatsAcomplPASS1x
759 userPassword: thatsAcomplPASS2
762 except LdbError, (num, msg):
763 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
764 self.assertTrue('00000775' in msg, msg)
766 res = self._check_account(userdn,
768 badPasswordTime=badPasswordTime,
769 logonCount=logonCount,
770 lockoutTime=lockoutTime,
772 lastLogonTimestamp=lastLogonTimestamp,
774 dsdb.UF_NORMAL_ACCOUNT,
775 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
778 # Correct old password
779 other_ldb.modify_ldif("""
780 dn: """ + userdn + """
783 userPassword: thatsAcomplPASS2
785 userPassword: thatsAcomplPASS2x
788 except LdbError, (num, msg):
789 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
790 self.assertTrue('00000775' in msg, msg)
792 res = self._check_account(userdn,
794 badPasswordTime=badPasswordTime,
795 logonCount=logonCount,
797 lastLogonTimestamp=lastLogonTimestamp,
798 lockoutTime=lockoutTime,
800 dsdb.UF_NORMAL_ACCOUNT,
801 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
803 # Now reset the password, which does NOT change the lockout!
804 self.ldb.modify_ldif("""
805 dn: """ + userdn + """
807 replace: userPassword
808 userPassword: thatsAcomplPASS2
811 res = self._check_account(userdn,
813 badPasswordTime=badPasswordTime,
814 logonCount=logonCount,
816 lastLogonTimestamp=lastLogonTimestamp,
817 lockoutTime=lockoutTime,
819 dsdb.UF_NORMAL_ACCOUNT,
820 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
823 # Correct old password
824 other_ldb.modify_ldif("""
825 dn: """ + userdn + """
828 userPassword: thatsAcomplPASS2
830 userPassword: thatsAcomplPASS2x
833 except LdbError, (num, msg):
834 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
835 self.assertTrue('00000775' in msg, msg)
837 res = self._check_account(userdn,
839 badPasswordTime=badPasswordTime,
840 logonCount=logonCount,
842 lastLogonTimestamp=lastLogonTimestamp,
843 lockoutTime=lockoutTime,
845 dsdb.UF_NORMAL_ACCOUNT,
846 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
849 m.dn = Dn(self.ldb, userdn)
850 m["userAccountControl"] = MessageElement(
851 str(dsdb.UF_LOCKOUT),
852 FLAG_MOD_REPLACE, "userAccountControl")
856 # This shows that setting the UF_LOCKOUT flag alone makes no difference
857 res = self._check_account(userdn,
859 badPasswordTime=badPasswordTime,
860 logonCount=logonCount,
862 lastLogonTimestamp=lastLogonTimestamp,
863 lockoutTime=lockoutTime,
865 dsdb.UF_NORMAL_ACCOUNT,
866 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
868 # This shows that setting the UF_LOCKOUT flag makes no difference
870 # Correct old password
871 other_ldb.modify_ldif("""
872 dn: """ + userdn + """
875 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
877 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
880 except LdbError, (num, msg):
881 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
882 self.assertTrue('00000775' in msg, msg)
884 res = self._check_account(userdn,
886 badPasswordTime=badPasswordTime,
887 logonCount=logonCount,
888 lockoutTime=lockoutTime,
890 lastLogonTimestamp=lastLogonTimestamp,
892 dsdb.UF_NORMAL_ACCOUNT,
893 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
895 self._reset_by_method(res, method)
897 # Here bad password counts are reset without logon success.
898 res = self._check_account(userdn,
900 badPasswordTime=badPasswordTime,
901 logonCount=logonCount,
904 lastLogonTimestamp=lastLogonTimestamp,
906 dsdb.UF_NORMAL_ACCOUNT,
907 msDSUserAccountControlComputed=0)
909 # The correct password after doing the unlock
911 other_ldb.modify_ldif("""
912 dn: """ + userdn + """
915 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
917 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
919 userpass = "thatsAcomplPASS2x"
920 creds.set_password(userpass)
922 res = self._check_account(userdn,
924 badPasswordTime=badPasswordTime,
925 logonCount=logonCount,
928 lastLogonTimestamp=lastLogonTimestamp,
930 dsdb.UF_NORMAL_ACCOUNT,
931 msDSUserAccountControlComputed=0)
935 other_ldb.modify_ldif("""
936 dn: """ + userdn + """
939 userPassword: thatsAcomplPASS1xyz
941 userPassword: thatsAcomplPASS2XYZ
944 except LdbError, (num, msg):
945 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
946 self.assertTrue('00000056' in msg, msg)
948 res = self._check_account(userdn,
950 badPasswordTime=("greater", badPasswordTime),
951 logonCount=logonCount,
954 lastLogonTimestamp=lastLogonTimestamp,
956 dsdb.UF_NORMAL_ACCOUNT,
957 msDSUserAccountControlComputed=0)
958 badPasswordTime = int(res[0]["badPasswordTime"][0])
962 other_ldb.modify_ldif("""
963 dn: """ + userdn + """
966 userPassword: thatsAcomplPASS1xyz
968 userPassword: thatsAcomplPASS2XYZ
971 except LdbError, (num, msg):
972 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
973 self.assertTrue('00000056' in msg, msg)
975 res = self._check_account(userdn,
977 badPasswordTime=("greater", badPasswordTime),
978 logonCount=logonCount,
981 lastLogonTimestamp=lastLogonTimestamp,
983 dsdb.UF_NORMAL_ACCOUNT,
984 msDSUserAccountControlComputed=0)
985 badPasswordTime = int(res[0]["badPasswordTime"][0])
987 self._reset_ldap_lockoutTime(res)
989 res = self._check_account(userdn,
991 badPasswordTime=badPasswordTime,
992 logonCount=logonCount,
994 lastLogonTimestamp=lastLogonTimestamp,
997 dsdb.UF_NORMAL_ACCOUNT,
998 msDSUserAccountControlComputed=0)
1000 def test_userPassword_lockout_with_clear_change_krb5_ldap_userAccountControl(self):
1001 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
1002 self.lockout2krb5_ldb,
1003 "ldap_userAccountControl")
1005 def test_userPassword_lockout_with_clear_change_krb5_ldap_lockoutTime(self):
1006 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
1007 self.lockout2krb5_ldb,
1010 def test_userPassword_lockout_with_clear_change_krb5_samr(self):
1011 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
1012 self.lockout2krb5_ldb,
1015 def test_userPassword_lockout_with_clear_change_ntlm_ldap_userAccountControl(self):
1016 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
1017 self.lockout2ntlm_ldb,
1018 "ldap_userAccountControl",
1019 initial_lastlogon_relation='greater')
1021 def test_userPassword_lockout_with_clear_change_ntlm_ldap_lockoutTime(self):
1022 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
1023 self.lockout2ntlm_ldb,
1025 initial_lastlogon_relation='greater')
1027 def test_userPassword_lockout_with_clear_change_ntlm_samr(self):
1028 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
1029 self.lockout2ntlm_ldb,
1031 initial_lastlogon_relation='greater')
1033 def _test_unicodePwd_lockout_with_clear_change(self, creds, other_ldb,
1034 initial_logoncount_relation=None):
1035 print "Performs a password cleartext change operation on 'unicodePwd'"
1036 username = creds.get_username()
1037 userpass = creds.get_password()
1038 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
1039 if initial_logoncount_relation is not None:
1040 logoncount_relation = initial_logoncount_relation
1042 logoncount_relation = "greater"
1044 res = self._check_account(userdn,
1046 badPasswordTime=("greater", 0),
1047 logonCount=(logoncount_relation, 0),
1048 lastLogon=("greater", 0),
1049 lastLogonTimestamp=("greater", 0),
1051 dsdb.UF_NORMAL_ACCOUNT,
1052 msDSUserAccountControlComputed=0)
1053 badPasswordTime = int(res[0]["badPasswordTime"][0])
1054 logonCount = int(res[0]["logonCount"][0])
1055 lastLogon = int(res[0]["lastLogon"][0])
1056 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1057 self.assertGreater(lastLogonTimestamp, badPasswordTime)
1058 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
1060 # Change password on a connection as another user
1062 # Wrong old password
1064 other_ldb.modify_ldif("""
1065 dn: """ + userdn + """
1068 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1070 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1073 except LdbError, (num, msg):
1074 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1075 self.assertTrue('00000056' in msg, msg)
1077 res = self._check_account(userdn,
1079 badPasswordTime=("greater", badPasswordTime),
1080 logonCount=logonCount,
1081 lastLogon=lastLogon,
1082 lastLogonTimestamp=lastLogonTimestamp,
1084 dsdb.UF_NORMAL_ACCOUNT,
1085 msDSUserAccountControlComputed=0)
1086 badPasswordTime = int(res[0]["badPasswordTime"][0])
1088 # Correct old password
1089 old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
1090 invalid_utf16 = "\"thatsAcomplPASSX\"".encode('utf-16-le')
1091 userpass = "thatsAcomplPASS2"
1092 creds.set_password(userpass)
1093 new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
1095 other_ldb.modify_ldif("""
1096 dn: """ + userdn + """
1099 unicodePwd:: """ + base64.b64encode(old_utf16) + """
1101 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1104 res = self._check_account(userdn,
1106 badPasswordTime=badPasswordTime,
1107 logonCount=logonCount,
1108 lastLogon=lastLogon,
1109 lastLogonTimestamp=lastLogonTimestamp,
1111 dsdb.UF_NORMAL_ACCOUNT,
1112 msDSUserAccountControlComputed=0)
1114 # Wrong old password
1116 other_ldb.modify_ldif("""
1117 dn: """ + userdn + """
1120 unicodePwd:: """ + base64.b64encode(old_utf16) + """
1122 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1125 except LdbError, (num, msg):
1126 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1127 self.assertTrue('00000056' in msg, msg)
1129 res = self._check_account(userdn,
1131 badPasswordTime=("greater", badPasswordTime),
1132 logonCount=logonCount,
1133 lastLogon=lastLogon,
1134 lastLogonTimestamp=lastLogonTimestamp,
1136 dsdb.UF_NORMAL_ACCOUNT,
1137 msDSUserAccountControlComputed=0)
1138 badPasswordTime = int(res[0]["badPasswordTime"][0])
1140 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1141 # It doesn't create "lockoutTime" = 0 and doesn't
1142 # reset "badPwdCount" = 0.
1143 self._reset_samr(res)
1145 res = self._check_account(userdn,
1147 badPasswordTime=badPasswordTime,
1148 logonCount=logonCount,
1149 lastLogon=lastLogon,
1150 lastLogonTimestamp=lastLogonTimestamp,
1152 dsdb.UF_NORMAL_ACCOUNT,
1153 msDSUserAccountControlComputed=0)
1155 print "two failed password change"
1157 # Wrong old password
1159 other_ldb.modify_ldif("""
1160 dn: """ + userdn + """
1163 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1165 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1168 except LdbError, (num, msg):
1169 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1170 self.assertTrue('00000056' in msg, msg)
1172 # this is strange, why do we have lockoutTime=badPasswordTime here?
1173 res = self._check_account(userdn,
1175 badPasswordTime=("greater", badPasswordTime),
1176 logonCount=logonCount,
1177 lastLogon=lastLogon,
1178 lastLogonTimestamp=lastLogonTimestamp,
1179 lockoutTime=("greater", badPasswordTime),
1181 dsdb.UF_NORMAL_ACCOUNT,
1182 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1183 badPasswordTime = int(res[0]["badPasswordTime"][0])
1184 lockoutTime = int(res[0]["lockoutTime"][0])
1186 # Wrong old password
1188 other_ldb.modify_ldif("""
1189 dn: """ + userdn + """
1192 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1194 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1197 except LdbError, (num, msg):
1198 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1199 self.assertTrue('00000775' in msg, msg)
1201 res = self._check_account(userdn,
1203 badPasswordTime=badPasswordTime,
1204 logonCount=logonCount,
1205 lastLogon=lastLogon,
1206 lastLogonTimestamp=lastLogonTimestamp,
1207 lockoutTime=lockoutTime,
1209 dsdb.UF_NORMAL_ACCOUNT,
1210 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1212 # Wrong old password
1214 other_ldb.modify_ldif("""
1215 dn: """ + userdn + """
1218 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1220 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1223 except LdbError, (num, msg):
1224 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1225 self.assertTrue('00000775' in msg, msg)
1227 res = self._check_account(userdn,
1229 badPasswordTime=badPasswordTime,
1230 logonCount=logonCount,
1231 lastLogon=lastLogon,
1232 lastLogonTimestamp=lastLogonTimestamp,
1233 lockoutTime=lockoutTime,
1235 dsdb.UF_NORMAL_ACCOUNT,
1236 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1239 # Correct old password
1240 other_ldb.modify_ldif("""
1241 dn: """ + userdn + """
1244 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1246 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1249 except LdbError, (num, msg):
1250 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1251 self.assertTrue('00000775' in msg, msg)
1253 res = self._check_account(userdn,
1255 badPasswordTime=badPasswordTime,
1256 logonCount=logonCount,
1257 lastLogon=lastLogon,
1258 lastLogonTimestamp=lastLogonTimestamp,
1259 lockoutTime=lockoutTime,
1261 dsdb.UF_NORMAL_ACCOUNT,
1262 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1264 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
1265 self._reset_samr(res);
1267 res = self._check_account(userdn,
1269 badPasswordTime=badPasswordTime,
1270 logonCount=logonCount,
1271 lastLogon=lastLogon,
1272 lastLogonTimestamp=lastLogonTimestamp,
1275 dsdb.UF_NORMAL_ACCOUNT,
1276 msDSUserAccountControlComputed=0)
1278 # Correct old password
1279 old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
1280 invalid_utf16 = "\"thatsAcomplPASSiX\"".encode('utf-16-le')
1281 userpass = "thatsAcomplPASS2x"
1282 creds.set_password(userpass)
1283 new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
1285 other_ldb.modify_ldif("""
1286 dn: """ + userdn + """
1289 unicodePwd:: """ + base64.b64encode(old_utf16) + """
1291 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1294 res = self._check_account(userdn,
1296 badPasswordTime=badPasswordTime,
1297 logonCount=logonCount,
1298 lastLogon=lastLogon,
1299 lastLogonTimestamp=lastLogonTimestamp,
1302 dsdb.UF_NORMAL_ACCOUNT,
1303 msDSUserAccountControlComputed=0)
1305 # Wrong old password
1307 other_ldb.modify_ldif("""
1308 dn: """ + userdn + """
1311 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1313 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1316 except LdbError, (num, msg):
1317 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1318 self.assertTrue('00000056' in msg, msg)
1320 res = self._check_account(userdn,
1322 badPasswordTime=("greater", badPasswordTime),
1323 logonCount=logonCount,
1324 lastLogon=lastLogon,
1325 lastLogonTimestamp=lastLogonTimestamp,
1328 dsdb.UF_NORMAL_ACCOUNT,
1329 msDSUserAccountControlComputed=0)
1330 badPasswordTime = int(res[0]["badPasswordTime"][0])
1332 # Wrong old password
1334 other_ldb.modify_ldif("""
1335 dn: """ + userdn + """
1338 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1340 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1343 except LdbError, (num, msg):
1344 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1345 self.assertTrue('00000056' in msg, msg)
1347 res = self._check_account(userdn,
1349 badPasswordTime=("greater", badPasswordTime),
1350 logonCount=logonCount,
1351 lastLogon=lastLogon,
1352 lastLogonTimestamp=lastLogonTimestamp,
1355 dsdb.UF_NORMAL_ACCOUNT,
1356 msDSUserAccountControlComputed=0)
1357 badPasswordTime = int(res[0]["badPasswordTime"][0])
1359 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1360 # It doesn't reset "badPwdCount" = 0.
1361 self._reset_samr(res)
1363 res = self._check_account(userdn,
1365 badPasswordTime=badPasswordTime,
1366 logonCount=logonCount,
1367 lastLogon=lastLogon,
1368 lastLogonTimestamp=lastLogonTimestamp,
1371 dsdb.UF_NORMAL_ACCOUNT,
1372 msDSUserAccountControlComputed=0)
1374 # Wrong old password
1376 other_ldb.modify_ldif("""
1377 dn: """ + userdn + """
1380 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1382 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1385 except LdbError, (num, msg):
1386 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1387 self.assertTrue('00000056' in msg, msg)
1389 res = self._check_account(userdn,
1391 badPasswordTime=("greater", badPasswordTime),
1392 logonCount=logonCount,
1393 lastLogon=lastLogon,
1394 lastLogonTimestamp=lastLogonTimestamp,
1395 lockoutTime=("greater", badPasswordTime),
1397 dsdb.UF_NORMAL_ACCOUNT,
1398 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1399 badPasswordTime = int(res[0]["badPasswordTime"][0])
1400 lockoutTime = int(res[0]["lockoutTime"][0])
1402 time.sleep(self.account_lockout_duration + 1)
1404 res = self._check_account(userdn,
1405 badPwdCount=3, effective_bad_password_count=0,
1406 badPasswordTime=badPasswordTime,
1407 logonCount=logonCount,
1408 lastLogon=lastLogon,
1409 lastLogonTimestamp=lastLogonTimestamp,
1410 lockoutTime=lockoutTime,
1412 dsdb.UF_NORMAL_ACCOUNT,
1413 msDSUserAccountControlComputed=0)
1415 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1416 # It doesn't reset "lockoutTime" = 0 and doesn't
1417 # reset "badPwdCount" = 0.
1418 self._reset_samr(res)
1420 res = self._check_account(userdn,
1421 badPwdCount=3, effective_bad_password_count=0,
1422 badPasswordTime=badPasswordTime,
1423 logonCount=logonCount,
1424 lockoutTime=lockoutTime,
1425 lastLogon=lastLogon,
1426 lastLogonTimestamp=lastLogonTimestamp,
1428 dsdb.UF_NORMAL_ACCOUNT,
1429 msDSUserAccountControlComputed=0)
1431 def test_unicodePwd_lockout_with_clear_change_krb5(self):
1432 self._test_unicodePwd_lockout_with_clear_change(self.lockout1krb5_creds,
1433 self.lockout2krb5_ldb)
1435 def test_unicodePwd_lockout_with_clear_change_ntlm(self):
1436 self._test_unicodePwd_lockout_with_clear_change(self.lockout1ntlm_creds,
1437 self.lockout2ntlm_ldb,
1438 initial_logoncount_relation="equal")
1440 def _test_login_lockout(self, creds):
1441 username = creds.get_username()
1442 userpass = creds.get_password()
1443 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
1445 use_kerberos = creds.get_kerberos_state()
1446 # This unlocks by waiting for account_lockout_duration
1447 if use_kerberos == MUST_USE_KERBEROS:
1448 logoncount_relation = 'greater'
1449 lastlogon_relation = 'greater'
1450 print "Performs a lockout attempt against LDAP using Kerberos"
1452 logoncount_relation = 'equal'
1453 lastlogon_relation = 'equal'
1454 print "Performs a lockout attempt against LDAP using NTLM"
1456 # Change password on a connection as another user
1457 res = self._check_account(userdn,
1459 badPasswordTime=("greater", 0),
1460 logonCount=(logoncount_relation, 0),
1461 lastLogon=("greater", 0),
1462 lastLogonTimestamp=("greater", 0),
1464 dsdb.UF_NORMAL_ACCOUNT,
1465 msDSUserAccountControlComputed=0)
1466 badPasswordTime = int(res[0]["badPasswordTime"][0])
1467 logonCount = int(res[0]["logonCount"][0])
1468 lastLogon = int(res[0]["lastLogon"][0])
1469 firstLogon = lastLogon
1470 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1472 print lastLogonTimestamp
1475 self.assertGreater(lastLogon, badPasswordTime)
1476 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
1478 # Open a second LDB connection with the user credentials. Use the
1479 # command line credentials for informations like the domain, the realm
1480 # and the workstation.
1481 creds_lockout = self.insta_creds(creds)
1483 # The wrong password
1484 creds_lockout.set_password("thatsAcomplPASS1x")
1486 self.assertLoginFailure(host_url, creds_lockout, lp)
1488 res = self._check_account(userdn,
1490 badPasswordTime=("greater", badPasswordTime),
1491 logonCount=logonCount,
1492 lastLogon=lastLogon,
1493 lastLogonTimestamp=lastLogonTimestamp,
1495 dsdb.UF_NORMAL_ACCOUNT,
1496 msDSUserAccountControlComputed=0,
1497 msg='lastlogontimestamp with wrong password')
1498 badPasswordTime = int(res[0]["badPasswordTime"][0])
1500 # Correct old password
1501 creds_lockout.set_password(userpass)
1503 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1505 # lastLogonTimestamp should not change
1506 # lastLogon increases if badPwdCount is non-zero (!)
1507 res = self._check_account(userdn,
1509 badPasswordTime=badPasswordTime,
1510 logonCount=(logoncount_relation, logonCount),
1511 lastLogon=('greater', lastLogon),
1512 lastLogonTimestamp=lastLogonTimestamp,
1514 dsdb.UF_NORMAL_ACCOUNT,
1515 msDSUserAccountControlComputed=0,
1516 msg='LLTimestamp is updated to lastlogon')
1518 logonCount = int(res[0]["logonCount"][0])
1519 lastLogon = int(res[0]["lastLogon"][0])
1520 self.assertGreater(lastLogon, badPasswordTime)
1521 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
1523 # The wrong password
1524 creds_lockout.set_password("thatsAcomplPASS1x")
1526 self.assertLoginFailure(host_url, creds_lockout, lp)
1528 res = self._check_account(userdn,
1530 badPasswordTime=("greater", badPasswordTime),
1531 logonCount=logonCount,
1532 lastLogon=lastLogon,
1533 lastLogonTimestamp=lastLogonTimestamp,
1535 dsdb.UF_NORMAL_ACCOUNT,
1536 msDSUserAccountControlComputed=0)
1537 badPasswordTime = int(res[0]["badPasswordTime"][0])
1539 # The wrong password
1540 creds_lockout.set_password("thatsAcomplPASS1x")
1543 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1546 except LdbError, (num, msg):
1547 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1549 res = self._check_account(userdn,
1551 badPasswordTime=("greater", badPasswordTime),
1552 logonCount=logonCount,
1553 lastLogon=lastLogon,
1554 lastLogonTimestamp=lastLogonTimestamp,
1556 dsdb.UF_NORMAL_ACCOUNT,
1557 msDSUserAccountControlComputed=0)
1558 badPasswordTime = int(res[0]["badPasswordTime"][0])
1560 print "two failed password change"
1562 # The wrong password
1563 creds_lockout.set_password("thatsAcomplPASS1x")
1566 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1569 except LdbError, (num, msg):
1570 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1572 res = self._check_account(userdn,
1574 badPasswordTime=("greater", badPasswordTime),
1575 logonCount=logonCount,
1576 lastLogon=lastLogon,
1577 lastLogonTimestamp=lastLogonTimestamp,
1578 lockoutTime=("greater", badPasswordTime),
1580 dsdb.UF_NORMAL_ACCOUNT,
1581 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1582 badPasswordTime = int(res[0]["badPasswordTime"][0])
1583 lockoutTime = int(res[0]["lockoutTime"][0])
1585 # The wrong password
1586 creds_lockout.set_password("thatsAcomplPASS1x")
1588 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1590 except LdbError, (num, msg):
1591 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1593 res = self._check_account(userdn,
1595 badPasswordTime=badPasswordTime,
1596 logonCount=logonCount,
1597 lastLogon=lastLogon,
1598 lastLogonTimestamp=lastLogonTimestamp,
1599 lockoutTime=lockoutTime,
1601 dsdb.UF_NORMAL_ACCOUNT,
1602 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1604 # The wrong password
1605 creds_lockout.set_password("thatsAcomplPASS1x")
1607 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1609 except LdbError, (num, msg):
1610 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1612 res = self._check_account(userdn,
1614 badPasswordTime=badPasswordTime,
1615 logonCount=logonCount,
1616 lastLogon=lastLogon,
1617 lastLogonTimestamp=lastLogonTimestamp,
1618 lockoutTime=lockoutTime,
1620 dsdb.UF_NORMAL_ACCOUNT,
1621 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1623 # The correct password, but we are locked out
1624 creds_lockout.set_password(userpass)
1626 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1628 except LdbError, (num, msg):
1629 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1631 res = self._check_account(userdn,
1633 badPasswordTime=badPasswordTime,
1634 logonCount=logonCount,
1635 lastLogon=lastLogon,
1636 lastLogonTimestamp=lastLogonTimestamp,
1637 lockoutTime=lockoutTime,
1639 dsdb.UF_NORMAL_ACCOUNT,
1640 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1642 # wait for the lockout to end
1643 time.sleep(self.account_lockout_duration + 1)
1644 print self.account_lockout_duration + 1
1646 res = self._check_account(userdn,
1647 badPwdCount=3, effective_bad_password_count=0,
1648 badPasswordTime=badPasswordTime,
1649 logonCount=logonCount,
1650 lockoutTime=lockoutTime,
1651 lastLogon=lastLogon,
1652 lastLogonTimestamp=lastLogonTimestamp,
1654 dsdb.UF_NORMAL_ACCOUNT,
1655 msDSUserAccountControlComputed=0)
1657 # The correct password after letting the timeout expire
1659 creds_lockout.set_password(userpass)
1661 creds_lockout2 = self.insta_creds(creds_lockout)
1663 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout2, lp=lp)
1666 res = self._check_account(userdn,
1668 badPasswordTime=badPasswordTime,
1669 logonCount=(logoncount_relation, logonCount),
1670 lastLogon=(lastlogon_relation, lastLogon),
1671 lastLogonTimestamp=lastLogonTimestamp,
1674 dsdb.UF_NORMAL_ACCOUNT,
1675 msDSUserAccountControlComputed=0,
1676 msg="lastLogon is way off")
1678 logonCount = int(res[0]["logonCount"][0])
1679 lastLogon = int(res[0]["lastLogon"][0])
1681 # The wrong password
1682 creds_lockout.set_password("thatsAcomplPASS1x")
1684 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1686 except LdbError, (num, msg):
1687 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1689 res = self._check_account(userdn,
1691 badPasswordTime=("greater", badPasswordTime),
1692 logonCount=logonCount,
1694 lastLogon=lastLogon,
1695 lastLogonTimestamp=lastLogonTimestamp,
1697 dsdb.UF_NORMAL_ACCOUNT,
1698 msDSUserAccountControlComputed=0)
1699 badPasswordTime = int(res[0]["badPasswordTime"][0])
1701 # The wrong password
1702 creds_lockout.set_password("thatsAcomplPASS1x")
1704 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1706 except LdbError, (num, msg):
1707 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1709 res = self._check_account(userdn,
1711 badPasswordTime=("greater", badPasswordTime),
1712 logonCount=logonCount,
1714 lastLogon=lastLogon,
1715 lastLogonTimestamp=lastLogonTimestamp,
1717 dsdb.UF_NORMAL_ACCOUNT,
1718 msDSUserAccountControlComputed=0)
1719 badPasswordTime = int(res[0]["badPasswordTime"][0])
1721 time.sleep(self.lockout_observation_window + 1)
1723 res = self._check_account(userdn,
1724 badPwdCount=2, effective_bad_password_count=0,
1725 badPasswordTime=badPasswordTime,
1726 logonCount=logonCount,
1728 lastLogon=lastLogon,
1729 lastLogonTimestamp=lastLogonTimestamp,
1731 dsdb.UF_NORMAL_ACCOUNT,
1732 msDSUserAccountControlComputed=0)
1734 # The wrong password
1735 creds_lockout.set_password("thatsAcomplPASS1x")
1737 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1739 except LdbError, (num, msg):
1740 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1742 res = self._check_account(userdn,
1744 badPasswordTime=("greater", badPasswordTime),
1745 logonCount=logonCount,
1747 lastLogon=lastLogon,
1748 lastLogonTimestamp=lastLogonTimestamp,
1750 dsdb.UF_NORMAL_ACCOUNT,
1751 msDSUserAccountControlComputed=0)
1752 badPasswordTime = int(res[0]["badPasswordTime"][0])
1754 # The correct password without letting the timeout expire
1755 creds_lockout.set_password(userpass)
1756 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1758 res = self._check_account(userdn,
1760 badPasswordTime=badPasswordTime,
1761 logonCount=(logoncount_relation, logonCount),
1763 lastLogon=("greater", lastLogon),
1764 lastLogonTimestamp=lastLogonTimestamp,
1766 dsdb.UF_NORMAL_ACCOUNT,
1767 msDSUserAccountControlComputed=0)
1770 def test_login_lockout_krb5(self):
1771 self._test_login_lockout(self.lockout1krb5_creds)
1773 def test_login_lockout_ntlm(self):
1774 self._test_login_lockout(self.lockout1ntlm_creds)
1776 def _test_multiple_logon(self, creds):
1777 # Test the happy case in which a user logs on correctly, then
1778 # logs on correctly again, so that the bad password and
1779 # lockout times are both zero the second time. The lastlogon
1780 # time should increase.
1782 # Open a second LDB connection with the user credentials. Use the
1783 # command line credentials for informations like the domain, the realm
1784 # and the workstation.
1785 username = creds.get_username()
1786 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
1788 use_kerberos = creds.get_kerberos_state()
1789 if use_kerberos == MUST_USE_KERBEROS:
1790 print "Testing multiple logon with Kerberos"
1791 logoncount_relation = 'greater'
1792 lastlogon_relation = 'greater'
1794 print "Testing multiple logon with NTLM"
1795 logoncount_relation = 'equal'
1796 lastlogon_relation = 'equal'
1798 SamDB(url=host_url, credentials=self.insta_creds(creds), lp=lp)
1800 res = self._check_account(userdn,
1802 badPasswordTime=("greater", 0),
1803 logonCount=(logoncount_relation, 0),
1804 lastLogon=("greater", 0),
1805 lastLogonTimestamp=("greater", 0),
1807 dsdb.UF_NORMAL_ACCOUNT,
1808 msDSUserAccountControlComputed=0)
1809 badPasswordTime = int(res[0]["badPasswordTime"][0])
1810 logonCount = int(res[0]["logonCount"][0])
1811 lastLogon = int(res[0]["lastLogon"][0])
1812 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1813 firstLogon = lastLogon
1814 print "last logon is %d" % lastLogon
1815 self.assertGreater(lastLogon, badPasswordTime)
1816 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
1819 SamDB(url=host_url, credentials=self.insta_creds(creds), lp=lp)
1821 res = self._check_account(userdn,
1823 badPasswordTime=badPasswordTime,
1824 logonCount=(logoncount_relation, logonCount),
1825 lastLogon=(lastlogon_relation, lastLogon),
1826 lastLogonTimestamp=lastLogonTimestamp,
1828 dsdb.UF_NORMAL_ACCOUNT,
1829 msDSUserAccountControlComputed=0,
1830 msg=("second logon, firstlogon was %s" %
1834 lastLogon = int(res[0]["lastLogon"][0])
1838 SamDB(url=host_url, credentials=self.insta_creds(creds), lp=lp)
1840 res = self._check_account(userdn,
1842 badPasswordTime=badPasswordTime,
1843 logonCount=(logoncount_relation, logonCount),
1844 lastLogon=(lastlogon_relation, lastLogon),
1845 lastLogonTimestamp=lastLogonTimestamp,
1847 dsdb.UF_NORMAL_ACCOUNT,
1848 msDSUserAccountControlComputed=0)
1850 def test_multiple_logon_krb5(self):
1851 self._test_multiple_logon(self.lockout1krb5_creds)
1853 def test_multiple_logon_ntlm(self):
1854 self._test_multiple_logon(self.lockout1ntlm_creds)
1858 super(PasswordTests, self).tearDown()
1860 host_url = "ldap://%s" % host
1862 TestProgram(module=__name__, opts=subunitopts)