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 _readd_user(self):
281 # (Re)adds the test user "testuser" with no password atm
282 delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
284 "dn": "cn=testuser,cn=users," + self.base_dn,
285 "objectclass": "user",
286 "sAMAccountName": "testuser"})
288 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
292 lastLogonTimestamp=('absent', None),
294 dsdb.UF_NORMAL_ACCOUNT |
295 dsdb.UF_ACCOUNTDISABLE |
296 dsdb.UF_PASSWD_NOTREQD,
297 msDSUserAccountControlComputed=
298 dsdb.UF_PASSWORD_EXPIRED)
300 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
301 # It doesn't create "lockoutTime" = 0.
302 self._reset_samr(res)
304 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
308 lastLogonTimestamp=('absent', None),
310 dsdb.UF_NORMAL_ACCOUNT |
311 dsdb.UF_ACCOUNTDISABLE |
312 dsdb.UF_PASSWD_NOTREQD,
313 msDSUserAccountControlComputed=
314 dsdb.UF_PASSWORD_EXPIRED)
316 # Tests a password change when we don't have any password yet with a
319 self.ldb.modify_ldif("""
320 dn: cn=testuser,cn=users,""" + self.base_dn + """
323 userPassword: noPassword
325 userPassword: thatsAcomplPASS2
328 except LdbError, (num, msg):
329 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
330 # Windows (2008 at least) seems to have some small bug here: it
331 # returns "0000056A" on longer (always wrong) previous passwords.
332 self.assertTrue('00000056' in msg, msg)
334 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
336 badPasswordTime=("greater", 0),
338 lastLogonTimestamp=('absent', None),
340 dsdb.UF_NORMAL_ACCOUNT |
341 dsdb.UF_ACCOUNTDISABLE |
342 dsdb.UF_PASSWD_NOTREQD,
343 msDSUserAccountControlComputed=
344 dsdb.UF_PASSWORD_EXPIRED)
345 badPasswordTime = int(res[0]["badPasswordTime"][0])
347 # Sets the initial user password with a "special" password change
348 # I think that this internally is a password set operation and it can
349 # only be performed by someone which has password set privileges on the
350 # account (at least in s4 we do handle it like that).
351 self.ldb.modify_ldif("""
352 dn: cn=testuser,cn=users,""" + self.base_dn + """
356 userPassword: thatsAcomplPASS1
359 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
361 badPasswordTime=badPasswordTime,
363 lastLogonTimestamp=('absent', None),
365 dsdb.UF_NORMAL_ACCOUNT |
366 dsdb.UF_ACCOUNTDISABLE |
367 dsdb.UF_PASSWD_NOTREQD,
368 msDSUserAccountControlComputed=0)
370 # Enables the user account
371 self.ldb.enable_account("(sAMAccountName=testuser)")
373 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
375 badPasswordTime=badPasswordTime,
377 lastLogonTimestamp=('absent', None),
379 dsdb.UF_NORMAL_ACCOUNT,
380 msDSUserAccountControlComputed=0)
382 # Open a second LDB connection with the user credentials. Use the
383 # command line credentials for informations like the domain, the realm
384 # and the workstation.
385 creds2 = insta_creds()
387 ldb = SamDB(url=host_url, credentials=creds2, lp=lp)
389 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
391 badPasswordTime=badPasswordTime,
392 lastLogon=('greater', 0),
393 lastLogonTimestamp=('greater', 0),
395 dsdb.UF_NORMAL_ACCOUNT,
396 msDSUserAccountControlComputed=0)
398 lastLogon = int(res[0]["lastLogon"][0])
399 self.assertGreater(lastLogon, badPasswordTime)
402 def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS):
404 ldb = SamDB(url=url, credentials=creds, lp=lp)
405 self.fail("Login unexpectedly succeeded")
406 except LdbError, (num, msg):
407 if errno is not None:
408 self.assertEquals(num, errno, ("Login failed in the wrong way"
409 "(got err %d, expected %d)" %
413 super(PasswordTests, self).setUp()
415 self.ldb = SamDB(url=host_url, session_info=system_session(lp),
416 credentials=global_creds, lp=lp)
418 # Gets back the basedn
419 base_dn = self.ldb.domain_dn()
421 # Gets back the configuration basedn
422 configuration_dn = self.ldb.get_config_basedn().get_linearized()
424 # Get the old "dSHeuristics" if it was set
425 dsheuristics = self.ldb.get_dsheuristics()
427 # Reset the "dSHeuristics" as they were before
428 self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
430 res = self.ldb.search(base_dn,
431 scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
433 if "lockoutDuration" in res[0]:
434 lockoutDuration = res[0]["lockoutDuration"][0]
438 if "lockoutObservationWindow" in res[0]:
439 lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
441 lockoutObservationWindow = 0
443 if "lockoutThreshold" in res[0]:
444 lockoutThreshold = res[0]["lockoutThreshold"][0]
448 self.addCleanup(self.ldb.modify_ldif, """
449 dn: """ + base_dn + """
451 replace: lockoutDuration
452 lockoutDuration: """ + str(lockoutDuration) + """
453 replace: lockoutObservationWindow
454 lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
455 replace: lockoutThreshold
456 lockoutThreshold: """ + str(lockoutThreshold) + """
460 m.dn = Dn(self.ldb, base_dn)
462 self.account_lockout_duration = 2
463 account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7))
465 m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
466 FLAG_MOD_REPLACE, "lockoutDuration")
468 account_lockout_threshold = 3
469 m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
470 FLAG_MOD_REPLACE, "lockoutThreshold")
472 self.lockout_observation_window = 2
473 lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7))
475 m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
476 FLAG_MOD_REPLACE, "lockOutObservationWindow")
480 # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
481 self.ldb.set_dsheuristics("000000001")
483 # Get the old "minPwdAge"
484 minPwdAge = self.ldb.get_minPwdAge()
486 # Reset the "minPwdAge" as it was before
487 self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
489 # Set it temporarely to "0"
490 self.ldb.set_minPwdAge("0")
492 self.base_dn = self.ldb.domain_dn()
494 self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
495 self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % host, lp, global_creds)
496 self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
497 self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
499 self.ldb2 = self._readd_user()
501 # (Re)adds the test user "testuser3" with no password atm
502 delete_force(self.ldb, "cn=testuser3,cn=users," + self.base_dn)
504 "dn": "cn=testuser3,cn=users," + self.base_dn,
505 "objectclass": "user",
506 "sAMAccountName": "testuser3"})
508 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
512 lastLogonTimestamp=('absent', None),
514 dsdb.UF_NORMAL_ACCOUNT |
515 dsdb.UF_ACCOUNTDISABLE |
516 dsdb.UF_PASSWD_NOTREQD,
517 msDSUserAccountControlComputed=
518 dsdb.UF_PASSWORD_EXPIRED)
520 # Tests a password change when we don't have any password yet with a
523 self.ldb.modify_ldif("""
524 dn: cn=testuser3,cn=users,""" + self.base_dn + """
527 userPassword: noPassword
529 userPassword: thatsAcomplPASS2
532 except LdbError, (num, msg):
533 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
534 # Windows (2008 at least) seems to have some small bug here: it
535 # returns "0000056A" on longer (always wrong) previous passwords.
536 self.assertTrue('00000056' in msg, msg)
538 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
540 badPasswordTime=("greater", 0),
542 lastLogonTimestamp=('absent', None),
544 dsdb.UF_NORMAL_ACCOUNT |
545 dsdb.UF_ACCOUNTDISABLE |
546 dsdb.UF_PASSWD_NOTREQD,
547 msDSUserAccountControlComputed=
548 dsdb.UF_PASSWORD_EXPIRED)
549 badPasswordTime3 = int(res[0]["badPasswordTime"][0])
551 # Sets the initial user password with a "special" password change
552 # I think that this internally is a password set operation and it can
553 # only be performed by someone which has password set privileges on the
554 # account (at least in s4 we do handle it like that).
555 self.ldb.modify_ldif("""
556 dn: cn=testuser3,cn=users,""" + self.base_dn + """
560 userPassword: thatsAcomplPASS1
563 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
565 badPasswordTime=badPasswordTime3,
567 lastLogonTimestamp=('absent', None),
569 dsdb.UF_NORMAL_ACCOUNT |
570 dsdb.UF_ACCOUNTDISABLE |
571 dsdb.UF_PASSWD_NOTREQD,
572 msDSUserAccountControlComputed=0)
574 # Enables the user account
575 self.ldb.enable_account("(sAMAccountName=testuser3)")
577 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
579 badPasswordTime=badPasswordTime3,
581 lastLogonTimestamp=('absent', None),
583 dsdb.UF_NORMAL_ACCOUNT,
584 msDSUserAccountControlComputed=0)
586 # Open a second LDB connection with the user credentials. Use the
587 # command line credentials for informations like the domain, the realm
588 # and the workstation.
589 creds3 = insta_creds()
590 creds3.set_username("testuser3")
591 creds3.set_password("thatsAcomplPASS1")
592 self.ldb3 = SamDB(url=host_url, credentials=creds3, lp=lp)
594 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
596 badPasswordTime=badPasswordTime3,
597 lastLogon=('greater', badPasswordTime3),
598 lastLogonTimestamp=('greater', badPasswordTime3),
600 dsdb.UF_NORMAL_ACCOUNT,
601 msDSUserAccountControlComputed=0)
603 def _test_userPassword_lockout_with_clear_change(self, method):
604 print "Performs a password cleartext change operation on 'userPassword'"
605 # Notice: This works only against Windows if "dSHeuristics" has been set
608 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
610 badPasswordTime=("greater", 0),
611 lastLogon=('greater', 0),
612 lastLogonTimestamp=('greater', 0),
614 dsdb.UF_NORMAL_ACCOUNT,
615 msDSUserAccountControlComputed=0)
616 badPasswordTime = int(res[0]["badPasswordTime"][0])
617 lastLogon = int(res[0]["lastLogon"][0])
618 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
620 # Change password on a connection as another user
624 self.ldb3.modify_ldif("""
625 dn: cn=testuser,cn=users,""" + self.base_dn + """
628 userPassword: thatsAcomplPASS1x
630 userPassword: thatsAcomplPASS2
633 except LdbError, (num, msg):
634 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
635 self.assertTrue('00000056' in msg, msg)
637 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
639 badPasswordTime=("greater", badPasswordTime),
641 lastLogonTimestamp=lastLogonTimestamp,
643 dsdb.UF_NORMAL_ACCOUNT,
644 msDSUserAccountControlComputed=0)
645 badPasswordTime = int(res[0]["badPasswordTime"][0])
647 # Correct old password
648 self.ldb3.modify_ldif("""
649 dn: cn=testuser,cn=users,""" + self.base_dn + """
652 userPassword: thatsAcomplPASS1
654 userPassword: thatsAcomplPASS2
657 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
659 badPasswordTime=badPasswordTime,
661 lastLogonTimestamp=lastLogonTimestamp,
663 dsdb.UF_NORMAL_ACCOUNT,
664 msDSUserAccountControlComputed=0)
668 self.ldb3.modify_ldif("""
669 dn: cn=testuser,cn=users,""" + self.base_dn + """
672 userPassword: thatsAcomplPASS1x
674 userPassword: thatsAcomplPASS2
677 except LdbError, (num, msg):
678 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
679 self.assertTrue('00000056' in msg, msg)
681 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
683 badPasswordTime=("greater", badPasswordTime),
685 lastLogonTimestamp=lastLogonTimestamp,
687 dsdb.UF_NORMAL_ACCOUNT,
688 msDSUserAccountControlComputed=0)
689 badPasswordTime = int(res[0]["badPasswordTime"][0])
691 print "two failed password change"
695 self.ldb3.modify_ldif("""
696 dn: cn=testuser,cn=users,""" + self.base_dn + """
699 userPassword: thatsAcomplPASS1x
701 userPassword: thatsAcomplPASS2
704 except LdbError, (num, msg):
705 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
706 self.assertTrue('00000056' in msg, msg)
708 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
710 badPasswordTime=("greater", badPasswordTime),
712 lastLogonTimestamp=lastLogonTimestamp,
713 lockoutTime=("greater", badPasswordTime),
715 dsdb.UF_NORMAL_ACCOUNT,
716 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
717 badPasswordTime = int(res[0]["badPasswordTime"][0])
718 lockoutTime = int(res[0]["lockoutTime"][0])
722 self.ldb3.modify_ldif("""
723 dn: cn=testuser,cn=users,""" + self.base_dn + """
726 userPassword: thatsAcomplPASS1x
728 userPassword: thatsAcomplPASS2
731 except LdbError, (num, msg):
732 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
733 self.assertTrue('00000775' in msg, msg)
735 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
737 badPasswordTime=badPasswordTime,
739 lastLogonTimestamp=lastLogonTimestamp,
740 lockoutTime=lockoutTime,
742 dsdb.UF_NORMAL_ACCOUNT,
743 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
747 self.ldb3.modify_ldif("""
748 dn: cn=testuser,cn=users,""" + self.base_dn + """
751 userPassword: thatsAcomplPASS1x
753 userPassword: thatsAcomplPASS2
756 except LdbError, (num, msg):
757 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
758 self.assertTrue('00000775' in msg, msg)
760 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
762 badPasswordTime=badPasswordTime,
763 lockoutTime=lockoutTime,
765 lastLogonTimestamp=lastLogonTimestamp,
767 dsdb.UF_NORMAL_ACCOUNT,
768 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
771 # Correct old password
772 self.ldb3.modify_ldif("""
773 dn: cn=testuser,cn=users,""" + self.base_dn + """
776 userPassword: thatsAcomplPASS2
778 userPassword: thatsAcomplPASS2x
781 except LdbError, (num, msg):
782 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
783 self.assertTrue('00000775' in msg, msg)
785 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
787 badPasswordTime=badPasswordTime,
789 lastLogonTimestamp=lastLogonTimestamp,
790 lockoutTime=lockoutTime,
792 dsdb.UF_NORMAL_ACCOUNT,
793 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
795 # Now reset the password, which does NOT change the lockout!
796 self.ldb.modify_ldif("""
797 dn: cn=testuser,cn=users,""" + self.base_dn + """
799 replace: userPassword
800 userPassword: thatsAcomplPASS2
803 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
805 badPasswordTime=badPasswordTime,
807 lastLogonTimestamp=lastLogonTimestamp,
808 lockoutTime=lockoutTime,
810 dsdb.UF_NORMAL_ACCOUNT,
811 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
814 # Correct old password
815 self.ldb3.modify_ldif("""
816 dn: cn=testuser,cn=users,""" + self.base_dn + """
819 userPassword: thatsAcomplPASS2
821 userPassword: thatsAcomplPASS2x
824 except LdbError, (num, msg):
825 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
826 self.assertTrue('00000775' in msg, msg)
828 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
830 badPasswordTime=badPasswordTime,
832 lastLogonTimestamp=lastLogonTimestamp,
833 lockoutTime=lockoutTime,
835 dsdb.UF_NORMAL_ACCOUNT,
836 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
839 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
840 m["userAccountControl"] = MessageElement(
841 str(dsdb.UF_LOCKOUT),
842 FLAG_MOD_REPLACE, "userAccountControl")
846 # This shows that setting the UF_LOCKOUT flag alone makes no difference
847 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
849 badPasswordTime=badPasswordTime,
851 lastLogonTimestamp=lastLogonTimestamp,
852 lockoutTime=lockoutTime,
854 dsdb.UF_NORMAL_ACCOUNT,
855 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
857 # This shows that setting the UF_LOCKOUT flag makes no difference
859 # Correct old password
860 self.ldb3.modify_ldif("""
861 dn: cn=testuser,cn=users,""" + self.base_dn + """
864 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
866 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
869 except LdbError, (num, msg):
870 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
871 self.assertTrue('00000775' in msg, msg)
873 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
875 badPasswordTime=badPasswordTime,
876 lockoutTime=lockoutTime,
878 lastLogonTimestamp=lastLogonTimestamp,
880 dsdb.UF_NORMAL_ACCOUNT,
881 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
883 self._reset_by_method(res, method)
885 # Here bad password counts are reset without logon success.
886 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
888 badPasswordTime=badPasswordTime,
891 lastLogonTimestamp=lastLogonTimestamp,
893 dsdb.UF_NORMAL_ACCOUNT,
894 msDSUserAccountControlComputed=0)
896 # The correct password after doing the unlock
898 self.ldb3.modify_ldif("""
899 dn: cn=testuser,cn=users,""" + self.base_dn + """
902 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
904 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
907 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
909 badPasswordTime=badPasswordTime,
912 lastLogonTimestamp=lastLogonTimestamp,
914 dsdb.UF_NORMAL_ACCOUNT,
915 msDSUserAccountControlComputed=0)
919 self.ldb3.modify_ldif("""
920 dn: cn=testuser,cn=users,""" + self.base_dn + """
923 userPassword: thatsAcomplPASS1xyz
925 userPassword: thatsAcomplPASS2XYZ
928 except LdbError, (num, msg):
929 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
930 self.assertTrue('00000056' in msg, msg)
932 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
934 badPasswordTime=("greater", badPasswordTime),
937 lastLogonTimestamp=lastLogonTimestamp,
939 dsdb.UF_NORMAL_ACCOUNT,
940 msDSUserAccountControlComputed=0)
941 badPasswordTime = int(res[0]["badPasswordTime"][0])
945 self.ldb3.modify_ldif("""
946 dn: cn=testuser,cn=users,""" + self.base_dn + """
949 userPassword: thatsAcomplPASS1xyz
951 userPassword: thatsAcomplPASS2XYZ
954 except LdbError, (num, msg):
955 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
956 self.assertTrue('00000056' in msg, msg)
958 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
960 badPasswordTime=("greater", badPasswordTime),
963 lastLogonTimestamp=lastLogonTimestamp,
965 dsdb.UF_NORMAL_ACCOUNT,
966 msDSUserAccountControlComputed=0)
967 badPasswordTime = int(res[0]["badPasswordTime"][0])
969 self._reset_ldap_lockoutTime(res)
971 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
973 badPasswordTime=badPasswordTime,
975 lastLogonTimestamp=lastLogonTimestamp,
978 dsdb.UF_NORMAL_ACCOUNT,
979 msDSUserAccountControlComputed=0)
981 def test_userPassword_lockout_with_clear_change_ldap_userAccountControl(self):
982 self._test_userPassword_lockout_with_clear_change("ldap_userAccountControl")
984 def test_userPassword_lockout_with_clear_change_ldap_lockoutTime(self):
985 self._test_userPassword_lockout_with_clear_change("ldap_lockoutTime")
987 def test_userPassword_lockout_with_clear_change_samr(self):
988 self._test_userPassword_lockout_with_clear_change("samr")
991 def test_unicodePwd_lockout_with_clear_change(self):
992 print "Performs a password cleartext change operation on 'unicodePwd'"
994 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
996 badPasswordTime=("greater", 0),
997 lastLogon=("greater", 0),
998 lastLogonTimestamp=("greater", 0),
1000 dsdb.UF_NORMAL_ACCOUNT,
1001 msDSUserAccountControlComputed=0)
1002 badPasswordTime = int(res[0]["badPasswordTime"][0])
1003 lastLogon = int(res[0]["lastLogon"][0])
1005 # Change password on a connection as another user
1007 # Wrong old password
1009 self.ldb3.modify_ldif("""
1010 dn: cn=testuser,cn=users,""" + self.base_dn + """
1013 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1015 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1018 except LdbError, (num, msg):
1019 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1020 self.assertTrue('00000056' in msg, msg)
1022 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1024 badPasswordTime=("greater", badPasswordTime),
1025 lastLogon=lastLogon,
1026 lastLogonTimestamp=lastLogon,
1028 dsdb.UF_NORMAL_ACCOUNT,
1029 msDSUserAccountControlComputed=0)
1030 badPasswordTime = int(res[0]["badPasswordTime"][0])
1032 # Correct old password
1033 self.ldb3.modify_ldif("""
1034 dn: cn=testuser,cn=users,""" + self.base_dn + """
1037 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
1039 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1042 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1044 badPasswordTime=badPasswordTime,
1045 lastLogon=lastLogon,
1046 lastLogonTimestamp=lastLogon,
1048 dsdb.UF_NORMAL_ACCOUNT,
1049 msDSUserAccountControlComputed=0)
1051 # Wrong old password
1053 self.ldb3.modify_ldif("""
1054 dn: cn=testuser,cn=users,""" + self.base_dn + """
1057 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
1059 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1062 except LdbError, (num, msg):
1063 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1064 self.assertTrue('00000056' in msg, msg)
1066 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1068 badPasswordTime=("greater", badPasswordTime),
1069 lastLogon=lastLogon,
1070 lastLogonTimestamp=lastLogon,
1072 dsdb.UF_NORMAL_ACCOUNT,
1073 msDSUserAccountControlComputed=0)
1074 badPasswordTime = int(res[0]["badPasswordTime"][0])
1076 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1077 # It doesn't create "lockoutTime" = 0 and doesn't
1078 # reset "badPwdCount" = 0.
1079 self._reset_samr(res)
1081 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1083 badPasswordTime=badPasswordTime,
1084 lastLogon=lastLogon,
1085 lastLogonTimestamp=lastLogon,
1087 dsdb.UF_NORMAL_ACCOUNT,
1088 msDSUserAccountControlComputed=0)
1090 print "two failed password change"
1092 # Wrong old password
1094 self.ldb3.modify_ldif("""
1095 dn: cn=testuser,cn=users,""" + self.base_dn + """
1098 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1100 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1103 except LdbError, (num, msg):
1104 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1105 self.assertTrue('00000056' in msg, msg)
1107 # this is strange, why do we have lockoutTime=badPasswordTime here?
1108 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1110 badPasswordTime=("greater", badPasswordTime),
1111 lastLogon=lastLogon,
1112 lastLogonTimestamp=lastLogon,
1113 lockoutTime=("greater", badPasswordTime),
1115 dsdb.UF_NORMAL_ACCOUNT,
1116 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1117 badPasswordTime = int(res[0]["badPasswordTime"][0])
1118 lockoutTime = int(res[0]["lockoutTime"][0])
1120 # Wrong old password
1122 self.ldb3.modify_ldif("""
1123 dn: cn=testuser,cn=users,""" + self.base_dn + """
1126 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1128 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1131 except LdbError, (num, msg):
1132 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1133 self.assertTrue('00000775' in msg, msg)
1135 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1137 badPasswordTime=badPasswordTime,
1138 lastLogon=lastLogon,
1139 lastLogonTimestamp=lastLogon,
1140 lockoutTime=lockoutTime,
1142 dsdb.UF_NORMAL_ACCOUNT,
1143 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1145 # Wrong old password
1147 self.ldb3.modify_ldif("""
1148 dn: cn=testuser,cn=users,""" + self.base_dn + """
1151 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1153 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1156 except LdbError, (num, msg):
1157 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1158 self.assertTrue('00000775' in msg, msg)
1160 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1162 badPasswordTime=badPasswordTime,
1163 lastLogon=lastLogon,
1164 lastLogonTimestamp=lastLogon,
1165 lockoutTime=lockoutTime,
1167 dsdb.UF_NORMAL_ACCOUNT,
1168 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1171 # Correct old password
1172 self.ldb3.modify_ldif("""
1173 dn: cn=testuser,cn=users,""" + self.base_dn + """
1176 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1178 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
1181 except LdbError, (num, msg):
1182 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1183 self.assertTrue('00000775' in msg, msg)
1185 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1187 badPasswordTime=badPasswordTime,
1188 lastLogon=lastLogon,
1189 lastLogonTimestamp=lastLogon,
1190 lockoutTime=lockoutTime,
1192 dsdb.UF_NORMAL_ACCOUNT,
1193 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1195 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
1196 self._reset_samr(res);
1198 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1200 badPasswordTime=badPasswordTime,
1201 lastLogon=lastLogon,
1202 lastLogonTimestamp=lastLogon,
1205 dsdb.UF_NORMAL_ACCOUNT,
1206 msDSUserAccountControlComputed=0)
1208 # Correct old password
1209 self.ldb3.modify_ldif("""
1210 dn: cn=testuser,cn=users,""" + self.base_dn + """
1213 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1215 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
1218 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1220 badPasswordTime=badPasswordTime,
1221 lastLogon=lastLogon,
1222 lastLogonTimestamp=lastLogon,
1225 dsdb.UF_NORMAL_ACCOUNT,
1226 msDSUserAccountControlComputed=0)
1228 # Wrong old password
1230 self.ldb3.modify_ldif("""
1231 dn: cn=testuser,cn=users,""" + self.base_dn + """
1234 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1236 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1239 except LdbError, (num, msg):
1240 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1241 self.assertTrue('00000056' in msg, msg)
1243 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1245 badPasswordTime=("greater", badPasswordTime),
1246 lastLogon=lastLogon,
1247 lastLogonTimestamp=lastLogon,
1250 dsdb.UF_NORMAL_ACCOUNT,
1251 msDSUserAccountControlComputed=0)
1252 badPasswordTime = int(res[0]["badPasswordTime"][0])
1254 # Wrong old password
1256 self.ldb3.modify_ldif("""
1257 dn: cn=testuser,cn=users,""" + self.base_dn + """
1260 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1262 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1265 except LdbError, (num, msg):
1266 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1267 self.assertTrue('00000056' in msg, msg)
1269 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1271 badPasswordTime=("greater", badPasswordTime),
1272 lastLogon=lastLogon,
1273 lastLogonTimestamp=lastLogon,
1276 dsdb.UF_NORMAL_ACCOUNT,
1277 msDSUserAccountControlComputed=0)
1278 badPasswordTime = int(res[0]["badPasswordTime"][0])
1280 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1281 # It doesn't reset "badPwdCount" = 0.
1282 self._reset_samr(res)
1284 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1286 badPasswordTime=badPasswordTime,
1287 lastLogon=lastLogon,
1288 lastLogonTimestamp=lastLogon,
1291 dsdb.UF_NORMAL_ACCOUNT,
1292 msDSUserAccountControlComputed=0)
1294 # Wrong old password
1296 self.ldb3.modify_ldif("""
1297 dn: cn=testuser,cn=users,""" + self.base_dn + """
1300 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1302 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1305 except LdbError, (num, msg):
1306 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1307 self.assertTrue('00000056' in msg, msg)
1309 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1311 badPasswordTime=("greater", badPasswordTime),
1312 lastLogon=lastLogon,
1313 lastLogonTimestamp=lastLogon,
1314 lockoutTime=("greater", badPasswordTime),
1316 dsdb.UF_NORMAL_ACCOUNT,
1317 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1318 badPasswordTime = int(res[0]["badPasswordTime"][0])
1319 lockoutTime = int(res[0]["lockoutTime"][0])
1321 time.sleep(self.account_lockout_duration + 1)
1323 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1324 badPwdCount=3, effective_bad_password_count=0,
1325 badPasswordTime=badPasswordTime,
1326 lastLogon=lastLogon,
1327 lastLogonTimestamp=lastLogon,
1328 lockoutTime=lockoutTime,
1330 dsdb.UF_NORMAL_ACCOUNT,
1331 msDSUserAccountControlComputed=0)
1333 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1334 # It doesn't reset "lockoutTime" = 0 and doesn't
1335 # reset "badPwdCount" = 0.
1336 self._reset_samr(res)
1338 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1339 badPwdCount=3, effective_bad_password_count=0,
1340 badPasswordTime=badPasswordTime,
1341 lockoutTime=lockoutTime,
1342 lastLogon=lastLogon,
1343 lastLogonTimestamp=lastLogon,
1345 dsdb.UF_NORMAL_ACCOUNT,
1346 msDSUserAccountControlComputed=0)
1348 def _test_login_lockout(self, use_kerberos):
1349 # This unlocks by waiting for account_lockout_duration
1350 if use_kerberos == MUST_USE_KERBEROS:
1351 lastlogon_relation = 'greater'
1352 print "Performs a lockout attempt against LDAP using Kerberos"
1354 lastlogon_relation = 'equal'
1355 print "Performs a lockout attempt against LDAP using NTLM"
1357 # Change password on a connection as another user
1358 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1360 badPasswordTime=("greater", 0),
1361 lastLogon=("greater", 0),
1362 lastLogonTimestamp=("greater", 0),
1364 dsdb.UF_NORMAL_ACCOUNT,
1365 msDSUserAccountControlComputed=0)
1366 badPasswordTime = int(res[0]["badPasswordTime"][0])
1367 lastLogon = int(res[0]["lastLogon"][0])
1368 firstLogon = lastLogon
1369 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1371 print lastLogonTimestamp
1374 self.assertGreater(lastLogon, badPasswordTime)
1376 # Open a second LDB connection with the user credentials. Use the
1377 # command line credentials for informations like the domain, the realm
1378 # and the workstation.
1379 creds_lockout = insta_creds()
1380 creds_lockout.set_kerberos_state(use_kerberos)
1382 # The wrong password
1383 creds_lockout.set_password("thatsAcomplPASS1x")
1385 self.assertLoginFailure(host_url, creds_lockout, lp)
1387 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1389 badPasswordTime=("greater", badPasswordTime),
1390 lastLogon=lastLogon,
1391 lastLogonTimestamp=lastLogonTimestamp,
1393 dsdb.UF_NORMAL_ACCOUNT,
1394 msDSUserAccountControlComputed=0,
1395 msg='lastlogontimestamp with wrong password')
1396 badPasswordTime = int(res[0]["badPasswordTime"][0])
1398 # Correct old password
1399 creds_lockout.set_password("thatsAcomplPASS1")
1401 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1403 # lastLogonTimestamp should not change
1404 # lastLogon increases if badPwdCount is non-zero (!)
1405 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1407 badPasswordTime=badPasswordTime,
1408 lastLogon=('greater', lastLogon),
1409 lastLogonTimestamp=lastLogonTimestamp,
1411 dsdb.UF_NORMAL_ACCOUNT,
1412 msDSUserAccountControlComputed=0,
1413 msg='LLTimestamp is updated to lastlogon')
1415 lastLogon = int(res[0]["lastLogon"][0])
1416 self.assertGreater(lastLogon, badPasswordTime)
1418 # The wrong password
1419 creds_lockout.set_password("thatsAcomplPASS1x")
1421 self.assertLoginFailure(host_url, creds_lockout, lp)
1423 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1425 badPasswordTime=("greater", badPasswordTime),
1426 lastLogon=lastLogon,
1427 lastLogonTimestamp=lastLogonTimestamp,
1429 dsdb.UF_NORMAL_ACCOUNT,
1430 msDSUserAccountControlComputed=0)
1431 badPasswordTime = int(res[0]["badPasswordTime"][0])
1433 # The wrong password
1434 creds_lockout.set_password("thatsAcomplPASS1x")
1437 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1440 except LdbError, (num, msg):
1441 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1443 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1445 badPasswordTime=("greater", badPasswordTime),
1446 lastLogon=lastLogon,
1447 lastLogonTimestamp=lastLogonTimestamp,
1449 dsdb.UF_NORMAL_ACCOUNT,
1450 msDSUserAccountControlComputed=0)
1451 badPasswordTime = int(res[0]["badPasswordTime"][0])
1453 print "two failed password change"
1455 # The wrong password
1456 creds_lockout.set_password("thatsAcomplPASS1x")
1459 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1462 except LdbError, (num, msg):
1463 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1465 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1467 badPasswordTime=("greater", badPasswordTime),
1468 lastLogon=lastLogon,
1469 lastLogonTimestamp=lastLogonTimestamp,
1470 lockoutTime=("greater", badPasswordTime),
1472 dsdb.UF_NORMAL_ACCOUNT,
1473 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1474 badPasswordTime = int(res[0]["badPasswordTime"][0])
1475 lockoutTime = int(res[0]["lockoutTime"][0])
1477 # The wrong password
1478 creds_lockout.set_password("thatsAcomplPASS1x")
1480 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1482 except LdbError, (num, msg):
1483 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1485 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1487 badPasswordTime=badPasswordTime,
1488 lastLogon=lastLogon,
1489 lastLogonTimestamp=lastLogonTimestamp,
1490 lockoutTime=lockoutTime,
1492 dsdb.UF_NORMAL_ACCOUNT,
1493 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1495 # The wrong password
1496 creds_lockout.set_password("thatsAcomplPASS1x")
1498 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1500 except LdbError, (num, msg):
1501 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1503 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1505 badPasswordTime=badPasswordTime,
1506 lastLogon=lastLogon,
1507 lastLogonTimestamp=lastLogonTimestamp,
1508 lockoutTime=lockoutTime,
1510 dsdb.UF_NORMAL_ACCOUNT,
1511 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1513 # The correct password, but we are locked out
1514 creds_lockout.set_password("thatsAcomplPASS1")
1516 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1518 except LdbError, (num, msg):
1519 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1521 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1523 badPasswordTime=badPasswordTime,
1524 lastLogon=lastLogon,
1525 lastLogonTimestamp=lastLogonTimestamp,
1526 lockoutTime=lockoutTime,
1528 dsdb.UF_NORMAL_ACCOUNT,
1529 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1531 # wait for the lockout to end
1532 time.sleep(self.account_lockout_duration + 1)
1533 print self.account_lockout_duration + 1
1535 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1536 badPwdCount=3, effective_bad_password_count=0,
1537 badPasswordTime=badPasswordTime,
1538 lockoutTime=lockoutTime,
1539 lastLogon=lastLogon,
1540 lastLogonTimestamp=lastLogonTimestamp,
1542 dsdb.UF_NORMAL_ACCOUNT,
1543 msDSUserAccountControlComputed=0)
1545 lastLogon = int(res[0]["lastLogon"][0])
1547 # The correct password after letting the timeout expire
1549 creds_lockout.set_password("thatsAcomplPASS1")
1551 creds_lockout2 = insta_creds(creds_lockout)
1553 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout2, lp=lp)
1556 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1558 badPasswordTime=badPasswordTime,
1559 lastLogon=(lastlogon_relation, lastLogon),
1560 lastLogonTimestamp=lastLogonTimestamp,
1563 dsdb.UF_NORMAL_ACCOUNT,
1564 msDSUserAccountControlComputed=0,
1565 msg="lastLogon is way off")
1567 lastLogon = int(res[0]["lastLogon"][0])
1569 # The wrong password
1570 creds_lockout.set_password("thatsAcomplPASS1x")
1572 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1574 except LdbError, (num, msg):
1575 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1577 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1579 badPasswordTime=("greater", badPasswordTime),
1581 lastLogon=lastLogon,
1582 lastLogonTimestamp=lastLogonTimestamp,
1584 dsdb.UF_NORMAL_ACCOUNT,
1585 msDSUserAccountControlComputed=0)
1586 badPasswordTime = int(res[0]["badPasswordTime"][0])
1588 # The wrong password
1589 creds_lockout.set_password("thatsAcomplPASS1x")
1591 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1593 except LdbError, (num, msg):
1594 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1596 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1598 badPasswordTime=("greater", badPasswordTime),
1600 lastLogon=lastLogon,
1601 lastLogonTimestamp=lastLogonTimestamp,
1603 dsdb.UF_NORMAL_ACCOUNT,
1604 msDSUserAccountControlComputed=0)
1605 badPasswordTime = int(res[0]["badPasswordTime"][0])
1607 time.sleep(self.lockout_observation_window + 1)
1609 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1610 badPwdCount=2, effective_bad_password_count=0,
1611 badPasswordTime=badPasswordTime,
1613 lastLogon=lastLogon,
1614 lastLogonTimestamp=lastLogonTimestamp,
1616 dsdb.UF_NORMAL_ACCOUNT,
1617 msDSUserAccountControlComputed=0)
1619 # The wrong password
1620 creds_lockout.set_password("thatsAcomplPASS1x")
1622 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1624 except LdbError, (num, msg):
1625 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1627 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1629 badPasswordTime=("greater", badPasswordTime),
1631 lastLogon=lastLogon,
1632 lastLogonTimestamp=lastLogonTimestamp,
1634 dsdb.UF_NORMAL_ACCOUNT,
1635 msDSUserAccountControlComputed=0)
1636 badPasswordTime = int(res[0]["badPasswordTime"][0])
1638 # The correct password without letting the timeout expire
1639 creds_lockout.set_password("thatsAcomplPASS1")
1640 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1642 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1644 badPasswordTime=badPasswordTime,
1646 lastLogon=("greater", lastLogon),
1647 lastLogonTimestamp=lastLogonTimestamp,
1649 dsdb.UF_NORMAL_ACCOUNT,
1650 msDSUserAccountControlComputed=0)
1652 def test_login_lockout_ntlm(self):
1653 self._test_login_lockout(DONT_USE_KERBEROS)
1655 def test_login_lockout_kerberos(self):
1656 self._test_login_lockout(MUST_USE_KERBEROS)
1658 def _test_multiple_logon(self, use_kerberos):
1659 # Test the happy case in which a user logs on correctly, then
1660 # logs on correctly again, so that the bad password and
1661 # lockout times are both zero the second time. The lastlogon
1662 # time should increase.
1664 # Open a second LDB connection with the user credentials. Use the
1665 # command line credentials for informations like the domain, the realm
1666 # and the workstation.
1667 creds2 = insta_creds()
1668 creds2.set_kerberos_state(use_kerberos)
1669 self.assertEqual(creds2.get_kerberos_state(), use_kerberos)
1671 if use_kerberos == MUST_USE_KERBEROS:
1672 print "Testing multiple logon with Kerberos"
1673 lastlogon_relation = 'greater'
1675 print "Testing multiple logon with NTLM"
1676 lastlogon_relation = 'equal'
1678 SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
1680 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1682 badPasswordTime=("greater", 0),
1683 lastLogon=("greater", 0),
1684 lastLogonTimestamp=("greater", 0),
1686 dsdb.UF_NORMAL_ACCOUNT,
1687 msDSUserAccountControlComputed=0)
1688 badPasswordTime = int(res[0]["badPasswordTime"][0])
1689 lastLogon = int(res[0]["lastLogon"][0])
1690 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1691 firstLogon = lastLogon
1692 print "last logon is %d" % lastLogon
1693 self.assertGreater(lastLogon, badPasswordTime)
1696 SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
1698 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1700 badPasswordTime=badPasswordTime,
1701 lastLogon=(lastlogon_relation, lastLogon),
1702 lastLogonTimestamp=lastLogonTimestamp,
1704 dsdb.UF_NORMAL_ACCOUNT,
1705 msDSUserAccountControlComputed=0,
1706 msg=("second logon, firstlogon was %s" %
1710 lastLogon = int(res[0]["lastLogon"][0])
1714 SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
1716 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1718 badPasswordTime=badPasswordTime,
1719 lastLogon=(lastlogon_relation, lastLogon),
1720 lastLogonTimestamp=lastLogonTimestamp,
1722 dsdb.UF_NORMAL_ACCOUNT,
1723 msDSUserAccountControlComputed=0)
1725 def test_multiple_logon_ntlm(self):
1726 self._test_multiple_logon(DONT_USE_KERBEROS)
1728 def test_multiple_logon_kerberos(self):
1729 self._test_multiple_logon(MUST_USE_KERBEROS)
1732 super(PasswordTests, self).tearDown()
1733 delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
1734 delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
1735 delete_force(self.ldb, "cn=testuser3,cn=users," + self.base_dn)
1736 # Close the second LDB connection (with the user credentials)
1739 host_url = "ldap://%s" % host
1741 TestProgram(module=__name__, opts=subunitopts)