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 def insta_creds(template=global_creds):
61 # get a copy of the global creds or a the passed in creds
63 c.set_username("testuser")
64 c.set_password("thatsAcomplPASS1")
65 c.set_domain(template.get_domain())
66 c.set_realm(template.get_realm())
67 c.set_workstation(template.get_workstation())
68 c.set_gensec_features(c.get_gensec_features()
69 | gensec.FEATURE_SEAL)
70 c.set_kerberos_state(template.get_kerberos_state())
77 class PasswordTests(samba.tests.TestCase):
79 def _open_samr_user(self, res):
80 self.assertTrue("objectSid" in res[0])
82 (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split()
83 self.assertEquals(self.domain_sid, domain_sid)
85 return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid)
87 def _reset_samr(self, res):
89 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
90 samr_user = self._open_samr_user(res)
91 acb_info = self.samr.QueryUserInfo(samr_user, 16)
92 acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
93 self.samr.SetUserInfo(samr_user, 16, acb_info)
94 self.samr.Close(samr_user)
96 def _reset_ldap_lockoutTime(self, res):
97 self.ldb.modify_ldif("""
98 dn: """ + str(res[0].dn) + """
104 def _reset_ldap_userAccountControl(self, res):
105 self.assertTrue("userAccountControl" in res[0])
106 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
108 uac = int(res[0]["userAccountControl"][0])
109 uacc = int(res[0]["msDS-User-Account-Control-Computed"][0])
112 uac = uac & ~dsdb.UF_LOCKOUT
114 self.ldb.modify_ldif("""
115 dn: """ + str(res[0].dn) + """
117 replace: userAccountControl
118 userAccountControl: %d
121 def _reset_by_method(self, res, method):
122 if method is "ldap_userAccountControl":
123 self._reset_ldap_userAccountControl(res)
124 elif method is "ldap_lockoutTime":
125 self._reset_ldap_lockoutTime(res)
126 elif method is "samr":
127 self._reset_samr(res)
129 self.assertTrue(False, msg="Invalid reset method[%s]" % method)
131 def _check_attribute(self, res, name, value):
133 self.assertTrue(name not in res[0],
134 msg="attr[%s]=%r on dn[%s]" %
135 (name, res[0], res[0].dn))
138 if isinstance(value, tuple):
139 (mode, value) = value
147 self.assertFalse(name in res[0],
148 msg="attr[%s] not missing on dn[%s]" %
152 self.assertTrue(name in res[0],
153 msg="attr[%s] missing on dn[%s]" %
155 self.assertTrue(len(res[0][name]) == 1,
156 msg="attr[%s]=%r on dn[%s]" %
157 (name, res[0][name], res[0].dn))
160 print "%s = '%s'" % (name, res[0][name][0])
162 if mode == "present":
166 v = int(res[0][name][0])
168 msg = ("attr[%s]=[%s] != [%s] on dn[%s]\n"
169 "(diff %d; actual value is %s than expected)" %
170 (name, v, value, res[0].dn, v - value,
171 ('less' if v < value else 'greater')))
173 self.assertTrue(v == value, msg)
176 if mode == "greater":
177 v = int(res[0][name][0])
178 self.assertTrue(v > int(value),
179 msg="attr[%s]=[%s] <= [%s] on dn[%s] (diff %d)" %
180 (name, v, int(value), res[0].dn, v - int(value)))
183 v = int(res[0][name][0])
184 self.assertTrue(v < int(value),
185 msg="attr[%s]=[%s] >= [%s] on dn[%s] (diff %d)" %
186 (name, v, int(value), res[0].dn, v - int(value)))
188 self.assertEqual(mode, not mode, "Invalid Mode[%s]" % mode)
190 def _check_account(self, dn,
192 badPasswordTime=None,
194 lastLogonTimestamp=None,
196 userAccountControl=None,
197 msDSUserAccountControlComputed=None,
198 effective_bad_password_count=None,
202 print "\033[01;32m %s \033[00m\n" % msg
208 "lastLogonTimestamp",
210 "userAccountControl",
211 "msDS-User-Account-Control-Computed"
214 # in order to prevent some time resolution problems we sleep for
218 res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
219 self.assertTrue(len(res) == 1)
220 self._check_attribute(res, "badPwdCount", badPwdCount)
221 self._check_attribute(res, "badPasswordTime", badPasswordTime)
222 self._check_attribute(res, "lastLogon", lastLogon)
223 self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp)
224 self._check_attribute(res, "lockoutTime", lockoutTime)
225 self._check_attribute(res, "userAccountControl", userAccountControl)
226 self._check_attribute(res, "msDS-User-Account-Control-Computed",
227 msDSUserAccountControlComputed)
229 lastLogon = int(res[0]["lastLogon"][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)
260 self.assertEquals(uinfo5.acct_flags, expected_acb_info)
261 self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count)
262 self.assertEquals(uinfo5.last_logon, lastLogon)
264 self.assertEquals(uinfo16.acct_flags, expected_acb_info)
266 self.assertEquals(uinfo21.acct_flags, expected_acb_info)
267 self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count)
268 self.assertEquals(uinfo21.last_logon, lastLogon)
270 # check LDAP again and make sure the samr.QueryUserInfo
271 # doesn't have any impact.
272 res2 = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
273 self.assertEquals(res[0], res2[0])
275 # in order to prevent some time resolution problems we sleep for
280 def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS):
282 ldb = SamDB(url=url, credentials=creds, lp=lp)
283 self.fail("Login unexpectedly succeeded")
284 except LdbError, (num, msg):
285 if errno is not None:
286 self.assertEquals(num, errno, ("Login failed in the wrong way"
287 "(got err %d, expected %d)" %
291 super(PasswordTests, self).setUp()
293 self.ldb = SamDB(url=host_url, session_info=system_session(lp),
294 credentials=global_creds, lp=lp)
296 # Gets back the basedn
297 base_dn = self.ldb.domain_dn()
299 # Gets back the configuration basedn
300 configuration_dn = self.ldb.get_config_basedn().get_linearized()
302 # Get the old "dSHeuristics" if it was set
303 dsheuristics = self.ldb.get_dsheuristics()
305 # Reset the "dSHeuristics" as they were before
306 self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
308 res = self.ldb.search(base_dn,
309 scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
311 if "lockoutDuration" in res[0]:
312 lockoutDuration = res[0]["lockoutDuration"][0]
316 if "lockoutObservationWindow" in res[0]:
317 lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
319 lockoutObservationWindow = 0
321 if "lockoutThreshold" in res[0]:
322 lockoutThreshold = res[0]["lockoutThreshold"][0]
326 self.addCleanup(self.ldb.modify_ldif, """
327 dn: """ + base_dn + """
329 replace: lockoutDuration
330 lockoutDuration: """ + str(lockoutDuration) + """
331 replace: lockoutObservationWindow
332 lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
333 replace: lockoutThreshold
334 lockoutThreshold: """ + str(lockoutThreshold) + """
338 m.dn = Dn(self.ldb, base_dn)
340 self.account_lockout_duration = 2
341 account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7))
343 m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
344 FLAG_MOD_REPLACE, "lockoutDuration")
346 account_lockout_threshold = 3
347 m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
348 FLAG_MOD_REPLACE, "lockoutThreshold")
350 self.lockout_observation_window = 2
351 lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7))
353 m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
354 FLAG_MOD_REPLACE, "lockOutObservationWindow")
358 # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
359 self.ldb.set_dsheuristics("000000001")
361 # Get the old "minPwdAge"
362 minPwdAge = self.ldb.get_minPwdAge()
364 # Reset the "minPwdAge" as it was before
365 self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
367 # Set it temporarely to "0"
368 self.ldb.set_minPwdAge("0")
370 self.base_dn = self.ldb.domain_dn()
372 self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
373 self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % host, lp, global_creds)
374 self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
375 self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
377 # (Re)adds the test user "testuser" with no password atm
378 delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
380 "dn": "cn=testuser,cn=users," + self.base_dn,
381 "objectclass": "user",
382 "sAMAccountName": "testuser"})
384 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
388 lastLogonTimestamp=('absent', None),
390 dsdb.UF_NORMAL_ACCOUNT |
391 dsdb.UF_ACCOUNTDISABLE |
392 dsdb.UF_PASSWD_NOTREQD,
393 msDSUserAccountControlComputed=
394 dsdb.UF_PASSWORD_EXPIRED)
396 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
397 # It doesn't create "lockoutTime" = 0.
398 self._reset_samr(res)
400 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
404 lastLogonTimestamp=('absent', None),
406 dsdb.UF_NORMAL_ACCOUNT |
407 dsdb.UF_ACCOUNTDISABLE |
408 dsdb.UF_PASSWD_NOTREQD,
409 msDSUserAccountControlComputed=
410 dsdb.UF_PASSWORD_EXPIRED)
412 # Tests a password change when we don't have any password yet with a
415 self.ldb.modify_ldif("""
416 dn: cn=testuser,cn=users,""" + self.base_dn + """
419 userPassword: noPassword
421 userPassword: thatsAcomplPASS2
424 except LdbError, (num, msg):
425 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
426 # Windows (2008 at least) seems to have some small bug here: it
427 # returns "0000056A" on longer (always wrong) previous passwords.
428 self.assertTrue('00000056' in msg)
430 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
432 badPasswordTime=("greater", 0),
434 lastLogonTimestamp=('absent', None),
436 dsdb.UF_NORMAL_ACCOUNT |
437 dsdb.UF_ACCOUNTDISABLE |
438 dsdb.UF_PASSWD_NOTREQD,
439 msDSUserAccountControlComputed=
440 dsdb.UF_PASSWORD_EXPIRED)
441 badPasswordTime = int(res[0]["badPasswordTime"][0])
443 # Sets the initial user password with a "special" password change
444 # I think that this internally is a password set operation and it can
445 # only be performed by someone which has password set privileges on the
446 # account (at least in s4 we do handle it like that).
447 self.ldb.modify_ldif("""
448 dn: cn=testuser,cn=users,""" + self.base_dn + """
452 userPassword: thatsAcomplPASS1
455 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
457 badPasswordTime=badPasswordTime,
459 lastLogonTimestamp=('absent', None),
461 dsdb.UF_NORMAL_ACCOUNT |
462 dsdb.UF_ACCOUNTDISABLE |
463 dsdb.UF_PASSWD_NOTREQD,
464 msDSUserAccountControlComputed=0)
466 # Enables the user account
467 self.ldb.enable_account("(sAMAccountName=testuser)")
469 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
471 badPasswordTime=badPasswordTime,
473 lastLogonTimestamp=('absent', None),
475 dsdb.UF_NORMAL_ACCOUNT,
476 msDSUserAccountControlComputed=0)
478 # Open a second LDB connection with the user credentials. Use the
479 # command line credentials for informations like the domain, the realm
480 # and the workstation.
481 creds2 = insta_creds()
483 self.ldb2 = SamDB(url=host_url, credentials=creds2, lp=lp)
485 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
487 badPasswordTime=badPasswordTime,
488 lastLogon=('greater', 0),
489 lastLogonTimestamp=('greater', 0),
491 dsdb.UF_NORMAL_ACCOUNT,
492 msDSUserAccountControlComputed=0)
494 lastLogon = int(res[0]["lastLogon"][0])
495 self.assertGreater(lastLogon, badPasswordTime)
497 # (Re)adds the test user "testuser3" with no password atm
498 delete_force(self.ldb, "cn=testuser3,cn=users," + self.base_dn)
500 "dn": "cn=testuser3,cn=users," + self.base_dn,
501 "objectclass": "user",
502 "sAMAccountName": "testuser3"})
504 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
508 lastLogonTimestamp=('absent', None),
510 dsdb.UF_NORMAL_ACCOUNT |
511 dsdb.UF_ACCOUNTDISABLE |
512 dsdb.UF_PASSWD_NOTREQD,
513 msDSUserAccountControlComputed=
514 dsdb.UF_PASSWORD_EXPIRED)
516 # Tests a password change when we don't have any password yet with a
519 self.ldb.modify_ldif("""
520 dn: cn=testuser3,cn=users,""" + self.base_dn + """
523 userPassword: noPassword
525 userPassword: thatsAcomplPASS2
528 except LdbError, (num, msg):
529 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
530 # Windows (2008 at least) seems to have some small bug here: it
531 # returns "0000056A" on longer (always wrong) previous passwords.
532 self.assertTrue('00000056' in msg)
534 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
536 badPasswordTime=("greater", 0),
538 lastLogonTimestamp=('absent', None),
540 dsdb.UF_NORMAL_ACCOUNT |
541 dsdb.UF_ACCOUNTDISABLE |
542 dsdb.UF_PASSWD_NOTREQD,
543 msDSUserAccountControlComputed=
544 dsdb.UF_PASSWORD_EXPIRED)
545 badPasswordTime3 = int(res[0]["badPasswordTime"][0])
547 # Sets the initial user password with a "special" password change
548 # I think that this internally is a password set operation and it can
549 # only be performed by someone which has password set privileges on the
550 # account (at least in s4 we do handle it like that).
551 self.ldb.modify_ldif("""
552 dn: cn=testuser3,cn=users,""" + self.base_dn + """
556 userPassword: thatsAcomplPASS1
559 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
561 badPasswordTime=badPasswordTime3,
563 lastLogonTimestamp=('absent', None),
565 dsdb.UF_NORMAL_ACCOUNT |
566 dsdb.UF_ACCOUNTDISABLE |
567 dsdb.UF_PASSWD_NOTREQD,
568 msDSUserAccountControlComputed=0)
570 # Enables the user account
571 self.ldb.enable_account("(sAMAccountName=testuser3)")
573 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
575 badPasswordTime=badPasswordTime3,
577 lastLogonTimestamp=('absent', None),
579 dsdb.UF_NORMAL_ACCOUNT,
580 msDSUserAccountControlComputed=0)
582 # Open a second LDB connection with the user credentials. Use the
583 # command line credentials for informations like the domain, the realm
584 # and the workstation.
585 creds3 = insta_creds()
586 creds3.set_username("testuser3")
587 creds3.set_password("thatsAcomplPASS1")
588 self.ldb3 = SamDB(url=host_url, credentials=creds3, lp=lp)
590 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
592 badPasswordTime=badPasswordTime3,
593 lastLogon=('greater', badPasswordTime3),
594 lastLogonTimestamp=('greater', badPasswordTime3),
596 dsdb.UF_NORMAL_ACCOUNT,
597 msDSUserAccountControlComputed=0)
599 def _test_userPassword_lockout_with_clear_change(self, method):
600 print "Performs a password cleartext change operation on 'userPassword'"
601 # Notice: This works only against Windows if "dSHeuristics" has been set
604 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
606 badPasswordTime=("greater", 0),
607 lastLogon=('greater', 0),
608 lastLogonTimestamp=('greater', 0),
610 dsdb.UF_NORMAL_ACCOUNT,
611 msDSUserAccountControlComputed=0)
612 badPasswordTime = int(res[0]["badPasswordTime"][0])
613 lastLogon = int(res[0]["lastLogon"][0])
614 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
616 # Change password on a connection as another user
620 self.ldb3.modify_ldif("""
621 dn: cn=testuser,cn=users,""" + self.base_dn + """
624 userPassword: thatsAcomplPASS1x
626 userPassword: thatsAcomplPASS2
629 except LdbError, (num, msg):
630 self.assertTrue('00000056' in msg)
631 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
633 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
635 badPasswordTime=("greater", badPasswordTime),
637 lastLogonTimestamp=lastLogonTimestamp,
639 dsdb.UF_NORMAL_ACCOUNT,
640 msDSUserAccountControlComputed=0)
641 badPasswordTime = int(res[0]["badPasswordTime"][0])
643 # Correct old password
644 self.ldb3.modify_ldif("""
645 dn: cn=testuser,cn=users,""" + self.base_dn + """
648 userPassword: thatsAcomplPASS1
650 userPassword: thatsAcomplPASS2
653 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
655 badPasswordTime=badPasswordTime,
657 lastLogonTimestamp=lastLogonTimestamp,
659 dsdb.UF_NORMAL_ACCOUNT,
660 msDSUserAccountControlComputed=0)
664 self.ldb3.modify_ldif("""
665 dn: cn=testuser,cn=users,""" + self.base_dn + """
668 userPassword: thatsAcomplPASS1x
670 userPassword: thatsAcomplPASS2
673 except LdbError, (num, msg):
674 self.assertTrue('00000056' in msg)
675 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
677 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
679 badPasswordTime=("greater", badPasswordTime),
681 lastLogonTimestamp=lastLogonTimestamp,
683 dsdb.UF_NORMAL_ACCOUNT,
684 msDSUserAccountControlComputed=0)
685 badPasswordTime = int(res[0]["badPasswordTime"][0])
687 print "two failed password change"
691 self.ldb3.modify_ldif("""
692 dn: cn=testuser,cn=users,""" + self.base_dn + """
695 userPassword: thatsAcomplPASS1x
697 userPassword: thatsAcomplPASS2
700 except LdbError, (num, msg):
701 self.assertTrue('00000056' in msg)
702 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
704 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
706 badPasswordTime=("greater", badPasswordTime),
708 lastLogonTimestamp=lastLogonTimestamp,
709 lockoutTime=("greater", badPasswordTime),
711 dsdb.UF_NORMAL_ACCOUNT,
712 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
713 badPasswordTime = int(res[0]["badPasswordTime"][0])
714 lockoutTime = int(res[0]["lockoutTime"][0])
718 self.ldb3.modify_ldif("""
719 dn: cn=testuser,cn=users,""" + self.base_dn + """
722 userPassword: thatsAcomplPASS1x
724 userPassword: thatsAcomplPASS2
727 except LdbError, (num, msg):
728 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
729 self.assertTrue('00000775' in msg)
731 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
733 badPasswordTime=badPasswordTime,
735 lastLogonTimestamp=lastLogonTimestamp,
736 lockoutTime=lockoutTime,
738 dsdb.UF_NORMAL_ACCOUNT,
739 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
743 self.ldb3.modify_ldif("""
744 dn: cn=testuser,cn=users,""" + self.base_dn + """
747 userPassword: thatsAcomplPASS1x
749 userPassword: thatsAcomplPASS2
752 except LdbError, (num, msg):
753 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
754 self.assertTrue('00000775' in msg)
756 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
758 badPasswordTime=badPasswordTime,
759 lockoutTime=lockoutTime,
761 lastLogonTimestamp=lastLogonTimestamp,
763 dsdb.UF_NORMAL_ACCOUNT,
764 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
767 # Correct old password
768 self.ldb3.modify_ldif("""
769 dn: cn=testuser,cn=users,""" + self.base_dn + """
772 userPassword: thatsAcomplPASS2
774 userPassword: thatsAcomplPASS2x
777 except LdbError, (num, msg):
778 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
779 self.assertTrue('0000775' in msg)
781 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
783 badPasswordTime=badPasswordTime,
785 lastLogonTimestamp=lastLogonTimestamp,
786 lockoutTime=lockoutTime,
788 dsdb.UF_NORMAL_ACCOUNT,
789 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
791 # Now reset the password, which does NOT change the lockout!
792 self.ldb.modify_ldif("""
793 dn: cn=testuser,cn=users,""" + self.base_dn + """
795 replace: userPassword
796 userPassword: thatsAcomplPASS2
799 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
801 badPasswordTime=badPasswordTime,
803 lastLogonTimestamp=lastLogonTimestamp,
804 lockoutTime=lockoutTime,
806 dsdb.UF_NORMAL_ACCOUNT,
807 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
810 # Correct old password
811 self.ldb3.modify_ldif("""
812 dn: cn=testuser,cn=users,""" + self.base_dn + """
815 userPassword: thatsAcomplPASS2
817 userPassword: thatsAcomplPASS2x
820 except LdbError, (num, msg):
821 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
822 self.assertTrue('0000775' in msg)
824 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
826 badPasswordTime=badPasswordTime,
828 lastLogonTimestamp=lastLogonTimestamp,
829 lockoutTime=lockoutTime,
831 dsdb.UF_NORMAL_ACCOUNT,
832 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
835 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
836 m["userAccountControl"] = MessageElement(
837 str(dsdb.UF_LOCKOUT),
838 FLAG_MOD_REPLACE, "userAccountControl")
842 # This shows that setting the UF_LOCKOUT flag alone makes no difference
843 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
845 badPasswordTime=badPasswordTime,
847 lastLogonTimestamp=lastLogonTimestamp,
848 lockoutTime=lockoutTime,
850 dsdb.UF_NORMAL_ACCOUNT,
851 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
853 # This shows that setting the UF_LOCKOUT flag makes no difference
855 # Correct old password
856 self.ldb3.modify_ldif("""
857 dn: cn=testuser,cn=users,""" + self.base_dn + """
860 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
862 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
865 except LdbError, (num, msg):
866 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
867 self.assertTrue('0000775' in msg)
869 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
871 badPasswordTime=badPasswordTime,
872 lockoutTime=lockoutTime,
874 lastLogonTimestamp=lastLogonTimestamp,
876 dsdb.UF_NORMAL_ACCOUNT,
877 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
879 self._reset_by_method(res, method)
881 # Here bad password counts are reset without logon success.
882 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
884 badPasswordTime=badPasswordTime,
887 lastLogonTimestamp=lastLogonTimestamp,
889 dsdb.UF_NORMAL_ACCOUNT,
890 msDSUserAccountControlComputed=0)
892 # The correct password after doing the unlock
894 self.ldb3.modify_ldif("""
895 dn: cn=testuser,cn=users,""" + self.base_dn + """
898 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
900 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
903 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
905 badPasswordTime=badPasswordTime,
908 lastLogonTimestamp=lastLogonTimestamp,
910 dsdb.UF_NORMAL_ACCOUNT,
911 msDSUserAccountControlComputed=0)
915 self.ldb3.modify_ldif("""
916 dn: cn=testuser,cn=users,""" + self.base_dn + """
919 userPassword: thatsAcomplPASS1xyz
921 userPassword: thatsAcomplPASS2XYZ
924 except LdbError, (num, msg):
925 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
926 self.assertTrue('00000056' in msg)
928 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
930 badPasswordTime=("greater", badPasswordTime),
933 lastLogonTimestamp=lastLogonTimestamp,
935 dsdb.UF_NORMAL_ACCOUNT,
936 msDSUserAccountControlComputed=0)
937 badPasswordTime = int(res[0]["badPasswordTime"][0])
941 self.ldb3.modify_ldif("""
942 dn: cn=testuser,cn=users,""" + self.base_dn + """
945 userPassword: thatsAcomplPASS1xyz
947 userPassword: thatsAcomplPASS2XYZ
950 except LdbError, (num, msg):
951 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
952 self.assertTrue('00000056' in msg)
954 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
956 badPasswordTime=("greater", badPasswordTime),
959 lastLogonTimestamp=lastLogonTimestamp,
961 dsdb.UF_NORMAL_ACCOUNT,
962 msDSUserAccountControlComputed=0)
963 badPasswordTime = int(res[0]["badPasswordTime"][0])
965 self._reset_ldap_lockoutTime(res)
967 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
969 badPasswordTime=badPasswordTime,
971 lastLogonTimestamp=lastLogonTimestamp,
974 dsdb.UF_NORMAL_ACCOUNT,
975 msDSUserAccountControlComputed=0)
977 def test_userPassword_lockout_with_clear_change_ldap_userAccountControl(self):
978 self._test_userPassword_lockout_with_clear_change("ldap_userAccountControl")
980 def test_userPassword_lockout_with_clear_change_ldap_lockoutTime(self):
981 self._test_userPassword_lockout_with_clear_change("ldap_lockoutTime")
983 def test_userPassword_lockout_with_clear_change_samr(self):
984 self._test_userPassword_lockout_with_clear_change("samr")
987 def test_unicodePwd_lockout_with_clear_change(self):
988 print "Performs a password cleartext change operation on 'unicodePwd'"
990 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
992 badPasswordTime=("greater", 0),
993 lastLogon=("greater", 0),
994 lastLogonTimestamp=("greater", 0),
996 dsdb.UF_NORMAL_ACCOUNT,
997 msDSUserAccountControlComputed=0)
998 badPasswordTime = int(res[0]["badPasswordTime"][0])
999 lastLogon = int(res[0]["lastLogon"][0])
1001 # Change password on a connection as another user
1003 # Wrong old password
1005 self.ldb3.modify_ldif("""
1006 dn: cn=testuser,cn=users,""" + self.base_dn + """
1009 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1011 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1014 except LdbError, (num, msg):
1015 self.assertTrue('00000056' in msg)
1016 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1018 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1020 badPasswordTime=("greater", badPasswordTime),
1021 lastLogon=lastLogon,
1022 lastLogonTimestamp=lastLogon,
1024 dsdb.UF_NORMAL_ACCOUNT,
1025 msDSUserAccountControlComputed=0)
1026 badPasswordTime = int(res[0]["badPasswordTime"][0])
1028 # Correct old password
1029 self.ldb3.modify_ldif("""
1030 dn: cn=testuser,cn=users,""" + self.base_dn + """
1033 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
1035 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1038 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1040 badPasswordTime=badPasswordTime,
1041 lastLogon=lastLogon,
1042 lastLogonTimestamp=lastLogon,
1044 dsdb.UF_NORMAL_ACCOUNT,
1045 msDSUserAccountControlComputed=0)
1047 # Wrong old password
1049 self.ldb3.modify_ldif("""
1050 dn: cn=testuser,cn=users,""" + self.base_dn + """
1053 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
1055 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1058 except LdbError, (num, msg):
1059 self.assertTrue('00000056' in msg)
1060 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1062 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1064 badPasswordTime=("greater", badPasswordTime),
1065 lastLogon=lastLogon,
1066 lastLogonTimestamp=lastLogon,
1068 dsdb.UF_NORMAL_ACCOUNT,
1069 msDSUserAccountControlComputed=0)
1070 badPasswordTime = int(res[0]["badPasswordTime"][0])
1072 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1073 # It doesn't create "lockoutTime" = 0 and doesn't
1074 # reset "badPwdCount" = 0.
1075 self._reset_samr(res)
1077 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1079 badPasswordTime=badPasswordTime,
1080 lastLogon=lastLogon,
1081 lastLogonTimestamp=lastLogon,
1083 dsdb.UF_NORMAL_ACCOUNT,
1084 msDSUserAccountControlComputed=0)
1086 print "two failed password change"
1088 # Wrong old password
1090 self.ldb3.modify_ldif("""
1091 dn: cn=testuser,cn=users,""" + self.base_dn + """
1094 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1096 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1099 except LdbError, (num, msg):
1100 self.assertTrue('00000056' in msg)
1101 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1103 # this is strange, why do we have lockoutTime=badPasswordTime here?
1104 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1106 badPasswordTime=("greater", badPasswordTime),
1107 lastLogon=lastLogon,
1108 lastLogonTimestamp=lastLogon,
1109 lockoutTime=("greater", badPasswordTime),
1111 dsdb.UF_NORMAL_ACCOUNT,
1112 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1113 badPasswordTime = int(res[0]["badPasswordTime"][0])
1114 lockoutTime = int(res[0]["lockoutTime"][0])
1116 # Wrong old password
1118 self.ldb3.modify_ldif("""
1119 dn: cn=testuser,cn=users,""" + self.base_dn + """
1122 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1124 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1127 except LdbError, (num, msg):
1128 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1129 self.assertTrue('00000775' in msg)
1131 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1133 badPasswordTime=badPasswordTime,
1134 lastLogon=lastLogon,
1135 lastLogonTimestamp=lastLogon,
1136 lockoutTime=lockoutTime,
1138 dsdb.UF_NORMAL_ACCOUNT,
1139 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1141 # Wrong old password
1143 self.ldb3.modify_ldif("""
1144 dn: cn=testuser,cn=users,""" + self.base_dn + """
1147 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1149 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1152 except LdbError, (num, msg):
1153 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1154 self.assertTrue('00000775' in msg)
1156 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1158 badPasswordTime=badPasswordTime,
1159 lastLogon=lastLogon,
1160 lastLogonTimestamp=lastLogon,
1161 lockoutTime=lockoutTime,
1163 dsdb.UF_NORMAL_ACCOUNT,
1164 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1167 # Correct old password
1168 self.ldb3.modify_ldif("""
1169 dn: cn=testuser,cn=users,""" + self.base_dn + """
1172 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1174 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
1177 except LdbError, (num, msg):
1178 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1179 self.assertTrue('0000775' in msg)
1181 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1183 badPasswordTime=badPasswordTime,
1184 lastLogon=lastLogon,
1185 lastLogonTimestamp=lastLogon,
1186 lockoutTime=lockoutTime,
1188 dsdb.UF_NORMAL_ACCOUNT,
1189 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1191 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
1192 self._reset_samr(res);
1194 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1196 badPasswordTime=badPasswordTime,
1197 lastLogon=lastLogon,
1198 lastLogonTimestamp=lastLogon,
1201 dsdb.UF_NORMAL_ACCOUNT,
1202 msDSUserAccountControlComputed=0)
1204 # Correct old password
1205 self.ldb3.modify_ldif("""
1206 dn: cn=testuser,cn=users,""" + self.base_dn + """
1209 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1211 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
1214 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1216 badPasswordTime=badPasswordTime,
1217 lastLogon=lastLogon,
1218 lastLogonTimestamp=lastLogon,
1221 dsdb.UF_NORMAL_ACCOUNT,
1222 msDSUserAccountControlComputed=0)
1224 # Wrong old password
1226 self.ldb3.modify_ldif("""
1227 dn: cn=testuser,cn=users,""" + self.base_dn + """
1230 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1232 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1235 except LdbError, (num, msg):
1236 self.assertTrue('00000056' in msg)
1237 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1239 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1241 badPasswordTime=("greater", badPasswordTime),
1242 lastLogon=lastLogon,
1243 lastLogonTimestamp=lastLogon,
1246 dsdb.UF_NORMAL_ACCOUNT,
1247 msDSUserAccountControlComputed=0)
1248 badPasswordTime = int(res[0]["badPasswordTime"][0])
1250 # Wrong old password
1252 self.ldb3.modify_ldif("""
1253 dn: cn=testuser,cn=users,""" + self.base_dn + """
1256 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1258 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1261 except LdbError, (num, msg):
1262 self.assertTrue('00000056' in msg)
1263 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1265 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1267 badPasswordTime=("greater", badPasswordTime),
1268 lastLogon=lastLogon,
1269 lastLogonTimestamp=lastLogon,
1272 dsdb.UF_NORMAL_ACCOUNT,
1273 msDSUserAccountControlComputed=0)
1274 badPasswordTime = int(res[0]["badPasswordTime"][0])
1276 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1277 # It doesn't reset "badPwdCount" = 0.
1278 self._reset_samr(res)
1280 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1282 badPasswordTime=badPasswordTime,
1283 lastLogon=lastLogon,
1284 lastLogonTimestamp=lastLogon,
1287 dsdb.UF_NORMAL_ACCOUNT,
1288 msDSUserAccountControlComputed=0)
1290 # Wrong old password
1292 self.ldb3.modify_ldif("""
1293 dn: cn=testuser,cn=users,""" + self.base_dn + """
1296 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1298 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1301 except LdbError, (num, msg):
1302 self.assertTrue('00000056' in msg)
1303 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1305 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1307 badPasswordTime=("greater", badPasswordTime),
1308 lastLogon=lastLogon,
1309 lastLogonTimestamp=lastLogon,
1310 lockoutTime=("greater", badPasswordTime),
1312 dsdb.UF_NORMAL_ACCOUNT,
1313 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1314 badPasswordTime = int(res[0]["badPasswordTime"][0])
1315 lockoutTime = int(res[0]["lockoutTime"][0])
1317 time.sleep(self.account_lockout_duration + 1)
1319 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1320 badPwdCount=3, effective_bad_password_count=0,
1321 badPasswordTime=badPasswordTime,
1322 lastLogon=lastLogon,
1323 lastLogonTimestamp=lastLogon,
1324 lockoutTime=lockoutTime,
1326 dsdb.UF_NORMAL_ACCOUNT,
1327 msDSUserAccountControlComputed=0)
1329 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1330 # It doesn't reset "lockoutTime" = 0 and doesn't
1331 # reset "badPwdCount" = 0.
1332 self._reset_samr(res)
1334 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1335 badPwdCount=3, effective_bad_password_count=0,
1336 badPasswordTime=badPasswordTime,
1337 lockoutTime=lockoutTime,
1338 lastLogon=lastLogon,
1339 lastLogonTimestamp=lastLogon,
1341 dsdb.UF_NORMAL_ACCOUNT,
1342 msDSUserAccountControlComputed=0)
1344 def _test_login_lockout(self, use_kerberos):
1345 # This unlocks by waiting for account_lockout_duration
1346 if use_kerberos == MUST_USE_KERBEROS:
1347 lastlogon_relation = 'greater'
1348 print "Performs a lockout attempt against LDAP using Kerberos"
1350 lastlogon_relation = 'equal'
1351 print "Performs a lockout attempt against LDAP using NTLM"
1353 # Change password on a connection as another user
1354 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1356 badPasswordTime=("greater", 0),
1357 lastLogon=("greater", 0),
1358 lastLogonTimestamp=("greater", 0),
1360 dsdb.UF_NORMAL_ACCOUNT,
1361 msDSUserAccountControlComputed=0)
1362 badPasswordTime = int(res[0]["badPasswordTime"][0])
1363 lastLogon = int(res[0]["lastLogon"][0])
1364 firstLogon = lastLogon
1365 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1367 print lastLogonTimestamp
1370 self.assertGreater(lastLogon, badPasswordTime)
1372 # Open a second LDB connection with the user credentials. Use the
1373 # command line credentials for informations like the domain, the realm
1374 # and the workstation.
1375 creds_lockout = insta_creds()
1376 creds_lockout.set_kerberos_state(use_kerberos)
1378 # The wrong password
1379 creds_lockout.set_password("thatsAcomplPASS1x")
1381 self.assertLoginFailure(host_url, creds_lockout, lp)
1383 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1385 badPasswordTime=("greater", badPasswordTime),
1386 lastLogon=lastLogon,
1387 lastLogonTimestamp=lastLogonTimestamp,
1389 dsdb.UF_NORMAL_ACCOUNT,
1390 msDSUserAccountControlComputed=0,
1391 msg='lastlogontimestamp with wrong password')
1392 badPasswordTime = int(res[0]["badPasswordTime"][0])
1394 # Correct old password
1395 creds_lockout.set_password("thatsAcomplPASS1")
1397 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1399 # lastLogonTimestamp should not change
1400 # lastLogon increases if badPwdCount is non-zero (!)
1401 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1403 badPasswordTime=badPasswordTime,
1404 lastLogon=('greater', lastLogon),
1405 lastLogonTimestamp=lastLogonTimestamp,
1407 dsdb.UF_NORMAL_ACCOUNT,
1408 msDSUserAccountControlComputed=0,
1409 msg='LLTimestamp is updated to lastlogon')
1411 lastLogon = int(res[0]["lastLogon"][0])
1412 self.assertGreater(lastLogon, badPasswordTime)
1414 # The wrong password
1415 creds_lockout.set_password("thatsAcomplPASS1x")
1417 self.assertLoginFailure(host_url, creds_lockout, lp)
1419 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1421 badPasswordTime=("greater", badPasswordTime),
1422 lastLogon=lastLogon,
1423 lastLogonTimestamp=lastLogonTimestamp,
1425 dsdb.UF_NORMAL_ACCOUNT,
1426 msDSUserAccountControlComputed=0)
1427 badPasswordTime = int(res[0]["badPasswordTime"][0])
1429 # The wrong password
1430 creds_lockout.set_password("thatsAcomplPASS1x")
1433 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1436 except LdbError, (num, msg):
1437 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1439 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1441 badPasswordTime=("greater", badPasswordTime),
1442 lastLogon=lastLogon,
1443 lastLogonTimestamp=lastLogonTimestamp,
1445 dsdb.UF_NORMAL_ACCOUNT,
1446 msDSUserAccountControlComputed=0)
1447 badPasswordTime = int(res[0]["badPasswordTime"][0])
1449 print "two failed password change"
1451 # The wrong password
1452 creds_lockout.set_password("thatsAcomplPASS1x")
1455 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1458 except LdbError, (num, msg):
1459 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1461 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1463 badPasswordTime=("greater", badPasswordTime),
1464 lastLogon=lastLogon,
1465 lastLogonTimestamp=lastLogonTimestamp,
1466 lockoutTime=("greater", badPasswordTime),
1468 dsdb.UF_NORMAL_ACCOUNT,
1469 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1470 badPasswordTime = int(res[0]["badPasswordTime"][0])
1471 lockoutTime = int(res[0]["lockoutTime"][0])
1473 # The wrong password
1474 creds_lockout.set_password("thatsAcomplPASS1x")
1476 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1478 except LdbError, (num, msg):
1479 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1481 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1483 badPasswordTime=badPasswordTime,
1484 lastLogon=lastLogon,
1485 lastLogonTimestamp=lastLogonTimestamp,
1486 lockoutTime=lockoutTime,
1488 dsdb.UF_NORMAL_ACCOUNT,
1489 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1491 # The wrong password
1492 creds_lockout.set_password("thatsAcomplPASS1x")
1494 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1496 except LdbError, (num, msg):
1497 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1499 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1501 badPasswordTime=badPasswordTime,
1502 lastLogon=lastLogon,
1503 lastLogonTimestamp=lastLogonTimestamp,
1504 lockoutTime=lockoutTime,
1506 dsdb.UF_NORMAL_ACCOUNT,
1507 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1509 # The correct password, but we are locked out
1510 creds_lockout.set_password("thatsAcomplPASS1")
1512 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1514 except LdbError, (num, msg):
1515 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1517 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1519 badPasswordTime=badPasswordTime,
1520 lastLogon=lastLogon,
1521 lastLogonTimestamp=lastLogonTimestamp,
1522 lockoutTime=lockoutTime,
1524 dsdb.UF_NORMAL_ACCOUNT,
1525 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1527 # wait for the lockout to end
1528 time.sleep(self.account_lockout_duration + 1)
1529 print self.account_lockout_duration + 1
1531 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1532 badPwdCount=3, effective_bad_password_count=0,
1533 badPasswordTime=badPasswordTime,
1534 lockoutTime=lockoutTime,
1535 lastLogon=lastLogon,
1536 lastLogonTimestamp=lastLogonTimestamp,
1538 dsdb.UF_NORMAL_ACCOUNT,
1539 msDSUserAccountControlComputed=0)
1541 lastLogon = int(res[0]["lastLogon"][0])
1543 # The correct password after letting the timeout expire
1545 creds_lockout.set_password("thatsAcomplPASS1")
1547 creds_lockout2 = insta_creds(creds_lockout)
1549 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout2, lp=lp)
1552 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1554 badPasswordTime=badPasswordTime,
1555 lastLogon=(lastlogon_relation, lastLogon),
1556 lastLogonTimestamp=lastLogonTimestamp,
1559 dsdb.UF_NORMAL_ACCOUNT,
1560 msDSUserAccountControlComputed=0,
1561 msg="lastLogon is way off")
1563 lastLogon = int(res[0]["lastLogon"][0])
1565 # The wrong password
1566 creds_lockout.set_password("thatsAcomplPASS1x")
1568 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1570 except LdbError, (num, msg):
1571 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1573 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1575 badPasswordTime=("greater", badPasswordTime),
1577 lastLogon=lastLogon,
1578 lastLogonTimestamp=lastLogonTimestamp,
1580 dsdb.UF_NORMAL_ACCOUNT,
1581 msDSUserAccountControlComputed=0)
1582 badPasswordTime = int(res[0]["badPasswordTime"][0])
1584 # The wrong password
1585 creds_lockout.set_password("thatsAcomplPASS1x")
1587 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1589 except LdbError, (num, msg):
1590 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1592 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1594 badPasswordTime=("greater", badPasswordTime),
1596 lastLogon=lastLogon,
1597 lastLogonTimestamp=lastLogonTimestamp,
1599 dsdb.UF_NORMAL_ACCOUNT,
1600 msDSUserAccountControlComputed=0)
1601 badPasswordTime = int(res[0]["badPasswordTime"][0])
1603 time.sleep(self.lockout_observation_window + 1)
1605 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1606 badPwdCount=2, effective_bad_password_count=0,
1607 badPasswordTime=badPasswordTime,
1609 lastLogon=lastLogon,
1610 lastLogonTimestamp=lastLogonTimestamp,
1612 dsdb.UF_NORMAL_ACCOUNT,
1613 msDSUserAccountControlComputed=0)
1615 # The wrong password
1616 creds_lockout.set_password("thatsAcomplPASS1x")
1618 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1620 except LdbError, (num, msg):
1621 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1623 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1625 badPasswordTime=("greater", badPasswordTime),
1627 lastLogon=lastLogon,
1628 lastLogonTimestamp=lastLogonTimestamp,
1630 dsdb.UF_NORMAL_ACCOUNT,
1631 msDSUserAccountControlComputed=0)
1632 badPasswordTime = int(res[0]["badPasswordTime"][0])
1634 # The correct password without letting the timeout expire
1635 creds_lockout.set_password("thatsAcomplPASS1")
1636 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1638 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1640 badPasswordTime=badPasswordTime,
1642 lastLogon=("greater", lastLogon),
1643 lastLogonTimestamp=lastLogonTimestamp,
1645 dsdb.UF_NORMAL_ACCOUNT,
1646 msDSUserAccountControlComputed=0)
1648 def test_login_lockout_ntlm(self):
1649 self._test_login_lockout(DONT_USE_KERBEROS)
1651 def test_login_lockout_kerberos(self):
1652 self._test_login_lockout(MUST_USE_KERBEROS)
1654 def _test_multiple_logon(self, use_kerberos):
1655 # Test the happy case in which a user logs on correctly, then
1656 # logs on correctly again, so that the bad password and
1657 # lockout times are both zero the second time. The lastlogon
1658 # time should increase.
1660 # Open a second LDB connection with the user credentials. Use the
1661 # command line credentials for informations like the domain, the realm
1662 # and the workstation.
1663 creds2 = insta_creds()
1664 creds2.set_kerberos_state(use_kerberos)
1665 self.assertEqual(creds2.get_kerberos_state(), use_kerberos)
1667 if use_kerberos == MUST_USE_KERBEROS:
1668 print "Testing multiple logon with Kerberos"
1669 lastlogon_relation = 'greater'
1671 print "Testing multiple logon with NTLM"
1672 lastlogon_relation = 'equal'
1674 SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
1676 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1678 badPasswordTime=("greater", 0),
1679 lastLogon=("greater", 0),
1680 lastLogonTimestamp=("greater", 0),
1682 dsdb.UF_NORMAL_ACCOUNT,
1683 msDSUserAccountControlComputed=0)
1684 badPasswordTime = int(res[0]["badPasswordTime"][0])
1685 lastLogon = int(res[0]["lastLogon"][0])
1686 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1687 firstLogon = lastLogon
1688 print "last logon is %d" % lastLogon
1689 self.assertGreater(lastLogon, badPasswordTime)
1692 SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
1694 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1696 badPasswordTime=badPasswordTime,
1697 lastLogon=(lastlogon_relation, lastLogon),
1698 lastLogonTimestamp=lastLogonTimestamp,
1700 dsdb.UF_NORMAL_ACCOUNT,
1701 msDSUserAccountControlComputed=0,
1702 msg=("second logon, firstlogon was %s" %
1706 lastLogon = int(res[0]["lastLogon"][0])
1710 SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
1712 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1714 badPasswordTime=badPasswordTime,
1715 lastLogon=(lastlogon_relation, lastLogon),
1716 lastLogonTimestamp=lastLogonTimestamp,
1718 dsdb.UF_NORMAL_ACCOUNT,
1719 msDSUserAccountControlComputed=0)
1721 def test_multiple_logon_ntlm(self):
1722 self._test_multiple_logon(DONT_USE_KERBEROS)
1724 def test_multiple_logon_kerberos(self):
1725 self._test_multiple_logon(MUST_USE_KERBEROS)
1728 super(PasswordTests, self).tearDown()
1729 delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
1730 delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
1731 delete_force(self.ldb, "cn=testuser3,cn=users," + self.base_dn)
1732 # Close the second LDB connection (with the user credentials)
1735 host_url = "ldap://%s" % host
1737 TestProgram(module=__name__, opts=subunitopts)