2 # -*- coding: utf-8 -*-
3 # This tests the password changes over LDAP for AD implementations
5 # Copyright Matthias Dieter Wallnoefer 2010
6 # Copyright Andrew Bartlett 2013
8 # Notice: This tests will also work against Windows Server if the connection is
9 # secured enough (SASL with a minimum of 128 Bit encryption) - consider
18 sys.path.insert(0, "bin/python")
20 samba.ensure_external_module("testtools", "testtools")
21 samba.ensure_external_module("subunit", "subunit/python")
23 import samba.getopt as options
25 from samba.auth import system_session
26 from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS
27 from ldb import SCOPE_BASE, LdbError
28 from ldb import ERR_ATTRIBUTE_OR_VALUE_EXISTS
29 from ldb import ERR_UNWILLING_TO_PERFORM, ERR_INSUFFICIENT_ACCESS_RIGHTS
30 from ldb import ERR_NO_SUCH_ATTRIBUTE
31 from ldb import ERR_CONSTRAINT_VIOLATION
32 from ldb import ERR_INVALID_CREDENTIALS
33 from ldb import Message, MessageElement, Dn
34 from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE
35 from samba import gensec, dsdb
36 from samba.samdb import SamDB
38 from samba.tests import delete_force
39 from subunit.run import SubunitTestRunner
41 from samba.dcerpc import security, samr
42 from samba.ndr import ndr_unpack
44 parser = optparse.OptionParser("passwords.py [options] <host>")
45 sambaopts = options.SambaOptions(parser)
46 parser.add_option_group(sambaopts)
47 parser.add_option_group(options.VersionOptions(parser))
48 # use command line creds if available
49 credopts = options.CredentialsOptions(parser)
50 parser.add_option_group(credopts)
51 opts, args = parser.parse_args()
59 lp = sambaopts.get_loadparm()
60 creds = credopts.get_credentials(lp)
62 # Force an encrypted connection
63 creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
69 class PasswordTests(samba.tests.TestCase):
72 super(PasswordTests, self).setUp()
74 self.base_dn = ldb.domain_dn()
76 # (Re)adds the test user "testuser" with no password atm
77 delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
79 "dn": "cn=testuser,cn=users," + self.base_dn,
80 "objectclass": "user",
81 "sAMAccountName": "testuser"})
83 # Tests a password change when we don't have any password yet with a
86 self.ldb.modify_ldif("""
87 dn: cn=testuser,cn=users,""" + self.base_dn + """
90 userPassword: noPassword
92 userPassword: thatsAcomplPASS2
95 except LdbError, (num, msg):
96 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
97 # Windows (2008 at least) seems to have some small bug here: it
98 # returns "0000056A" on longer (always wrong) previous passwords.
99 self.assertTrue('00000056' in msg)
101 # Sets the initial user password with a "special" password change
102 # I think that this internally is a password set operation and it can
103 # only be performed by someone which has password set privileges on the
104 # account (at least in s4 we do handle it like that).
105 self.ldb.modify_ldif("""
106 dn: cn=testuser,cn=users,""" + self.base_dn + """
110 userPassword: thatsAcomplPASS1
113 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
114 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
116 self.assertTrue(len(res) == 1)
117 self.assertTrue("badPwdCount" in res[0])
118 self.assertEquals(res[0]["badPwdCount"][0], "1")
119 self.assertTrue("lockoutTime" not in res[0])
120 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
121 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
123 # Enables the user account
124 self.ldb.enable_account("(sAMAccountName=testuser)")
126 # Open a second LDB connection with the user credentials. Use the
127 # command line credentials for informations like the domain, the realm
128 # and the workstation.
129 creds2 = Credentials()
130 creds2.set_username("testuser")
131 creds2.set_password("thatsAcomplPASS1")
132 creds2.set_domain(creds.get_domain())
133 creds2.set_realm(creds.get_realm())
134 creds2.set_workstation(creds.get_workstation())
135 creds2.set_gensec_features(creds2.get_gensec_features()
136 | gensec.FEATURE_SEAL)
138 self.ldb2 = SamDB(url=host_url, credentials=creds2, lp=lp)
140 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
141 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
143 self.assertTrue(len(res) == 1)
144 self.assertTrue("badPwdCount" in res[0])
145 self.assertEquals(res[0]["badPwdCount"][0], "0")
146 self.assertTrue("lockoutTime" not in res[0])
147 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
148 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
150 # (Re)adds the test user "testuser3" with no password atm
151 delete_force(self.ldb, "cn=testuser3,cn=users," + self.base_dn)
153 "dn": "cn=testuser3,cn=users," + self.base_dn,
154 "objectclass": "user",
155 "sAMAccountName": "testuser3"})
157 # Tests a password change when we don't have any password yet with a
160 self.ldb.modify_ldif("""
161 dn: cn=testuser3,cn=users,""" + self.base_dn + """
164 userPassword: noPassword
166 userPassword: thatsAcomplPASS2
169 except LdbError, (num, msg):
170 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
171 # Windows (2008 at least) seems to have some small bug here: it
172 # returns "0000056A" on longer (always wrong) previous passwords.
173 self.assertTrue('00000056' in msg)
175 # Sets the initial user password with a "special" password change
176 # I think that this internally is a password set operation and it can
177 # only be performed by someone which has password set privileges on the
178 # account (at least in s4 we do handle it like that).
179 self.ldb.modify_ldif("""
180 dn: cn=testuser3,cn=users,""" + self.base_dn + """
184 userPassword: thatsAcomplPASS1
187 # Enables the user account
188 self.ldb.enable_account("(sAMAccountName=testuser3)")
190 # Open a second LDB connection with the user credentials. Use the
191 # command line credentials for informations like the domain, the realm
192 # and the workstation.
193 creds3 = Credentials()
194 creds3.set_username("testuser3")
195 creds3.set_password("thatsAcomplPASS1")
196 creds3.set_domain(creds.get_domain())
197 creds3.set_realm(creds.get_realm())
198 creds3.set_workstation(creds.get_workstation())
199 creds3.set_gensec_features(creds3.get_gensec_features()
200 | gensec.FEATURE_SEAL)
201 self.ldb3 = SamDB(url=host_url, credentials=creds3, lp=lp)
204 self.samr = samr.samr("ncacn_ip_tcp:%s[sign]" % host, lp, creds)
205 self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
206 self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, security.dom_sid(self.ldb.get_domain_sid()))
208 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
209 scope=SCOPE_BASE, attrs=["objectSid"])
210 self.assertTrue(len(res) == 1)
211 self.assertTrue("objectSid" in res[0])
212 (domain_sid, self.rid) = ndr_unpack( security.dom_sid,res[0]["objectSid"][0]).split()
213 self.assertEquals(security.dom_sid(self.ldb.get_domain_sid()), domain_sid)
215 self.samr_user = self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, self.rid)
217 def test_userPassword_lockout_with_clear_change(self):
218 print "Performs a password cleartext change operation on 'userPassword'"
219 # Notice: This works only against Windows if "dSHeuristics" has been set
222 # Change password on a connection as another user
226 self.ldb3.modify_ldif("""
227 dn: cn=testuser,cn=users,""" + self.base_dn + """
230 userPassword: thatsAcomplPASS1x
232 userPassword: thatsAcomplPASS2
235 except LdbError, (num, msg):
236 self.assertTrue('00000056' in msg)
237 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
239 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
240 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
242 self.assertTrue(len(res) == 1)
243 self.assertTrue("badPwdCount" in res[0])
244 self.assertEquals(res[0]["badPwdCount"][0], "1")
245 self.assertTrue("lockoutTime" not in res[0])
246 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
247 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
250 # Correct old password
251 self.ldb3.modify_ldif("""
252 dn: cn=testuser,cn=users,""" + self.base_dn + """
255 userPassword: thatsAcomplPASS1
257 userPassword: thatsAcomplPASS2
260 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
261 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
263 self.assertTrue(len(res) == 1)
264 self.assertTrue("badPwdCount" in res[0])
265 self.assertEquals(res[0]["badPwdCount"][0], "1")
266 self.assertTrue("lockoutTime" not in res[0])
267 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
268 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
272 self.ldb3.modify_ldif("""
273 dn: cn=testuser,cn=users,""" + self.base_dn + """
276 userPassword: thatsAcomplPASS1x
278 userPassword: thatsAcomplPASS2
281 except LdbError, (num, msg):
282 self.assertTrue('00000056' in msg)
283 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
285 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
286 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
288 self.assertTrue(len(res) == 1)
289 self.assertTrue("badPwdCount" in res[0])
290 self.assertEquals(res[0]["badPwdCount"][0], "2")
291 self.assertTrue("lockoutTime" not in res[0])
292 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
293 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
295 print "two failed password change"
299 self.ldb3.modify_ldif("""
300 dn: cn=testuser,cn=users,""" + self.base_dn + """
303 userPassword: thatsAcomplPASS1x
305 userPassword: thatsAcomplPASS2
308 except LdbError, (num, msg):
309 self.assertTrue('00000056' in msg)
310 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
312 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
313 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
315 self.assertTrue(len(res) == 1)
316 self.assertTrue("badPwdCount" in res[0])
317 self.assertTrue("lockoutTime" in res[0])
318 self.assertEquals(res[0]["badPwdCount"][0], "3")
319 self.assertNotEquals(res[0]["lockoutTime"][0], "0")
320 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
321 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), dsdb.UF_LOCKOUT)
325 self.ldb3.modify_ldif("""
326 dn: cn=testuser,cn=users,""" + self.base_dn + """
329 userPassword: thatsAcomplPASS1x
331 userPassword: thatsAcomplPASS2
334 except LdbError, (num, msg):
335 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
336 self.assertTrue('00000775' in msg)
338 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
339 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed"])
340 self.assertTrue(len(res) == 1)
341 self.assertTrue("badPwdCount" in res[0])
342 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
343 self.assertEquals(res[0]["badPwdCount"][0], "3")
344 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), dsdb.UF_LOCKOUT)
348 self.ldb3.modify_ldif("""
349 dn: cn=testuser,cn=users,""" + self.base_dn + """
352 userPassword: thatsAcomplPASS1x
354 userPassword: thatsAcomplPASS2
357 except LdbError, (num, msg):
358 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
359 self.assertTrue('00000775' in msg)
361 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
362 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed"])
363 self.assertTrue(len(res) == 1)
364 self.assertTrue("badPwdCount" in res[0])
365 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
366 self.assertEquals(res[0]["badPwdCount"][0], "3")
367 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), dsdb.UF_LOCKOUT)
370 # Correct old password
371 self.ldb3.modify_ldif("""
372 dn: cn=testuser,cn=users,""" + self.base_dn + """
375 userPassword: thatsAcomplPASS2
377 userPassword: thatsAcomplPASS2x
380 except LdbError, (num, msg):
381 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
382 self.assertTrue('0000775' in msg)
384 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
385 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed"])
386 self.assertTrue(len(res) == 1)
387 self.assertTrue("badPwdCount" in res[0])
388 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
389 self.assertEquals(res[0]["badPwdCount"][0], "3")
390 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), dsdb.UF_LOCKOUT)
393 # Now reset the password, which does NOT change the lockout!
394 self.ldb.modify_ldif("""
395 dn: cn=testuser,cn=users,""" + self.base_dn + """
397 replace: userPassword
398 userPassword: thatsAcomplPASS2
401 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
402 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed", "lockoutTime"])
403 self.assertTrue(len(res) == 1)
404 self.assertTrue("badPwdCount" in res[0])
405 self.assertTrue("lockoutTime" in res[0])
406 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
407 self.assertEquals(res[0]["badPwdCount"][0], "3")
408 self.assertNotEquals(res[0]["lockoutTime"][0], "0")
409 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), dsdb.UF_LOCKOUT)
412 # Correct old password
413 self.ldb3.modify_ldif("""
414 dn: cn=testuser,cn=users,""" + self.base_dn + """
417 userPassword: thatsAcomplPASS2
419 userPassword: thatsAcomplPASS2x
422 except LdbError, (num, msg):
423 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
424 self.assertTrue('0000775' in msg)
426 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
427 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed", "lockoutTime"])
428 self.assertTrue(len(res) == 1)
429 self.assertTrue("badPwdCount" in res[0])
430 self.assertTrue("lockoutTime" in res[0])
431 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
432 self.assertEquals(res[0]["badPwdCount"][0], "3")
433 self.assertNotEquals(res[0]["lockoutTime"][0], "0")
434 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), dsdb.UF_LOCKOUT)
436 lockoutTime = res[0]["lockoutTime"][0]
439 m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
440 m["userAccountControl"] = MessageElement(
441 str(dsdb.UF_LOCKOUT),
442 FLAG_MOD_REPLACE, "userAccountControl")
446 # This shows that setting the UF_LOCKOUT flag makes no difference
447 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
448 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
449 "lockoutTime", "userAccountControl"])
450 self.assertTrue(len(res) == 1)
451 self.assertTrue("badPwdCount" in res[0])
452 self.assertEquals(res[0]["badPwdCount"][0], "3")
453 self.assertTrue("lockoutTime"in res[0])
454 self.assertEquals(res[0]["lockoutTime"][0], str(lockoutTime))
455 self.assertTrue("userAccountControl" in res[0])
456 self.assertTrue(int(res[0]["userAccountControl"][0]) & dsdb.UF_NORMAL_ACCOUNT == dsdb.UF_NORMAL_ACCOUNT)
457 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
458 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), dsdb.UF_LOCKOUT)
460 # This shows that setting the UF_LOCKOUT flag makes no difference
462 # Correct old password
463 self.ldb3.modify_ldif("""
464 dn: cn=testuser,cn=users,""" + self.base_dn + """
467 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
469 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
472 except LdbError, (num, msg):
473 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
474 self.assertTrue('0000775' in msg)
477 def test_unicodePwd_lockout_with_clear_change(self):
478 print "Performs a password cleartext change operation on 'unicodePwd'"
479 # Notice: This works only against Windows if "dSHeuristics" has been set
482 # Change password on a connection as another user
486 self.ldb3.modify_ldif("""
487 dn: cn=testuser,cn=users,""" + self.base_dn + """
490 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
492 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
495 except LdbError, (num, msg):
496 self.assertTrue('00000056' in msg)
497 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
499 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
500 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
502 self.assertTrue(len(res) == 1)
503 self.assertTrue("badPwdCount" in res[0])
504 self.assertEquals(res[0]["badPwdCount"][0], "1")
505 self.assertTrue("lockoutTime" not in res[0])
506 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
507 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
509 # Correct old password
510 self.ldb3.modify_ldif("""
511 dn: cn=testuser,cn=users,""" + self.base_dn + """
514 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
516 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
519 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
520 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
522 self.assertTrue(len(res) == 1)
523 self.assertTrue("badPwdCount" in res[0])
524 self.assertEquals(res[0]["badPwdCount"][0], "1")
525 self.assertTrue("lockoutTime" not in res[0])
526 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
527 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
531 self.ldb3.modify_ldif("""
532 dn: cn=testuser,cn=users,""" + self.base_dn + """
535 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
537 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
540 except LdbError, (num, msg):
541 self.assertTrue('00000056' in msg)
542 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
544 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
545 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed"])
546 self.assertTrue(len(res) == 1)
547 self.assertTrue("badPwdCount" in res[0])
548 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
549 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
550 self.assertEquals(res[0]["badPwdCount"][0], "2")
552 print "two failed password change"
556 self.ldb3.modify_ldif("""
557 dn: cn=testuser,cn=users,""" + self.base_dn + """
560 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
562 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
565 except LdbError, (num, msg):
566 self.assertTrue('00000056' in msg)
567 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
569 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
570 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
572 self.assertTrue(len(res) == 1)
573 self.assertTrue("badPwdCount" in res[0])
574 self.assertEquals(res[0]["badPwdCount"][0], "3")
575 self.assertTrue("lockoutTime" in res[0])
576 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
577 self.assertNotEquals(res[0]["lockoutTime"][0], "0")
578 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), dsdb.UF_LOCKOUT)
582 self.ldb3.modify_ldif("""
583 dn: cn=testuser,cn=users,""" + self.base_dn + """
586 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
588 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
591 except LdbError, (num, msg):
592 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
593 self.assertTrue('00000775' in msg)
595 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
596 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
598 self.assertTrue(len(res) == 1)
599 self.assertTrue("badPwdCount" in res[0])
600 self.assertEquals(res[0]["badPwdCount"][0], "3")
601 self.assertTrue("lockoutTime" in res[0])
602 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
603 self.assertNotEquals(res[0]["lockoutTime"][0], "0")
604 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), dsdb.UF_LOCKOUT)
609 self.ldb3.modify_ldif("""
610 dn: cn=testuser,cn=users,""" + self.base_dn + """
613 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
615 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
618 except LdbError, (num, msg):
619 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
620 self.assertTrue('00000775' in msg)
622 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
623 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed"])
624 self.assertTrue(len(res) == 1)
625 self.assertTrue("badPwdCount" in res[0])
626 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
627 self.assertEquals(res[0]["badPwdCount"][0], "3")
628 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), dsdb.UF_LOCKOUT)
631 # Correct old password
632 self.ldb3.modify_ldif("""
633 dn: cn=testuser,cn=users,""" + self.base_dn + """
636 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
638 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
641 except LdbError, (num, msg):
642 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
643 self.assertTrue('0000775' in msg)
645 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
646 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed", "lockoutTime"])
647 self.assertTrue(len(res) == 1)
648 self.assertTrue("badPwdCount" in res[0])
649 self.assertTrue("lockoutTime" in res[0])
650 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
651 self.assertEquals(res[0]["badPwdCount"][0], "3")
652 self.assertNotEquals(res[0]["lockoutTime"][0], "0")
653 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), dsdb.UF_LOCKOUT)
655 samr_user = self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, self.rid)
657 acb_info = self.samr.QueryUserInfo(samr_user, 16)
658 self.assertEquals(acb_info.acct_flags, samr.ACB_NORMAL| samr.ACB_AUTOLOCK)
660 acb_info.acct_flags = samr.ACB_NORMAL
662 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
663 self.samr.SetUserInfo(samr_user, 16, acb_info)
665 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
666 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed", "lockoutTime"])
667 self.assertTrue(len(res) == 1)
668 self.assertTrue("badPwdCount" in res[0])
669 self.assertTrue("lockoutTime" in res[0])
670 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
671 self.assertEquals(res[0]["badPwdCount"][0], "0")
672 self.assertEquals(res[0]["lockoutTime"][0], "0")
673 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
675 # Correct old password
676 self.ldb3.modify_ldif("""
677 dn: cn=testuser,cn=users,""" + self.base_dn + """
680 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
682 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
685 def _test_login_lockout(self, use_kerberos):
686 print "Performs a lockout attempt against LDAP using NTLM"
688 # Change password on a connection as another user
690 # Open a second LDB connection with the user credentials. Use the
691 # command line credentials for informations like the domain, the realm
692 # and the workstation.
693 creds_lockout = Credentials()
694 creds_lockout.set_username("testuser")
695 creds_lockout.set_domain(creds.get_domain())
696 creds_lockout.set_realm(creds.get_realm())
697 creds_lockout.set_workstation(creds.get_workstation())
698 creds_lockout.set_gensec_features(creds_lockout.get_gensec_features()
699 | gensec.FEATURE_SEAL)
700 creds_lockout.set_kerberos_state(use_kerberos)
703 creds_lockout.set_password("thatsAcomplPASS1x")
706 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
708 except LdbError, (num, msg):
709 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
711 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
712 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
714 self.assertTrue(len(res) == 1)
715 self.assertTrue("badPwdCount" in res[0])
716 self.assertEquals(res[0]["badPwdCount"][0], "1")
717 self.assertTrue("lockoutTime" not in res[0])
718 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
719 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
721 # Correct old password
722 creds_lockout.set_password("thatsAcomplPASS1")
724 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
726 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
727 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
729 self.assertTrue(len(res) == 1)
730 self.assertTrue("badPwdCount" in res[0])
731 self.assertEquals(res[0]["badPwdCount"][0], "0")
732 self.assertTrue("lockoutTime" not in res[0])
733 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
734 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
737 creds_lockout.set_password("thatsAcomplPASS1x")
740 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
742 except LdbError, (num, msg):
743 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
745 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
746 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
748 self.assertTrue(len(res) == 1)
749 self.assertTrue("badPwdCount" in res[0])
750 self.assertEquals(res[0]["badPwdCount"][0], "1")
751 self.assertTrue("lockoutTime" not in res[0])
752 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
753 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
756 creds_lockout.set_password("thatsAcomplPASS1x")
759 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
762 except LdbError, (num, msg):
763 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
765 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
766 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
768 self.assertTrue(len(res) == 1)
769 self.assertTrue("badPwdCount" in res[0])
770 self.assertEquals(res[0]["badPwdCount"][0], "2")
771 self.assertTrue("lockoutTime" not in res[0])
772 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
773 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
775 print "two failed password change"
778 creds_lockout.set_password("thatsAcomplPASS1x")
781 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
784 except LdbError, (num, msg):
785 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
787 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
788 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
790 self.assertTrue(len(res) == 1)
791 self.assertTrue("badPwdCount" in res[0])
792 self.assertTrue("lockoutTime" in res[0])
793 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
794 self.assertEquals(res[0]["badPwdCount"][0], "3")
795 self.assertNotEquals(res[0]["lockoutTime"][0], "0")
796 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), dsdb.UF_LOCKOUT)
799 creds_lockout.set_password("thatsAcomplPASS1x")
801 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
803 except LdbError, (num, msg):
804 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
806 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
807 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
809 self.assertTrue(len(res) == 1)
810 self.assertTrue("badPwdCount" in res[0])
811 self.assertTrue("lockoutTime" in res[0])
812 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
813 self.assertEquals(res[0]["badPwdCount"][0], "3")
814 self.assertNotEquals(res[0]["lockoutTime"][0], "0")
815 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), dsdb.UF_LOCKOUT)
818 creds_lockout.set_password("thatsAcomplPASS1x")
820 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
822 except LdbError, (num, msg):
823 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
825 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
826 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
828 self.assertTrue(len(res) == 1)
829 self.assertTrue("badPwdCount" in res[0])
830 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
831 self.assertTrue("lockoutTime" in res[0])
832 self.assertEquals(res[0]["badPwdCount"][0], "3")
833 self.assertNotEquals(res[0]["lockoutTime"][0], "0")
834 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), dsdb.UF_LOCKOUT)
836 # The correct password
837 creds_lockout.set_password("thatsAcomplPASS1")
839 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
841 except LdbError, (num, msg):
842 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
844 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
845 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
847 self.assertTrue(len(res) == 1)
848 self.assertTrue("badPwdCount" in res[0])
849 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
850 self.assertEquals(res[0]["badPwdCount"][0], "3")
851 self.assertNotEquals(res[0]["lockoutTime"][0], "0")
852 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), dsdb.UF_LOCKOUT)
854 samr_user = self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, self.rid)
856 acb_info = self.samr.QueryUserInfo(samr_user, 16)
857 self.assertEquals(acb_info.acct_flags, samr.ACB_NORMAL| samr.ACB_AUTOLOCK)
859 time.sleep(account_lockout_duration + 1)
861 # The correct password after letting the timeout expire
862 creds_lockout.set_password("thatsAcomplPASS1")
863 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
865 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
866 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
868 self.assertTrue(len(res) == 1)
869 self.assertTrue("badPwdCount" in res[0])
870 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
871 self.assertEquals(res[0]["badPwdCount"][0], "0")
872 self.assertTrue("lockoutTime" not in res[0] or res[0]["lockoutTime"][0] == "0")
873 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
875 samr_user = self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, self.rid)
877 acb_info = self.samr.QueryUserInfo(samr_user, 16)
878 self.assertEquals(acb_info.acct_flags, samr.ACB_NORMAL)
881 creds_lockout.set_password("thatsAcomplPASS1x")
883 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
885 except LdbError, (num, msg):
886 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
888 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
889 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
891 self.assertTrue(len(res) == 1)
892 self.assertTrue("badPwdCount" in res[0])
893 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
894 self.assertEquals(res[0]["badPwdCount"][0], "1")
895 self.assertTrue("lockoutTime" not in res[0] or res[0]["lockoutTime"][0] == "0")
896 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
899 creds_lockout.set_password("thatsAcomplPASS1x")
901 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
903 except LdbError, (num, msg):
904 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
906 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
907 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
909 self.assertTrue(len(res) == 1)
910 self.assertTrue("badPwdCount" in res[0])
911 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
912 self.assertEquals(res[0]["badPwdCount"][0], "2")
913 self.assertTrue("lockoutTime" not in res[0] or res[0]["lockoutTime"][0] == "0")
914 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
916 time.sleep(lockout_observation_window + 1)
919 creds_lockout.set_password("thatsAcomplPASS1x")
921 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
923 except LdbError, (num, msg):
924 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
926 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
927 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
929 self.assertTrue(len(res) == 1)
930 self.assertTrue("badPwdCount" in res[0])
931 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
932 self.assertEquals(res[0]["badPwdCount"][0], "1")
933 self.assertTrue("lockoutTime" not in res[0] or res[0]["lockoutTime"][0] == "0")
934 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
936 # The correct password without letting the timeout expire
937 creds_lockout.set_password("thatsAcomplPASS1")
938 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
940 res = ldb.search("cn=testuser,cn=users," + self.base_dn,
941 scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
943 self.assertTrue(len(res) == 1)
944 self.assertTrue("badPwdCount" in res[0])
945 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
946 self.assertEquals(res[0]["badPwdCount"][0], "0")
947 self.assertTrue("lockoutTime" not in res[0] or res[0]["lockoutTime"][0] == "0")
948 self.assertEquals(int(res[0]["msDS-User-Account-Control-Computed"][0]), 0)
950 samr_user = self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, self.rid)
952 acb_info = self.samr.QueryUserInfo(samr_user, 16)
953 self.assertEquals(acb_info.acct_flags, samr.ACB_NORMAL)
956 def test_login_lockout_ntlm(self):
957 self._test_login_lockout(DONT_USE_KERBEROS)
960 def test_login_lockout_kerberos(self):
961 self._test_login_lockout(MUST_USE_KERBEROS)
965 super(PasswordTests, self).tearDown()
966 delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
967 delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
968 delete_force(self.ldb, "cn=testuser3,cn=users," + self.base_dn)
969 # Close the second LDB connection (with the user credentials)
972 host_url = "ldap://%s" % host
974 ldb = SamDB(url=host_url, session_info=system_session(lp), credentials=creds, lp=lp)
976 # Gets back the basedn
977 base_dn = ldb.domain_dn()
979 # Gets back the configuration basedn
980 configuration_dn = ldb.get_config_basedn().get_linearized()
982 # Get the old "dSHeuristics" if it was set
983 dsheuristics = ldb.get_dsheuristics()
985 res = ldb.search(base_dn,
986 scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
988 if "lockoutDuration" in res[0]:
989 lockoutDuration = res[0]["lockoutDuration"][0]
993 if "lockoutObservationWindow" in res[0]:
994 lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
996 lockoutObservationWindow = 0
998 if "lockoutThreshold" in res[0]:
999 lockoutThreshold = res[0]["lockoutThreshold"][0]
1004 m.dn = Dn(ldb, base_dn)
1006 account_lockout_duration = 10
1007 account_lockout_duration_ticks = -int(account_lockout_duration * (1e7))
1009 m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
1010 FLAG_MOD_REPLACE, "lockoutDuration")
1012 account_lockout_threshold = 3
1013 m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
1014 FLAG_MOD_REPLACE, "lockoutThreshold")
1016 lockout_observation_window = 5
1017 lockout_observation_window_ticks = -int(lockout_observation_window * (1e7))
1019 m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
1020 FLAG_MOD_REPLACE, "lockOutObservationWindow")
1024 # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
1025 ldb.set_dsheuristics("000000001")
1027 # Get the old "minPwdAge"
1028 minPwdAge = ldb.get_minPwdAge()
1030 # Set it temporarely to "0"
1031 ldb.set_minPwdAge("0")
1033 runner = SubunitTestRunner()
1035 if not runner.run(unittest.makeSuite(PasswordTests)).wasSuccessful():
1038 # Reset the "dSHeuristics" as they were before
1039 ldb.set_dsheuristics(dsheuristics)
1041 # Reset the "minPwdAge" as it was before
1042 ldb.set_minPwdAge(minPwdAge)
1045 dn: """ + base_dn + """
1047 replace: lockoutDuration
1048 lockoutDuration: """ + str(lockoutDuration) + """
1049 replace: lockoutObservationWindow
1050 lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
1051 replace: lockoutThreshold
1052 lockoutThreshold: """ + str(lockoutThreshold) + """