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 import password_lockout_base
62 class PasswordTests(password_lockout_base.BasePasswordTestCase):
65 self.host_url = host_url
67 self.global_creds = global_creds
68 self.ldb = SamDB(url=self.host_url, session_info=system_session(self.lp),
69 credentials=self.global_creds, lp=self.lp)
70 super(PasswordTests, self).setUp()
72 self.lockout2krb5_creds = self.insta_creds(self.template_creds,
73 username="lockout2krb5",
74 userpass="thatsAcomplPASS0",
75 kerberos_state=MUST_USE_KERBEROS)
76 self.lockout2krb5_ldb = self._readd_user(self.lockout2krb5_creds,
77 lockOutObservationWindow=self.lockout_observation_window)
79 self.lockout2ntlm_creds = self.insta_creds(self.template_creds,
80 username="lockout2ntlm",
81 userpass="thatsAcomplPASS0",
82 kerberos_state=DONT_USE_KERBEROS)
83 self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds,
84 lockOutObservationWindow=self.lockout_observation_window)
86 def _reset_ldap_lockoutTime(self, res):
87 self.ldb.modify_ldif("""
88 dn: """ + str(res[0].dn) + """
94 def _reset_ldap_userAccountControl(self, res):
95 self.assertTrue("userAccountControl" in res[0])
96 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
98 uac = int(res[0]["userAccountControl"][0])
99 uacc = int(res[0]["msDS-User-Account-Control-Computed"][0])
102 uac = uac & ~dsdb.UF_LOCKOUT
104 self.ldb.modify_ldif("""
105 dn: """ + str(res[0].dn) + """
107 replace: userAccountControl
108 userAccountControl: %d
111 def _reset_by_method(self, res, method):
112 if method is "ldap_userAccountControl":
113 self._reset_ldap_userAccountControl(res)
114 elif method is "ldap_lockoutTime":
115 self._reset_ldap_lockoutTime(res)
116 elif method is "samr":
117 self._reset_samr(res)
119 self.assertTrue(False, msg="Invalid reset method[%s]" % method)
121 def _test_userPassword_lockout_with_clear_change(self, creds, other_ldb, method,
122 initial_lastlogon_relation=None):
123 # Notice: This works only against Windows if "dSHeuristics" has been set
125 username = creds.get_username()
126 userpass = creds.get_password()
127 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
129 use_kerberos = creds.get_kerberos_state()
130 if use_kerberos == MUST_USE_KERBEROS:
131 logoncount_relation = 'greater'
132 lastlogon_relation = 'greater'
133 print "Performs a password cleartext change operation on 'userPassword' using Kerberos"
135 logoncount_relation = 'equal'
136 lastlogon_relation = 'equal'
137 print "Performs a password cleartext change operation on 'userPassword' using NTLMSSP"
139 if initial_lastlogon_relation is not None:
140 lastlogon_relation = initial_lastlogon_relation
142 res = self._check_account(userdn,
144 badPasswordTime=("greater", 0),
145 logonCount=(logoncount_relation, 0),
146 lastLogon=(lastlogon_relation, 0),
147 lastLogonTimestamp=('greater', 0),
149 dsdb.UF_NORMAL_ACCOUNT,
150 msDSUserAccountControlComputed=0)
151 badPasswordTime = int(res[0]["badPasswordTime"][0])
152 logonCount = int(res[0]["logonCount"][0])
153 lastLogon = int(res[0]["lastLogon"][0])
154 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
155 if lastlogon_relation == 'greater':
156 self.assertGreater(lastLogon, badPasswordTime)
157 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
159 # Change password on a connection as another user
163 other_ldb.modify_ldif("""
164 dn: """ + userdn + """
167 userPassword: thatsAcomplPASS1x
169 userPassword: thatsAcomplPASS2
172 except LdbError, (num, msg):
173 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
174 self.assertTrue('00000056' in msg, msg)
176 res = self._check_account(userdn,
178 badPasswordTime=("greater", badPasswordTime),
179 logonCount=logonCount,
181 lastLogonTimestamp=lastLogonTimestamp,
183 dsdb.UF_NORMAL_ACCOUNT,
184 msDSUserAccountControlComputed=0)
185 badPasswordTime = int(res[0]["badPasswordTime"][0])
187 # Correct old password
188 other_ldb.modify_ldif("""
189 dn: """ + userdn + """
192 userPassword: """ + userpass + """
194 userPassword: thatsAcomplPASS2
197 res = self._check_account(userdn,
199 badPasswordTime=badPasswordTime,
200 logonCount=logonCount,
202 lastLogonTimestamp=lastLogonTimestamp,
204 dsdb.UF_NORMAL_ACCOUNT,
205 msDSUserAccountControlComputed=0)
209 other_ldb.modify_ldif("""
210 dn: """ + userdn + """
213 userPassword: thatsAcomplPASS1x
215 userPassword: thatsAcomplPASS2
218 except LdbError, (num, msg):
219 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
220 self.assertTrue('00000056' in msg, msg)
222 res = self._check_account(userdn,
224 badPasswordTime=("greater", badPasswordTime),
225 logonCount=logonCount,
227 lastLogonTimestamp=lastLogonTimestamp,
229 dsdb.UF_NORMAL_ACCOUNT,
230 msDSUserAccountControlComputed=0)
231 badPasswordTime = int(res[0]["badPasswordTime"][0])
233 print "two failed password change"
237 other_ldb.modify_ldif("""
238 dn: """ + userdn + """
241 userPassword: thatsAcomplPASS1x
243 userPassword: thatsAcomplPASS2
246 except LdbError, (num, msg):
247 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
248 self.assertTrue('00000056' in msg, msg)
250 res = self._check_account(userdn,
252 badPasswordTime=("greater", badPasswordTime),
253 logonCount=logonCount,
255 lastLogonTimestamp=lastLogonTimestamp,
256 lockoutTime=("greater", badPasswordTime),
258 dsdb.UF_NORMAL_ACCOUNT,
259 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
260 badPasswordTime = int(res[0]["badPasswordTime"][0])
261 lockoutTime = int(res[0]["lockoutTime"][0])
265 other_ldb.modify_ldif("""
266 dn: """ + userdn + """
269 userPassword: thatsAcomplPASS1x
271 userPassword: thatsAcomplPASS2
274 except LdbError, (num, msg):
275 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
276 self.assertTrue('00000775' in msg, msg)
278 res = self._check_account(userdn,
280 badPasswordTime=badPasswordTime,
281 logonCount=logonCount,
283 lastLogonTimestamp=lastLogonTimestamp,
284 lockoutTime=lockoutTime,
286 dsdb.UF_NORMAL_ACCOUNT,
287 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
291 other_ldb.modify_ldif("""
292 dn: """ + userdn + """
295 userPassword: thatsAcomplPASS1x
297 userPassword: thatsAcomplPASS2
300 except LdbError, (num, msg):
301 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
302 self.assertTrue('00000775' in msg, msg)
304 res = self._check_account(userdn,
306 badPasswordTime=badPasswordTime,
307 logonCount=logonCount,
308 lockoutTime=lockoutTime,
310 lastLogonTimestamp=lastLogonTimestamp,
312 dsdb.UF_NORMAL_ACCOUNT,
313 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
316 # Correct old password
317 other_ldb.modify_ldif("""
318 dn: """ + userdn + """
321 userPassword: thatsAcomplPASS2
323 userPassword: thatsAcomplPASS2x
326 except LdbError, (num, msg):
327 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
328 self.assertTrue('00000775' in msg, msg)
330 res = self._check_account(userdn,
332 badPasswordTime=badPasswordTime,
333 logonCount=logonCount,
335 lastLogonTimestamp=lastLogonTimestamp,
336 lockoutTime=lockoutTime,
338 dsdb.UF_NORMAL_ACCOUNT,
339 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
341 # Now reset the password, which does NOT change the lockout!
342 self.ldb.modify_ldif("""
343 dn: """ + userdn + """
345 replace: userPassword
346 userPassword: thatsAcomplPASS2
349 res = self._check_account(userdn,
351 badPasswordTime=badPasswordTime,
352 logonCount=logonCount,
354 lastLogonTimestamp=lastLogonTimestamp,
355 lockoutTime=lockoutTime,
357 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, (num, msg):
372 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
373 self.assertTrue('00000775' in msg, msg)
375 res = self._check_account(userdn,
377 badPasswordTime=badPasswordTime,
378 logonCount=logonCount,
380 lastLogonTimestamp=lastLogonTimestamp,
381 lockoutTime=lockoutTime,
383 dsdb.UF_NORMAL_ACCOUNT,
384 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
387 m.dn = Dn(self.ldb, userdn)
388 m["userAccountControl"] = MessageElement(
389 str(dsdb.UF_LOCKOUT),
390 FLAG_MOD_REPLACE, "userAccountControl")
394 # This shows that setting the UF_LOCKOUT flag alone makes no difference
395 res = self._check_account(userdn,
397 badPasswordTime=badPasswordTime,
398 logonCount=logonCount,
400 lastLogonTimestamp=lastLogonTimestamp,
401 lockoutTime=lockoutTime,
403 dsdb.UF_NORMAL_ACCOUNT,
404 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
406 # This shows that setting the UF_LOCKOUT flag makes no difference
408 # Correct old password
409 other_ldb.modify_ldif("""
410 dn: """ + userdn + """
413 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
415 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
418 except LdbError, (num, msg):
419 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
420 self.assertTrue('00000775' in msg, msg)
422 res = self._check_account(userdn,
424 badPasswordTime=badPasswordTime,
425 logonCount=logonCount,
426 lockoutTime=lockoutTime,
428 lastLogonTimestamp=lastLogonTimestamp,
430 dsdb.UF_NORMAL_ACCOUNT,
431 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
433 self._reset_by_method(res, method)
435 # Here bad password counts are reset without logon success.
436 res = self._check_account(userdn,
438 badPasswordTime=badPasswordTime,
439 logonCount=logonCount,
442 lastLogonTimestamp=lastLogonTimestamp,
444 dsdb.UF_NORMAL_ACCOUNT,
445 msDSUserAccountControlComputed=0)
447 # The correct password after doing the unlock
449 other_ldb.modify_ldif("""
450 dn: """ + userdn + """
453 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
455 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
457 userpass = "thatsAcomplPASS2x"
458 creds.set_password(userpass)
460 res = self._check_account(userdn,
462 badPasswordTime=badPasswordTime,
463 logonCount=logonCount,
466 lastLogonTimestamp=lastLogonTimestamp,
468 dsdb.UF_NORMAL_ACCOUNT,
469 msDSUserAccountControlComputed=0)
473 other_ldb.modify_ldif("""
474 dn: """ + userdn + """
477 userPassword: thatsAcomplPASS1xyz
479 userPassword: thatsAcomplPASS2XYZ
482 except LdbError, (num, msg):
483 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
484 self.assertTrue('00000056' in msg, msg)
486 res = self._check_account(userdn,
488 badPasswordTime=("greater", badPasswordTime),
489 logonCount=logonCount,
492 lastLogonTimestamp=lastLogonTimestamp,
494 dsdb.UF_NORMAL_ACCOUNT,
495 msDSUserAccountControlComputed=0)
496 badPasswordTime = int(res[0]["badPasswordTime"][0])
500 other_ldb.modify_ldif("""
501 dn: """ + userdn + """
504 userPassword: thatsAcomplPASS1xyz
506 userPassword: thatsAcomplPASS2XYZ
509 except LdbError, (num, msg):
510 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
511 self.assertTrue('00000056' in msg, msg)
513 res = self._check_account(userdn,
515 badPasswordTime=("greater", badPasswordTime),
516 logonCount=logonCount,
519 lastLogonTimestamp=lastLogonTimestamp,
521 dsdb.UF_NORMAL_ACCOUNT,
522 msDSUserAccountControlComputed=0)
523 badPasswordTime = int(res[0]["badPasswordTime"][0])
525 self._reset_ldap_lockoutTime(res)
527 res = self._check_account(userdn,
529 badPasswordTime=badPasswordTime,
530 logonCount=logonCount,
532 lastLogonTimestamp=lastLogonTimestamp,
535 dsdb.UF_NORMAL_ACCOUNT,
536 msDSUserAccountControlComputed=0)
538 def test_userPassword_lockout_with_clear_change_krb5_ldap_userAccountControl(self):
539 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
540 self.lockout2krb5_ldb,
541 "ldap_userAccountControl")
543 def test_userPassword_lockout_with_clear_change_krb5_ldap_lockoutTime(self):
544 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
545 self.lockout2krb5_ldb,
548 def test_userPassword_lockout_with_clear_change_krb5_samr(self):
549 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
550 self.lockout2krb5_ldb,
553 def test_userPassword_lockout_with_clear_change_ntlm_ldap_userAccountControl(self):
554 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
555 self.lockout2ntlm_ldb,
556 "ldap_userAccountControl",
557 initial_lastlogon_relation='greater')
559 def test_userPassword_lockout_with_clear_change_ntlm_ldap_lockoutTime(self):
560 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
561 self.lockout2ntlm_ldb,
563 initial_lastlogon_relation='greater')
565 def test_userPassword_lockout_with_clear_change_ntlm_samr(self):
566 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
567 self.lockout2ntlm_ldb,
569 initial_lastlogon_relation='greater')
571 def _test_unicodePwd_lockout_with_clear_change(self, creds, other_ldb,
572 initial_logoncount_relation=None):
573 print "Performs a password cleartext change operation on 'unicodePwd'"
574 username = creds.get_username()
575 userpass = creds.get_password()
576 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
577 if initial_logoncount_relation is not None:
578 logoncount_relation = initial_logoncount_relation
580 logoncount_relation = "greater"
582 res = self._check_account(userdn,
584 badPasswordTime=("greater", 0),
585 logonCount=(logoncount_relation, 0),
586 lastLogon=("greater", 0),
587 lastLogonTimestamp=("greater", 0),
589 dsdb.UF_NORMAL_ACCOUNT,
590 msDSUserAccountControlComputed=0)
591 badPasswordTime = int(res[0]["badPasswordTime"][0])
592 logonCount = int(res[0]["logonCount"][0])
593 lastLogon = int(res[0]["lastLogon"][0])
594 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
595 self.assertGreater(lastLogonTimestamp, badPasswordTime)
596 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
598 # Change password on a connection as another user
602 other_ldb.modify_ldif("""
603 dn: """ + userdn + """
606 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
608 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
611 except LdbError, (num, msg):
612 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
613 self.assertTrue('00000056' in msg, msg)
615 res = self._check_account(userdn,
617 badPasswordTime=("greater", badPasswordTime),
618 logonCount=logonCount,
620 lastLogonTimestamp=lastLogonTimestamp,
622 dsdb.UF_NORMAL_ACCOUNT,
623 msDSUserAccountControlComputed=0)
624 badPasswordTime = int(res[0]["badPasswordTime"][0])
626 # Correct old password
627 old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
628 invalid_utf16 = "\"thatsAcomplPASSX\"".encode('utf-16-le')
629 userpass = "thatsAcomplPASS2"
630 creds.set_password(userpass)
631 new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
633 other_ldb.modify_ldif("""
634 dn: """ + userdn + """
637 unicodePwd:: """ + base64.b64encode(old_utf16) + """
639 unicodePwd:: """ + base64.b64encode(new_utf16) + """
642 res = self._check_account(userdn,
644 badPasswordTime=badPasswordTime,
645 logonCount=logonCount,
647 lastLogonTimestamp=lastLogonTimestamp,
649 dsdb.UF_NORMAL_ACCOUNT,
650 msDSUserAccountControlComputed=0)
654 other_ldb.modify_ldif("""
655 dn: """ + userdn + """
658 unicodePwd:: """ + base64.b64encode(old_utf16) + """
660 unicodePwd:: """ + base64.b64encode(new_utf16) + """
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),
670 logonCount=logonCount,
672 lastLogonTimestamp=lastLogonTimestamp,
674 dsdb.UF_NORMAL_ACCOUNT,
675 msDSUserAccountControlComputed=0)
676 badPasswordTime = int(res[0]["badPasswordTime"][0])
678 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
679 # It doesn't create "lockoutTime" = 0 and doesn't
680 # reset "badPwdCount" = 0.
681 self._reset_samr(res)
683 res = self._check_account(userdn,
685 badPasswordTime=badPasswordTime,
686 logonCount=logonCount,
688 lastLogonTimestamp=lastLogonTimestamp,
690 dsdb.UF_NORMAL_ACCOUNT,
691 msDSUserAccountControlComputed=0)
693 print "two failed password change"
697 other_ldb.modify_ldif("""
698 dn: """ + userdn + """
701 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
703 unicodePwd:: """ + base64.b64encode(new_utf16) + """
706 except LdbError, (num, msg):
707 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
708 self.assertTrue('00000056' in msg, msg)
710 # this is strange, why do we have lockoutTime=badPasswordTime here?
711 res = self._check_account(userdn,
713 badPasswordTime=("greater", badPasswordTime),
714 logonCount=logonCount,
716 lastLogonTimestamp=lastLogonTimestamp,
717 lockoutTime=("greater", badPasswordTime),
719 dsdb.UF_NORMAL_ACCOUNT,
720 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
721 badPasswordTime = int(res[0]["badPasswordTime"][0])
722 lockoutTime = int(res[0]["lockoutTime"][0])
726 other_ldb.modify_ldif("""
727 dn: """ + userdn + """
730 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
732 unicodePwd:: """ + base64.b64encode(new_utf16) + """
735 except LdbError, (num, msg):
736 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
737 self.assertTrue('00000775' in msg, msg)
739 res = self._check_account(userdn,
741 badPasswordTime=badPasswordTime,
742 logonCount=logonCount,
744 lastLogonTimestamp=lastLogonTimestamp,
745 lockoutTime=lockoutTime,
747 dsdb.UF_NORMAL_ACCOUNT,
748 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
752 other_ldb.modify_ldif("""
753 dn: """ + userdn + """
756 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
758 unicodePwd:: """ + base64.b64encode(new_utf16) + """
761 except LdbError, (num, msg):
762 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
763 self.assertTrue('00000775' in msg, msg)
765 res = self._check_account(userdn,
767 badPasswordTime=badPasswordTime,
768 logonCount=logonCount,
770 lastLogonTimestamp=lastLogonTimestamp,
771 lockoutTime=lockoutTime,
773 dsdb.UF_NORMAL_ACCOUNT,
774 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
777 # Correct old password
778 other_ldb.modify_ldif("""
779 dn: """ + userdn + """
782 unicodePwd:: """ + base64.b64encode(new_utf16) + """
784 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
787 except LdbError, (num, msg):
788 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
789 self.assertTrue('00000775' in msg, msg)
791 res = self._check_account(userdn,
793 badPasswordTime=badPasswordTime,
794 logonCount=logonCount,
796 lastLogonTimestamp=lastLogonTimestamp,
797 lockoutTime=lockoutTime,
799 dsdb.UF_NORMAL_ACCOUNT,
800 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
802 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
803 self._reset_samr(res);
805 res = self._check_account(userdn,
807 badPasswordTime=badPasswordTime,
808 logonCount=logonCount,
810 lastLogonTimestamp=lastLogonTimestamp,
813 dsdb.UF_NORMAL_ACCOUNT,
814 msDSUserAccountControlComputed=0)
816 # Correct old password
817 old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
818 invalid_utf16 = "\"thatsAcomplPASSiX\"".encode('utf-16-le')
819 userpass = "thatsAcomplPASS2x"
820 creds.set_password(userpass)
821 new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
823 other_ldb.modify_ldif("""
824 dn: """ + userdn + """
827 unicodePwd:: """ + base64.b64encode(old_utf16) + """
829 unicodePwd:: """ + base64.b64encode(new_utf16) + """
832 res = self._check_account(userdn,
834 badPasswordTime=badPasswordTime,
835 logonCount=logonCount,
837 lastLogonTimestamp=lastLogonTimestamp,
840 dsdb.UF_NORMAL_ACCOUNT,
841 msDSUserAccountControlComputed=0)
845 other_ldb.modify_ldif("""
846 dn: """ + userdn + """
849 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
851 unicodePwd:: """ + base64.b64encode(new_utf16) + """
854 except LdbError, (num, msg):
855 self.assertEquals(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,
866 dsdb.UF_NORMAL_ACCOUNT,
867 msDSUserAccountControlComputed=0)
868 badPasswordTime = int(res[0]["badPasswordTime"][0])
872 other_ldb.modify_ldif("""
873 dn: """ + userdn + """
876 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
878 unicodePwd:: """ + base64.b64encode(new_utf16) + """
881 except LdbError, (num, msg):
882 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
883 self.assertTrue('00000056' in msg, msg)
885 res = self._check_account(userdn,
887 badPasswordTime=("greater", badPasswordTime),
888 logonCount=logonCount,
890 lastLogonTimestamp=lastLogonTimestamp,
893 dsdb.UF_NORMAL_ACCOUNT,
894 msDSUserAccountControlComputed=0)
895 badPasswordTime = int(res[0]["badPasswordTime"][0])
897 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
898 # It doesn't reset "badPwdCount" = 0.
899 self._reset_samr(res)
901 res = self._check_account(userdn,
903 badPasswordTime=badPasswordTime,
904 logonCount=logonCount,
906 lastLogonTimestamp=lastLogonTimestamp,
909 dsdb.UF_NORMAL_ACCOUNT,
910 msDSUserAccountControlComputed=0)
914 other_ldb.modify_ldif("""
915 dn: """ + userdn + """
918 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
920 unicodePwd:: """ + base64.b64encode(new_utf16) + """
923 except LdbError, (num, msg):
924 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
925 self.assertTrue('00000056' in msg, msg)
927 res = self._check_account(userdn,
929 badPasswordTime=("greater", badPasswordTime),
930 logonCount=logonCount,
932 lastLogonTimestamp=lastLogonTimestamp,
933 lockoutTime=("greater", badPasswordTime),
935 dsdb.UF_NORMAL_ACCOUNT,
936 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
937 badPasswordTime = int(res[0]["badPasswordTime"][0])
938 lockoutTime = int(res[0]["lockoutTime"][0])
940 time.sleep(self.account_lockout_duration + 1)
942 res = self._check_account(userdn,
943 badPwdCount=3, effective_bad_password_count=0,
944 badPasswordTime=badPasswordTime,
945 logonCount=logonCount,
947 lastLogonTimestamp=lastLogonTimestamp,
948 lockoutTime=lockoutTime,
950 dsdb.UF_NORMAL_ACCOUNT,
951 msDSUserAccountControlComputed=0)
953 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
954 # It doesn't reset "lockoutTime" = 0 and doesn't
955 # reset "badPwdCount" = 0.
956 self._reset_samr(res)
958 res = self._check_account(userdn,
959 badPwdCount=3, effective_bad_password_count=0,
960 badPasswordTime=badPasswordTime,
961 logonCount=logonCount,
962 lockoutTime=lockoutTime,
964 lastLogonTimestamp=lastLogonTimestamp,
966 dsdb.UF_NORMAL_ACCOUNT,
967 msDSUserAccountControlComputed=0)
969 def test_unicodePwd_lockout_with_clear_change_krb5(self):
970 self._test_unicodePwd_lockout_with_clear_change(self.lockout1krb5_creds,
971 self.lockout2krb5_ldb)
973 def test_unicodePwd_lockout_with_clear_change_ntlm(self):
974 self._test_unicodePwd_lockout_with_clear_change(self.lockout1ntlm_creds,
975 self.lockout2ntlm_ldb,
976 initial_logoncount_relation="equal")
978 def test_login_lockout_krb5(self):
979 self._test_login_lockout(self.lockout1krb5_creds)
981 def test_login_lockout_ntlm(self):
982 self._test_login_lockout(self.lockout1ntlm_creds)
984 def test_multiple_logon_krb5(self):
985 self._test_multiple_logon(self.lockout1krb5_creds)
987 def test_multiple_logon_ntlm(self):
988 self._test_multiple_logon(self.lockout1ntlm_creds)
990 def _testing_add_user(self, creds, lockOutObservationWindow=0):
991 username = creds.get_username()
992 userpass = creds.get_password()
993 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
995 use_kerberos = creds.get_kerberos_state()
996 if use_kerberos == MUST_USE_KERBEROS:
997 logoncount_relation = 'greater'
998 lastlogon_relation = 'greater'
1000 logoncount_relation = 'equal'
1001 if lockOutObservationWindow == 0:
1002 lastlogon_relation = 'greater'
1004 lastlogon_relation = 'equal'
1006 delete_force(self.ldb, userdn)
1009 "objectclass": "user",
1010 "sAMAccountName": username})
1012 self.addCleanup(delete_force, self.ldb, userdn)
1014 res = self._check_account(userdn,
1019 lastLogonTimestamp=('absent', None),
1021 dsdb.UF_NORMAL_ACCOUNT |
1022 dsdb.UF_ACCOUNTDISABLE |
1023 dsdb.UF_PASSWD_NOTREQD,
1024 msDSUserAccountControlComputed=
1025 dsdb.UF_PASSWORD_EXPIRED)
1027 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1028 # It doesn't create "lockoutTime" = 0.
1029 self._reset_samr(res)
1031 res = self._check_account(userdn,
1036 lastLogonTimestamp=('absent', None),
1038 dsdb.UF_NORMAL_ACCOUNT |
1039 dsdb.UF_ACCOUNTDISABLE |
1040 dsdb.UF_PASSWD_NOTREQD,
1041 msDSUserAccountControlComputed=
1042 dsdb.UF_PASSWORD_EXPIRED)
1044 # Tests a password change when we don't have any password yet with a
1045 # wrong old password
1047 self.ldb.modify_ldif("""
1048 dn: """ + userdn + """
1050 delete: userPassword
1051 userPassword: noPassword
1053 userPassword: thatsAcomplPASS2
1056 except LdbError, (num, msg):
1057 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1058 # Windows (2008 at least) seems to have some small bug here: it
1059 # returns "0000056A" on longer (always wrong) previous passwords.
1060 self.assertTrue('00000056' in msg, msg)
1062 res = self._check_account(userdn,
1064 badPasswordTime=("greater", 0),
1067 lastLogonTimestamp=('absent', None),
1069 dsdb.UF_NORMAL_ACCOUNT |
1070 dsdb.UF_ACCOUNTDISABLE |
1071 dsdb.UF_PASSWD_NOTREQD,
1072 msDSUserAccountControlComputed=
1073 dsdb.UF_PASSWORD_EXPIRED)
1074 badPwdCount = int(res[0]["badPwdCount"][0])
1075 badPasswordTime = int(res[0]["badPasswordTime"][0])
1077 # Sets the initial user password with a "special" password change
1078 # I think that this internally is a password set operation and it can
1079 # only be performed by someone which has password set privileges on the
1080 # account (at least in s4 we do handle it like that).
1081 self.ldb.modify_ldif("""
1082 dn: """ + userdn + """
1084 delete: userPassword
1086 userPassword: """ + userpass + """
1089 res = self._check_account(userdn,
1090 badPwdCount=badPwdCount,
1091 badPasswordTime=badPasswordTime,
1094 lastLogonTimestamp=('absent', None),
1096 dsdb.UF_NORMAL_ACCOUNT |
1097 dsdb.UF_ACCOUNTDISABLE |
1098 dsdb.UF_PASSWD_NOTREQD,
1099 msDSUserAccountControlComputed=0)
1101 # Enables the user account
1102 self.ldb.enable_account("(sAMAccountName=%s)" % username)
1104 res = self._check_account(userdn,
1105 badPwdCount=badPwdCount,
1106 badPasswordTime=badPasswordTime,
1109 lastLogonTimestamp=('absent', None),
1111 dsdb.UF_NORMAL_ACCOUNT,
1112 msDSUserAccountControlComputed=0)
1113 if lockOutObservationWindow != 0:
1114 time.sleep(lockOutObservationWindow + 1)
1115 effective_bad_password_count = 0
1117 effective_bad_password_count = badPwdCount
1119 res = self._check_account(userdn,
1120 badPwdCount=badPwdCount,
1121 effective_bad_password_count=effective_bad_password_count,
1122 badPasswordTime=badPasswordTime,
1125 lastLogonTimestamp=('absent', None),
1127 dsdb.UF_NORMAL_ACCOUNT,
1128 msDSUserAccountControlComputed=0)
1130 ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp)
1132 if lockOutObservationWindow == 0:
1134 effective_bad_password_count = 0
1135 if use_kerberos == MUST_USE_KERBEROS:
1137 effective_bad_password_count = 0
1139 res = self._check_account(userdn,
1140 badPwdCount=badPwdCount,
1141 effective_bad_password_count=effective_bad_password_count,
1142 badPasswordTime=badPasswordTime,
1143 logonCount=(logoncount_relation, 0),
1144 lastLogon=(lastlogon_relation, 0),
1145 lastLogonTimestamp=('greater', badPasswordTime),
1147 dsdb.UF_NORMAL_ACCOUNT,
1148 msDSUserAccountControlComputed=0)
1150 logonCount = int(res[0]["logonCount"][0])
1151 lastLogon = int(res[0]["lastLogon"][0])
1152 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1153 if lastlogon_relation == 'greater':
1154 self.assertGreater(lastLogon, badPasswordTime)
1155 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
1157 res = self._check_account(userdn,
1158 badPwdCount=badPwdCount,
1159 effective_bad_password_count=effective_bad_password_count,
1160 badPasswordTime=badPasswordTime,
1161 logonCount=logonCount,
1162 lastLogon=lastLogon,
1163 lastLogonTimestamp=lastLogonTimestamp,
1165 dsdb.UF_NORMAL_ACCOUNT,
1166 msDSUserAccountControlComputed=0)
1169 def _reset_samr(self, res):
1171 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
1172 samr_user = self._open_samr_user(res)
1173 acb_info = self.samr.QueryUserInfo(samr_user, 16)
1174 acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
1175 self.samr.SetUserInfo(samr_user, 16, acb_info)
1176 self.samr.Close(samr_user)
1178 def test_lockout_observation_window(self):
1179 lockout3krb5_creds = self.insta_creds(self.template_creds,
1180 username="lockout3krb5",
1181 userpass="thatsAcomplPASS0",
1182 kerberos_state=MUST_USE_KERBEROS)
1183 self._testing_add_user(lockout3krb5_creds)
1185 lockout4krb5_creds = self.insta_creds(self.template_creds,
1186 username="lockout4krb5",
1187 userpass="thatsAcomplPASS0",
1188 kerberos_state=MUST_USE_KERBEROS)
1189 self._testing_add_user(lockout4krb5_creds,
1190 lockOutObservationWindow=self.lockout_observation_window)
1192 lockout3ntlm_creds = self.insta_creds(self.template_creds,
1193 username="lockout3ntlm",
1194 userpass="thatsAcomplPASS0",
1195 kerberos_state=DONT_USE_KERBEROS)
1196 self._testing_add_user(lockout3ntlm_creds)
1197 lockout4ntlm_creds = self.insta_creds(self.template_creds,
1198 username="lockout4ntlm",
1199 userpass="thatsAcomplPASS0",
1200 kerberos_state=DONT_USE_KERBEROS)
1201 self._testing_add_user(lockout4ntlm_creds,
1202 lockOutObservationWindow=self.lockout_observation_window)
1204 host_url = "ldap://%s" % host
1206 TestProgram(module=__name__, opts=subunitopts)