selftest: Add test for password lockout
[Samba.git] / source4 / dsdb / tests / python / password_lockout.py
blob89e5d69c4a119e1b57a8d4ad5f0b0672b1b5134b
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 import optparse
11 import sys
12 import base64
13 import time
14 import os
16 sys.path.insert(0, "bin/python")
17 import samba
18 samba.ensure_external_module("testtools", "testtools")
19 samba.ensure_external_module("subunit", "subunit/python")
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_ATTRIBUTE_OR_VALUE_EXISTS
27 from ldb import ERR_UNWILLING_TO_PERFORM, ERR_INSUFFICIENT_ACCESS_RIGHTS
28 from ldb import ERR_NO_SUCH_ATTRIBUTE
29 from ldb import ERR_CONSTRAINT_VIOLATION
30 from ldb import ERR_INVALID_CREDENTIALS
31 from ldb import Message, MessageElement, Dn
32 from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE
33 from samba import gensec, dsdb
34 from samba.samdb import SamDB
35 import samba.tests
36 from samba.tests import delete_force
37 from subunit.run import SubunitTestRunner
38 import unittest
39 from samba.dcerpc import security, samr
40 from samba.ndr import ndr_unpack
42 parser = optparse.OptionParser("passwords.py [options] <host>")
43 sambaopts = options.SambaOptions(parser)
44 parser.add_option_group(sambaopts)
45 parser.add_option_group(options.VersionOptions(parser))
46 # use command line creds if available
47 credopts = options.CredentialsOptions(parser)
48 parser.add_option_group(credopts)
49 opts, args = parser.parse_args()
51 if len(args) < 1:
52 parser.print_usage()
53 sys.exit(1)
55 host = args[0]
57 lp = sambaopts.get_loadparm()
58 creds = credopts.get_credentials(lp)
60 # Force an encrypted connection
61 creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
64 # Tests start here
67 class PasswordTests(samba.tests.TestCase):
69 def _open_samr_user(self, res):
70 self.assertTrue("objectSid" in res[0])
72 (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split()
73 self.assertEquals(self.domain_sid, domain_sid)
75 return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid)
77 def _reset_samr(self, res):
79 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
80 samr_user = self._open_samr_user(res)
81 acb_info = self.samr.QueryUserInfo(samr_user, 16)
82 acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
83 self.samr.SetUserInfo(samr_user, 16, acb_info)
84 self.samr.Close(samr_user)
86 def _reset_ldap_lockoutTime(self, res):
87 self.ldb.modify_ldif("""
88 dn: """ + str(res[0].dn) + """
89 changetype: modify
90 replace: lockoutTime
91 lockoutTime: 0
92 """)
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])
101 uac |= uacc
102 uac = uac & ~dsdb.UF_LOCKOUT
104 self.ldb.modify_ldif("""
105 dn: """ + str(res[0].dn) + """
106 changetype: modify
107 replace: userAccountControl
108 userAccountControl: %d
109 """ % uac)
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)
118 else:
119 self.assertTrue(False, msg="Invalid reset method[%s]" % method)
121 def _check_attribute(self, res, name, value):
122 if value is None:
123 self.assertTrue(name not in res[0],
124 msg="attr[%s]=%r on dn[%s]" %
125 (name, res[0], res[0].dn))
126 return
128 if isinstance(value, tuple):
129 (mode, value) = value
130 else:
131 mode = "equal"
133 if mode == "ignore":
134 return
136 self.assertTrue(name in res[0],
137 msg="attr[%s] missing on dn[%s]" %
138 (name, res[0].dn))
139 self.assertTrue(len(res[0][name]) == 1,
140 msg="attr[%s]=%r on dn[%s]" %
141 (name, res[0][name], res[0].dn))
143 if mode == "present":
144 return
145 if mode == "equal":
146 self.assertTrue(str(res[0][name][0]) == str(value),
147 msg="attr[%s]=[%s] != [%s] on dn[%s]" %
148 (name, str(res[0][name][0]), str(value), res[0].dn))
149 return
150 if mode == "greater":
151 v = int(res[0][name][0])
152 self.assertTrue(v > int(value),
153 msg="attr[%s]=[%s] <= [%s] on dn[%s]" %
154 (name, v, int(value), res[0].dn))
155 return
156 if mode == "less":
157 v = int(res[0][name][0])
158 self.assertTrue(v < int(value),
159 msg="attr[%s]=[%s] >= [%s] on dn[%s]" %
160 (name, v, int(value), res[0].dn))
161 return
162 self.assertEqual(mode, not mode, "Invalid Mode[%s]" % mode)
164 def _check_account(self, dn,
165 badPwdCount=None,
166 badPasswordTime=None,
167 lockoutTime=None,
168 userAccountControl=None,
169 msDSUserAccountControlComputed=None,
170 effective_bad_password_count=None):
172 attrs = [
173 "objectSid",
174 "badPwdCount",
175 "badPasswordTime",
176 "lockoutTime",
177 "userAccountControl",
178 "msDS-User-Account-Control-Computed"
181 # in order to prevent some time resolution problems we sleep for
182 # 10 micro second
183 time.sleep(0.01)
185 res = ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
186 self.assertTrue(len(res) == 1)
187 self._check_attribute(res, "badPwdCount", badPwdCount)
188 self._check_attribute(res, "badPasswordTime", badPasswordTime)
189 self._check_attribute(res, "lockoutTime", lockoutTime)
190 self._check_attribute(res, "userAccountControl", userAccountControl)
191 self._check_attribute(res, "msDS-User-Account-Control-Computed",
192 msDSUserAccountControlComputed)
194 samr_user = self._open_samr_user(res)
195 uinfo3 = self.samr.QueryUserInfo(samr_user, 3)
196 uinfo5 = self.samr.QueryUserInfo(samr_user, 5)
197 uinfo16 = self.samr.QueryUserInfo(samr_user, 16)
198 uinfo21 = self.samr.QueryUserInfo(samr_user, 21)
199 self.samr.Close(samr_user)
201 expected_acb_info = 0
202 if userAccountControl & dsdb.UF_NORMAL_ACCOUNT:
203 expected_acb_info |= samr.ACB_NORMAL
204 if userAccountControl & dsdb.UF_ACCOUNTDISABLE:
205 expected_acb_info |= samr.ACB_DISABLED
206 if userAccountControl & dsdb.UF_PASSWD_NOTREQD:
207 expected_acb_info |= samr.ACB_PWNOTREQ
208 if msDSUserAccountControlComputed & dsdb.UF_LOCKOUT:
209 expected_acb_info |= samr.ACB_AUTOLOCK
210 if msDSUserAccountControlComputed & dsdb.UF_PASSWORD_EXPIRED:
211 expected_acb_info |= samr.ACB_PW_EXPIRED
213 expected_bad_password_count = 0
214 if badPwdCount is not None:
215 expected_bad_password_count = badPwdCount
216 if effective_bad_password_count is None:
217 effective_bad_password_count = expected_bad_password_count
219 self.assertEquals(uinfo3.acct_flags, expected_acb_info)
220 self.assertEquals(uinfo3.bad_password_count, expected_bad_password_count)
222 self.assertEquals(uinfo5.acct_flags, expected_acb_info)
223 self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count)
225 self.assertEquals(uinfo16.acct_flags, expected_acb_info)
227 self.assertEquals(uinfo21.acct_flags, expected_acb_info)
228 self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count)
230 # check LDAP again and make sure the samr.QueryUserInfo
231 # doesn't have any impact.
232 res2 = ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
233 self.assertEquals(res[0], res2[0])
235 # in order to prevent some time resolution problems we sleep for
236 # 10 micro second
237 time.sleep(0.01)
238 return res
240 def setUp(self):
241 super(PasswordTests, self).setUp()
242 self.ldb = ldb
243 self.base_dn = ldb.domain_dn()
245 self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
246 self.samr = samr.samr("ncacn_ip_tcp:%s[sign]" % host, lp, creds)
247 self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
248 self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
250 # (Re)adds the test user "testuser" with no password atm
251 delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
252 self.ldb.add({
253 "dn": "cn=testuser,cn=users," + self.base_dn,
254 "objectclass": "user",
255 "sAMAccountName": "testuser"})
257 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
258 badPwdCount=0,
259 badPasswordTime=0,
260 userAccountControl=
261 dsdb.UF_NORMAL_ACCOUNT |
262 dsdb.UF_ACCOUNTDISABLE |
263 dsdb.UF_PASSWD_NOTREQD,
264 msDSUserAccountControlComputed=
265 dsdb.UF_PASSWORD_EXPIRED)
267 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
268 # It doesn't create "lockoutTime" = 0.
269 self._reset_samr(res)
271 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
272 badPwdCount=0,
273 badPasswordTime=0,
274 userAccountControl=
275 dsdb.UF_NORMAL_ACCOUNT |
276 dsdb.UF_ACCOUNTDISABLE |
277 dsdb.UF_PASSWD_NOTREQD,
278 msDSUserAccountControlComputed=
279 dsdb.UF_PASSWORD_EXPIRED)
281 # Tests a password change when we don't have any password yet with a
282 # wrong old password
283 try:
284 self.ldb.modify_ldif("""
285 dn: cn=testuser,cn=users,""" + self.base_dn + """
286 changetype: modify
287 delete: userPassword
288 userPassword: noPassword
289 add: userPassword
290 userPassword: thatsAcomplPASS2
291 """)
292 self.fail()
293 except LdbError, (num, msg):
294 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
295 # Windows (2008 at least) seems to have some small bug here: it
296 # returns "0000056A" on longer (always wrong) previous passwords.
297 self.assertTrue('00000056' in msg)
299 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
300 badPwdCount=1,
301 badPasswordTime=("greater", 0),
302 userAccountControl=
303 dsdb.UF_NORMAL_ACCOUNT |
304 dsdb.UF_ACCOUNTDISABLE |
305 dsdb.UF_PASSWD_NOTREQD,
306 msDSUserAccountControlComputed=
307 dsdb.UF_PASSWORD_EXPIRED)
308 badPasswordTime = int(res[0]["badPasswordTime"][0])
310 # Sets the initial user password with a "special" password change
311 # I think that this internally is a password set operation and it can
312 # only be performed by someone which has password set privileges on the
313 # account (at least in s4 we do handle it like that).
314 self.ldb.modify_ldif("""
315 dn: cn=testuser,cn=users,""" + self.base_dn + """
316 changetype: modify
317 delete: userPassword
318 add: userPassword
319 userPassword: thatsAcomplPASS1
320 """)
322 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
323 badPwdCount=1,
324 badPasswordTime=badPasswordTime,
325 userAccountControl=
326 dsdb.UF_NORMAL_ACCOUNT |
327 dsdb.UF_ACCOUNTDISABLE |
328 dsdb.UF_PASSWD_NOTREQD,
329 msDSUserAccountControlComputed=0)
331 # Enables the user account
332 self.ldb.enable_account("(sAMAccountName=testuser)")
334 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
335 badPwdCount=1,
336 badPasswordTime=badPasswordTime,
337 userAccountControl=
338 dsdb.UF_NORMAL_ACCOUNT,
339 msDSUserAccountControlComputed=0)
341 # Open a second LDB connection with the user credentials. Use the
342 # command line credentials for informations like the domain, the realm
343 # and the workstation.
344 creds2 = Credentials()
345 creds2.set_username("testuser")
346 creds2.set_password("thatsAcomplPASS1")
347 creds2.set_domain(creds.get_domain())
348 creds2.set_realm(creds.get_realm())
349 creds2.set_workstation(creds.get_workstation())
350 creds2.set_gensec_features(creds2.get_gensec_features()
351 | gensec.FEATURE_SEAL)
353 self.ldb2 = SamDB(url=host_url, credentials=creds2, lp=lp)
355 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
356 badPwdCount=0,
357 badPasswordTime=badPasswordTime,
358 userAccountControl=
359 dsdb.UF_NORMAL_ACCOUNT,
360 msDSUserAccountControlComputed=0)
362 # (Re)adds the test user "testuser3" with no password atm
363 delete_force(self.ldb, "cn=testuser3,cn=users," + self.base_dn)
364 self.ldb.add({
365 "dn": "cn=testuser3,cn=users," + self.base_dn,
366 "objectclass": "user",
367 "sAMAccountName": "testuser3"})
369 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
370 badPwdCount=0,
371 badPasswordTime=0,
372 userAccountControl=
373 dsdb.UF_NORMAL_ACCOUNT |
374 dsdb.UF_ACCOUNTDISABLE |
375 dsdb.UF_PASSWD_NOTREQD,
376 msDSUserAccountControlComputed=
377 dsdb.UF_PASSWORD_EXPIRED)
379 # Tests a password change when we don't have any password yet with a
380 # wrong old password
381 try:
382 self.ldb.modify_ldif("""
383 dn: cn=testuser3,cn=users,""" + self.base_dn + """
384 changetype: modify
385 delete: userPassword
386 userPassword: noPassword
387 add: userPassword
388 userPassword: thatsAcomplPASS2
389 """)
390 self.fail()
391 except LdbError, (num, msg):
392 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
393 # Windows (2008 at least) seems to have some small bug here: it
394 # returns "0000056A" on longer (always wrong) previous passwords.
395 self.assertTrue('00000056' in msg)
397 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
398 badPwdCount=1,
399 badPasswordTime=("greater", 0),
400 userAccountControl=
401 dsdb.UF_NORMAL_ACCOUNT |
402 dsdb.UF_ACCOUNTDISABLE |
403 dsdb.UF_PASSWD_NOTREQD,
404 msDSUserAccountControlComputed=
405 dsdb.UF_PASSWORD_EXPIRED)
406 badPasswordTime3 = int(res[0]["badPasswordTime"][0])
408 # Sets the initial user password with a "special" password change
409 # I think that this internally is a password set operation and it can
410 # only be performed by someone which has password set privileges on the
411 # account (at least in s4 we do handle it like that).
412 self.ldb.modify_ldif("""
413 dn: cn=testuser3,cn=users,""" + self.base_dn + """
414 changetype: modify
415 delete: userPassword
416 add: userPassword
417 userPassword: thatsAcomplPASS1
418 """)
420 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
421 badPwdCount=1,
422 badPasswordTime=badPasswordTime3,
423 userAccountControl=
424 dsdb.UF_NORMAL_ACCOUNT |
425 dsdb.UF_ACCOUNTDISABLE |
426 dsdb.UF_PASSWD_NOTREQD,
427 msDSUserAccountControlComputed=0)
429 # Enables the user account
430 self.ldb.enable_account("(sAMAccountName=testuser3)")
432 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
433 badPwdCount=1,
434 badPasswordTime=badPasswordTime3,
435 userAccountControl=
436 dsdb.UF_NORMAL_ACCOUNT,
437 msDSUserAccountControlComputed=0)
439 # Open a second LDB connection with the user credentials. Use the
440 # command line credentials for informations like the domain, the realm
441 # and the workstation.
442 creds3 = Credentials()
443 creds3.set_username("testuser3")
444 creds3.set_password("thatsAcomplPASS1")
445 creds3.set_domain(creds.get_domain())
446 creds3.set_realm(creds.get_realm())
447 creds3.set_workstation(creds.get_workstation())
448 creds3.set_gensec_features(creds3.get_gensec_features()
449 | gensec.FEATURE_SEAL)
450 self.ldb3 = SamDB(url=host_url, credentials=creds3, lp=lp)
452 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
453 badPwdCount=0,
454 badPasswordTime=badPasswordTime3,
455 userAccountControl=
456 dsdb.UF_NORMAL_ACCOUNT,
457 msDSUserAccountControlComputed=0)
459 def _test_userPassword_lockout_with_clear_change(self, method):
460 print "Performs a password cleartext change operation on 'userPassword'"
461 # Notice: This works only against Windows if "dSHeuristics" has been set
462 # properly
464 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
465 badPwdCount=0,
466 badPasswordTime=("greater", 0),
467 userAccountControl=
468 dsdb.UF_NORMAL_ACCOUNT,
469 msDSUserAccountControlComputed=0)
470 badPasswordTime = int(res[0]["badPasswordTime"][0])
472 # Change password on a connection as another user
474 # Wrong old password
475 try:
476 self.ldb3.modify_ldif("""
477 dn: cn=testuser,cn=users,""" + self.base_dn + """
478 changetype: modify
479 delete: userPassword
480 userPassword: thatsAcomplPASS1x
481 add: userPassword
482 userPassword: thatsAcomplPASS2
483 """)
484 self.fail()
485 except LdbError, (num, msg):
486 self.assertTrue('00000056' in msg)
487 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
489 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
490 badPwdCount=1,
491 badPasswordTime=("greater", badPasswordTime),
492 userAccountControl=
493 dsdb.UF_NORMAL_ACCOUNT,
494 msDSUserAccountControlComputed=0)
495 badPasswordTime = int(res[0]["badPasswordTime"][0])
497 # Correct old password
498 self.ldb3.modify_ldif("""
499 dn: cn=testuser,cn=users,""" + self.base_dn + """
500 changetype: modify
501 delete: userPassword
502 userPassword: thatsAcomplPASS1
503 add: userPassword
504 userPassword: thatsAcomplPASS2
505 """)
507 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
508 badPwdCount=1,
509 badPasswordTime=badPasswordTime,
510 userAccountControl=
511 dsdb.UF_NORMAL_ACCOUNT,
512 msDSUserAccountControlComputed=0)
514 # Wrong old password
515 try:
516 self.ldb3.modify_ldif("""
517 dn: cn=testuser,cn=users,""" + self.base_dn + """
518 changetype: modify
519 delete: userPassword
520 userPassword: thatsAcomplPASS1x
521 add: userPassword
522 userPassword: thatsAcomplPASS2
523 """)
524 self.fail()
525 except LdbError, (num, msg):
526 self.assertTrue('00000056' in msg)
527 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
529 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
530 badPwdCount=2,
531 badPasswordTime=("greater", badPasswordTime),
532 userAccountControl=
533 dsdb.UF_NORMAL_ACCOUNT,
534 msDSUserAccountControlComputed=0)
535 badPasswordTime = int(res[0]["badPasswordTime"][0])
537 print "two failed password change"
539 # Wrong old password
540 try:
541 self.ldb3.modify_ldif("""
542 dn: cn=testuser,cn=users,""" + self.base_dn + """
543 changetype: modify
544 delete: userPassword
545 userPassword: thatsAcomplPASS1x
546 add: userPassword
547 userPassword: thatsAcomplPASS2
548 """)
549 self.fail()
550 except LdbError, (num, msg):
551 self.assertTrue('00000056' in msg)
552 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
554 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
555 badPwdCount=3,
556 badPasswordTime=("greater", badPasswordTime),
557 lockoutTime=("greater", badPasswordTime),
558 userAccountControl=
559 dsdb.UF_NORMAL_ACCOUNT,
560 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
561 badPasswordTime = int(res[0]["badPasswordTime"][0])
562 lockoutTime = int(res[0]["lockoutTime"][0])
564 # Wrong old password
565 try:
566 self.ldb3.modify_ldif("""
567 dn: cn=testuser,cn=users,""" + self.base_dn + """
568 changetype: modify
569 delete: userPassword
570 userPassword: thatsAcomplPASS1x
571 add: userPassword
572 userPassword: thatsAcomplPASS2
573 """)
574 self.fail()
575 except LdbError, (num, msg):
576 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
577 self.assertTrue('00000775' in msg)
579 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
580 badPwdCount=3,
581 badPasswordTime=badPasswordTime,
582 lockoutTime=lockoutTime,
583 userAccountControl=
584 dsdb.UF_NORMAL_ACCOUNT,
585 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
587 # Wrong old password
588 try:
589 self.ldb3.modify_ldif("""
590 dn: cn=testuser,cn=users,""" + self.base_dn + """
591 changetype: modify
592 delete: userPassword
593 userPassword: thatsAcomplPASS1x
594 add: userPassword
595 userPassword: thatsAcomplPASS2
596 """)
597 self.fail()
598 except LdbError, (num, msg):
599 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
600 self.assertTrue('00000775' in msg)
602 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
603 badPwdCount=3,
604 badPasswordTime=badPasswordTime,
605 lockoutTime=lockoutTime,
606 userAccountControl=
607 dsdb.UF_NORMAL_ACCOUNT,
608 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
610 try:
611 # Correct old password
612 self.ldb3.modify_ldif("""
613 dn: cn=testuser,cn=users,""" + self.base_dn + """
614 changetype: modify
615 delete: userPassword
616 userPassword: thatsAcomplPASS2
617 add: userPassword
618 userPassword: thatsAcomplPASS2x
619 """)
620 self.fail()
621 except LdbError, (num, msg):
622 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
623 self.assertTrue('0000775' in msg)
625 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
626 badPwdCount=3,
627 badPasswordTime=badPasswordTime,
628 lockoutTime=lockoutTime,
629 userAccountControl=
630 dsdb.UF_NORMAL_ACCOUNT,
631 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
633 # Now reset the password, which does NOT change the lockout!
634 self.ldb.modify_ldif("""
635 dn: cn=testuser,cn=users,""" + self.base_dn + """
636 changetype: modify
637 replace: userPassword
638 userPassword: thatsAcomplPASS2
639 """)
641 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
642 badPwdCount=3,
643 badPasswordTime=badPasswordTime,
644 lockoutTime=lockoutTime,
645 userAccountControl=
646 dsdb.UF_NORMAL_ACCOUNT,
647 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
649 try:
650 # Correct old password
651 self.ldb3.modify_ldif("""
652 dn: cn=testuser,cn=users,""" + self.base_dn + """
653 changetype: modify
654 delete: userPassword
655 userPassword: thatsAcomplPASS2
656 add: userPassword
657 userPassword: thatsAcomplPASS2x
658 """)
659 self.fail()
660 except LdbError, (num, msg):
661 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
662 self.assertTrue('0000775' in msg)
664 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
665 badPwdCount=3,
666 badPasswordTime=badPasswordTime,
667 lockoutTime=lockoutTime,
668 userAccountControl=
669 dsdb.UF_NORMAL_ACCOUNT,
670 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
672 m = Message()
673 m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
674 m["userAccountControl"] = MessageElement(
675 str(dsdb.UF_LOCKOUT),
676 FLAG_MOD_REPLACE, "userAccountControl")
678 self.ldb.modify(m)
680 # This shows that setting the UF_LOCKOUT flag alone makes no difference
681 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
682 badPwdCount=3,
683 badPasswordTime=badPasswordTime,
684 lockoutTime=lockoutTime,
685 userAccountControl=
686 dsdb.UF_NORMAL_ACCOUNT,
687 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
689 # This shows that setting the UF_LOCKOUT flag makes no difference
690 try:
691 # Correct old password
692 self.ldb3.modify_ldif("""
693 dn: cn=testuser,cn=users,""" + self.base_dn + """
694 changetype: modify
695 delete: unicodePwd
696 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
697 add: unicodePwd
698 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
699 """)
700 self.fail()
701 except LdbError, (num, msg):
702 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
703 self.assertTrue('0000775' in msg)
705 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
706 badPwdCount=3,
707 badPasswordTime=badPasswordTime,
708 lockoutTime=lockoutTime,
709 userAccountControl=
710 dsdb.UF_NORMAL_ACCOUNT,
711 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
713 self._reset_by_method(res, method)
715 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
716 badPwdCount=0,
717 badPasswordTime=badPasswordTime,
718 lockoutTime=0,
719 userAccountControl=
720 dsdb.UF_NORMAL_ACCOUNT,
721 msDSUserAccountControlComputed=0)
723 # The correct password after doing the unlock
725 self.ldb3.modify_ldif("""
726 dn: cn=testuser,cn=users,""" + self.base_dn + """
727 changetype: modify
728 delete: unicodePwd
729 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
730 add: unicodePwd
731 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
732 """)
734 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
735 badPwdCount=0,
736 badPasswordTime=badPasswordTime,
737 lockoutTime=0,
738 userAccountControl=
739 dsdb.UF_NORMAL_ACCOUNT,
740 msDSUserAccountControlComputed=0)
742 # Wrong old password
743 try:
744 self.ldb3.modify_ldif("""
745 dn: cn=testuser,cn=users,""" + self.base_dn + """
746 changetype: modify
747 delete: userPassword
748 userPassword: thatsAcomplPASS1xyz
749 add: userPassword
750 userPassword: thatsAcomplPASS2XYZ
751 """)
752 self.fail()
753 except LdbError, (num, msg):
754 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
755 self.assertTrue('00000056' in msg)
757 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
758 badPwdCount=1,
759 badPasswordTime=("greater", badPasswordTime),
760 lockoutTime=0,
761 userAccountControl=
762 dsdb.UF_NORMAL_ACCOUNT,
763 msDSUserAccountControlComputed=0)
764 badPasswordTime = int(res[0]["badPasswordTime"][0])
766 # Wrong old password
767 try:
768 self.ldb3.modify_ldif("""
769 dn: cn=testuser,cn=users,""" + self.base_dn + """
770 changetype: modify
771 delete: userPassword
772 userPassword: thatsAcomplPASS1xyz
773 add: userPassword
774 userPassword: thatsAcomplPASS2XYZ
775 """)
776 self.fail()
777 except LdbError, (num, msg):
778 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
779 self.assertTrue('00000056' in msg)
781 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
782 badPwdCount=2,
783 badPasswordTime=("greater", badPasswordTime),
784 lockoutTime=0,
785 userAccountControl=
786 dsdb.UF_NORMAL_ACCOUNT,
787 msDSUserAccountControlComputed=0)
788 badPasswordTime = int(res[0]["badPasswordTime"][0])
790 self._reset_ldap_lockoutTime(res)
792 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
793 badPwdCount=0,
794 badPasswordTime=badPasswordTime,
795 lockoutTime=0,
796 userAccountControl=
797 dsdb.UF_NORMAL_ACCOUNT,
798 msDSUserAccountControlComputed=0)
800 def test_userPassword_lockout_with_clear_change_ldap_userAccountControl(self):
801 self._test_userPassword_lockout_with_clear_change("ldap_userAccountControl")
803 def test_userPassword_lockout_with_clear_change_ldap_lockoutTime(self):
804 self._test_userPassword_lockout_with_clear_change("ldap_lockoutTime")
806 def test_userPassword_lockout_with_clear_change_samr(self):
807 self._test_userPassword_lockout_with_clear_change("samr")
810 def test_unicodePwd_lockout_with_clear_change(self):
811 print "Performs a password cleartext change operation on 'unicodePwd'"
813 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
814 badPwdCount=0,
815 badPasswordTime=("greater", 0),
816 userAccountControl=
817 dsdb.UF_NORMAL_ACCOUNT,
818 msDSUserAccountControlComputed=0)
819 badPasswordTime = int(res[0]["badPasswordTime"][0])
821 # Change password on a connection as another user
823 # Wrong old password
824 try:
825 self.ldb3.modify_ldif("""
826 dn: cn=testuser,cn=users,""" + self.base_dn + """
827 changetype: modify
828 delete: unicodePwd
829 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
830 add: unicodePwd
831 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
832 """)
833 self.fail()
834 except LdbError, (num, msg):
835 self.assertTrue('00000056' in msg)
836 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
838 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
839 badPwdCount=1,
840 badPasswordTime=("greater", badPasswordTime),
841 userAccountControl=
842 dsdb.UF_NORMAL_ACCOUNT,
843 msDSUserAccountControlComputed=0)
844 badPasswordTime = int(res[0]["badPasswordTime"][0])
846 # Correct old password
847 self.ldb3.modify_ldif("""
848 dn: cn=testuser,cn=users,""" + self.base_dn + """
849 changetype: modify
850 delete: unicodePwd
851 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
852 add: unicodePwd
853 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
854 """)
856 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
857 badPwdCount=1,
858 badPasswordTime=badPasswordTime,
859 userAccountControl=
860 dsdb.UF_NORMAL_ACCOUNT,
861 msDSUserAccountControlComputed=0)
863 # Wrong old password
864 try:
865 self.ldb3.modify_ldif("""
866 dn: cn=testuser,cn=users,""" + self.base_dn + """
867 changetype: modify
868 delete: unicodePwd
869 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
870 add: unicodePwd
871 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
872 """)
873 self.fail()
874 except LdbError, (num, msg):
875 self.assertTrue('00000056' in msg)
876 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
878 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
879 badPwdCount=2,
880 badPasswordTime=("greater", badPasswordTime),
881 userAccountControl=
882 dsdb.UF_NORMAL_ACCOUNT,
883 msDSUserAccountControlComputed=0)
884 badPasswordTime = int(res[0]["badPasswordTime"][0])
886 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
887 # It doesn't create "lockoutTime" = 0 and doesn't
888 # reset "badPwdCount" = 0.
889 self._reset_samr(res)
891 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
892 badPwdCount=2,
893 badPasswordTime=badPasswordTime,
894 userAccountControl=
895 dsdb.UF_NORMAL_ACCOUNT,
896 msDSUserAccountControlComputed=0)
898 print "two failed password change"
900 # Wrong old password
901 try:
902 self.ldb3.modify_ldif("""
903 dn: cn=testuser,cn=users,""" + self.base_dn + """
904 changetype: modify
905 delete: unicodePwd
906 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
907 add: unicodePwd
908 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
909 """)
910 self.fail()
911 except LdbError, (num, msg):
912 self.assertTrue('00000056' in msg)
913 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
915 # this is strange, why do we have lockoutTime=badPasswordTime here?
916 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
917 badPwdCount=3,
918 badPasswordTime=("greater", badPasswordTime),
919 lockoutTime=("greater", badPasswordTime),
920 userAccountControl=
921 dsdb.UF_NORMAL_ACCOUNT,
922 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
923 badPasswordTime = int(res[0]["badPasswordTime"][0])
924 lockoutTime = int(res[0]["lockoutTime"][0])
926 # Wrong old password
927 try:
928 self.ldb3.modify_ldif("""
929 dn: cn=testuser,cn=users,""" + self.base_dn + """
930 changetype: modify
931 delete: unicodePwd
932 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
933 add: unicodePwd
934 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
935 """)
936 self.fail()
937 except LdbError, (num, msg):
938 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
939 self.assertTrue('00000775' in msg)
941 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
942 badPwdCount=3,
943 badPasswordTime=badPasswordTime,
944 lockoutTime=lockoutTime,
945 userAccountControl=
946 dsdb.UF_NORMAL_ACCOUNT,
947 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
949 # Wrong old password
950 try:
951 self.ldb3.modify_ldif("""
952 dn: cn=testuser,cn=users,""" + self.base_dn + """
953 changetype: modify
954 delete: unicodePwd
955 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
956 add: unicodePwd
957 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
958 """)
959 self.fail()
960 except LdbError, (num, msg):
961 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
962 self.assertTrue('00000775' in msg)
964 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
965 badPwdCount=3,
966 badPasswordTime=badPasswordTime,
967 lockoutTime=lockoutTime,
968 userAccountControl=
969 dsdb.UF_NORMAL_ACCOUNT,
970 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
972 try:
973 # Correct old password
974 self.ldb3.modify_ldif("""
975 dn: cn=testuser,cn=users,""" + self.base_dn + """
976 changetype: modify
977 delete: unicodePwd
978 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
979 add: unicodePwd
980 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
981 """)
982 self.fail()
983 except LdbError, (num, msg):
984 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
985 self.assertTrue('0000775' in msg)
987 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
988 badPwdCount=3,
989 badPasswordTime=badPasswordTime,
990 lockoutTime=lockoutTime,
991 userAccountControl=
992 dsdb.UF_NORMAL_ACCOUNT,
993 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
995 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
996 self._reset_samr(res);
998 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
999 badPwdCount=0,
1000 badPasswordTime=badPasswordTime,
1001 lockoutTime=0,
1002 userAccountControl=
1003 dsdb.UF_NORMAL_ACCOUNT,
1004 msDSUserAccountControlComputed=0)
1006 # Correct old password
1007 self.ldb3.modify_ldif("""
1008 dn: cn=testuser,cn=users,""" + self.base_dn + """
1009 changetype: modify
1010 delete: unicodePwd
1011 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1012 add: unicodePwd
1013 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
1014 """)
1016 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1017 badPwdCount=0,
1018 badPasswordTime=badPasswordTime,
1019 lockoutTime=0,
1020 userAccountControl=
1021 dsdb.UF_NORMAL_ACCOUNT,
1022 msDSUserAccountControlComputed=0)
1024 # Wrong old password
1025 try:
1026 self.ldb3.modify_ldif("""
1027 dn: cn=testuser,cn=users,""" + self.base_dn + """
1028 changetype: modify
1029 delete: unicodePwd
1030 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1031 add: unicodePwd
1032 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1033 """)
1034 self.fail()
1035 except LdbError, (num, msg):
1036 self.assertTrue('00000056' in msg)
1037 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1039 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1040 badPwdCount=1,
1041 badPasswordTime=("greater", badPasswordTime),
1042 lockoutTime=0,
1043 userAccountControl=
1044 dsdb.UF_NORMAL_ACCOUNT,
1045 msDSUserAccountControlComputed=0)
1046 badPasswordTime = int(res[0]["badPasswordTime"][0])
1048 # Wrong old password
1049 try:
1050 self.ldb3.modify_ldif("""
1051 dn: cn=testuser,cn=users,""" + self.base_dn + """
1052 changetype: modify
1053 delete: unicodePwd
1054 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1055 add: unicodePwd
1056 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1057 """)
1058 self.fail()
1059 except LdbError, (num, msg):
1060 self.assertTrue('00000056' in msg)
1061 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1063 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1064 badPwdCount=2,
1065 badPasswordTime=("greater", badPasswordTime),
1066 lockoutTime=0,
1067 userAccountControl=
1068 dsdb.UF_NORMAL_ACCOUNT,
1069 msDSUserAccountControlComputed=0)
1070 badPasswordTime = int(res[0]["badPasswordTime"][0])
1072 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1073 # It doesn't reset "badPwdCount" = 0.
1074 self._reset_samr(res)
1076 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1077 badPwdCount=2,
1078 badPasswordTime=badPasswordTime,
1079 lockoutTime=0,
1080 userAccountControl=
1081 dsdb.UF_NORMAL_ACCOUNT,
1082 msDSUserAccountControlComputed=0)
1084 # Wrong old password
1085 try:
1086 self.ldb3.modify_ldif("""
1087 dn: cn=testuser,cn=users,""" + self.base_dn + """
1088 changetype: modify
1089 delete: unicodePwd
1090 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1091 add: unicodePwd
1092 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1093 """)
1094 self.fail()
1095 except LdbError, (num, msg):
1096 self.assertTrue('00000056' in msg)
1097 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1099 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1100 badPwdCount=3,
1101 badPasswordTime=("greater", badPasswordTime),
1102 lockoutTime=("greater", badPasswordTime),
1103 userAccountControl=
1104 dsdb.UF_NORMAL_ACCOUNT,
1105 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1106 badPasswordTime = int(res[0]["badPasswordTime"][0])
1107 lockoutTime = int(res[0]["lockoutTime"][0])
1109 time.sleep(account_lockout_duration + 1)
1111 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1112 badPwdCount=3, effective_bad_password_count=0,
1113 badPasswordTime=badPasswordTime,
1114 lockoutTime=lockoutTime,
1115 userAccountControl=
1116 dsdb.UF_NORMAL_ACCOUNT,
1117 msDSUserAccountControlComputed=0)
1119 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1120 # It doesn't reset "lockoutTime" = 0 and doesn't
1121 # reset "badPwdCount" = 0.
1122 self._reset_samr(res)
1124 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1125 badPwdCount=3, effective_bad_password_count=0,
1126 badPasswordTime=badPasswordTime,
1127 lockoutTime=lockoutTime,
1128 userAccountControl=
1129 dsdb.UF_NORMAL_ACCOUNT,
1130 msDSUserAccountControlComputed=0)
1132 def _test_login_lockout(self, use_kerberos):
1133 # This unlocks by waiting for account_lockout_duration
1134 print "Performs a lockout attempt against LDAP using NTLM or Kerberos"
1136 # Change password on a connection as another user
1138 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1139 badPwdCount=0,
1140 badPasswordTime=("greater", 0),
1141 userAccountControl=
1142 dsdb.UF_NORMAL_ACCOUNT,
1143 msDSUserAccountControlComputed=0)
1144 badPasswordTime = int(res[0]["badPasswordTime"][0])
1146 # Open a second LDB connection with the user credentials. Use the
1147 # command line credentials for informations like the domain, the realm
1148 # and the workstation.
1149 creds_lockout = Credentials()
1150 creds_lockout.set_username("testuser")
1151 creds_lockout.set_domain(creds.get_domain())
1152 creds_lockout.set_realm(creds.get_realm())
1153 creds_lockout.set_workstation(creds.get_workstation())
1154 creds_lockout.set_gensec_features(creds_lockout.get_gensec_features()
1155 | gensec.FEATURE_SEAL)
1156 creds_lockout.set_kerberos_state(use_kerberos)
1158 # The wrong password
1159 creds_lockout.set_password("thatsAcomplPASS1x")
1161 try:
1162 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1164 except LdbError, (num, msg):
1165 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1167 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1168 badPwdCount=1,
1169 badPasswordTime=("greater", badPasswordTime),
1170 userAccountControl=
1171 dsdb.UF_NORMAL_ACCOUNT,
1172 msDSUserAccountControlComputed=0)
1173 badPasswordTime = int(res[0]["badPasswordTime"][0])
1175 # Correct old password
1176 creds_lockout.set_password("thatsAcomplPASS1")
1178 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1180 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1181 badPwdCount=0,
1182 badPasswordTime=badPasswordTime,
1183 userAccountControl=
1184 dsdb.UF_NORMAL_ACCOUNT,
1185 msDSUserAccountControlComputed=0)
1187 # The wrong password
1188 creds_lockout.set_password("thatsAcomplPASS1x")
1190 try:
1191 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1193 except LdbError, (num, msg):
1194 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1196 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1197 badPwdCount=1,
1198 badPasswordTime=("greater", badPasswordTime),
1199 userAccountControl=
1200 dsdb.UF_NORMAL_ACCOUNT,
1201 msDSUserAccountControlComputed=0)
1202 badPasswordTime = int(res[0]["badPasswordTime"][0])
1204 # The wrong password
1205 creds_lockout.set_password("thatsAcomplPASS1x")
1207 try:
1208 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1209 self.fail()
1211 except LdbError, (num, msg):
1212 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1214 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1215 badPwdCount=2,
1216 badPasswordTime=("greater", badPasswordTime),
1217 userAccountControl=
1218 dsdb.UF_NORMAL_ACCOUNT,
1219 msDSUserAccountControlComputed=0)
1220 badPasswordTime = int(res[0]["badPasswordTime"][0])
1222 print "two failed password change"
1224 # The wrong password
1225 creds_lockout.set_password("thatsAcomplPASS1x")
1227 try:
1228 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1229 self.fail()
1231 except LdbError, (num, msg):
1232 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1234 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1235 badPwdCount=3,
1236 badPasswordTime=("greater", badPasswordTime),
1237 lockoutTime=("greater", badPasswordTime),
1238 userAccountControl=
1239 dsdb.UF_NORMAL_ACCOUNT,
1240 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1241 badPasswordTime = int(res[0]["badPasswordTime"][0])
1242 lockoutTime = int(res[0]["lockoutTime"][0])
1244 # The wrong password
1245 creds_lockout.set_password("thatsAcomplPASS1x")
1246 try:
1247 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1248 self.fail()
1249 except LdbError, (num, msg):
1250 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1252 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1253 badPwdCount=3,
1254 badPasswordTime=badPasswordTime,
1255 lockoutTime=lockoutTime,
1256 userAccountControl=
1257 dsdb.UF_NORMAL_ACCOUNT,
1258 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1260 # The wrong password
1261 creds_lockout.set_password("thatsAcomplPASS1x")
1262 try:
1263 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1264 self.fail()
1265 except LdbError, (num, msg):
1266 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1268 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1269 badPwdCount=3,
1270 badPasswordTime=badPasswordTime,
1271 lockoutTime=lockoutTime,
1272 userAccountControl=
1273 dsdb.UF_NORMAL_ACCOUNT,
1274 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1276 # The correct password
1277 creds_lockout.set_password("thatsAcomplPASS1")
1278 try:
1279 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1280 self.fail()
1281 except LdbError, (num, msg):
1282 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1284 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1285 badPwdCount=3,
1286 badPasswordTime=badPasswordTime,
1287 lockoutTime=lockoutTime,
1288 userAccountControl=
1289 dsdb.UF_NORMAL_ACCOUNT,
1290 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1292 time.sleep(account_lockout_duration + 1)
1294 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1295 badPwdCount=3, effective_bad_password_count=0,
1296 badPasswordTime=badPasswordTime,
1297 lockoutTime=lockoutTime,
1298 userAccountControl=
1299 dsdb.UF_NORMAL_ACCOUNT,
1300 msDSUserAccountControlComputed=0)
1302 # The correct password after letting the timeout expire
1303 creds_lockout.set_password("thatsAcomplPASS1")
1304 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1306 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1307 badPwdCount=0,
1308 badPasswordTime=badPasswordTime,
1309 lockoutTime=0,
1310 userAccountControl=
1311 dsdb.UF_NORMAL_ACCOUNT,
1312 msDSUserAccountControlComputed=0)
1314 # The wrong password
1315 creds_lockout.set_password("thatsAcomplPASS1x")
1316 try:
1317 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1318 self.fail()
1319 except LdbError, (num, msg):
1320 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1322 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1323 badPwdCount=1,
1324 badPasswordTime=("greater", badPasswordTime),
1325 lockoutTime=0,
1326 userAccountControl=
1327 dsdb.UF_NORMAL_ACCOUNT,
1328 msDSUserAccountControlComputed=0)
1329 badPasswordTime = int(res[0]["badPasswordTime"][0])
1331 # The wrong password
1332 creds_lockout.set_password("thatsAcomplPASS1x")
1333 try:
1334 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1335 self.fail()
1336 except LdbError, (num, msg):
1337 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1339 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1340 badPwdCount=2,
1341 badPasswordTime=("greater", badPasswordTime),
1342 lockoutTime=0,
1343 userAccountControl=
1344 dsdb.UF_NORMAL_ACCOUNT,
1345 msDSUserAccountControlComputed=0)
1346 badPasswordTime = int(res[0]["badPasswordTime"][0])
1348 time.sleep(lockout_observation_window + 1)
1350 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1351 badPwdCount=2, effective_bad_password_count=0,
1352 badPasswordTime=badPasswordTime,
1353 lockoutTime=0,
1354 userAccountControl=
1355 dsdb.UF_NORMAL_ACCOUNT,
1356 msDSUserAccountControlComputed=0)
1358 # The wrong password
1359 creds_lockout.set_password("thatsAcomplPASS1x")
1360 try:
1361 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1362 self.fail()
1363 except LdbError, (num, msg):
1364 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1366 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1367 badPwdCount=1,
1368 badPasswordTime=("greater", badPasswordTime),
1369 lockoutTime=0,
1370 userAccountControl=
1371 dsdb.UF_NORMAL_ACCOUNT,
1372 msDSUserAccountControlComputed=0)
1373 badPasswordTime = int(res[0]["badPasswordTime"][0])
1375 # The correct password without letting the timeout expire
1376 creds_lockout.set_password("thatsAcomplPASS1")
1377 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1379 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1380 badPwdCount=0,
1381 badPasswordTime=badPasswordTime,
1382 lockoutTime=0,
1383 userAccountControl=
1384 dsdb.UF_NORMAL_ACCOUNT,
1385 msDSUserAccountControlComputed=0)
1387 def test_login_lockout_ntlm(self):
1388 self._test_login_lockout(DONT_USE_KERBEROS)
1390 def test_login_lockout_kerberos(self):
1391 self._test_login_lockout(MUST_USE_KERBEROS)
1393 def tearDown(self):
1394 super(PasswordTests, self).tearDown()
1395 delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
1396 delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
1397 delete_force(self.ldb, "cn=testuser3,cn=users," + self.base_dn)
1398 # Close the second LDB connection (with the user credentials)
1399 self.ldb2 = None
1401 host_url = "ldap://%s" % host
1403 ldb = SamDB(url=host_url, session_info=system_session(lp), credentials=creds, lp=lp)
1405 # Gets back the basedn
1406 base_dn = ldb.domain_dn()
1408 # Gets back the configuration basedn
1409 configuration_dn = ldb.get_config_basedn().get_linearized()
1411 # Get the old "dSHeuristics" if it was set
1412 dsheuristics = ldb.get_dsheuristics()
1414 res = ldb.search(base_dn,
1415 scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
1417 if "lockoutDuration" in res[0]:
1418 lockoutDuration = res[0]["lockoutDuration"][0]
1419 else:
1420 lockoutDuration = 0
1422 if "lockoutObservationWindow" in res[0]:
1423 lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
1424 else:
1425 lockoutObservationWindow = 0
1427 if "lockoutThreshold" in res[0]:
1428 lockoutThreshold = res[0]["lockoutThreshold"][0]
1429 else:
1430 lockoutTreshold = 0
1432 m = Message()
1433 m.dn = Dn(ldb, base_dn)
1435 account_lockout_duration = 10
1436 account_lockout_duration_ticks = -int(account_lockout_duration * (1e7))
1438 m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
1439 FLAG_MOD_REPLACE, "lockoutDuration")
1441 account_lockout_threshold = 3
1442 m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
1443 FLAG_MOD_REPLACE, "lockoutThreshold")
1445 lockout_observation_window = 5
1446 lockout_observation_window_ticks = -int(lockout_observation_window * (1e7))
1448 m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
1449 FLAG_MOD_REPLACE, "lockOutObservationWindow")
1451 ldb.modify(m)
1453 # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
1454 ldb.set_dsheuristics("000000001")
1456 # Get the old "minPwdAge"
1457 minPwdAge = ldb.get_minPwdAge()
1459 # Set it temporarely to "0"
1460 ldb.set_minPwdAge("0")
1462 runner = SubunitTestRunner()
1463 rc = 0
1464 if not runner.run(unittest.makeSuite(PasswordTests)).wasSuccessful():
1465 rc = 1
1467 # Reset the "dSHeuristics" as they were before
1468 ldb.set_dsheuristics(dsheuristics)
1470 # Reset the "minPwdAge" as it was before
1471 ldb.set_minPwdAge(minPwdAge)
1473 ldb.modify_ldif("""
1474 dn: """ + base_dn + """
1475 changetype: modify
1476 replace: lockoutDuration
1477 lockoutDuration: """ + str(lockoutDuration) + """
1478 replace: lockoutObservationWindow
1479 lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
1480 replace: lockoutThreshold
1481 lockoutThreshold: """ + str(lockoutThreshold) + """
1482 """)
1484 sys.exit(rc)