CVE-2018-16857 selftest: Split up password_lockout into tests with and without a...
[Samba.git] / source4 / dsdb / tests / python / password_lockout.py
blob0022dee21ba5bd6d647d99ed7a8b895a9c10fe97
1 #!/usr/bin/env python
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
11 import optparse
12 import sys
13 import base64
14 import time
16 sys.path.insert(0, "bin/python")
17 import samba
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
32 import samba.tests
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
39 import ctypes
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()
52 if len(args) < 1:
53 parser.print_usage()
54 sys.exit(1)
56 host = args[0]
58 lp = sambaopts.get_loadparm()
59 global_creds = credopts.get_credentials(lp)
61 import password_lockout_base
64 # Tests start here
67 class PasswordTests(password_lockout_base.BasePasswordTestCase):
68 def setUp(self):
69 self.host = host
70 self.host_url = host_url
71 self.lp = lp
72 self.global_creds = global_creds
73 self.ldb = SamDB(url=self.host_url, session_info=system_session(self.lp),
74 credentials=self.global_creds, lp=self.lp)
75 super(PasswordTests, self).setUp()
77 self.lockout2krb5_creds = self.insta_creds(self.template_creds,
78 username="lockout2krb5",
79 userpass="thatsAcomplPASS0",
80 kerberos_state=MUST_USE_KERBEROS)
81 self.lockout2krb5_ldb = self._readd_user(self.lockout2krb5_creds,
82 lockOutObservationWindow=self.lockout_observation_window)
84 self.lockout2ntlm_creds = self.insta_creds(self.template_creds,
85 username="lockout2ntlm",
86 userpass="thatsAcomplPASS0",
87 kerberos_state=DONT_USE_KERBEROS)
88 self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds,
89 lockOutObservationWindow=self.lockout_observation_window)
92 def use_pso_lockout_settings(self, creds):
94 # create a PSO with the lockout settings the test cases normally expect
96 # Some test cases sleep() for self.account_lockout_duration
97 pso = PasswordSettings("lockout-PSO", self.ldb, lockout_attempts=3,
98 lockout_duration=self.account_lockout_duration)
99 self.addCleanup(self.ldb.delete, pso.dn)
101 userdn = "cn=%s,cn=users,%s" % (creds.get_username(), self.base_dn)
102 pso.apply_to(userdn)
104 # update the global lockout settings to be wildly different to what
105 # the test cases normally expect
106 self.update_lockout_settings(threshold=10, duration=600,
107 observation_window=600)
109 def _reset_samr(self, res):
111 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
112 samr_user = self._open_samr_user(res)
113 acb_info = self.samr.QueryUserInfo(samr_user, 16)
114 acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
115 self.samr.SetUserInfo(samr_user, 16, acb_info)
116 self.samr.Close(samr_user)
119 class PasswordTestsWithoutSleep(PasswordTests):
120 def setUp(self):
121 # The tests in this class do not sleep, so we can have a
122 # longer window and not flap on slower hosts
123 self.account_lockout_duration = 30
124 self.lockout_observation_window = 30
125 super(PasswordTestsWithoutSleep, self).setUp()
127 def _reset_ldap_lockoutTime(self, res):
128 self.ldb.modify_ldif("""
129 dn: """ + str(res[0].dn) + """
130 changetype: modify
131 replace: lockoutTime
132 lockoutTime: 0
133 """)
135 def _reset_ldap_userAccountControl(self, res):
136 self.assertTrue("userAccountControl" in res[0])
137 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
139 uac = int(res[0]["userAccountControl"][0])
140 uacc = int(res[0]["msDS-User-Account-Control-Computed"][0])
142 uac |= uacc
143 uac = uac & ~dsdb.UF_LOCKOUT
145 self.ldb.modify_ldif("""
146 dn: """ + str(res[0].dn) + """
147 changetype: modify
148 replace: userAccountControl
149 userAccountControl: %d
150 """ % uac)
152 def _reset_by_method(self, res, method):
153 if method is "ldap_userAccountControl":
154 self._reset_ldap_userAccountControl(res)
155 elif method is "ldap_lockoutTime":
156 self._reset_ldap_lockoutTime(res)
157 elif method is "samr":
158 self._reset_samr(res)
159 else:
160 self.assertTrue(False, msg="Invalid reset method[%s]" % method)
162 def _test_userPassword_lockout_with_clear_change(self, creds, other_ldb, method,
163 initial_lastlogon_relation=None):
165 Tests user lockout behaviour when we try to change the user's password
166 but specify an incorrect old-password. The method parameter specifies
167 how to reset the locked out account (e.g. by resetting lockoutTime)
169 # Notice: This works only against Windows if "dSHeuristics" has been set
170 # properly
171 username = creds.get_username()
172 userpass = creds.get_password()
173 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
175 use_kerberos = creds.get_kerberos_state()
176 if use_kerberos == MUST_USE_KERBEROS:
177 logoncount_relation = 'greater'
178 lastlogon_relation = 'greater'
179 print("Performs a password cleartext change operation on 'userPassword' using Kerberos")
180 else:
181 logoncount_relation = 'equal'
182 lastlogon_relation = 'equal'
183 print("Performs a password cleartext change operation on 'userPassword' using NTLMSSP")
185 if initial_lastlogon_relation is not None:
186 lastlogon_relation = initial_lastlogon_relation
188 res = self._check_account(userdn,
189 badPwdCount=0,
190 badPasswordTime=("greater", 0),
191 logonCount=(logoncount_relation, 0),
192 lastLogon=(lastlogon_relation, 0),
193 lastLogonTimestamp=('greater', 0),
194 userAccountControl=
195 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
207 # Wrong old password
208 try:
209 other_ldb.modify_ldif("""
210 dn: """ + userdn + """
211 changetype: modify
212 delete: userPassword
213 userPassword: thatsAcomplPASS1x
214 add: userPassword
215 userPassword: thatsAcomplPASS2
216 """)
217 self.fail()
218 except LdbError as e:
219 (num, msg) = e.args
220 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
221 self.assertTrue('00000056' in msg, msg)
223 res = self._check_account(userdn,
224 badPwdCount=1,
225 badPasswordTime=("greater", badPasswordTime),
226 logonCount=logonCount,
227 lastLogon=lastLogon,
228 lastLogonTimestamp=lastLogonTimestamp,
229 userAccountControl=
230 dsdb.UF_NORMAL_ACCOUNT,
231 msDSUserAccountControlComputed=0)
232 badPasswordTime = int(res[0]["badPasswordTime"][0])
234 # Correct old password
235 other_ldb.modify_ldif("""
236 dn: """ + userdn + """
237 changetype: modify
238 delete: userPassword
239 userPassword: """ + userpass + """
240 add: userPassword
241 userPassword: thatsAcomplPASS2
242 """)
244 res = self._check_account(userdn,
245 badPwdCount=1,
246 badPasswordTime=badPasswordTime,
247 logonCount=logonCount,
248 lastLogon=lastLogon,
249 lastLogonTimestamp=lastLogonTimestamp,
250 userAccountControl=
251 dsdb.UF_NORMAL_ACCOUNT,
252 msDSUserAccountControlComputed=0)
254 # Wrong old password
255 try:
256 other_ldb.modify_ldif("""
257 dn: """ + userdn + """
258 changetype: modify
259 delete: userPassword
260 userPassword: thatsAcomplPASS1x
261 add: userPassword
262 userPassword: thatsAcomplPASS2
263 """)
264 self.fail()
265 except LdbError as e1:
266 (num, msg) = e1.args
267 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
268 self.assertTrue('00000056' in msg, msg)
270 res = self._check_account(userdn,
271 badPwdCount=2,
272 badPasswordTime=("greater", badPasswordTime),
273 logonCount=logonCount,
274 lastLogon=lastLogon,
275 lastLogonTimestamp=lastLogonTimestamp,
276 userAccountControl=
277 dsdb.UF_NORMAL_ACCOUNT,
278 msDSUserAccountControlComputed=0)
279 badPasswordTime = int(res[0]["badPasswordTime"][0])
281 print("two failed password change")
283 # Wrong old password
284 try:
285 other_ldb.modify_ldif("""
286 dn: """ + userdn + """
287 changetype: modify
288 delete: userPassword
289 userPassword: thatsAcomplPASS1x
290 add: userPassword
291 userPassword: thatsAcomplPASS2
292 """)
293 self.fail()
294 except LdbError as e2:
295 (num, msg) = e2.args
296 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
297 self.assertTrue('00000056' in msg, msg)
299 res = self._check_account(userdn,
300 badPwdCount=3,
301 badPasswordTime=("greater", badPasswordTime),
302 logonCount=logonCount,
303 lastLogon=lastLogon,
304 lastLogonTimestamp=lastLogonTimestamp,
305 lockoutTime=("greater", badPasswordTime),
306 userAccountControl=
307 dsdb.UF_NORMAL_ACCOUNT,
308 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
309 badPasswordTime = int(res[0]["badPasswordTime"][0])
310 lockoutTime = int(res[0]["lockoutTime"][0])
312 # Wrong old password
313 try:
314 other_ldb.modify_ldif("""
315 dn: """ + userdn + """
316 changetype: modify
317 delete: userPassword
318 userPassword: thatsAcomplPASS1x
319 add: userPassword
320 userPassword: thatsAcomplPASS2
321 """)
322 self.fail()
323 except LdbError as e3:
324 (num, msg) = e3.args
325 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
326 self.assertTrue('00000775' in msg, msg)
328 res = self._check_account(userdn,
329 badPwdCount=3,
330 badPasswordTime=badPasswordTime,
331 logonCount=logonCount,
332 lastLogon=lastLogon,
333 lastLogonTimestamp=lastLogonTimestamp,
334 lockoutTime=lockoutTime,
335 userAccountControl=
336 dsdb.UF_NORMAL_ACCOUNT,
337 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
339 # Wrong old password
340 try:
341 other_ldb.modify_ldif("""
342 dn: """ + userdn + """
343 changetype: modify
344 delete: userPassword
345 userPassword: thatsAcomplPASS1x
346 add: userPassword
347 userPassword: thatsAcomplPASS2
348 """)
349 self.fail()
350 except LdbError as e4:
351 (num, msg) = e4.args
352 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
353 self.assertTrue('00000775' in msg, msg)
355 res = self._check_account(userdn,
356 badPwdCount=3,
357 badPasswordTime=badPasswordTime,
358 logonCount=logonCount,
359 lockoutTime=lockoutTime,
360 lastLogon=lastLogon,
361 lastLogonTimestamp=lastLogonTimestamp,
362 userAccountControl=
363 dsdb.UF_NORMAL_ACCOUNT,
364 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
366 try:
367 # Correct old password
368 other_ldb.modify_ldif("""
369 dn: """ + userdn + """
370 changetype: modify
371 delete: userPassword
372 userPassword: thatsAcomplPASS2
373 add: userPassword
374 userPassword: thatsAcomplPASS2x
375 """)
376 self.fail()
377 except LdbError as e5:
378 (num, msg) = e5.args
379 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
380 self.assertTrue('00000775' in msg, msg)
382 res = self._check_account(userdn,
383 badPwdCount=3,
384 badPasswordTime=badPasswordTime,
385 logonCount=logonCount,
386 lastLogon=lastLogon,
387 lastLogonTimestamp=lastLogonTimestamp,
388 lockoutTime=lockoutTime,
389 userAccountControl=
390 dsdb.UF_NORMAL_ACCOUNT,
391 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
393 # Now reset the password, which does NOT change the lockout!
394 self.ldb.modify_ldif("""
395 dn: """ + userdn + """
396 changetype: modify
397 replace: userPassword
398 userPassword: thatsAcomplPASS2
399 """)
401 res = self._check_account(userdn,
402 badPwdCount=3,
403 badPasswordTime=badPasswordTime,
404 logonCount=logonCount,
405 lastLogon=lastLogon,
406 lastLogonTimestamp=lastLogonTimestamp,
407 lockoutTime=lockoutTime,
408 userAccountControl=
409 dsdb.UF_NORMAL_ACCOUNT,
410 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
412 try:
413 # Correct old password
414 other_ldb.modify_ldif("""
415 dn: """ + userdn + """
416 changetype: modify
417 delete: userPassword
418 userPassword: thatsAcomplPASS2
419 add: userPassword
420 userPassword: thatsAcomplPASS2x
421 """)
422 self.fail()
423 except LdbError as e6:
424 (num, msg) = e6.args
425 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
426 self.assertTrue('00000775' in msg, msg)
428 res = self._check_account(userdn,
429 badPwdCount=3,
430 badPasswordTime=badPasswordTime,
431 logonCount=logonCount,
432 lastLogon=lastLogon,
433 lastLogonTimestamp=lastLogonTimestamp,
434 lockoutTime=lockoutTime,
435 userAccountControl=
436 dsdb.UF_NORMAL_ACCOUNT,
437 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
439 m = Message()
440 m.dn = Dn(self.ldb, userdn)
441 m["userAccountControl"] = MessageElement(
442 str(dsdb.UF_LOCKOUT),
443 FLAG_MOD_REPLACE, "userAccountControl")
445 self.ldb.modify(m)
447 # This shows that setting the UF_LOCKOUT flag alone makes no difference
448 res = self._check_account(userdn,
449 badPwdCount=3,
450 badPasswordTime=badPasswordTime,
451 logonCount=logonCount,
452 lastLogon=lastLogon,
453 lastLogonTimestamp=lastLogonTimestamp,
454 lockoutTime=lockoutTime,
455 userAccountControl=
456 dsdb.UF_NORMAL_ACCOUNT,
457 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
459 # This shows that setting the UF_LOCKOUT flag makes no difference
460 try:
461 # Correct old password
462 other_ldb.modify_ldif("""
463 dn: """ + userdn + """
464 changetype: modify
465 delete: unicodePwd
466 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
467 add: unicodePwd
468 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')).decode('utf8') + """
469 """)
470 self.fail()
471 except LdbError as e7:
472 (num, msg) = e7.args
473 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
474 self.assertTrue('00000775' in msg, msg)
476 res = self._check_account(userdn,
477 badPwdCount=3,
478 badPasswordTime=badPasswordTime,
479 logonCount=logonCount,
480 lockoutTime=lockoutTime,
481 lastLogon=lastLogon,
482 lastLogonTimestamp=lastLogonTimestamp,
483 userAccountControl=
484 dsdb.UF_NORMAL_ACCOUNT,
485 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
487 self._reset_by_method(res, method)
489 # Here bad password counts are reset without logon success.
490 res = self._check_account(userdn,
491 badPwdCount=0,
492 badPasswordTime=badPasswordTime,
493 logonCount=logonCount,
494 lockoutTime=0,
495 lastLogon=lastLogon,
496 lastLogonTimestamp=lastLogonTimestamp,
497 userAccountControl=
498 dsdb.UF_NORMAL_ACCOUNT,
499 msDSUserAccountControlComputed=0)
501 # The correct password after doing the unlock
503 other_ldb.modify_ldif("""
504 dn: """ + userdn + """
505 changetype: modify
506 delete: unicodePwd
507 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
508 add: unicodePwd
509 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')).decode('utf8') + """
510 """)
511 userpass = "thatsAcomplPASS2x"
512 creds.set_password(userpass)
514 res = self._check_account(userdn,
515 badPwdCount=0,
516 badPasswordTime=badPasswordTime,
517 logonCount=logonCount,
518 lockoutTime=0,
519 lastLogon=lastLogon,
520 lastLogonTimestamp=lastLogonTimestamp,
521 userAccountControl=
522 dsdb.UF_NORMAL_ACCOUNT,
523 msDSUserAccountControlComputed=0)
525 # Wrong old password
526 try:
527 other_ldb.modify_ldif("""
528 dn: """ + userdn + """
529 changetype: modify
530 delete: userPassword
531 userPassword: thatsAcomplPASS1xyz
532 add: userPassword
533 userPassword: thatsAcomplPASS2XYZ
534 """)
535 self.fail()
536 except LdbError as e8:
537 (num, msg) = e8.args
538 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
539 self.assertTrue('00000056' in msg, msg)
541 res = self._check_account(userdn,
542 badPwdCount=1,
543 badPasswordTime=("greater", badPasswordTime),
544 logonCount=logonCount,
545 lockoutTime=0,
546 lastLogon=lastLogon,
547 lastLogonTimestamp=lastLogonTimestamp,
548 userAccountControl=
549 dsdb.UF_NORMAL_ACCOUNT,
550 msDSUserAccountControlComputed=0)
551 badPasswordTime = int(res[0]["badPasswordTime"][0])
553 # Wrong old password
554 try:
555 other_ldb.modify_ldif("""
556 dn: """ + userdn + """
557 changetype: modify
558 delete: userPassword
559 userPassword: thatsAcomplPASS1xyz
560 add: userPassword
561 userPassword: thatsAcomplPASS2XYZ
562 """)
563 self.fail()
564 except LdbError as e9:
565 (num, msg) = e9.args
566 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
567 self.assertTrue('00000056' in msg, msg)
569 res = self._check_account(userdn,
570 badPwdCount=2,
571 badPasswordTime=("greater", badPasswordTime),
572 logonCount=logonCount,
573 lockoutTime=0,
574 lastLogon=lastLogon,
575 lastLogonTimestamp=lastLogonTimestamp,
576 userAccountControl=
577 dsdb.UF_NORMAL_ACCOUNT,
578 msDSUserAccountControlComputed=0)
579 badPasswordTime = int(res[0]["badPasswordTime"][0])
581 self._reset_ldap_lockoutTime(res)
583 res = self._check_account(userdn,
584 badPwdCount=0,
585 badPasswordTime=badPasswordTime,
586 logonCount=logonCount,
587 lastLogon=lastLogon,
588 lastLogonTimestamp=lastLogonTimestamp,
589 lockoutTime=0,
590 userAccountControl=
591 dsdb.UF_NORMAL_ACCOUNT,
592 msDSUserAccountControlComputed=0)
594 # The following test lockout behaviour when modifying a user's password
595 # and specifying an invalid old password. There are variants for both
596 # NTLM and kerberos user authentication. As well as that, there are 3 ways
597 # to reset the locked out account: by clearing the lockout bit for
598 # userAccountControl (via LDAP), resetting it via SAMR, and by resetting
599 # the lockoutTime.
600 def test_userPassword_lockout_with_clear_change_krb5_ldap_userAccountControl(self):
601 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
602 self.lockout2krb5_ldb,
603 "ldap_userAccountControl")
605 def test_userPassword_lockout_with_clear_change_krb5_ldap_lockoutTime(self):
606 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
607 self.lockout2krb5_ldb,
608 "ldap_lockoutTime")
610 def test_userPassword_lockout_with_clear_change_krb5_samr(self):
611 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
612 self.lockout2krb5_ldb,
613 "samr")
615 def test_userPassword_lockout_with_clear_change_ntlm_ldap_userAccountControl(self):
616 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
617 self.lockout2ntlm_ldb,
618 "ldap_userAccountControl",
619 initial_lastlogon_relation='greater')
621 def test_userPassword_lockout_with_clear_change_ntlm_ldap_lockoutTime(self):
622 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
623 self.lockout2ntlm_ldb,
624 "ldap_lockoutTime",
625 initial_lastlogon_relation='greater')
627 def test_userPassword_lockout_with_clear_change_ntlm_samr(self):
628 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
629 self.lockout2ntlm_ldb,
630 "samr",
631 initial_lastlogon_relation='greater')
633 # For PSOs, just test a selection of the above combinations
634 def test_pso_userPassword_lockout_with_clear_change_krb5_ldap_userAccountControl(self):
635 self.use_pso_lockout_settings(self.lockout1krb5_creds)
636 self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
637 self.lockout2krb5_ldb,
638 "ldap_userAccountControl")
640 def test_pso_userPassword_lockout_with_clear_change_ntlm_ldap_lockoutTime(self):
641 self.use_pso_lockout_settings(self.lockout1ntlm_creds)
642 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
643 self.lockout2ntlm_ldb,
644 "ldap_lockoutTime",
645 initial_lastlogon_relation='greater')
647 def test_pso_userPassword_lockout_with_clear_change_ntlm_samr(self):
648 self.use_pso_lockout_settings(self.lockout1ntlm_creds)
649 self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
650 self.lockout2ntlm_ldb,
651 "samr",
652 initial_lastlogon_relation='greater')
654 def test_multiple_logon_krb5(self):
655 self._test_multiple_logon(self.lockout1krb5_creds)
657 def test_multiple_logon_ntlm(self):
658 self._test_multiple_logon(self.lockout1ntlm_creds)
660 def _test_samr_password_change(self, creds, other_creds, lockout_threshold=3):
661 """Tests user lockout by using bad password in SAMR password_change"""
663 # create a connection for SAMR using another user's credentials
664 lp = self.get_loadparm()
665 net = Net(other_creds, lp, server=self.host)
667 # work out the initial account values for this user
668 username = creds.get_username()
669 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
670 res = self._check_account(userdn,
671 badPwdCount=0,
672 badPasswordTime=("greater", 0),
673 badPwdCountOnly=True)
674 badPasswordTime = int(res[0]["badPasswordTime"][0])
675 logonCount = int(res[0]["logonCount"][0])
676 lastLogon = int(res[0]["lastLogon"][0])
677 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
679 # prove we can change the user password (using the correct password)
680 new_password = "thatsAcomplPASS2"
681 net.change_password(newpassword=new_password.encode('utf-8'),
682 username=username,
683 oldpassword=creds.get_password())
684 creds.set_password(new_password)
686 # try entering 'x' many bad passwords in a row to lock the user out
687 new_password = "thatsAcomplPASS3"
688 for i in range(lockout_threshold):
689 badPwdCount = i + 1
690 try:
691 print("Trying bad password, attempt #%u" % badPwdCount)
692 net.change_password(newpassword=new_password.encode('utf-8'),
693 username=creds.get_username(),
694 oldpassword="bad-password")
695 self.fail("Invalid SAMR change_password accepted")
696 except NTSTATUSError as e:
697 enum = ctypes.c_uint32(e[0]).value
698 self.assertEquals(enum, ntstatus.NT_STATUS_WRONG_PASSWORD)
700 # check the status of the account is updated after each bad attempt
701 account_flags = 0
702 lockoutTime = None
703 if badPwdCount >= lockout_threshold:
704 account_flags = dsdb.UF_LOCKOUT
705 lockoutTime = ("greater", badPasswordTime)
707 res = self._check_account(userdn,
708 badPwdCount=badPwdCount,
709 badPasswordTime=("greater", badPasswordTime),
710 logonCount=logonCount,
711 lastLogon=lastLogon,
712 lastLogonTimestamp=lastLogonTimestamp,
713 lockoutTime=lockoutTime,
714 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
715 msDSUserAccountControlComputed=account_flags)
716 badPasswordTime = int(res[0]["badPasswordTime"][0])
718 # the user is now locked out
719 lockoutTime = int(res[0]["lockoutTime"][0])
721 # check the user remains locked out regardless of whether they use a
722 # good or a bad password now
723 for password in (creds.get_password(), "bad-password"):
724 try:
725 print("Trying password %s" % password)
726 net.change_password(newpassword=new_password.encode('utf-8'),
727 username=creds.get_username(),
728 oldpassword=password)
729 self.fail("Invalid SAMR change_password accepted")
730 except NTSTATUSError as e:
731 enum = ctypes.c_uint32(e[0]).value
732 self.assertEquals(enum, ntstatus.NT_STATUS_ACCOUNT_LOCKED_OUT)
734 res = self._check_account(userdn,
735 badPwdCount=lockout_threshold,
736 badPasswordTime=badPasswordTime,
737 logonCount=logonCount,
738 lastLogon=lastLogon,
739 lastLogonTimestamp=lastLogonTimestamp,
740 lockoutTime=lockoutTime,
741 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
742 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
744 # reset the user account lockout
745 self._reset_samr(res)
747 # check bad password counts are reset
748 res = self._check_account(userdn,
749 badPwdCount=0,
750 badPasswordTime=badPasswordTime,
751 logonCount=logonCount,
752 lockoutTime=0,
753 lastLogon=lastLogon,
754 lastLogonTimestamp=lastLogonTimestamp,
755 userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
756 msDSUserAccountControlComputed=0)
758 # check we can change the user password successfully now
759 net.change_password(newpassword=new_password.encode('utf-8'),
760 username=username,
761 oldpassword=creds.get_password())
762 creds.set_password(new_password)
764 def test_samr_change_password(self):
765 self._test_samr_password_change(self.lockout1ntlm_creds,
766 other_creds=self.lockout2ntlm_creds)
768 # same as above, but use a PSO to enforce the lockout
769 def test_pso_samr_change_password(self):
770 self.use_pso_lockout_settings(self.lockout1ntlm_creds)
771 self._test_samr_password_change(self.lockout1ntlm_creds,
772 other_creds=self.lockout2ntlm_creds)
775 class PasswordTestsWithSleep(PasswordTests):
776 def setUp(self):
777 super(PasswordTestsWithSleep, self).setUp()
779 def _test_unicodePwd_lockout_with_clear_change(self, creds, other_ldb,
780 initial_logoncount_relation=None):
781 print("Performs a password cleartext change operation on 'unicodePwd'")
782 username = creds.get_username()
783 userpass = creds.get_password()
784 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
785 if initial_logoncount_relation is not None:
786 logoncount_relation = initial_logoncount_relation
787 else:
788 logoncount_relation = "greater"
790 res = self._check_account(userdn,
791 badPwdCount=0,
792 badPasswordTime=("greater", 0),
793 logonCount=(logoncount_relation, 0),
794 lastLogon=("greater", 0),
795 lastLogonTimestamp=("greater", 0),
796 userAccountControl=
797 dsdb.UF_NORMAL_ACCOUNT,
798 msDSUserAccountControlComputed=0)
799 badPasswordTime = int(res[0]["badPasswordTime"][0])
800 logonCount = int(res[0]["logonCount"][0])
801 lastLogon = int(res[0]["lastLogon"][0])
802 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
803 self.assertGreater(lastLogonTimestamp, badPasswordTime)
804 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
806 # Change password on a connection as another user
808 # Wrong old password
809 try:
810 other_ldb.modify_ldif("""
811 dn: """ + userdn + """
812 changetype: modify
813 delete: unicodePwd
814 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')).decode('utf8') + """
815 add: unicodePwd
816 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
817 """)
818 self.fail()
819 except LdbError as e10:
820 (num, msg) = e10.args
821 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
822 self.assertTrue('00000056' in msg, msg)
824 res = self._check_account(userdn,
825 badPwdCount=1,
826 badPasswordTime=("greater", badPasswordTime),
827 logonCount=logonCount,
828 lastLogon=lastLogon,
829 lastLogonTimestamp=lastLogonTimestamp,
830 userAccountControl=
831 dsdb.UF_NORMAL_ACCOUNT,
832 msDSUserAccountControlComputed=0)
833 badPasswordTime = int(res[0]["badPasswordTime"][0])
835 # Correct old password
836 old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
837 invalid_utf16 = "\"thatsAcomplPASSX\"".encode('utf-16-le')
838 userpass = "thatsAcomplPASS2"
839 creds.set_password(userpass)
840 new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
842 other_ldb.modify_ldif("""
843 dn: """ + userdn + """
844 changetype: modify
845 delete: unicodePwd
846 unicodePwd:: """ + base64.b64encode(old_utf16).decode('utf8') + """
847 add: unicodePwd
848 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
849 """)
851 res = self._check_account(userdn,
852 badPwdCount=1,
853 badPasswordTime=badPasswordTime,
854 logonCount=logonCount,
855 lastLogon=lastLogon,
856 lastLogonTimestamp=lastLogonTimestamp,
857 userAccountControl=
858 dsdb.UF_NORMAL_ACCOUNT,
859 msDSUserAccountControlComputed=0)
861 # Wrong old password
862 try:
863 other_ldb.modify_ldif("""
864 dn: """ + userdn + """
865 changetype: modify
866 delete: unicodePwd
867 unicodePwd:: """ + base64.b64encode(old_utf16).decode('utf8') + """
868 add: unicodePwd
869 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
870 """)
871 self.fail()
872 except LdbError as e11:
873 (num, msg) = e11.args
874 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
875 self.assertTrue('00000056' in msg, msg)
877 res = self._check_account(userdn,
878 badPwdCount=2,
879 badPasswordTime=("greater", badPasswordTime),
880 logonCount=logonCount,
881 lastLogon=lastLogon,
882 lastLogonTimestamp=lastLogonTimestamp,
883 userAccountControl=
884 dsdb.UF_NORMAL_ACCOUNT,
885 msDSUserAccountControlComputed=0)
886 badPasswordTime = int(res[0]["badPasswordTime"][0])
888 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
889 # It doesn't create "lockoutTime" = 0 and doesn't
890 # reset "badPwdCount" = 0.
891 self._reset_samr(res)
893 res = self._check_account(userdn,
894 badPwdCount=2,
895 badPasswordTime=badPasswordTime,
896 logonCount=logonCount,
897 lastLogon=lastLogon,
898 lastLogonTimestamp=lastLogonTimestamp,
899 userAccountControl=
900 dsdb.UF_NORMAL_ACCOUNT,
901 msDSUserAccountControlComputed=0)
903 print("two failed password change")
905 # Wrong old password
906 try:
907 other_ldb.modify_ldif("""
908 dn: """ + userdn + """
909 changetype: modify
910 delete: unicodePwd
911 unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
912 add: unicodePwd
913 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
914 """)
915 self.fail()
916 except LdbError as e12:
917 (num, msg) = e12.args
918 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
919 self.assertTrue('00000056' in msg, msg)
921 # this is strange, why do we have lockoutTime=badPasswordTime here?
922 res = self._check_account(userdn,
923 badPwdCount=3,
924 badPasswordTime=("greater", badPasswordTime),
925 logonCount=logonCount,
926 lastLogon=lastLogon,
927 lastLogonTimestamp=lastLogonTimestamp,
928 lockoutTime=("greater", badPasswordTime),
929 userAccountControl=
930 dsdb.UF_NORMAL_ACCOUNT,
931 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
932 badPasswordTime = int(res[0]["badPasswordTime"][0])
933 lockoutTime = int(res[0]["lockoutTime"][0])
935 # Wrong old password
936 try:
937 other_ldb.modify_ldif("""
938 dn: """ + userdn + """
939 changetype: modify
940 delete: unicodePwd
941 unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
942 add: unicodePwd
943 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
944 """)
945 self.fail()
946 except LdbError as e13:
947 (num, msg) = e13.args
948 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
949 self.assertTrue('00000775' in msg, msg)
951 res = self._check_account(userdn,
952 badPwdCount=3,
953 badPasswordTime=badPasswordTime,
954 logonCount=logonCount,
955 lastLogon=lastLogon,
956 lastLogonTimestamp=lastLogonTimestamp,
957 lockoutTime=lockoutTime,
958 userAccountControl=
959 dsdb.UF_NORMAL_ACCOUNT,
960 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
962 # Wrong old password
963 try:
964 other_ldb.modify_ldif("""
965 dn: """ + userdn + """
966 changetype: modify
967 delete: unicodePwd
968 unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
969 add: unicodePwd
970 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
971 """)
972 self.fail()
973 except LdbError as e14:
974 (num, msg) = e14.args
975 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
976 self.assertTrue('00000775' in msg, msg)
978 res = self._check_account(userdn,
979 badPwdCount=3,
980 badPasswordTime=badPasswordTime,
981 logonCount=logonCount,
982 lastLogon=lastLogon,
983 lastLogonTimestamp=lastLogonTimestamp,
984 lockoutTime=lockoutTime,
985 userAccountControl=
986 dsdb.UF_NORMAL_ACCOUNT,
987 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
989 try:
990 # Correct old password
991 other_ldb.modify_ldif("""
992 dn: """ + userdn + """
993 changetype: modify
994 delete: unicodePwd
995 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
996 add: unicodePwd
997 unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
998 """)
999 self.fail()
1000 except LdbError as e15:
1001 (num, msg) = e15.args
1002 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1003 self.assertTrue('00000775' in msg, msg)
1005 res = self._check_account(userdn,
1006 badPwdCount=3,
1007 badPasswordTime=badPasswordTime,
1008 logonCount=logonCount,
1009 lastLogon=lastLogon,
1010 lastLogonTimestamp=lastLogonTimestamp,
1011 lockoutTime=lockoutTime,
1012 userAccountControl=
1013 dsdb.UF_NORMAL_ACCOUNT,
1014 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1016 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
1017 self._reset_samr(res);
1019 res = self._check_account(userdn,
1020 badPwdCount=0,
1021 badPasswordTime=badPasswordTime,
1022 logonCount=logonCount,
1023 lastLogon=lastLogon,
1024 lastLogonTimestamp=lastLogonTimestamp,
1025 lockoutTime=0,
1026 userAccountControl=
1027 dsdb.UF_NORMAL_ACCOUNT,
1028 msDSUserAccountControlComputed=0)
1030 # Correct old password
1031 old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
1032 invalid_utf16 = "\"thatsAcomplPASSiX\"".encode('utf-16-le')
1033 userpass = "thatsAcomplPASS2x"
1034 creds.set_password(userpass)
1035 new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
1037 other_ldb.modify_ldif("""
1038 dn: """ + userdn + """
1039 changetype: modify
1040 delete: unicodePwd
1041 unicodePwd:: """ + base64.b64encode(old_utf16).decode('utf8') + """
1042 add: unicodePwd
1043 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
1044 """)
1046 res = self._check_account(userdn,
1047 badPwdCount=0,
1048 badPasswordTime=badPasswordTime,
1049 logonCount=logonCount,
1050 lastLogon=lastLogon,
1051 lastLogonTimestamp=lastLogonTimestamp,
1052 lockoutTime=0,
1053 userAccountControl=
1054 dsdb.UF_NORMAL_ACCOUNT,
1055 msDSUserAccountControlComputed=0)
1057 # Wrong old password
1058 try:
1059 other_ldb.modify_ldif("""
1060 dn: """ + userdn + """
1061 changetype: modify
1062 delete: unicodePwd
1063 unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
1064 add: unicodePwd
1065 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
1066 """)
1067 self.fail()
1068 except LdbError as e16:
1069 (num, msg) = e16.args
1070 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1071 self.assertTrue('00000056' in msg, msg)
1073 res = self._check_account(userdn,
1074 badPwdCount=1,
1075 badPasswordTime=("greater", badPasswordTime),
1076 logonCount=logonCount,
1077 lastLogon=lastLogon,
1078 lastLogonTimestamp=lastLogonTimestamp,
1079 lockoutTime=0,
1080 userAccountControl=
1081 dsdb.UF_NORMAL_ACCOUNT,
1082 msDSUserAccountControlComputed=0)
1083 badPasswordTime = int(res[0]["badPasswordTime"][0])
1085 # Wrong old password
1086 try:
1087 other_ldb.modify_ldif("""
1088 dn: """ + userdn + """
1089 changetype: modify
1090 delete: unicodePwd
1091 unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
1092 add: unicodePwd
1093 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
1094 """)
1095 self.fail()
1096 except LdbError as e17:
1097 (num, msg) = e17.args
1098 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1099 self.assertTrue('00000056' in msg, msg)
1101 res = self._check_account(userdn,
1102 badPwdCount=2,
1103 badPasswordTime=("greater", badPasswordTime),
1104 logonCount=logonCount,
1105 lastLogon=lastLogon,
1106 lastLogonTimestamp=lastLogonTimestamp,
1107 lockoutTime=0,
1108 userAccountControl=
1109 dsdb.UF_NORMAL_ACCOUNT,
1110 msDSUserAccountControlComputed=0)
1111 badPasswordTime = int(res[0]["badPasswordTime"][0])
1113 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1114 # It doesn't reset "badPwdCount" = 0.
1115 self._reset_samr(res)
1117 res = self._check_account(userdn,
1118 badPwdCount=2,
1119 badPasswordTime=badPasswordTime,
1120 logonCount=logonCount,
1121 lastLogon=lastLogon,
1122 lastLogonTimestamp=lastLogonTimestamp,
1123 lockoutTime=0,
1124 userAccountControl=
1125 dsdb.UF_NORMAL_ACCOUNT,
1126 msDSUserAccountControlComputed=0)
1128 # Wrong old password
1129 try:
1130 other_ldb.modify_ldif("""
1131 dn: """ + userdn + """
1132 changetype: modify
1133 delete: unicodePwd
1134 unicodePwd:: """ + base64.b64encode(invalid_utf16).decode('utf8') + """
1135 add: unicodePwd
1136 unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
1137 """)
1138 self.fail()
1139 except LdbError as e18:
1140 (num, msg) = e18.args
1141 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1142 self.assertTrue('00000056' in msg, msg)
1144 res = self._check_account(userdn,
1145 badPwdCount=3,
1146 badPasswordTime=("greater", badPasswordTime),
1147 logonCount=logonCount,
1148 lastLogon=lastLogon,
1149 lastLogonTimestamp=lastLogonTimestamp,
1150 lockoutTime=("greater", badPasswordTime),
1151 userAccountControl=
1152 dsdb.UF_NORMAL_ACCOUNT,
1153 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1154 badPasswordTime = int(res[0]["badPasswordTime"][0])
1155 lockoutTime = int(res[0]["lockoutTime"][0])
1157 time.sleep(self.account_lockout_duration + 1)
1159 res = self._check_account(userdn,
1160 badPwdCount=3, effective_bad_password_count=0,
1161 badPasswordTime=badPasswordTime,
1162 logonCount=logonCount,
1163 lastLogon=lastLogon,
1164 lastLogonTimestamp=lastLogonTimestamp,
1165 lockoutTime=lockoutTime,
1166 userAccountControl=
1167 dsdb.UF_NORMAL_ACCOUNT,
1168 msDSUserAccountControlComputed=0)
1170 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1171 # It doesn't reset "lockoutTime" = 0 and doesn't
1172 # reset "badPwdCount" = 0.
1173 self._reset_samr(res)
1175 res = self._check_account(userdn,
1176 badPwdCount=3, effective_bad_password_count=0,
1177 badPasswordTime=badPasswordTime,
1178 logonCount=logonCount,
1179 lockoutTime=lockoutTime,
1180 lastLogon=lastLogon,
1181 lastLogonTimestamp=lastLogonTimestamp,
1182 userAccountControl=
1183 dsdb.UF_NORMAL_ACCOUNT,
1184 msDSUserAccountControlComputed=0)
1186 def test_unicodePwd_lockout_with_clear_change_krb5(self):
1187 self._test_unicodePwd_lockout_with_clear_change(self.lockout1krb5_creds,
1188 self.lockout2krb5_ldb)
1190 def test_unicodePwd_lockout_with_clear_change_ntlm(self):
1191 self._test_unicodePwd_lockout_with_clear_change(self.lockout1ntlm_creds,
1192 self.lockout2ntlm_ldb,
1193 initial_logoncount_relation="equal")
1195 def test_login_lockout_krb5(self):
1196 self._test_login_lockout(self.lockout1krb5_creds)
1198 def test_login_lockout_ntlm(self):
1199 self._test_login_lockout(self.lockout1ntlm_creds)
1201 # Repeat the login lockout tests using PSOs
1202 def test_pso_login_lockout_krb5(self):
1203 """Check the PSO lockout settings get applied to the user correctly"""
1204 self.use_pso_lockout_settings(self.lockout1krb5_creds)
1205 self._test_login_lockout(self.lockout1krb5_creds)
1207 def test_pso_login_lockout_ntlm(self):
1208 """Check the PSO lockout settings get applied to the user correctly"""
1209 self.use_pso_lockout_settings(self.lockout1ntlm_creds)
1210 self._test_login_lockout(self.lockout1ntlm_creds)
1212 def _testing_add_user(self, creds, lockOutObservationWindow=0):
1213 username = creds.get_username()
1214 userpass = creds.get_password()
1215 userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
1217 use_kerberos = creds.get_kerberos_state()
1218 if use_kerberos == MUST_USE_KERBEROS:
1219 logoncount_relation = 'greater'
1220 lastlogon_relation = 'greater'
1221 else:
1222 logoncount_relation = 'equal'
1223 if lockOutObservationWindow == 0:
1224 lastlogon_relation = 'greater'
1225 else:
1226 lastlogon_relation = 'equal'
1228 delete_force(self.ldb, userdn)
1229 self.ldb.add({
1230 "dn": userdn,
1231 "objectclass": "user",
1232 "sAMAccountName": username})
1234 self.addCleanup(delete_force, self.ldb, userdn)
1236 res = self._check_account(userdn,
1237 badPwdCount=0,
1238 badPasswordTime=0,
1239 logonCount=0,
1240 lastLogon=0,
1241 lastLogonTimestamp=('absent', None),
1242 userAccountControl=
1243 dsdb.UF_NORMAL_ACCOUNT |
1244 dsdb.UF_ACCOUNTDISABLE |
1245 dsdb.UF_PASSWD_NOTREQD,
1246 msDSUserAccountControlComputed=
1247 dsdb.UF_PASSWORD_EXPIRED)
1249 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1250 # It doesn't create "lockoutTime" = 0.
1251 self._reset_samr(res)
1253 res = self._check_account(userdn,
1254 badPwdCount=0,
1255 badPasswordTime=0,
1256 logonCount=0,
1257 lastLogon=0,
1258 lastLogonTimestamp=('absent', None),
1259 userAccountControl=
1260 dsdb.UF_NORMAL_ACCOUNT |
1261 dsdb.UF_ACCOUNTDISABLE |
1262 dsdb.UF_PASSWD_NOTREQD,
1263 msDSUserAccountControlComputed=
1264 dsdb.UF_PASSWORD_EXPIRED)
1266 # Tests a password change when we don't have any password yet with a
1267 # wrong old password
1268 try:
1269 self.ldb.modify_ldif("""
1270 dn: """ + userdn + """
1271 changetype: modify
1272 delete: userPassword
1273 userPassword: noPassword
1274 add: userPassword
1275 userPassword: thatsAcomplPASS2
1276 """)
1277 self.fail()
1278 except LdbError as e19:
1279 (num, msg) = e19.args
1280 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1281 # Windows (2008 at least) seems to have some small bug here: it
1282 # returns "0000056A" on longer (always wrong) previous passwords.
1283 self.assertTrue('00000056' in msg, msg)
1285 res = self._check_account(userdn,
1286 badPwdCount=1,
1287 badPasswordTime=("greater", 0),
1288 logonCount=0,
1289 lastLogon=0,
1290 lastLogonTimestamp=('absent', None),
1291 userAccountControl=
1292 dsdb.UF_NORMAL_ACCOUNT |
1293 dsdb.UF_ACCOUNTDISABLE |
1294 dsdb.UF_PASSWD_NOTREQD,
1295 msDSUserAccountControlComputed=
1296 dsdb.UF_PASSWORD_EXPIRED)
1297 badPwdCount = int(res[0]["badPwdCount"][0])
1298 badPasswordTime = int(res[0]["badPasswordTime"][0])
1300 # Sets the initial user password with a "special" password change
1301 # I think that this internally is a password set operation and it can
1302 # only be performed by someone which has password set privileges on the
1303 # account (at least in s4 we do handle it like that).
1304 self.ldb.modify_ldif("""
1305 dn: """ + userdn + """
1306 changetype: modify
1307 delete: userPassword
1308 add: userPassword
1309 userPassword: """ + userpass + """
1310 """)
1312 res = self._check_account(userdn,
1313 badPwdCount=badPwdCount,
1314 badPasswordTime=badPasswordTime,
1315 logonCount=0,
1316 lastLogon=0,
1317 lastLogonTimestamp=('absent', None),
1318 userAccountControl=
1319 dsdb.UF_NORMAL_ACCOUNT |
1320 dsdb.UF_ACCOUNTDISABLE |
1321 dsdb.UF_PASSWD_NOTREQD,
1322 msDSUserAccountControlComputed=0)
1324 # Enables the user account
1325 self.ldb.enable_account("(sAMAccountName=%s)" % username)
1327 res = self._check_account(userdn,
1328 badPwdCount=badPwdCount,
1329 badPasswordTime=badPasswordTime,
1330 logonCount=0,
1331 lastLogon=0,
1332 lastLogonTimestamp=('absent', None),
1333 userAccountControl=
1334 dsdb.UF_NORMAL_ACCOUNT,
1335 msDSUserAccountControlComputed=0)
1336 if lockOutObservationWindow != 0:
1337 time.sleep(lockOutObservationWindow + 1)
1338 effective_bad_password_count = 0
1339 else:
1340 effective_bad_password_count = badPwdCount
1342 res = self._check_account(userdn,
1343 badPwdCount=badPwdCount,
1344 effective_bad_password_count=effective_bad_password_count,
1345 badPasswordTime=badPasswordTime,
1346 logonCount=0,
1347 lastLogon=0,
1348 lastLogonTimestamp=('absent', None),
1349 userAccountControl=
1350 dsdb.UF_NORMAL_ACCOUNT,
1351 msDSUserAccountControlComputed=0)
1353 ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp)
1355 if lockOutObservationWindow == 0:
1356 badPwdCount = 0
1357 effective_bad_password_count = 0
1358 if use_kerberos == MUST_USE_KERBEROS:
1359 badPwdCount = 0
1360 effective_bad_password_count = 0
1362 res = self._check_account(userdn,
1363 badPwdCount=badPwdCount,
1364 effective_bad_password_count=effective_bad_password_count,
1365 badPasswordTime=badPasswordTime,
1366 logonCount=(logoncount_relation, 0),
1367 lastLogon=(lastlogon_relation, 0),
1368 lastLogonTimestamp=('greater', badPasswordTime),
1369 userAccountControl=
1370 dsdb.UF_NORMAL_ACCOUNT,
1371 msDSUserAccountControlComputed=0)
1373 logonCount = int(res[0]["logonCount"][0])
1374 lastLogon = int(res[0]["lastLogon"][0])
1375 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1376 if lastlogon_relation == 'greater':
1377 self.assertGreater(lastLogon, badPasswordTime)
1378 self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
1380 res = self._check_account(userdn,
1381 badPwdCount=badPwdCount,
1382 effective_bad_password_count=effective_bad_password_count,
1383 badPasswordTime=badPasswordTime,
1384 logonCount=logonCount,
1385 lastLogon=lastLogon,
1386 lastLogonTimestamp=lastLogonTimestamp,
1387 userAccountControl=
1388 dsdb.UF_NORMAL_ACCOUNT,
1389 msDSUserAccountControlComputed=0)
1390 return ldb
1392 def test_lockout_observation_window(self):
1393 lockout3krb5_creds = self.insta_creds(self.template_creds,
1394 username="lockout3krb5",
1395 userpass="thatsAcomplPASS0",
1396 kerberos_state=MUST_USE_KERBEROS)
1397 self._testing_add_user(lockout3krb5_creds)
1399 lockout4krb5_creds = self.insta_creds(self.template_creds,
1400 username="lockout4krb5",
1401 userpass="thatsAcomplPASS0",
1402 kerberos_state=MUST_USE_KERBEROS)
1403 self._testing_add_user(lockout4krb5_creds,
1404 lockOutObservationWindow=self.lockout_observation_window)
1406 lockout3ntlm_creds = self.insta_creds(self.template_creds,
1407 username="lockout3ntlm",
1408 userpass="thatsAcomplPASS0",
1409 kerberos_state=DONT_USE_KERBEROS)
1410 self._testing_add_user(lockout3ntlm_creds)
1411 lockout4ntlm_creds = self.insta_creds(self.template_creds,
1412 username="lockout4ntlm",
1413 userpass="thatsAcomplPASS0",
1414 kerberos_state=DONT_USE_KERBEROS)
1415 self._testing_add_user(lockout4ntlm_creds,
1416 lockOutObservationWindow=self.lockout_observation_window)
1419 host_url = "ldap://%s" % host
1421 TestProgram(module=__name__, opts=subunitopts)