2 # -*- coding: utf-8 -*-
3 # This tests the password lockout behavior for AD implementations
5 # Copyright Matthias Dieter Wallnoefer 2010
6 # Copyright Andrew Bartlett 2013
7 # Copyright Stefan Metzmacher 2014
15 sys.path.insert(0, "bin/python")
18 from samba.tests.subunitrun import TestProgram, SubunitOptions
20 import samba.getopt as options
22 from samba.auth import system_session
23 from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS
24 from ldb import SCOPE_BASE, LdbError
25 from ldb import ERR_CONSTRAINT_VIOLATION
26 from ldb import ERR_INVALID_CREDENTIALS
27 from ldb import Message, MessageElement, Dn
28 from ldb import FLAG_MOD_REPLACE
29 from samba import gensec, dsdb
30 from samba.samdb import SamDB
32 from samba.tests import delete_force
33 from samba.dcerpc import security, samr
34 from samba.ndr import ndr_unpack
36 parser = optparse.OptionParser("password_lockout.py [options] <host>")
37 sambaopts = options.SambaOptions(parser)
38 parser.add_option_group(sambaopts)
39 parser.add_option_group(options.VersionOptions(parser))
40 # use command line creds if available
41 credopts = options.CredentialsOptions(parser)
42 parser.add_option_group(credopts)
43 subunitopts = SubunitOptions(parser)
44 parser.add_option_group(subunitopts)
45 opts, args = parser.parse_args()
53 lp = sambaopts.get_loadparm()
54 global_creds = credopts.get_credentials(lp)
56 # Force an encrypted connection
57 global_creds.set_gensec_features(global_creds.get_gensec_features() |
60 template_creds = Credentials()
61 template_creds.set_username("testuser")
62 template_creds.set_password("thatsAcomplPASS1")
63 template_creds.set_domain(global_creds.get_domain())
64 template_creds.set_realm(global_creds.get_realm())
65 template_creds.set_workstation(global_creds.get_workstation())
66 template_creds.set_gensec_features(global_creds.get_gensec_features())
67 template_creds.set_kerberos_state(global_creds.get_kerberos_state())
69 def insta_creds(template=template_creds, username=None, userpass=None, kerberos_state=None):
70 if username is not None:
71 assert userpass is not None
74 assert userpass is None
76 username = template.get_username()
77 userpass = template.get_password()
79 if kerberos_state is None:
80 kerberos_state = template.get_kerberos_state()
82 # get a copy of the global creds or a the passed in creds
84 c.set_username(username)
85 c.set_password(userpass)
86 c.set_domain(template.get_domain())
87 c.set_realm(template.get_realm())
88 c.set_workstation(template.get_workstation())
89 c.set_gensec_features(c.get_gensec_features()
90 | gensec.FEATURE_SEAL)
91 c.set_kerberos_state(kerberos_state)
98 class PasswordTests(samba.tests.TestCase):
100 def _open_samr_user(self, res):
101 self.assertTrue("objectSid" in res[0])
103 (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split()
104 self.assertEquals(self.domain_sid, domain_sid)
106 return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid)
108 def _reset_samr(self, res):
110 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
111 samr_user = self._open_samr_user(res)
112 acb_info = self.samr.QueryUserInfo(samr_user, 16)
113 acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
114 self.samr.SetUserInfo(samr_user, 16, acb_info)
115 self.samr.Close(samr_user)
117 def _reset_ldap_lockoutTime(self, res):
118 self.ldb.modify_ldif("""
119 dn: """ + str(res[0].dn) + """
125 def _reset_ldap_userAccountControl(self, res):
126 self.assertTrue("userAccountControl" in res[0])
127 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
129 uac = int(res[0]["userAccountControl"][0])
130 uacc = int(res[0]["msDS-User-Account-Control-Computed"][0])
133 uac = uac & ~dsdb.UF_LOCKOUT
135 self.ldb.modify_ldif("""
136 dn: """ + str(res[0].dn) + """
138 replace: userAccountControl
139 userAccountControl: %d
142 def _reset_by_method(self, res, method):
143 if method is "ldap_userAccountControl":
144 self._reset_ldap_userAccountControl(res)
145 elif method is "ldap_lockoutTime":
146 self._reset_ldap_lockoutTime(res)
147 elif method is "samr":
148 self._reset_samr(res)
150 self.assertTrue(False, msg="Invalid reset method[%s]" % method)
152 def _check_attribute(self, res, name, value):
154 self.assertTrue(name not in res[0],
155 msg="attr[%s]=%r on dn[%s]" %
156 (name, res[0], res[0].dn))
159 if isinstance(value, tuple):
160 (mode, value) = value
168 self.assertFalse(name in res[0],
169 msg="attr[%s] not missing on dn[%s]" %
173 self.assertTrue(name in res[0],
174 msg="attr[%s] missing on dn[%s]" %
176 self.assertTrue(len(res[0][name]) == 1,
177 msg="attr[%s]=%r on dn[%s]" %
178 (name, res[0][name], res[0].dn))
181 print "%s = '%s'" % (name, res[0][name][0])
183 if mode == "present":
187 v = int(res[0][name][0])
189 msg = ("attr[%s]=[%s] != [%s] on dn[%s]\n"
190 "(diff %d; actual value is %s than expected)" %
191 (name, v, value, res[0].dn, v - value,
192 ('less' if v < value else 'greater')))
194 self.assertTrue(v == value, msg)
197 if mode == "greater":
198 v = int(res[0][name][0])
199 self.assertTrue(v > int(value),
200 msg="attr[%s]=[%s] <= [%s] on dn[%s] (diff %d)" %
201 (name, v, int(value), res[0].dn, v - int(value)))
204 v = int(res[0][name][0])
205 self.assertTrue(v < int(value),
206 msg="attr[%s]=[%s] >= [%s] on dn[%s] (diff %d)" %
207 (name, v, int(value), res[0].dn, v - int(value)))
209 self.assertEqual(mode, not mode, "Invalid Mode[%s]" % mode)
211 def _check_account(self, dn,
213 badPasswordTime=None,
215 lastLogonTimestamp=None,
217 userAccountControl=None,
218 msDSUserAccountControlComputed=None,
219 effective_bad_password_count=None,
223 print "\033[01;32m %s \033[00m\n" % msg
229 "lastLogonTimestamp",
231 "userAccountControl",
232 "msDS-User-Account-Control-Computed"
235 # in order to prevent some time resolution problems we sleep for
239 res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
240 self.assertTrue(len(res) == 1)
241 self._check_attribute(res, "badPwdCount", badPwdCount)
242 self._check_attribute(res, "badPasswordTime", badPasswordTime)
243 self._check_attribute(res, "lastLogon", lastLogon)
244 self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp)
245 self._check_attribute(res, "lockoutTime", lockoutTime)
246 self._check_attribute(res, "userAccountControl", userAccountControl)
247 self._check_attribute(res, "msDS-User-Account-Control-Computed",
248 msDSUserAccountControlComputed)
250 lastLogon = int(res[0]["lastLogon"][0])
252 samr_user = self._open_samr_user(res)
253 uinfo3 = self.samr.QueryUserInfo(samr_user, 3)
254 uinfo5 = self.samr.QueryUserInfo(samr_user, 5)
255 uinfo16 = self.samr.QueryUserInfo(samr_user, 16)
256 uinfo21 = self.samr.QueryUserInfo(samr_user, 21)
257 self.samr.Close(samr_user)
259 expected_acb_info = 0
260 if userAccountControl & dsdb.UF_NORMAL_ACCOUNT:
261 expected_acb_info |= samr.ACB_NORMAL
262 if userAccountControl & dsdb.UF_ACCOUNTDISABLE:
263 expected_acb_info |= samr.ACB_DISABLED
264 if userAccountControl & dsdb.UF_PASSWD_NOTREQD:
265 expected_acb_info |= samr.ACB_PWNOTREQ
266 if msDSUserAccountControlComputed & dsdb.UF_LOCKOUT:
267 expected_acb_info |= samr.ACB_AUTOLOCK
268 if msDSUserAccountControlComputed & dsdb.UF_PASSWORD_EXPIRED:
269 expected_acb_info |= samr.ACB_PW_EXPIRED
271 expected_bad_password_count = 0
272 if badPwdCount is not None:
273 expected_bad_password_count = badPwdCount
274 if effective_bad_password_count is None:
275 effective_bad_password_count = expected_bad_password_count
277 self.assertEquals(uinfo3.acct_flags, expected_acb_info)
278 self.assertEquals(uinfo3.bad_password_count, expected_bad_password_count)
279 self.assertEquals(uinfo3.last_logon, lastLogon)
281 self.assertEquals(uinfo5.acct_flags, expected_acb_info)
282 self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count)
283 self.assertEquals(uinfo5.last_logon, lastLogon)
285 self.assertEquals(uinfo16.acct_flags, expected_acb_info)
287 self.assertEquals(uinfo21.acct_flags, expected_acb_info)
288 self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count)
289 self.assertEquals(uinfo21.last_logon, lastLogon)
291 # check LDAP again and make sure the samr.QueryUserInfo
292 # doesn't have any impact.
293 res2 = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
294 self.assertEquals(res[0], res2[0])
296 # in order to prevent some time resolution problems we sleep for
301 def _readd_user(self, creds, lockOutObservationWindow=0):
302 username = creds.get_username()
303 userpass = creds.get_password()
304 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
306 use_kerberos = creds.get_kerberos_state()
307 if use_kerberos == MUST_USE_KERBEROS:
308 lastlogon_relation = 'greater'
310 if lockOutObservationWindow == 0:
311 lastlogon_relation = 'greater'
313 lastlogon_relation = 'equal'
315 delete_force(self.ldb, userdn)
318 "objectclass": "user",
319 "sAMAccountName": username})
321 self.addCleanup(delete_force, self.ldb, userdn)
323 res = self._check_account(userdn,
327 lastLogonTimestamp=('absent', None),
329 dsdb.UF_NORMAL_ACCOUNT |
330 dsdb.UF_ACCOUNTDISABLE |
331 dsdb.UF_PASSWD_NOTREQD,
332 msDSUserAccountControlComputed=
333 dsdb.UF_PASSWORD_EXPIRED)
335 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
336 # It doesn't create "lockoutTime" = 0.
337 self._reset_samr(res)
339 res = self._check_account(userdn,
343 lastLogonTimestamp=('absent', None),
345 dsdb.UF_NORMAL_ACCOUNT |
346 dsdb.UF_ACCOUNTDISABLE |
347 dsdb.UF_PASSWD_NOTREQD,
348 msDSUserAccountControlComputed=
349 dsdb.UF_PASSWORD_EXPIRED)
351 # Tests a password change when we don't have any password yet with a
354 self.ldb.modify_ldif("""
355 dn: """ + userdn + """
358 userPassword: noPassword
360 userPassword: thatsAcomplPASS2
363 except LdbError, (num, msg):
364 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
365 # Windows (2008 at least) seems to have some small bug here: it
366 # returns "0000056A" on longer (always wrong) previous passwords.
367 self.assertTrue('00000056' in msg, msg)
369 res = self._check_account(userdn,
371 badPasswordTime=("greater", 0),
373 lastLogonTimestamp=('absent', None),
375 dsdb.UF_NORMAL_ACCOUNT |
376 dsdb.UF_ACCOUNTDISABLE |
377 dsdb.UF_PASSWD_NOTREQD,
378 msDSUserAccountControlComputed=
379 dsdb.UF_PASSWORD_EXPIRED)
380 badPwdCount = int(res[0]["badPwdCount"][0])
381 badPasswordTime = int(res[0]["badPasswordTime"][0])
383 # Sets the initial user password with a "special" password change
384 # I think that this internally is a password set operation and it can
385 # only be performed by someone which has password set privileges on the
386 # account (at least in s4 we do handle it like that).
387 self.ldb.modify_ldif("""
388 dn: """ + userdn + """
392 userPassword: """ + userpass + """
395 res = self._check_account(userdn,
396 badPwdCount=badPwdCount,
397 badPasswordTime=badPasswordTime,
399 lastLogonTimestamp=('absent', None),
401 dsdb.UF_NORMAL_ACCOUNT |
402 dsdb.UF_ACCOUNTDISABLE |
403 dsdb.UF_PASSWD_NOTREQD,
404 msDSUserAccountControlComputed=0)
406 # Enables the user account
407 self.ldb.enable_account("(sAMAccountName=%s)" % username)
409 res = self._check_account(userdn,
410 badPwdCount=badPwdCount,
411 badPasswordTime=badPasswordTime,
413 lastLogonTimestamp=('absent', None),
415 dsdb.UF_NORMAL_ACCOUNT,
416 msDSUserAccountControlComputed=0)
417 if lockOutObservationWindow != 0:
418 time.sleep(lockOutObservationWindow + 1)
419 effective_bad_password_count = 0
421 effective_bad_password_count = badPwdCount
423 res = self._check_account(userdn,
424 badPwdCount=badPwdCount,
425 effective_bad_password_count=effective_bad_password_count,
426 badPasswordTime=badPasswordTime,
428 lastLogonTimestamp=('absent', None),
430 dsdb.UF_NORMAL_ACCOUNT,
431 msDSUserAccountControlComputed=0)
433 ldb = SamDB(url=host_url, credentials=creds, lp=lp)
435 if lockOutObservationWindow == 0:
437 effective_bad_password_count = 0
438 if use_kerberos == MUST_USE_KERBEROS:
440 effective_bad_password_count = 0
442 res = self._check_account(userdn,
443 badPwdCount=badPwdCount,
444 effective_bad_password_count=effective_bad_password_count,
445 badPasswordTime=badPasswordTime,
446 lastLogon=(lastlogon_relation, 0),
447 lastLogonTimestamp=('greater', badPasswordTime),
449 dsdb.UF_NORMAL_ACCOUNT,
450 msDSUserAccountControlComputed=0)
452 lastLogon = int(res[0]["lastLogon"][0])
453 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
454 if lastlogon_relation == 'greater':
455 self.assertGreater(lastLogon, badPasswordTime)
456 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
458 res = self._check_account(userdn,
459 badPwdCount=badPwdCount,
460 effective_bad_password_count=effective_bad_password_count,
461 badPasswordTime=badPasswordTime,
463 lastLogonTimestamp=lastLogonTimestamp,
465 dsdb.UF_NORMAL_ACCOUNT,
466 msDSUserAccountControlComputed=0)
469 def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS):
471 ldb = SamDB(url=url, credentials=creds, lp=lp)
472 self.fail("Login unexpectedly succeeded")
473 except LdbError, (num, msg):
474 if errno is not None:
475 self.assertEquals(num, errno, ("Login failed in the wrong way"
476 "(got err %d, expected %d)" %
480 super(PasswordTests, self).setUp()
482 self.ldb = SamDB(url=host_url, session_info=system_session(lp),
483 credentials=global_creds, lp=lp)
485 # Gets back the basedn
486 base_dn = self.ldb.domain_dn()
488 # Gets back the configuration basedn
489 configuration_dn = self.ldb.get_config_basedn().get_linearized()
491 # Get the old "dSHeuristics" if it was set
492 dsheuristics = self.ldb.get_dsheuristics()
494 # Reset the "dSHeuristics" as they were before
495 self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
497 res = self.ldb.search(base_dn,
498 scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
500 if "lockoutDuration" in res[0]:
501 lockoutDuration = res[0]["lockoutDuration"][0]
505 if "lockoutObservationWindow" in res[0]:
506 lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
508 lockoutObservationWindow = 0
510 if "lockoutThreshold" in res[0]:
511 lockoutThreshold = res[0]["lockoutThreshold"][0]
515 self.addCleanup(self.ldb.modify_ldif, """
516 dn: """ + base_dn + """
518 replace: lockoutDuration
519 lockoutDuration: """ + str(lockoutDuration) + """
520 replace: lockoutObservationWindow
521 lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
522 replace: lockoutThreshold
523 lockoutThreshold: """ + str(lockoutThreshold) + """
527 m.dn = Dn(self.ldb, base_dn)
529 self.account_lockout_duration = 2
530 account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7))
532 m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
533 FLAG_MOD_REPLACE, "lockoutDuration")
535 account_lockout_threshold = 3
536 m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
537 FLAG_MOD_REPLACE, "lockoutThreshold")
539 self.lockout_observation_window = 2
540 lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7))
542 m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
543 FLAG_MOD_REPLACE, "lockOutObservationWindow")
547 # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
548 self.ldb.set_dsheuristics("000000001")
550 # Get the old "minPwdAge"
551 minPwdAge = self.ldb.get_minPwdAge()
553 # Reset the "minPwdAge" as it was before
554 self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
556 # Set it temporarely to "0"
557 self.ldb.set_minPwdAge("0")
559 self.base_dn = self.ldb.domain_dn()
561 self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
562 self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % host, lp, global_creds)
563 self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
564 self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
566 # (Re)adds the test user accounts
567 self.lockout1krb5_creds = insta_creds(username="lockout1krb5",
568 userpass="thatsAcomplPASS0",
569 kerberos_state=MUST_USE_KERBEROS)
570 self.lockout1krb5_ldb = self._readd_user(self.lockout1krb5_creds)
571 self.lockout2krb5_creds = insta_creds(username="lockout2krb5",
572 userpass="thatsAcomplPASS0",
573 kerberos_state=MUST_USE_KERBEROS)
574 self.lockout2krb5_ldb = self._readd_user(self.lockout2krb5_creds,
575 lockOutObservationWindow=self.lockout_observation_window)
576 self.lockout1ntlm_creds = insta_creds(username="lockout1ntlm",
577 userpass="thatsAcomplPASS0",
578 kerberos_state=DONT_USE_KERBEROS)
579 self.lockout1ntlm_ldb = self._readd_user(self.lockout1ntlm_creds)
580 self.lockout2ntlm_creds = insta_creds(username="lockout2ntlm",
581 userpass="thatsAcomplPASS0",
582 kerberos_state=DONT_USE_KERBEROS)
583 self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds,
584 lockOutObservationWindow=self.lockout_observation_window)
586 def _test_userPassword_lockout_with_clear_change(self, creds, other_ldb, method):
587 print "Performs a password cleartext change operation on 'userPassword'"
588 # Notice: This works only against Windows if "dSHeuristics" has been set
590 username = creds.get_username()
591 userpass = creds.get_password()
592 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
594 res = self._check_account(userdn,
596 badPasswordTime=("greater", 0),
597 lastLogon=('greater', 0),
598 lastLogonTimestamp=('greater', 0),
600 dsdb.UF_NORMAL_ACCOUNT,
601 msDSUserAccountControlComputed=0)
602 badPasswordTime = int(res[0]["badPasswordTime"][0])
603 lastLogon = int(res[0]["lastLogon"][0])
604 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
606 # Change password on a connection as another user
610 other_ldb.modify_ldif("""
611 dn: """ + userdn + """
614 userPassword: thatsAcomplPASS1x
616 userPassword: thatsAcomplPASS2
619 except LdbError, (num, msg):
620 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
621 self.assertTrue('00000056' in msg, msg)
623 res = self._check_account(userdn,
625 badPasswordTime=("greater", badPasswordTime),
627 lastLogonTimestamp=lastLogonTimestamp,
629 dsdb.UF_NORMAL_ACCOUNT,
630 msDSUserAccountControlComputed=0)
631 badPasswordTime = int(res[0]["badPasswordTime"][0])
633 # Correct old password
634 other_ldb.modify_ldif("""
635 dn: """ + userdn + """
638 userPassword: """ + userpass + """
640 userPassword: thatsAcomplPASS2
643 res = self._check_account(userdn,
645 badPasswordTime=badPasswordTime,
647 lastLogonTimestamp=lastLogonTimestamp,
649 dsdb.UF_NORMAL_ACCOUNT,
650 msDSUserAccountControlComputed=0)
654 other_ldb.modify_ldif("""
655 dn: """ + userdn + """
658 userPassword: thatsAcomplPASS1x
660 userPassword: thatsAcomplPASS2
663 except LdbError, (num, msg):
664 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
665 self.assertTrue('00000056' in msg, msg)
667 res = self._check_account(userdn,
669 badPasswordTime=("greater", badPasswordTime),
671 lastLogonTimestamp=lastLogonTimestamp,
673 dsdb.UF_NORMAL_ACCOUNT,
674 msDSUserAccountControlComputed=0)
675 badPasswordTime = int(res[0]["badPasswordTime"][0])
677 print "two failed password change"
681 other_ldb.modify_ldif("""
682 dn: """ + userdn + """
685 userPassword: thatsAcomplPASS1x
687 userPassword: thatsAcomplPASS2
690 except LdbError, (num, msg):
691 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
692 self.assertTrue('00000056' in msg, msg)
694 res = self._check_account(userdn,
696 badPasswordTime=("greater", badPasswordTime),
698 lastLogonTimestamp=lastLogonTimestamp,
699 lockoutTime=("greater", badPasswordTime),
701 dsdb.UF_NORMAL_ACCOUNT,
702 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
703 badPasswordTime = int(res[0]["badPasswordTime"][0])
704 lockoutTime = int(res[0]["lockoutTime"][0])
708 other_ldb.modify_ldif("""
709 dn: """ + userdn + """
712 userPassword: thatsAcomplPASS1x
714 userPassword: thatsAcomplPASS2
717 except LdbError, (num, msg):
718 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
719 self.assertTrue('00000775' in msg, msg)
721 res = self._check_account(userdn,
723 badPasswordTime=badPasswordTime,
725 lastLogonTimestamp=lastLogonTimestamp,
726 lockoutTime=lockoutTime,
728 dsdb.UF_NORMAL_ACCOUNT,
729 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
733 other_ldb.modify_ldif("""
734 dn: """ + userdn + """
737 userPassword: thatsAcomplPASS1x
739 userPassword: thatsAcomplPASS2
742 except LdbError, (num, msg):
743 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
744 self.assertTrue('00000775' in msg, msg)
746 res = self._check_account(userdn,
748 badPasswordTime=badPasswordTime,
749 lockoutTime=lockoutTime,
751 lastLogonTimestamp=lastLogonTimestamp,
753 dsdb.UF_NORMAL_ACCOUNT,
754 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
757 # Correct old password
758 other_ldb.modify_ldif("""
759 dn: """ + userdn + """
762 userPassword: thatsAcomplPASS2
764 userPassword: thatsAcomplPASS2x
767 except LdbError, (num, msg):
768 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
769 self.assertTrue('00000775' in msg, msg)
771 res = self._check_account(userdn,
773 badPasswordTime=badPasswordTime,
775 lastLogonTimestamp=lastLogonTimestamp,
776 lockoutTime=lockoutTime,
778 dsdb.UF_NORMAL_ACCOUNT,
779 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
781 # Now reset the password, which does NOT change the lockout!
782 self.ldb.modify_ldif("""
783 dn: """ + userdn + """
785 replace: userPassword
786 userPassword: thatsAcomplPASS2
789 res = self._check_account(userdn,
791 badPasswordTime=badPasswordTime,
793 lastLogonTimestamp=lastLogonTimestamp,
794 lockoutTime=lockoutTime,
796 dsdb.UF_NORMAL_ACCOUNT,
797 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
800 # Correct old password
801 other_ldb.modify_ldif("""
802 dn: """ + userdn + """
805 userPassword: thatsAcomplPASS2
807 userPassword: thatsAcomplPASS2x
810 except LdbError, (num, msg):
811 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
812 self.assertTrue('00000775' in msg, msg)
814 res = self._check_account(userdn,
816 badPasswordTime=badPasswordTime,
818 lastLogonTimestamp=lastLogonTimestamp,
819 lockoutTime=lockoutTime,
821 dsdb.UF_NORMAL_ACCOUNT,
822 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
825 m.dn = Dn(self.ldb, userdn)
826 m["userAccountControl"] = MessageElement(
827 str(dsdb.UF_LOCKOUT),
828 FLAG_MOD_REPLACE, "userAccountControl")
832 # This shows that setting the UF_LOCKOUT flag alone makes no difference
833 res = self._check_account(userdn,
835 badPasswordTime=badPasswordTime,
837 lastLogonTimestamp=lastLogonTimestamp,
838 lockoutTime=lockoutTime,
840 dsdb.UF_NORMAL_ACCOUNT,
841 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
843 # This shows that setting the UF_LOCKOUT flag makes no difference
845 # Correct old password
846 other_ldb.modify_ldif("""
847 dn: """ + userdn + """
850 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
852 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
855 except LdbError, (num, msg):
856 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
857 self.assertTrue('00000775' in msg, msg)
859 res = self._check_account(userdn,
861 badPasswordTime=badPasswordTime,
862 lockoutTime=lockoutTime,
864 lastLogonTimestamp=lastLogonTimestamp,
866 dsdb.UF_NORMAL_ACCOUNT,
867 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
869 self._reset_by_method(res, method)
871 # Here bad password counts are reset without logon success.
872 res = self._check_account(userdn,
874 badPasswordTime=badPasswordTime,
877 lastLogonTimestamp=lastLogonTimestamp,
879 dsdb.UF_NORMAL_ACCOUNT,
880 msDSUserAccountControlComputed=0)
882 # The correct password after doing the unlock
884 other_ldb.modify_ldif("""
885 dn: """ + userdn + """
888 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
890 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
892 userpass = "thatsAcomplPASS2x"
893 creds.set_password(userpass)
895 res = self._check_account(userdn,
897 badPasswordTime=badPasswordTime,
900 lastLogonTimestamp=lastLogonTimestamp,
902 dsdb.UF_NORMAL_ACCOUNT,
903 msDSUserAccountControlComputed=0)
907 other_ldb.modify_ldif("""
908 dn: """ + userdn + """
911 userPassword: thatsAcomplPASS1xyz
913 userPassword: thatsAcomplPASS2XYZ
916 except LdbError, (num, msg):
917 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
918 self.assertTrue('00000056' in msg, msg)
920 res = self._check_account(userdn,
922 badPasswordTime=("greater", badPasswordTime),
925 lastLogonTimestamp=lastLogonTimestamp,
927 dsdb.UF_NORMAL_ACCOUNT,
928 msDSUserAccountControlComputed=0)
929 badPasswordTime = int(res[0]["badPasswordTime"][0])
933 other_ldb.modify_ldif("""
934 dn: """ + userdn + """
937 userPassword: thatsAcomplPASS1xyz
939 userPassword: thatsAcomplPASS2XYZ
942 except LdbError, (num, msg):
943 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
944 self.assertTrue('00000056' in msg, msg)
946 res = self._check_account(userdn,
948 badPasswordTime=("greater", badPasswordTime),
951 lastLogonTimestamp=lastLogonTimestamp,
953 dsdb.UF_NORMAL_ACCOUNT,
954 msDSUserAccountControlComputed=0)
955 badPasswordTime = int(res[0]["badPasswordTime"][0])
957 self._reset_ldap_lockoutTime(res)
959 res = self._check_account(userdn,
961 badPasswordTime=badPasswordTime,
963 lastLogonTimestamp=lastLogonTimestamp,
966 dsdb.UF_NORMAL_ACCOUNT,
967 msDSUserAccountControlComputed=0)
969 def test_userPassword_lockout_with_clear_change_krb5_ldap_userAccountControl(self):
970 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
971 self.lockout2krb5_ldb,
972 "ldap_userAccountControl")
974 def test_userPassword_lockout_with_clear_change_krb5_ldap_lockoutTime(self):
975 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
976 self.lockout2krb5_ldb,
979 def test_userPassword_lockout_with_clear_change_krb5_samr(self):
980 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
981 self.lockout2krb5_ldb,
984 def test_userPassword_lockout_with_clear_change_ntlm_ldap_userAccountControl(self):
985 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
986 self.lockout2ntlm_ldb,
987 "ldap_userAccountControl")
989 def test_userPassword_lockout_with_clear_change_ntlm_ldap_lockoutTime(self):
990 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
991 self.lockout2ntlm_ldb,
994 def test_userPassword_lockout_with_clear_change_ntlm_samr(self):
995 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
996 self.lockout2ntlm_ldb,
999 def _test_unicodePwd_lockout_with_clear_change(self, creds, other_ldb):
1000 print "Performs a password cleartext change operation on 'unicodePwd'"
1001 username = creds.get_username()
1002 userpass = creds.get_password()
1003 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
1005 res = self._check_account(userdn,
1007 badPasswordTime=("greater", 0),
1008 lastLogon=("greater", 0),
1009 lastLogonTimestamp=("greater", 0),
1011 dsdb.UF_NORMAL_ACCOUNT,
1012 msDSUserAccountControlComputed=0)
1013 badPasswordTime = int(res[0]["badPasswordTime"][0])
1014 lastLogon = int(res[0]["lastLogon"][0])
1016 # Change password on a connection as another user
1018 # Wrong old password
1020 other_ldb.modify_ldif("""
1021 dn: """ + userdn + """
1024 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1026 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1029 except LdbError, (num, msg):
1030 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1031 self.assertTrue('00000056' in msg, msg)
1033 res = self._check_account(userdn,
1035 badPasswordTime=("greater", badPasswordTime),
1036 lastLogon=lastLogon,
1037 lastLogonTimestamp=lastLogon,
1039 dsdb.UF_NORMAL_ACCOUNT,
1040 msDSUserAccountControlComputed=0)
1041 badPasswordTime = int(res[0]["badPasswordTime"][0])
1043 # Correct old password
1044 old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
1045 invalid_utf16 = "\"thatsAcomplPASSX\"".encode('utf-16-le')
1046 userpass = "thatsAcomplPASS2"
1047 creds.set_password(userpass)
1048 new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
1050 other_ldb.modify_ldif("""
1051 dn: """ + userdn + """
1054 unicodePwd:: """ + base64.b64encode(old_utf16) + """
1056 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1059 res = self._check_account(userdn,
1061 badPasswordTime=badPasswordTime,
1062 lastLogon=lastLogon,
1063 lastLogonTimestamp=lastLogon,
1065 dsdb.UF_NORMAL_ACCOUNT,
1066 msDSUserAccountControlComputed=0)
1068 # Wrong old password
1070 other_ldb.modify_ldif("""
1071 dn: """ + userdn + """
1074 unicodePwd:: """ + base64.b64encode(old_utf16) + """
1076 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1079 except LdbError, (num, msg):
1080 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1081 self.assertTrue('00000056' in msg, msg)
1083 res = self._check_account(userdn,
1085 badPasswordTime=("greater", badPasswordTime),
1086 lastLogon=lastLogon,
1087 lastLogonTimestamp=lastLogon,
1089 dsdb.UF_NORMAL_ACCOUNT,
1090 msDSUserAccountControlComputed=0)
1091 badPasswordTime = int(res[0]["badPasswordTime"][0])
1093 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1094 # It doesn't create "lockoutTime" = 0 and doesn't
1095 # reset "badPwdCount" = 0.
1096 self._reset_samr(res)
1098 res = self._check_account(userdn,
1100 badPasswordTime=badPasswordTime,
1101 lastLogon=lastLogon,
1102 lastLogonTimestamp=lastLogon,
1104 dsdb.UF_NORMAL_ACCOUNT,
1105 msDSUserAccountControlComputed=0)
1107 print "two failed password change"
1109 # Wrong old password
1111 other_ldb.modify_ldif("""
1112 dn: """ + userdn + """
1115 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1117 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1120 except LdbError, (num, msg):
1121 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1122 self.assertTrue('00000056' in msg, msg)
1124 # this is strange, why do we have lockoutTime=badPasswordTime here?
1125 res = self._check_account(userdn,
1127 badPasswordTime=("greater", badPasswordTime),
1128 lastLogon=lastLogon,
1129 lastLogonTimestamp=lastLogon,
1130 lockoutTime=("greater", badPasswordTime),
1132 dsdb.UF_NORMAL_ACCOUNT,
1133 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1134 badPasswordTime = int(res[0]["badPasswordTime"][0])
1135 lockoutTime = int(res[0]["lockoutTime"][0])
1137 # Wrong old password
1139 other_ldb.modify_ldif("""
1140 dn: """ + userdn + """
1143 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1145 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1148 except LdbError, (num, msg):
1149 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1150 self.assertTrue('00000775' in msg, msg)
1152 res = self._check_account(userdn,
1154 badPasswordTime=badPasswordTime,
1155 lastLogon=lastLogon,
1156 lastLogonTimestamp=lastLogon,
1157 lockoutTime=lockoutTime,
1159 dsdb.UF_NORMAL_ACCOUNT,
1160 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1162 # Wrong old password
1164 other_ldb.modify_ldif("""
1165 dn: """ + userdn + """
1168 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1170 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1173 except LdbError, (num, msg):
1174 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1175 self.assertTrue('00000775' in msg, msg)
1177 res = self._check_account(userdn,
1179 badPasswordTime=badPasswordTime,
1180 lastLogon=lastLogon,
1181 lastLogonTimestamp=lastLogon,
1182 lockoutTime=lockoutTime,
1184 dsdb.UF_NORMAL_ACCOUNT,
1185 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1188 # Correct old password
1189 other_ldb.modify_ldif("""
1190 dn: """ + userdn + """
1193 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1195 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1198 except LdbError, (num, msg):
1199 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1200 self.assertTrue('00000775' in msg, msg)
1202 res = self._check_account(userdn,
1204 badPasswordTime=badPasswordTime,
1205 lastLogon=lastLogon,
1206 lastLogonTimestamp=lastLogon,
1207 lockoutTime=lockoutTime,
1209 dsdb.UF_NORMAL_ACCOUNT,
1210 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1212 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
1213 self._reset_samr(res);
1215 res = self._check_account(userdn,
1217 badPasswordTime=badPasswordTime,
1218 lastLogon=lastLogon,
1219 lastLogonTimestamp=lastLogon,
1222 dsdb.UF_NORMAL_ACCOUNT,
1223 msDSUserAccountControlComputed=0)
1225 # Correct old password
1226 old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
1227 invalid_utf16 = "\"thatsAcomplPASSiX\"".encode('utf-16-le')
1228 userpass = "thatsAcomplPASS2x"
1229 creds.set_password(userpass)
1230 new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
1232 other_ldb.modify_ldif("""
1233 dn: """ + userdn + """
1236 unicodePwd:: """ + base64.b64encode(old_utf16) + """
1238 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1241 res = self._check_account(userdn,
1243 badPasswordTime=badPasswordTime,
1244 lastLogon=lastLogon,
1245 lastLogonTimestamp=lastLogon,
1248 dsdb.UF_NORMAL_ACCOUNT,
1249 msDSUserAccountControlComputed=0)
1251 # Wrong old password
1253 other_ldb.modify_ldif("""
1254 dn: """ + userdn + """
1257 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1259 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1262 except LdbError, (num, msg):
1263 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1264 self.assertTrue('00000056' in msg, msg)
1266 res = self._check_account(userdn,
1268 badPasswordTime=("greater", badPasswordTime),
1269 lastLogon=lastLogon,
1270 lastLogonTimestamp=lastLogon,
1273 dsdb.UF_NORMAL_ACCOUNT,
1274 msDSUserAccountControlComputed=0)
1275 badPasswordTime = int(res[0]["badPasswordTime"][0])
1277 # Wrong old password
1279 other_ldb.modify_ldif("""
1280 dn: """ + userdn + """
1283 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1285 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1288 except LdbError, (num, msg):
1289 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1290 self.assertTrue('00000056' in msg, msg)
1292 res = self._check_account(userdn,
1294 badPasswordTime=("greater", badPasswordTime),
1295 lastLogon=lastLogon,
1296 lastLogonTimestamp=lastLogon,
1299 dsdb.UF_NORMAL_ACCOUNT,
1300 msDSUserAccountControlComputed=0)
1301 badPasswordTime = int(res[0]["badPasswordTime"][0])
1303 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1304 # It doesn't reset "badPwdCount" = 0.
1305 self._reset_samr(res)
1307 res = self._check_account(userdn,
1309 badPasswordTime=badPasswordTime,
1310 lastLogon=lastLogon,
1311 lastLogonTimestamp=lastLogon,
1314 dsdb.UF_NORMAL_ACCOUNT,
1315 msDSUserAccountControlComputed=0)
1317 # Wrong old password
1319 other_ldb.modify_ldif("""
1320 dn: """ + userdn + """
1323 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1325 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1328 except LdbError, (num, msg):
1329 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1330 self.assertTrue('00000056' in msg, msg)
1332 res = self._check_account(userdn,
1334 badPasswordTime=("greater", badPasswordTime),
1335 lastLogon=lastLogon,
1336 lastLogonTimestamp=lastLogon,
1337 lockoutTime=("greater", badPasswordTime),
1339 dsdb.UF_NORMAL_ACCOUNT,
1340 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1341 badPasswordTime = int(res[0]["badPasswordTime"][0])
1342 lockoutTime = int(res[0]["lockoutTime"][0])
1344 time.sleep(self.account_lockout_duration + 1)
1346 res = self._check_account(userdn,
1347 badPwdCount=3, effective_bad_password_count=0,
1348 badPasswordTime=badPasswordTime,
1349 lastLogon=lastLogon,
1350 lastLogonTimestamp=lastLogon,
1351 lockoutTime=lockoutTime,
1353 dsdb.UF_NORMAL_ACCOUNT,
1354 msDSUserAccountControlComputed=0)
1356 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1357 # It doesn't reset "lockoutTime" = 0 and doesn't
1358 # reset "badPwdCount" = 0.
1359 self._reset_samr(res)
1361 res = self._check_account(userdn,
1362 badPwdCount=3, effective_bad_password_count=0,
1363 badPasswordTime=badPasswordTime,
1364 lockoutTime=lockoutTime,
1365 lastLogon=lastLogon,
1366 lastLogonTimestamp=lastLogon,
1368 dsdb.UF_NORMAL_ACCOUNT,
1369 msDSUserAccountControlComputed=0)
1371 def test_unicodePwd_lockout_with_clear_change_krb5(self):
1372 self._test_unicodePwd_lockout_with_clear_change(self.lockout1krb5_creds,
1373 self.lockout2krb5_ldb)
1375 def test_unicodePwd_lockout_with_clear_change_ntlm(self):
1376 self._test_unicodePwd_lockout_with_clear_change(self.lockout1ntlm_creds,
1377 self.lockout2ntlm_ldb)
1379 def _test_login_lockout(self, creds):
1380 username = creds.get_username()
1381 userpass = creds.get_password()
1382 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
1384 use_kerberos = creds.get_kerberos_state()
1385 # This unlocks by waiting for account_lockout_duration
1386 if use_kerberos == MUST_USE_KERBEROS:
1387 lastlogon_relation = 'greater'
1388 print "Performs a lockout attempt against LDAP using Kerberos"
1390 lastlogon_relation = 'equal'
1391 print "Performs a lockout attempt against LDAP using NTLM"
1393 # Change password on a connection as another user
1394 res = self._check_account(userdn,
1396 badPasswordTime=("greater", 0),
1397 lastLogon=("greater", 0),
1398 lastLogonTimestamp=("greater", 0),
1400 dsdb.UF_NORMAL_ACCOUNT,
1401 msDSUserAccountControlComputed=0)
1402 badPasswordTime = int(res[0]["badPasswordTime"][0])
1403 lastLogon = int(res[0]["lastLogon"][0])
1404 firstLogon = lastLogon
1405 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1407 print lastLogonTimestamp
1410 self.assertGreater(lastLogon, badPasswordTime)
1412 # Open a second LDB connection with the user credentials. Use the
1413 # command line credentials for informations like the domain, the realm
1414 # and the workstation.
1415 creds_lockout = insta_creds(creds)
1417 # The wrong password
1418 creds_lockout.set_password("thatsAcomplPASS1x")
1420 self.assertLoginFailure(host_url, creds_lockout, lp)
1422 res = self._check_account(userdn,
1424 badPasswordTime=("greater", badPasswordTime),
1425 lastLogon=lastLogon,
1426 lastLogonTimestamp=lastLogonTimestamp,
1428 dsdb.UF_NORMAL_ACCOUNT,
1429 msDSUserAccountControlComputed=0,
1430 msg='lastlogontimestamp with wrong password')
1431 badPasswordTime = int(res[0]["badPasswordTime"][0])
1433 # Correct old password
1434 creds_lockout.set_password(userpass)
1436 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1438 # lastLogonTimestamp should not change
1439 # lastLogon increases if badPwdCount is non-zero (!)
1440 res = self._check_account(userdn,
1442 badPasswordTime=badPasswordTime,
1443 lastLogon=('greater', lastLogon),
1444 lastLogonTimestamp=lastLogonTimestamp,
1446 dsdb.UF_NORMAL_ACCOUNT,
1447 msDSUserAccountControlComputed=0,
1448 msg='LLTimestamp is updated to lastlogon')
1450 lastLogon = int(res[0]["lastLogon"][0])
1451 self.assertGreater(lastLogon, badPasswordTime)
1453 # The wrong password
1454 creds_lockout.set_password("thatsAcomplPASS1x")
1456 self.assertLoginFailure(host_url, creds_lockout, lp)
1458 res = self._check_account(userdn,
1460 badPasswordTime=("greater", badPasswordTime),
1461 lastLogon=lastLogon,
1462 lastLogonTimestamp=lastLogonTimestamp,
1464 dsdb.UF_NORMAL_ACCOUNT,
1465 msDSUserAccountControlComputed=0)
1466 badPasswordTime = int(res[0]["badPasswordTime"][0])
1468 # The wrong password
1469 creds_lockout.set_password("thatsAcomplPASS1x")
1472 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1475 except LdbError, (num, msg):
1476 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1478 res = self._check_account(userdn,
1480 badPasswordTime=("greater", badPasswordTime),
1481 lastLogon=lastLogon,
1482 lastLogonTimestamp=lastLogonTimestamp,
1484 dsdb.UF_NORMAL_ACCOUNT,
1485 msDSUserAccountControlComputed=0)
1486 badPasswordTime = int(res[0]["badPasswordTime"][0])
1488 print "two failed password change"
1490 # The wrong password
1491 creds_lockout.set_password("thatsAcomplPASS1x")
1494 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1497 except LdbError, (num, msg):
1498 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1500 res = self._check_account(userdn,
1502 badPasswordTime=("greater", badPasswordTime),
1503 lastLogon=lastLogon,
1504 lastLogonTimestamp=lastLogonTimestamp,
1505 lockoutTime=("greater", badPasswordTime),
1507 dsdb.UF_NORMAL_ACCOUNT,
1508 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1509 badPasswordTime = int(res[0]["badPasswordTime"][0])
1510 lockoutTime = int(res[0]["lockoutTime"][0])
1512 # The wrong password
1513 creds_lockout.set_password("thatsAcomplPASS1x")
1515 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1517 except LdbError, (num, msg):
1518 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1520 res = self._check_account(userdn,
1522 badPasswordTime=badPasswordTime,
1523 lastLogon=lastLogon,
1524 lastLogonTimestamp=lastLogonTimestamp,
1525 lockoutTime=lockoutTime,
1527 dsdb.UF_NORMAL_ACCOUNT,
1528 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1530 # The wrong password
1531 creds_lockout.set_password("thatsAcomplPASS1x")
1533 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1535 except LdbError, (num, msg):
1536 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1538 res = self._check_account(userdn,
1540 badPasswordTime=badPasswordTime,
1541 lastLogon=lastLogon,
1542 lastLogonTimestamp=lastLogonTimestamp,
1543 lockoutTime=lockoutTime,
1545 dsdb.UF_NORMAL_ACCOUNT,
1546 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1548 # The correct password, but we are locked out
1549 creds_lockout.set_password(userpass)
1551 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1553 except LdbError, (num, msg):
1554 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1556 res = self._check_account(userdn,
1558 badPasswordTime=badPasswordTime,
1559 lastLogon=lastLogon,
1560 lastLogonTimestamp=lastLogonTimestamp,
1561 lockoutTime=lockoutTime,
1563 dsdb.UF_NORMAL_ACCOUNT,
1564 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1566 # wait for the lockout to end
1567 time.sleep(self.account_lockout_duration + 1)
1568 print self.account_lockout_duration + 1
1570 res = self._check_account(userdn,
1571 badPwdCount=3, effective_bad_password_count=0,
1572 badPasswordTime=badPasswordTime,
1573 lockoutTime=lockoutTime,
1574 lastLogon=lastLogon,
1575 lastLogonTimestamp=lastLogonTimestamp,
1577 dsdb.UF_NORMAL_ACCOUNT,
1578 msDSUserAccountControlComputed=0)
1580 lastLogon = int(res[0]["lastLogon"][0])
1582 # The correct password after letting the timeout expire
1584 creds_lockout.set_password(userpass)
1586 creds_lockout2 = insta_creds(creds_lockout)
1588 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout2, lp=lp)
1591 res = self._check_account(userdn,
1593 badPasswordTime=badPasswordTime,
1594 lastLogon=(lastlogon_relation, lastLogon),
1595 lastLogonTimestamp=lastLogonTimestamp,
1598 dsdb.UF_NORMAL_ACCOUNT,
1599 msDSUserAccountControlComputed=0,
1600 msg="lastLogon is way off")
1602 lastLogon = int(res[0]["lastLogon"][0])
1604 # The wrong password
1605 creds_lockout.set_password("thatsAcomplPASS1x")
1607 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1609 except LdbError, (num, msg):
1610 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1612 res = self._check_account(userdn,
1614 badPasswordTime=("greater", badPasswordTime),
1616 lastLogon=lastLogon,
1617 lastLogonTimestamp=lastLogonTimestamp,
1619 dsdb.UF_NORMAL_ACCOUNT,
1620 msDSUserAccountControlComputed=0)
1621 badPasswordTime = int(res[0]["badPasswordTime"][0])
1623 # The wrong password
1624 creds_lockout.set_password("thatsAcomplPASS1x")
1626 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1628 except LdbError, (num, msg):
1629 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1631 res = self._check_account(userdn,
1633 badPasswordTime=("greater", badPasswordTime),
1635 lastLogon=lastLogon,
1636 lastLogonTimestamp=lastLogonTimestamp,
1638 dsdb.UF_NORMAL_ACCOUNT,
1639 msDSUserAccountControlComputed=0)
1640 badPasswordTime = int(res[0]["badPasswordTime"][0])
1642 time.sleep(self.lockout_observation_window + 1)
1644 res = self._check_account(userdn,
1645 badPwdCount=2, effective_bad_password_count=0,
1646 badPasswordTime=badPasswordTime,
1648 lastLogon=lastLogon,
1649 lastLogonTimestamp=lastLogonTimestamp,
1651 dsdb.UF_NORMAL_ACCOUNT,
1652 msDSUserAccountControlComputed=0)
1654 # The wrong password
1655 creds_lockout.set_password("thatsAcomplPASS1x")
1657 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1659 except LdbError, (num, msg):
1660 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1662 res = self._check_account(userdn,
1664 badPasswordTime=("greater", badPasswordTime),
1666 lastLogon=lastLogon,
1667 lastLogonTimestamp=lastLogonTimestamp,
1669 dsdb.UF_NORMAL_ACCOUNT,
1670 msDSUserAccountControlComputed=0)
1671 badPasswordTime = int(res[0]["badPasswordTime"][0])
1673 # The correct password without letting the timeout expire
1674 creds_lockout.set_password(userpass)
1675 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1677 res = self._check_account(userdn,
1679 badPasswordTime=badPasswordTime,
1681 lastLogon=("greater", lastLogon),
1682 lastLogonTimestamp=lastLogonTimestamp,
1684 dsdb.UF_NORMAL_ACCOUNT,
1685 msDSUserAccountControlComputed=0)
1688 def test_login_lockout_krb5(self):
1689 self._test_login_lockout(self.lockout1krb5_creds)
1691 def test_login_lockout_ntlm(self):
1692 self._test_login_lockout(self.lockout1ntlm_creds)
1694 def _test_multiple_logon(self, creds):
1695 # Test the happy case in which a user logs on correctly, then
1696 # logs on correctly again, so that the bad password and
1697 # lockout times are both zero the second time. The lastlogon
1698 # time should increase.
1700 # Open a second LDB connection with the user credentials. Use the
1701 # command line credentials for informations like the domain, the realm
1702 # and the workstation.
1703 username = creds.get_username()
1704 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
1706 use_kerberos = creds.get_kerberos_state()
1707 if use_kerberos == MUST_USE_KERBEROS:
1708 print "Testing multiple logon with Kerberos"
1709 lastlogon_relation = 'greater'
1711 print "Testing multiple logon with NTLM"
1712 lastlogon_relation = 'equal'
1714 SamDB(url=host_url, credentials=insta_creds(creds), lp=lp)
1716 res = self._check_account(userdn,
1718 badPasswordTime=("greater", 0),
1719 lastLogon=("greater", 0),
1720 lastLogonTimestamp=("greater", 0),
1722 dsdb.UF_NORMAL_ACCOUNT,
1723 msDSUserAccountControlComputed=0)
1724 badPasswordTime = int(res[0]["badPasswordTime"][0])
1725 lastLogon = int(res[0]["lastLogon"][0])
1726 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1727 firstLogon = lastLogon
1728 print "last logon is %d" % lastLogon
1729 self.assertGreater(lastLogon, badPasswordTime)
1732 SamDB(url=host_url, credentials=insta_creds(creds), lp=lp)
1734 res = self._check_account(userdn,
1736 badPasswordTime=badPasswordTime,
1737 lastLogon=(lastlogon_relation, lastLogon),
1738 lastLogonTimestamp=lastLogonTimestamp,
1740 dsdb.UF_NORMAL_ACCOUNT,
1741 msDSUserAccountControlComputed=0,
1742 msg=("second logon, firstlogon was %s" %
1746 lastLogon = int(res[0]["lastLogon"][0])
1750 SamDB(url=host_url, credentials=insta_creds(creds), lp=lp)
1752 res = self._check_account(userdn,
1754 badPasswordTime=badPasswordTime,
1755 lastLogon=(lastlogon_relation, lastLogon),
1756 lastLogonTimestamp=lastLogonTimestamp,
1758 dsdb.UF_NORMAL_ACCOUNT,
1759 msDSUserAccountControlComputed=0)
1761 def test_multiple_logon_krb5(self):
1762 self._test_multiple_logon(self.lockout1krb5_creds)
1764 def test_multiple_logon_ntlm(self):
1765 self._test_multiple_logon(self.lockout1ntlm_creds)
1769 super(PasswordTests, self).tearDown()
1771 host_url = "ldap://%s" % host
1773 TestProgram(module=__name__, opts=subunitopts)