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
10 from __future__ import print_function
16 sys.path.insert(0, "bin/python")
19 from samba.tests.subunitrun import TestProgram, SubunitOptions
21 import samba.getopt as options
23 from samba.auth import system_session
24 from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS
25 from ldb import SCOPE_BASE, LdbError
26 from ldb import ERR_CONSTRAINT_VIOLATION
27 from ldb import ERR_INVALID_CREDENTIALS
28 from ldb import Message, MessageElement, Dn
29 from ldb import FLAG_MOD_REPLACE
30 from samba import gensec, dsdb
31 from samba.samdb import SamDB
33 from samba.tests import delete_force
34 from samba.dcerpc import security, samr
35 from samba.ndr import ndr_unpack
36 from samba.tests.pso import PasswordSettings
37 from samba.net import Net
38 from samba import NTSTATUSError, ntstatus
41 parser = optparse.OptionParser("password_lockout.py [options] <host>")
42 sambaopts = options.SambaOptions(parser)
43 parser.add_option_group(sambaopts)
44 parser.add_option_group(options.VersionOptions(parser))
45 # use command line creds if available
46 credopts = options.CredentialsOptions(parser)
47 parser.add_option_group(credopts)
48 subunitopts = SubunitOptions(parser)
49 parser.add_option_group(subunitopts)
50 opts, args = parser.parse_args()
58 lp = sambaopts.get_loadparm()
59 global_creds = credopts.get_credentials(lp)
61 import password_lockout_base
68 class PasswordTests(password_lockout_base.BasePasswordTestCase):
71 self.host_url = host_url
73 self.global_creds = global_creds
74 self.ldb = SamDB(url=self.host_url, session_info=system_session(self.lp),
75 credentials=self.global_creds, lp=self.lp)
76 super(PasswordTests, self).setUp()
78 self.lockout2krb5_creds = self.insta_creds(self.template_creds,
79 username="lockout2krb5",
80 userpass="thatsAcomplPASS0",
81 kerberos_state=MUST_USE_KERBEROS)
82 self.lockout2krb5_ldb = self._readd_user(self.lockout2krb5_creds,
83 lockOutObservationWindow=self.lockout_observation_window)
85 self.lockout2ntlm_creds = self.insta_creds(self.template_creds,
86 username="lockout2ntlm",
87 userpass="thatsAcomplPASS0",
88 kerberos_state=DONT_USE_KERBEROS)
89 self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds,
90 lockOutObservationWindow=self.lockout_observation_window)
93 def use_pso_lockout_settings(self, creds):
95 # create a PSO with the lockout settings the test cases normally expect
97 # Some test cases sleep() for self.account_lockout_duration
98 pso = PasswordSettings("lockout-PSO", self.ldb, lockout_attempts=3,
99 lockout_duration=self.account_lockout_duration)
100 self.addCleanup(self.ldb.delete, pso.dn)
102 userdn = "cn=%s,cn=users,%s" % (creds.get_username(), self.base_dn)
105 # update the global lockout settings to be wildly different to what
106 # the test cases normally expect
107 self.update_lockout_settings(threshold=10, duration=600,
108 observation_window=600)
110 def _reset_samr(self, res):
112 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
113 samr_user = self._open_samr_user(res)
114 acb_info = self.samr.QueryUserInfo(samr_user, 16)
115 acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
116 self.samr.SetUserInfo(samr_user, 16, acb_info)
117 self.samr.Close(samr_user)
120 class PasswordTestsWithoutSleep(PasswordTests):
122 # The tests in this class do not sleep, so we can have a
123 # longer window and not flap on slower hosts
124 self.account_lockout_duration = 30
125 self.lockout_observation_window = 30
126 super(PasswordTestsWithoutSleep, self).setUp()
128 def _reset_ldap_lockoutTime(self, res):
129 self.ldb.modify_ldif("""
130 dn: """ + str(res[0].dn) + """
136 def _reset_ldap_userAccountControl(self, res):
137 self.assertTrue("userAccountControl" in res[0])
138 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
140 uac = int(res[0]["userAccountControl"][0])
141 uacc = int(res[0]["msDS-User-Account-Control-Computed"][0])
144 uac = uac & ~dsdb.UF_LOCKOUT
146 self.ldb.modify_ldif("""
147 dn: """ + str(res[0].dn) + """
149 replace: userAccountControl
150 userAccountControl: %d
153 def _reset_by_method(self, res, method):
154 if method == "ldap_userAccountControl":
155 self._reset_ldap_userAccountControl(res)
156 elif method == "ldap_lockoutTime":
157 self._reset_ldap_lockoutTime(res)
158 elif method == "samr":
159 self._reset_samr(res)
161 self.assertTrue(False, msg="Invalid reset method[%s]" % method)
163 def _test_userPassword_lockout_with_clear_change(self, creds, other_ldb, method,
164 initial_lastlogon_relation=None):
166 Tests user lockout behaviour when we try to change the user's password
167 but specify an incorrect old-password. The method parameter specifies
168 how to reset the locked out account (e.g. by resetting lockoutTime)
170 # Notice: This works only against Windows if "dSHeuristics" has been set
172 username = creds.get_username()
173 userpass = creds.get_password()
174 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
176 use_kerberos = creds.get_kerberos_state()
177 if use_kerberos == MUST_USE_KERBEROS:
178 logoncount_relation = 'greater'
179 lastlogon_relation = 'greater'
180 print("Performs a password cleartext change operation on 'userPassword' using Kerberos")
182 logoncount_relation = 'equal'
183 lastlogon_relation = 'equal'
184 print("Performs a password cleartext change operation on 'userPassword' using NTLMSSP")
186 if initial_lastlogon_relation is not None:
187 lastlogon_relation = initial_lastlogon_relation
189 res = self._check_account(userdn,
191 badPasswordTime=("greater", 0),
192 logonCount=(logoncount_relation, 0),
193 lastLogon=(lastlogon_relation, 0),
194 lastLogonTimestamp=('greater', 0),
195 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
196 msDSUserAccountControlComputed=0)
197 badPasswordTime = int(res[0]["badPasswordTime"][0])
198 logonCount = int(res[0]["logonCount"][0])
199 lastLogon = int(res[0]["lastLogon"][0])
200 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
201 if lastlogon_relation == 'greater':
202 self.assertGreater(lastLogon, badPasswordTime)
203 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
205 # Change password on a connection as another user
209 other_ldb.modify_ldif("""
210 dn: """ + userdn + """
213 userPassword: thatsAcomplPASS1x
215 userPassword: thatsAcomplPASS2
218 except LdbError as e:
220 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
221 self.assertTrue('00000056' in msg, msg)
223 res = self._check_account(userdn,
225 badPasswordTime=("greater", badPasswordTime),
226 logonCount=logonCount,
228 lastLogonTimestamp=lastLogonTimestamp,
229 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
230 msDSUserAccountControlComputed=0)
231 badPasswordTime = int(res[0]["badPasswordTime"][0])
233 # Correct old password
234 other_ldb.modify_ldif("""
235 dn: """ + userdn + """
238 userPassword: """ + userpass + """
240 userPassword: thatsAcomplPASS2
243 res = self._check_account(userdn,
245 badPasswordTime=badPasswordTime,
246 logonCount=logonCount,
248 lastLogonTimestamp=lastLogonTimestamp,
249 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
250 msDSUserAccountControlComputed=0)
254 other_ldb.modify_ldif("""
255 dn: """ + userdn + """
258 userPassword: thatsAcomplPASS1x
260 userPassword: thatsAcomplPASS2
263 except LdbError as e1:
265 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
266 self.assertTrue('00000056' in msg, msg)
268 res = self._check_account(userdn,
270 badPasswordTime=("greater", badPasswordTime),
271 logonCount=logonCount,
273 lastLogonTimestamp=lastLogonTimestamp,
274 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
275 msDSUserAccountControlComputed=0)
276 badPasswordTime = int(res[0]["badPasswordTime"][0])
278 print("two failed password change")
282 other_ldb.modify_ldif("""
283 dn: """ + userdn + """
286 userPassword: thatsAcomplPASS1x
288 userPassword: thatsAcomplPASS2
291 except LdbError as e2:
293 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
294 self.assertTrue('00000056' in msg, msg)
296 res = self._check_account(userdn,
298 badPasswordTime=("greater", badPasswordTime),
299 logonCount=logonCount,
301 lastLogonTimestamp=lastLogonTimestamp,
302 lockoutTime=("greater", badPasswordTime),
303 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
304 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
305 badPasswordTime = int(res[0]["badPasswordTime"][0])
306 lockoutTime = int(res[0]["lockoutTime"][0])
310 other_ldb.modify_ldif("""
311 dn: """ + userdn + """
314 userPassword: thatsAcomplPASS1x
316 userPassword: thatsAcomplPASS2
319 except LdbError as e3:
321 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
322 self.assertTrue('00000775' in msg, msg)
324 res = self._check_account(userdn,
326 badPasswordTime=badPasswordTime,
327 logonCount=logonCount,
329 lastLogonTimestamp=lastLogonTimestamp,
330 lockoutTime=lockoutTime,
331 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
332 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
336 other_ldb.modify_ldif("""
337 dn: """ + userdn + """
340 userPassword: thatsAcomplPASS1x
342 userPassword: thatsAcomplPASS2
345 except LdbError as e4:
347 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
348 self.assertTrue('00000775' in msg, msg)
350 res = self._check_account(userdn,
352 badPasswordTime=badPasswordTime,
353 logonCount=logonCount,
354 lockoutTime=lockoutTime,
356 lastLogonTimestamp=lastLogonTimestamp,
357 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
358 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
361 # Correct old password
362 other_ldb.modify_ldif("""
363 dn: """ + userdn + """
366 userPassword: thatsAcomplPASS2
368 userPassword: thatsAcomplPASS2x
371 except LdbError as e5:
373 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
374 self.assertTrue('00000775' in msg, msg)
376 res = self._check_account(userdn,
378 badPasswordTime=badPasswordTime,
379 logonCount=logonCount,
381 lastLogonTimestamp=lastLogonTimestamp,
382 lockoutTime=lockoutTime,
383 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
384 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
386 # Now reset the password, which does NOT change the lockout!
387 self.ldb.modify_ldif("""
388 dn: """ + userdn + """
390 replace: userPassword
391 userPassword: thatsAcomplPASS2
394 res = self._check_account(userdn,
396 badPasswordTime=badPasswordTime,
397 logonCount=logonCount,
399 lastLogonTimestamp=lastLogonTimestamp,
400 lockoutTime=lockoutTime,
401 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
402 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
405 # Correct old password
406 other_ldb.modify_ldif("""
407 dn: """ + userdn + """
410 userPassword: thatsAcomplPASS2
412 userPassword: thatsAcomplPASS2x
415 except LdbError as e6:
417 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
418 self.assertTrue('00000775' in msg, msg)
420 res = self._check_account(userdn,
422 badPasswordTime=badPasswordTime,
423 logonCount=logonCount,
425 lastLogonTimestamp=lastLogonTimestamp,
426 lockoutTime=lockoutTime,
427 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
428 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
431 m.dn = Dn(self.ldb, userdn)
432 m["userAccountControl"] = MessageElement(
433 str(dsdb.UF_LOCKOUT),
434 FLAG_MOD_REPLACE, "userAccountControl")
438 # This shows that setting the UF_LOCKOUT flag alone makes no difference
439 res = self._check_account(userdn,
441 badPasswordTime=badPasswordTime,
442 logonCount=logonCount,
444 lastLogonTimestamp=lastLogonTimestamp,
445 lockoutTime=lockoutTime,
446 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
447 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
449 # This shows that setting the UF_LOCKOUT flag makes no difference
451 # Correct old password
452 other_ldb.modify_ldif("""
453 dn: """ + userdn + """
456 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
458 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')).decode('utf8') + """
461 except LdbError as e7:
463 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
464 self.assertTrue('00000775' in msg, msg)
466 res = self._check_account(userdn,
468 badPasswordTime=badPasswordTime,
469 logonCount=logonCount,
470 lockoutTime=lockoutTime,
472 lastLogonTimestamp=lastLogonTimestamp,
473 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
474 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
476 self._reset_by_method(res, method)
478 # Here bad password counts are reset without logon success.
479 res = self._check_account(userdn,
481 badPasswordTime=badPasswordTime,
482 logonCount=logonCount,
485 lastLogonTimestamp=lastLogonTimestamp,
486 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
487 msDSUserAccountControlComputed=0)
489 # The correct password after doing the unlock
491 other_ldb.modify_ldif("""
492 dn: """ + userdn + """
495 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
497 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')).decode('utf8') + """
499 userpass = "thatsAcomplPASS2x"
500 creds.set_password(userpass)
502 res = self._check_account(userdn,
504 badPasswordTime=badPasswordTime,
505 logonCount=logonCount,
508 lastLogonTimestamp=lastLogonTimestamp,
509 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
510 msDSUserAccountControlComputed=0)
514 other_ldb.modify_ldif("""
515 dn: """ + userdn + """
518 userPassword: thatsAcomplPASS1xyz
520 userPassword: thatsAcomplPASS2XYZ
523 except LdbError as e8:
525 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
526 self.assertTrue('00000056' in msg, msg)
528 res = self._check_account(userdn,
530 badPasswordTime=("greater", badPasswordTime),
531 logonCount=logonCount,
534 lastLogonTimestamp=lastLogonTimestamp,
535 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
536 msDSUserAccountControlComputed=0)
537 badPasswordTime = int(res[0]["badPasswordTime"][0])
541 other_ldb.modify_ldif("""
542 dn: """ + userdn + """
545 userPassword: thatsAcomplPASS1xyz
547 userPassword: thatsAcomplPASS2XYZ
550 except LdbError as e9:
552 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
553 self.assertTrue('00000056' in msg, msg)
555 res = self._check_account(userdn,
557 badPasswordTime=("greater", badPasswordTime),
558 logonCount=logonCount,
561 lastLogonTimestamp=lastLogonTimestamp,
562 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
563 msDSUserAccountControlComputed=0)
564 badPasswordTime = int(res[0]["badPasswordTime"][0])
566 self._reset_ldap_lockoutTime(res)
568 res = self._check_account(userdn,
570 badPasswordTime=badPasswordTime,
571 logonCount=logonCount,
573 lastLogonTimestamp=lastLogonTimestamp,
575 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
576 msDSUserAccountControlComputed=0)
578 # The following test lockout behaviour when modifying a user's password
579 # and specifying an invalid old password. There are variants for both
580 # NTLM and kerberos user authentication. As well as that, there are 3 ways
581 # to reset the locked out account: by clearing the lockout bit for
582 # userAccountControl (via LDAP), resetting it via SAMR, and by resetting
584 def test_userPassword_lockout_with_clear_change_krb5_ldap_userAccountControl(self):
585 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
586 self.lockout2krb5_ldb,
587 "ldap_userAccountControl")
589 def test_userPassword_lockout_with_clear_change_krb5_ldap_lockoutTime(self):
590 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
591 self.lockout2krb5_ldb,
594 def test_userPassword_lockout_with_clear_change_krb5_samr(self):
595 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
596 self.lockout2krb5_ldb,
599 def test_userPassword_lockout_with_clear_change_ntlm_ldap_userAccountControl(self):
600 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
601 self.lockout2ntlm_ldb,
602 "ldap_userAccountControl",
603 initial_lastlogon_relation='greater')
605 def test_userPassword_lockout_with_clear_change_ntlm_ldap_lockoutTime(self):
606 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
607 self.lockout2ntlm_ldb,
609 initial_lastlogon_relation='greater')
611 def test_userPassword_lockout_with_clear_change_ntlm_samr(self):
612 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
613 self.lockout2ntlm_ldb,
615 initial_lastlogon_relation='greater')
617 # For PSOs, just test a selection of the above combinations
618 def test_pso_userPassword_lockout_with_clear_change_krb5_ldap_userAccountControl(self):
619 self.use_pso_lockout_settings(self.lockout1krb5_creds)
620 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
621 self.lockout2krb5_ldb,
622 "ldap_userAccountControl")
624 def test_pso_userPassword_lockout_with_clear_change_ntlm_ldap_lockoutTime(self):
625 self.use_pso_lockout_settings(self.lockout1ntlm_creds)
626 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
627 self.lockout2ntlm_ldb,
629 initial_lastlogon_relation='greater')
631 def test_pso_userPassword_lockout_with_clear_change_ntlm_samr(self):
632 self.use_pso_lockout_settings(self.lockout1ntlm_creds)
633 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
634 self.lockout2ntlm_ldb,
636 initial_lastlogon_relation='greater')
638 def test_multiple_logon_krb5(self):
639 self._test_multiple_logon(self.lockout1krb5_creds)
641 def test_multiple_logon_ntlm(self):
642 self._test_multiple_logon(self.lockout1ntlm_creds)
644 def _test_samr_password_change(self, creds, other_creds, lockout_threshold=3):
645 """Tests user lockout by using bad password in SAMR password_change"""
647 # create a connection for SAMR using another user's credentials
648 lp = self.get_loadparm()
649 net = Net(other_creds, lp, server=self.host)
651 # work out the initial account values for this user
652 username = creds.get_username()
653 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
654 res = self._check_account(userdn,
656 badPasswordTime=("greater", 0),
657 badPwdCountOnly=True)
658 badPasswordTime = int(res[0]["badPasswordTime"][0])
659 logonCount = int(res[0]["logonCount"][0])
660 lastLogon = int(res[0]["lastLogon"][0])
661 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
663 # prove we can change the user password (using the correct password)
664 new_password = "thatsAcomplPASS2"
665 net.change_password(newpassword=new_password,
667 oldpassword=creds.get_password())
668 creds.set_password(new_password)
670 # try entering 'x' many bad passwords in a row to lock the user out
671 new_password = "thatsAcomplPASS3"
672 for i in range(lockout_threshold):
675 print("Trying bad password, attempt #%u" % badPwdCount)
676 net.change_password(newpassword=new_password,
677 username=creds.get_username(),
678 oldpassword="bad-password")
679 self.fail("Invalid SAMR change_password accepted")
680 except NTSTATUSError as e:
681 enum = ctypes.c_uint32(e.args[0]).value
682 self.assertEqual(enum, ntstatus.NT_STATUS_WRONG_PASSWORD)
684 # check the status of the account is updated after each bad attempt
687 if badPwdCount >= lockout_threshold:
688 account_flags = dsdb.UF_LOCKOUT
689 lockoutTime = ("greater", badPasswordTime)
691 res = self._check_account(userdn,
692 badPwdCount=badPwdCount,
693 badPasswordTime=("greater", badPasswordTime),
694 logonCount=logonCount,
696 lastLogonTimestamp=lastLogonTimestamp,
697 lockoutTime=lockoutTime,
698 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
699 msDSUserAccountControlComputed=account_flags)
700 badPasswordTime = int(res[0]["badPasswordTime"][0])
702 # the user is now locked out
703 lockoutTime = int(res[0]["lockoutTime"][0])
705 # check the user remains locked out regardless of whether they use a
706 # good or a bad password now
707 for password in (creds.get_password(), "bad-password"):
709 print("Trying password %s" % password)
710 net.change_password(newpassword=new_password,
711 username=creds.get_username(),
712 oldpassword=password)
713 self.fail("Invalid SAMR change_password accepted")
714 except NTSTATUSError as e:
715 enum = ctypes.c_uint32(e.args[0]).value
716 self.assertEqual(enum, ntstatus.NT_STATUS_ACCOUNT_LOCKED_OUT)
718 res = self._check_account(userdn,
719 badPwdCount=lockout_threshold,
720 badPasswordTime=badPasswordTime,
721 logonCount=logonCount,
723 lastLogonTimestamp=lastLogonTimestamp,
724 lockoutTime=lockoutTime,
725 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
726 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
728 # reset the user account lockout
729 self._reset_samr(res)
731 # check bad password counts are reset
732 res = self._check_account(userdn,
734 badPasswordTime=badPasswordTime,
735 logonCount=logonCount,
738 lastLogonTimestamp=lastLogonTimestamp,
739 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
740 msDSUserAccountControlComputed=0)
742 # check we can change the user password successfully now
743 net.change_password(newpassword=new_password,
745 oldpassword=creds.get_password())
746 creds.set_password(new_password)
748 def test_samr_change_password(self):
749 self._test_samr_password_change(self.lockout1ntlm_creds,
750 other_creds=self.lockout2ntlm_creds)
752 # same as above, but use a PSO to enforce the lockout
753 def test_pso_samr_change_password(self):
754 self.use_pso_lockout_settings(self.lockout1ntlm_creds)
755 self._test_samr_password_change(self.lockout1ntlm_creds,
756 other_creds=self.lockout2ntlm_creds)
759 class PasswordTestsWithSleep(PasswordTests):
761 super(PasswordTestsWithSleep, self).setUp()
763 def _test_unicodePwd_lockout_with_clear_change(self, creds, other_ldb,
764 initial_logoncount_relation=None):
765 print("Performs a password cleartext change operation on 'unicodePwd'")
766 username = creds.get_username()
767 userpass = creds.get_password()
768 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
769 if initial_logoncount_relation is not None:
770 logoncount_relation = initial_logoncount_relation
772 logoncount_relation = "greater"
774 res = self._check_account(userdn,
776 badPasswordTime=("greater", 0),
777 logonCount=(logoncount_relation, 0),
778 lastLogon=("greater", 0),
779 lastLogonTimestamp=("greater", 0),
780 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
781 msDSUserAccountControlComputed=0)
782 badPasswordTime = int(res[0]["badPasswordTime"][0])
783 logonCount = int(res[0]["logonCount"][0])
784 lastLogon = int(res[0]["lastLogon"][0])
785 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
786 self.assertGreater(lastLogonTimestamp, badPasswordTime)
787 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
789 # Change password on a connection as another user
793 other_ldb.modify_ldif("""
794 dn: """ + userdn + """
797 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')).decode('utf8') + """
799 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
802 except LdbError as e10:
803 (num, msg) = e10.args
804 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
805 self.assertTrue('00000056' in msg, msg)
807 res = self._check_account(userdn,
809 badPasswordTime=("greater", badPasswordTime),
810 logonCount=logonCount,
812 lastLogonTimestamp=lastLogonTimestamp,
813 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
814 msDSUserAccountControlComputed=0)
815 badPasswordTime = int(res[0]["badPasswordTime"][0])
817 # Correct old password
818 old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
819 invalid_utf16 = "\"thatsAcomplPASSX\"".encode('utf-16-le')
820 userpass = "thatsAcomplPASS2"
821 creds.set_password(userpass)
822 new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
824 other_ldb.modify_ldif("""
825 dn: """ + userdn + """
828 unicodePwd:: """ + base64.b64encode(old_utf16).decode('utf8') + """
830 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
833 res = self._check_account(userdn,
835 badPasswordTime=badPasswordTime,
836 logonCount=logonCount,
838 lastLogonTimestamp=lastLogonTimestamp,
839 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
840 msDSUserAccountControlComputed=0)
844 other_ldb.modify_ldif("""
845 dn: """ + userdn + """
848 unicodePwd:: """ + base64.b64encode(old_utf16).decode('utf8') + """
850 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
853 except LdbError as e11:
854 (num, msg) = e11.args
855 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
856 self.assertTrue('00000056' in msg, msg)
858 res = self._check_account(userdn,
860 badPasswordTime=("greater", badPasswordTime),
861 logonCount=logonCount,
863 lastLogonTimestamp=lastLogonTimestamp,
864 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
865 msDSUserAccountControlComputed=0)
866 badPasswordTime = int(res[0]["badPasswordTime"][0])
868 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
869 # It doesn't create "lockoutTime" = 0 and doesn't
870 # reset "badPwdCount" = 0.
871 self._reset_samr(res)
873 res = self._check_account(userdn,
875 badPasswordTime=badPasswordTime,
876 logonCount=logonCount,
878 lastLogonTimestamp=lastLogonTimestamp,
879 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
880 msDSUserAccountControlComputed=0)
882 print("two failed password change")
886 other_ldb.modify_ldif("""
887 dn: """ + userdn + """
890 unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
892 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
895 except LdbError as e12:
896 (num, msg) = e12.args
897 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
898 self.assertTrue('00000056' in msg, msg)
900 # this is strange, why do we have lockoutTime=badPasswordTime here?
901 res = self._check_account(userdn,
903 badPasswordTime=("greater", badPasswordTime),
904 logonCount=logonCount,
906 lastLogonTimestamp=lastLogonTimestamp,
907 lockoutTime=("greater", badPasswordTime),
908 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
909 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
910 badPasswordTime = int(res[0]["badPasswordTime"][0])
911 lockoutTime = int(res[0]["lockoutTime"][0])
915 other_ldb.modify_ldif("""
916 dn: """ + userdn + """
919 unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
921 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
924 except LdbError as e13:
925 (num, msg) = e13.args
926 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
927 self.assertTrue('00000775' in msg, msg)
929 res = self._check_account(userdn,
931 badPasswordTime=badPasswordTime,
932 logonCount=logonCount,
934 lastLogonTimestamp=lastLogonTimestamp,
935 lockoutTime=lockoutTime,
936 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
937 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
941 other_ldb.modify_ldif("""
942 dn: """ + userdn + """
945 unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
947 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
950 except LdbError as e14:
951 (num, msg) = e14.args
952 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
953 self.assertTrue('00000775' in msg, msg)
955 res = self._check_account(userdn,
957 badPasswordTime=badPasswordTime,
958 logonCount=logonCount,
960 lastLogonTimestamp=lastLogonTimestamp,
961 lockoutTime=lockoutTime,
962 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
963 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
966 # Correct old password
967 other_ldb.modify_ldif("""
968 dn: """ + userdn + """
971 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
973 unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
976 except LdbError as e15:
977 (num, msg) = e15.args
978 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
979 self.assertTrue('00000775' in msg, msg)
981 res = self._check_account(userdn,
983 badPasswordTime=badPasswordTime,
984 logonCount=logonCount,
986 lastLogonTimestamp=lastLogonTimestamp,
987 lockoutTime=lockoutTime,
988 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
989 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
991 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
992 self._reset_samr(res)
994 res = self._check_account(userdn,
996 badPasswordTime=badPasswordTime,
997 logonCount=logonCount,
999 lastLogonTimestamp=lastLogonTimestamp,
1001 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
1002 msDSUserAccountControlComputed=0)
1004 # Correct old password
1005 old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
1006 invalid_utf16 = "\"thatsAcomplPASSiX\"".encode('utf-16-le')
1007 userpass = "thatsAcomplPASS2x"
1008 creds.set_password(userpass)
1009 new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
1011 other_ldb.modify_ldif("""
1012 dn: """ + userdn + """
1015 unicodePwd:: """ + base64.b64encode(old_utf16).decode('utf8') + """
1017 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
1020 res = self._check_account(userdn,
1022 badPasswordTime=badPasswordTime,
1023 logonCount=logonCount,
1024 lastLogon=lastLogon,
1025 lastLogonTimestamp=lastLogonTimestamp,
1027 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
1028 msDSUserAccountControlComputed=0)
1030 # Wrong old password
1032 other_ldb.modify_ldif("""
1033 dn: """ + userdn + """
1036 unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
1038 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
1041 except LdbError as e16:
1042 (num, msg) = e16.args
1043 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
1044 self.assertTrue('00000056' in msg, msg)
1046 res = self._check_account(userdn,
1048 badPasswordTime=("greater", badPasswordTime),
1049 logonCount=logonCount,
1050 lastLogon=lastLogon,
1051 lastLogonTimestamp=lastLogonTimestamp,
1053 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
1054 msDSUserAccountControlComputed=0)
1055 badPasswordTime = int(res[0]["badPasswordTime"][0])
1057 # Wrong old password
1059 other_ldb.modify_ldif("""
1060 dn: """ + userdn + """
1063 unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
1065 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
1068 except LdbError as e17:
1069 (num, msg) = e17.args
1070 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
1071 self.assertTrue('00000056' in msg, msg)
1073 res = self._check_account(userdn,
1075 badPasswordTime=("greater", badPasswordTime),
1076 logonCount=logonCount,
1077 lastLogon=lastLogon,
1078 lastLogonTimestamp=lastLogonTimestamp,
1080 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
1081 msDSUserAccountControlComputed=0)
1082 badPasswordTime = int(res[0]["badPasswordTime"][0])
1084 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1085 # It doesn't reset "badPwdCount" = 0.
1086 self._reset_samr(res)
1088 res = self._check_account(userdn,
1090 badPasswordTime=badPasswordTime,
1091 logonCount=logonCount,
1092 lastLogon=lastLogon,
1093 lastLogonTimestamp=lastLogonTimestamp,
1095 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
1096 msDSUserAccountControlComputed=0)
1098 # Wrong old password
1100 other_ldb.modify_ldif("""
1101 dn: """ + userdn + """
1104 unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
1106 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
1109 except LdbError as e18:
1110 (num, msg) = e18.args
1111 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
1112 self.assertTrue('00000056' in msg, msg)
1114 res = self._check_account(userdn,
1116 badPasswordTime=("greater", badPasswordTime),
1117 logonCount=logonCount,
1118 lastLogon=lastLogon,
1119 lastLogonTimestamp=lastLogonTimestamp,
1120 lockoutTime=("greater", badPasswordTime),
1121 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
1122 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1123 badPasswordTime = int(res[0]["badPasswordTime"][0])
1124 lockoutTime = int(res[0]["lockoutTime"][0])
1126 time.sleep(self.account_lockout_duration + 1)
1128 res = self._check_account(userdn,
1129 badPwdCount=3, effective_bad_password_count=0,
1130 badPasswordTime=badPasswordTime,
1131 logonCount=logonCount,
1132 lastLogon=lastLogon,
1133 lastLogonTimestamp=lastLogonTimestamp,
1134 lockoutTime=lockoutTime,
1135 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
1136 msDSUserAccountControlComputed=0)
1138 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1139 # It doesn't reset "lockoutTime" = 0 and doesn't
1140 # reset "badPwdCount" = 0.
1141 self._reset_samr(res)
1143 res = self._check_account(userdn,
1144 badPwdCount=3, effective_bad_password_count=0,
1145 badPasswordTime=badPasswordTime,
1146 logonCount=logonCount,
1147 lockoutTime=lockoutTime,
1148 lastLogon=lastLogon,
1149 lastLogonTimestamp=lastLogonTimestamp,
1150 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
1151 msDSUserAccountControlComputed=0)
1153 def test_unicodePwd_lockout_with_clear_change_krb5(self):
1154 self._test_unicodePwd_lockout_with_clear_change(self.lockout1krb5_creds,
1155 self.lockout2krb5_ldb)
1157 def test_unicodePwd_lockout_with_clear_change_ntlm(self):
1158 self._test_unicodePwd_lockout_with_clear_change(self.lockout1ntlm_creds,
1159 self.lockout2ntlm_ldb,
1160 initial_logoncount_relation="equal")
1162 def test_login_lockout_krb5(self):
1163 self._test_login_lockout(self.lockout1krb5_creds)
1165 def test_login_lockout_ntlm(self):
1166 self._test_login_lockout(self.lockout1ntlm_creds)
1168 # Repeat the login lockout tests using PSOs
1169 def test_pso_login_lockout_krb5(self):
1170 """Check the PSO lockout settings get applied to the user correctly"""
1171 self.use_pso_lockout_settings(self.lockout1krb5_creds)
1172 self._test_login_lockout(self.lockout1krb5_creds)
1174 def test_pso_login_lockout_ntlm(self):
1175 """Check the PSO lockout settings get applied to the user correctly"""
1176 self.use_pso_lockout_settings(self.lockout1ntlm_creds)
1177 self._test_login_lockout(self.lockout1ntlm_creds)
1179 def _testing_add_user(self, creds, lockOutObservationWindow=0):
1180 username = creds.get_username()
1181 userpass = creds.get_password()
1182 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
1184 use_kerberos = creds.get_kerberos_state()
1185 if use_kerberos == MUST_USE_KERBEROS:
1186 logoncount_relation = 'greater'
1187 lastlogon_relation = 'greater'
1189 logoncount_relation = 'equal'
1190 if lockOutObservationWindow == 0:
1191 lastlogon_relation = 'greater'
1193 lastlogon_relation = 'equal'
1195 delete_force(self.ldb, userdn)
1198 "objectclass": "user",
1199 "sAMAccountName": username})
1201 self.addCleanup(delete_force, self.ldb, userdn)
1203 res = self._check_account(userdn,
1208 lastLogonTimestamp=('absent', None),
1209 userAccountControl=(dsdb.UF_NORMAL_ACCOUNT |
1210 dsdb.UF_ACCOUNTDISABLE |
1211 dsdb.UF_PASSWD_NOTREQD),
1212 msDSUserAccountControlComputed=dsdb.UF_PASSWORD_EXPIRED)
1214 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1215 # It doesn't create "lockoutTime" = 0.
1216 self._reset_samr(res)
1218 res = self._check_account(userdn,
1223 lastLogonTimestamp=('absent', None),
1224 userAccountControl=(dsdb.UF_NORMAL_ACCOUNT |
1225 dsdb.UF_ACCOUNTDISABLE |
1226 dsdb.UF_PASSWD_NOTREQD),
1227 msDSUserAccountControlComputed=dsdb.UF_PASSWORD_EXPIRED)
1229 # Tests a password change when we don't have any password yet with a
1230 # wrong old password
1232 self.ldb.modify_ldif("""
1233 dn: """ + userdn + """
1235 delete: userPassword
1236 userPassword: noPassword
1238 userPassword: thatsAcomplPASS2
1241 except LdbError as e19:
1242 (num, msg) = e19.args
1243 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
1244 # Windows (2008 at least) seems to have some small bug here: it
1245 # returns "0000056A" on longer (always wrong) previous passwords.
1246 self.assertTrue('00000056' in msg, msg)
1248 res = self._check_account(userdn,
1250 badPasswordTime=("greater", 0),
1253 lastLogonTimestamp=('absent', None),
1254 userAccountControl=(dsdb.UF_NORMAL_ACCOUNT |
1255 dsdb.UF_ACCOUNTDISABLE |
1256 dsdb.UF_PASSWD_NOTREQD),
1257 msDSUserAccountControlComputed=dsdb.UF_PASSWORD_EXPIRED)
1258 badPwdCount = int(res[0]["badPwdCount"][0])
1259 badPasswordTime = int(res[0]["badPasswordTime"][0])
1261 # Sets the initial user password with a "special" password change
1262 # I think that this internally is a password set operation and it can
1263 # only be performed by someone which has password set privileges on the
1264 # account (at least in s4 we do handle it like that).
1265 self.ldb.modify_ldif("""
1266 dn: """ + userdn + """
1268 delete: userPassword
1270 userPassword: """ + userpass + """
1273 res = self._check_account(userdn,
1274 badPwdCount=badPwdCount,
1275 badPasswordTime=badPasswordTime,
1278 lastLogonTimestamp=('absent', None),
1279 userAccountControl=(dsdb.UF_NORMAL_ACCOUNT |
1280 dsdb.UF_ACCOUNTDISABLE |
1281 dsdb.UF_PASSWD_NOTREQD),
1282 msDSUserAccountControlComputed=0)
1284 # Enables the user account
1285 self.ldb.enable_account("(sAMAccountName=%s)" % username)
1287 res = self._check_account(userdn,
1288 badPwdCount=badPwdCount,
1289 badPasswordTime=badPasswordTime,
1292 lastLogonTimestamp=('absent', None),
1293 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
1294 msDSUserAccountControlComputed=0)
1295 if lockOutObservationWindow != 0:
1296 time.sleep(lockOutObservationWindow + 1)
1297 effective_bad_password_count = 0
1299 effective_bad_password_count = badPwdCount
1301 res = self._check_account(userdn,
1302 badPwdCount=badPwdCount,
1303 effective_bad_password_count=effective_bad_password_count,
1304 badPasswordTime=badPasswordTime,
1307 lastLogonTimestamp=('absent', None),
1308 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
1309 msDSUserAccountControlComputed=0)
1311 ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp)
1313 if lockOutObservationWindow == 0:
1315 effective_bad_password_count = 0
1316 if use_kerberos == MUST_USE_KERBEROS:
1318 effective_bad_password_count = 0
1320 res = self._check_account(userdn,
1321 badPwdCount=badPwdCount,
1322 effective_bad_password_count=effective_bad_password_count,
1323 badPasswordTime=badPasswordTime,
1324 logonCount=(logoncount_relation, 0),
1325 lastLogon=(lastlogon_relation, 0),
1326 lastLogonTimestamp=('greater', badPasswordTime),
1327 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
1328 msDSUserAccountControlComputed=0)
1330 logonCount = int(res[0]["logonCount"][0])
1331 lastLogon = int(res[0]["lastLogon"][0])
1332 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1333 if lastlogon_relation == 'greater':
1334 self.assertGreater(lastLogon, badPasswordTime)
1335 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
1337 res = self._check_account(userdn,
1338 badPwdCount=badPwdCount,
1339 effective_bad_password_count=effective_bad_password_count,
1340 badPasswordTime=badPasswordTime,
1341 logonCount=logonCount,
1342 lastLogon=lastLogon,
1343 lastLogonTimestamp=lastLogonTimestamp,
1344 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
1345 msDSUserAccountControlComputed=0)
1348 def test_lockout_observation_window(self):
1349 lockout3krb5_creds = self.insta_creds(self.template_creds,
1350 username="lockout3krb5",
1351 userpass="thatsAcomplPASS0",
1352 kerberos_state=MUST_USE_KERBEROS)
1353 self._testing_add_user(lockout3krb5_creds)
1355 lockout4krb5_creds = self.insta_creds(self.template_creds,
1356 username="lockout4krb5",
1357 userpass="thatsAcomplPASS0",
1358 kerberos_state=MUST_USE_KERBEROS)
1359 self._testing_add_user(lockout4krb5_creds,
1360 lockOutObservationWindow=self.lockout_observation_window)
1362 lockout3ntlm_creds = self.insta_creds(self.template_creds,
1363 username="lockout3ntlm",
1364 userpass="thatsAcomplPASS0",
1365 kerberos_state=DONT_USE_KERBEROS)
1366 self._testing_add_user(lockout3ntlm_creds)
1367 lockout4ntlm_creds = self.insta_creds(self.template_creds,
1368 username="lockout4ntlm",
1369 userpass="thatsAcomplPASS0",
1370 kerberos_state=DONT_USE_KERBEROS)
1371 self._testing_add_user(lockout4ntlm_creds,
1372 lockOutObservationWindow=self.lockout_observation_window)
1374 class PasswordTestsWithDefaults(PasswordTests):
1376 # The tests in this class do not sleep, so we can use the default
1377 # timeout windows here
1378 self.account_lockout_duration = 30 * 60
1379 self.lockout_observation_window = 30 * 60
1380 super(PasswordTestsWithDefaults, self).setUp()
1382 # sanity-check that user lockout works with the default settings (we just
1383 # check the user is locked out - we don't wait for the lockout to expire)
1384 def test_login_lockout_krb5(self):
1385 self._test_login_lockout(self.lockout1krb5_creds,
1386 wait_lockout_duration=False)
1388 def test_login_lockout_ntlm(self):
1389 self._test_login_lockout(self.lockout1ntlm_creds,
1390 wait_lockout_duration=False)
1392 # Repeat the login lockout tests using PSOs
1393 def test_pso_login_lockout_krb5(self):
1394 """Check the PSO lockout settings get applied to the user correctly"""
1395 self.use_pso_lockout_settings(self.lockout1krb5_creds)
1396 self._test_login_lockout(self.lockout1krb5_creds,
1397 wait_lockout_duration=False)
1399 def test_pso_login_lockout_ntlm(self):
1400 """Check the PSO lockout settings get applied to the user correctly"""
1401 self.use_pso_lockout_settings(self.lockout1ntlm_creds)
1402 self._test_login_lockout(self.lockout1ntlm_creds,
1403 wait_lockout_duration=False)
1405 host_url = "ldap://%s" % host
1407 TestProgram(module=__name__, opts=subunitopts)