REVIEW/RUN selftest: Add test for password lockout
[metze/samba/wip.git] / source4 / dsdb / tests / python / password_lockout.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # This tests the password changes over LDAP for AD implementations
4 #
5 # Copyright Matthias Dieter Wallnoefer 2010
6 # Copyright Andrew Bartlett 2013
7 #
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
10 # MS-ADTS 3.1.1.3.1.5
11
12 import optparse
13 import sys
14 import base64
15 import time
16 import os
17
18 sys.path.insert(0, "bin/python")
19 import samba
20 samba.ensure_external_module("testtools", "testtools")
21 samba.ensure_external_module("subunit", "subunit/python")
22
23 import samba.getopt as options
24
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
37 import samba.tests
38 from samba.tests import delete_force
39 from subunit.run import SubunitTestRunner
40 import unittest
41 from samba.dcerpc import security, samr
42 from samba.ndr import ndr_unpack
43
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()
52
53 if len(args) < 1:
54     parser.print_usage()
55     sys.exit(1)
56
57 host = args[0]
58
59 lp = sambaopts.get_loadparm()
60 creds = credopts.get_credentials(lp)
61
62 # Force an encrypted connection
63 creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
64
65 #
66 # Tests start here
67 #
68
69 class PasswordTests(samba.tests.TestCase):
70
71     def setUp(self):
72         super(PasswordTests, self).setUp()
73         self.ldb = ldb
74         self.base_dn = ldb.domain_dn()
75
76         # (Re)adds the test user "testuser" with no password atm
77         delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
78         self.ldb.add({
79              "dn": "cn=testuser,cn=users," + self.base_dn,
80              "objectclass": "user",
81              "sAMAccountName": "testuser"})
82
83         # Tests a password change when we don't have any password yet with a
84         # wrong old password
85         try:
86             self.ldb.modify_ldif("""
87 dn: cn=testuser,cn=users,""" + self.base_dn + """
88 changetype: modify
89 delete: userPassword
90 userPassword: noPassword
91 add: userPassword
92 userPassword: thatsAcomplPASS2
93 """)
94             self.fail()
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)
100
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 + """
107 changetype: modify
108 delete: userPassword
109 add: userPassword
110 userPassword: thatsAcomplPASS1
111 """)
112
113         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
114                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
115                                                   "lockoutTime"])
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)
122
123         # Enables the user account
124         self.ldb.enable_account("(sAMAccountName=testuser)")
125
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)
137
138         self.ldb2 = SamDB(url=host_url, credentials=creds2, lp=lp)
139
140         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
141                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
142                                                   "lockoutTime"])
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)
149
150      # (Re)adds the test user "testuser3" with no password atm
151         delete_force(self.ldb, "cn=testuser3,cn=users," + self.base_dn)
152         self.ldb.add({
153              "dn": "cn=testuser3,cn=users," + self.base_dn,
154              "objectclass": "user",
155              "sAMAccountName": "testuser3"})
156
157         # Tests a password change when we don't have any password yet with a
158         # wrong old password
159         try:
160             self.ldb.modify_ldif("""
161 dn: cn=testuser3,cn=users,""" + self.base_dn + """
162 changetype: modify
163 delete: userPassword
164 userPassword: noPassword
165 add: userPassword
166 userPassword: thatsAcomplPASS2
167 """)
168             self.fail()
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)
174
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 + """
181 changetype: modify
182 delete: userPassword
183 add: userPassword
184 userPassword: thatsAcomplPASS1
185 """)
186
187         # Enables the user account
188         self.ldb.enable_account("(sAMAccountName=testuser3)")
189
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)
202
203
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()))
207
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)
214
215         self.samr_user = self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, self.rid)
216
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
220         # properly
221
222         # Change password on a connection as another user
223
224         # Wrong old password
225         try:
226             self.ldb3.modify_ldif("""
227 dn: cn=testuser,cn=users,""" + self.base_dn + """
228 changetype: modify
229 delete: userPassword
230 userPassword: thatsAcomplPASS1x
231 add: userPassword
232 userPassword: thatsAcomplPASS2
233 """)
234             self.fail()
235         except LdbError, (num, msg):
236             self.assertTrue('00000056' in msg)
237             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
238
239         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
240                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
241                                                   "lockoutTime"])
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)
248
249
250         # Correct old password
251         self.ldb3.modify_ldif("""
252 dn: cn=testuser,cn=users,""" + self.base_dn + """
253 changetype: modify
254 delete: userPassword
255 userPassword: thatsAcomplPASS1
256 add: userPassword
257 userPassword: thatsAcomplPASS2
258 """)
259
260         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
261                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
262                                                   "lockoutTime"])
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)
269
270         # Wrong old password
271         try:
272             self.ldb3.modify_ldif("""
273 dn: cn=testuser,cn=users,""" + self.base_dn + """
274 changetype: modify
275 delete: userPassword
276 userPassword: thatsAcomplPASS1x
277 add: userPassword
278 userPassword: thatsAcomplPASS2
279 """)
280             self.fail()
281         except LdbError, (num, msg):
282             self.assertTrue('00000056' in msg)
283             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
284
285         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
286                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
287                                                   "lockoutTime"])
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)
294
295         print "two failed password change"
296
297         # Wrong old password
298         try:
299             self.ldb3.modify_ldif("""
300 dn: cn=testuser,cn=users,""" + self.base_dn + """
301 changetype: modify
302 delete: userPassword
303 userPassword: thatsAcomplPASS1x
304 add: userPassword
305 userPassword: thatsAcomplPASS2
306 """)
307             self.fail()
308         except LdbError, (num, msg):
309             self.assertTrue('00000056' in msg)
310             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
311
312         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
313                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
314                                                   "lockoutTime"])
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)
322
323         # Wrong old password
324         try:
325             self.ldb3.modify_ldif("""
326 dn: cn=testuser,cn=users,""" + self.base_dn + """
327 changetype: modify
328 delete: userPassword
329 userPassword: thatsAcomplPASS1x
330 add: userPassword
331 userPassword: thatsAcomplPASS2
332 """)
333             self.fail()
334         except LdbError, (num, msg):
335             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
336             self.assertTrue('00000775' in msg)
337
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)
345
346         # Wrong old password
347         try:
348             self.ldb3.modify_ldif("""
349 dn: cn=testuser,cn=users,""" + self.base_dn + """
350 changetype: modify
351 delete: userPassword
352 userPassword: thatsAcomplPASS1x
353 add: userPassword
354 userPassword: thatsAcomplPASS2
355 """)
356             self.fail()
357         except LdbError, (num, msg):
358             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
359             self.assertTrue('00000775' in msg)
360
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)
368
369         try:
370             # Correct old password
371             self.ldb3.modify_ldif("""
372 dn: cn=testuser,cn=users,""" + self.base_dn + """
373 changetype: modify
374 delete: userPassword
375 userPassword: thatsAcomplPASS2
376 add: userPassword
377 userPassword: thatsAcomplPASS2x
378 """)
379             self.fail()
380         except LdbError, (num, msg):
381             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
382             self.assertTrue('0000775' in msg)
383
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)
391
392
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 + """
396 changetype: modify
397 replace: userPassword
398 userPassword: thatsAcomplPASS2
399 """)
400
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)
410
411         try:
412             # Correct old password
413             self.ldb3.modify_ldif("""
414 dn: cn=testuser,cn=users,""" + self.base_dn + """
415 changetype: modify
416 delete: userPassword
417 userPassword: thatsAcomplPASS2
418 add: userPassword
419 userPassword: thatsAcomplPASS2x
420 """)
421             self.fail()
422         except LdbError, (num, msg):
423             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
424             self.assertTrue('0000775' in msg)
425
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)
435
436         lockoutTime = res[0]["lockoutTime"][0]
437
438         m = Message()
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")
443
444         self.ldb.modify(m)
445
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)
459
460         # This shows that setting the UF_LOCKOUT flag makes no difference
461         try:
462             # Correct old password
463             self.ldb3.modify_ldif("""
464 dn: cn=testuser,cn=users,""" + self.base_dn + """
465 changetype: modify
466 delete: unicodePwd
467 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
468 add: unicodePwd
469 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
470 """)
471             self.fail()
472         except LdbError, (num, msg):
473             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
474             self.assertTrue('0000775' in msg)
475
476
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
480         # properly
481
482         # Change password on a connection as another user
483
484         # Wrong old password
485         try:
486             self.ldb3.modify_ldif("""
487 dn: cn=testuser,cn=users,""" + self.base_dn + """
488 changetype: modify
489 delete: unicodePwd
490 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
491 add: unicodePwd
492 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
493 """)
494             self.fail()
495         except LdbError, (num, msg):
496             self.assertTrue('00000056' in msg)
497             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
498
499         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
500                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
501                                                   "lockoutTime"])
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)
508
509         # Correct old password
510         self.ldb3.modify_ldif("""
511 dn: cn=testuser,cn=users,""" + self.base_dn + """
512 changetype: modify
513 delete: unicodePwd
514 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
515 add: unicodePwd
516 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
517 """)
518
519         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
520                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
521                                                   "lockoutTime"])
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)
528
529         # Wrong old password
530         try:
531             self.ldb3.modify_ldif("""
532 dn: cn=testuser,cn=users,""" + self.base_dn + """
533 changetype: modify
534 delete: unicodePwd
535 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
536 add: unicodePwd
537 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
538 """)
539             self.fail()
540         except LdbError, (num, msg):
541             self.assertTrue('00000056' in msg)
542             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
543
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")
551
552         print "two failed password change"
553
554         # Wrong old password
555         try:
556             self.ldb3.modify_ldif("""
557 dn: cn=testuser,cn=users,""" + self.base_dn + """
558 changetype: modify
559 delete: unicodePwd
560 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
561 add: unicodePwd
562 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
563 """)
564             self.fail()
565         except LdbError, (num, msg):
566             self.assertTrue('00000056' in msg)
567             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
568
569         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
570                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
571                                                   "lockoutTime"])
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)
579
580         # Wrong old password
581         try:
582             self.ldb3.modify_ldif("""
583 dn: cn=testuser,cn=users,""" + self.base_dn + """
584 changetype: modify
585 delete: unicodePwd
586 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
587 add: unicodePwd
588 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
589 """)
590             self.fail()
591         except LdbError, (num, msg):
592             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
593             self.assertTrue('00000775' in msg)
594
595         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
596                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
597                                                   "lockoutTime"])
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)
605
606
607         # Wrong old password
608         try:
609             self.ldb3.modify_ldif("""
610 dn: cn=testuser,cn=users,""" + self.base_dn + """
611 changetype: modify
612 delete: unicodePwd
613 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
614 add: unicodePwd
615 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
616 """)
617             self.fail()
618         except LdbError, (num, msg):
619             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
620             self.assertTrue('00000775' in msg)
621
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)
629
630         try:
631             # Correct old password
632             self.ldb3.modify_ldif("""
633 dn: cn=testuser,cn=users,""" + self.base_dn + """
634 changetype: modify
635 delete: unicodePwd
636 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
637 add: unicodePwd
638 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
639 """)
640             self.fail()
641         except LdbError, (num, msg):
642             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
643             self.assertTrue('0000775' in msg)
644
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)
654
655         samr_user = self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, self.rid)
656
657         acb_info = self.samr.QueryUserInfo(samr_user, 16)
658         self.assertEquals(acb_info.acct_flags, samr.ACB_NORMAL| samr.ACB_AUTOLOCK)
659
660         acb_info.acct_flags = samr.ACB_NORMAL
661
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)
664
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)
674
675         # Correct old password
676         self.ldb3.modify_ldif("""
677 dn: cn=testuser,cn=users,""" + self.base_dn + """
678 changetype: modify
679 delete: unicodePwd
680 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
681 add: unicodePwd
682 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
683 """)
684
685     def _test_login_lockout(self, use_kerberos):
686         print "Performs a lockout attempt against LDAP using NTLM"
687
688         # Change password on a connection as another user
689
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)
701
702         # The wrong password
703         creds_lockout.set_password("thatsAcomplPASS1x")
704
705         try:
706             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
707
708         except LdbError, (num, msg):
709             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
710
711         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
712                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
713                                                   "lockoutTime"])
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)
720
721         # Correct old password
722         creds_lockout.set_password("thatsAcomplPASS1")
723
724         ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
725
726         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
727                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
728                                                   "lockoutTime"])
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)
735
736         # The wrong password
737         creds_lockout.set_password("thatsAcomplPASS1x")
738
739         try:
740             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
741
742         except LdbError, (num, msg):
743             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
744
745         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
746                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
747                                                   "lockoutTime"])
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)
754
755         # The wrong password
756         creds_lockout.set_password("thatsAcomplPASS1x")
757
758         try:
759             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
760             self.fail()
761
762         except LdbError, (num, msg):
763             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
764
765         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
766                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
767                                                   "lockoutTime"])
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)
774
775         print "two failed password change"
776
777         # The wrong password
778         creds_lockout.set_password("thatsAcomplPASS1x")
779
780         try:
781             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
782             self.fail()
783
784         except LdbError, (num, msg):
785             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
786
787         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
788                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
789                                                   "lockoutTime"])
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)
797
798         # The wrong password
799         creds_lockout.set_password("thatsAcomplPASS1x")
800         try:
801             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
802             self.fail()
803         except LdbError, (num, msg):
804             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
805
806         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
807                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
808                                                   "lockoutTime"])
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)
816
817         # The wrong password
818         creds_lockout.set_password("thatsAcomplPASS1x")
819         try:
820             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
821             self.fail()
822         except LdbError, (num, msg):
823             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
824
825         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
826                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
827                                                   "lockoutTime"])
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)
835
836         # The correct password
837         creds_lockout.set_password("thatsAcomplPASS1")
838         try:
839             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
840             self.fail()
841         except LdbError, (num, msg):
842             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
843
844         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
845                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
846                                                   "lockoutTime"])
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)
853
854         samr_user = self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, self.rid)
855
856         acb_info = self.samr.QueryUserInfo(samr_user, 16)
857         self.assertEquals(acb_info.acct_flags, samr.ACB_NORMAL| samr.ACB_AUTOLOCK)
858
859         time.sleep(account_lockout_duration + 1)
860
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)
864
865         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
866                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
867                                                   "lockoutTime"])
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)
874
875         samr_user = self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, self.rid)
876
877         acb_info = self.samr.QueryUserInfo(samr_user, 16)
878         self.assertEquals(acb_info.acct_flags, samr.ACB_NORMAL)
879
880         # The wrong password
881         creds_lockout.set_password("thatsAcomplPASS1x")
882         try:
883             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
884             self.fail()
885         except LdbError, (num, msg):
886             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
887
888         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
889                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
890                                                   "lockoutTime"])
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)
897
898         # The wrong password
899         creds_lockout.set_password("thatsAcomplPASS1x")
900         try:
901             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
902             self.fail()
903         except LdbError, (num, msg):
904             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
905
906         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
907                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
908                                                   "lockoutTime"])
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)
915
916         time.sleep(lockout_observation_window + 1)
917
918         # The wrong password
919         creds_lockout.set_password("thatsAcomplPASS1x")
920         try:
921             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
922             self.fail()
923         except LdbError, (num, msg):
924             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
925
926         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
927                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
928                                                   "lockoutTime"])
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)
935
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)
939
940         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
941                          scope=SCOPE_BASE, attrs=["badPwdCount", "msDS-User-Account-Control-Computed",
942                                                   "lockoutTime"])
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)
949
950         samr_user = self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, self.rid)
951
952         acb_info = self.samr.QueryUserInfo(samr_user, 16)
953         self.assertEquals(acb_info.acct_flags, samr.ACB_NORMAL)
954
955
956     def test_login_lockout_ntlm(self):
957         self._test_login_lockout(DONT_USE_KERBEROS)
958
959
960     def test_login_lockout_kerberos(self):
961         self._test_login_lockout(MUST_USE_KERBEROS)
962
963
964     def tearDown(self):
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)
970         self.ldb2 = None
971
972 host_url = "ldap://%s" % host
973
974 ldb = SamDB(url=host_url, session_info=system_session(lp), credentials=creds, lp=lp)
975
976 # Gets back the basedn
977 base_dn = ldb.domain_dn()
978
979 # Gets back the configuration basedn
980 configuration_dn = ldb.get_config_basedn().get_linearized()
981
982 # Get the old "dSHeuristics" if it was set
983 dsheuristics = ldb.get_dsheuristics()
984
985 res = ldb.search(base_dn,
986                  scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
987
988 if "lockoutDuration" in res[0]:
989     lockoutDuration = res[0]["lockoutDuration"][0]
990 else:
991     lockoutDuration = 0
992
993 if "lockoutObservationWindow" in res[0]:
994     lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
995 else:
996     lockoutObservationWindow = 0
997
998 if "lockoutThreshold" in res[0]:
999     lockoutThreshold = res[0]["lockoutThreshold"][0]
1000 else:
1001     lockoutTreshold = 0
1002
1003 m = Message()
1004 m.dn = Dn(ldb, base_dn)
1005
1006 account_lockout_duration = 10
1007 account_lockout_duration_ticks = -int(account_lockout_duration * (1e7))
1008
1009 m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
1010                                       FLAG_MOD_REPLACE, "lockoutDuration")
1011
1012 account_lockout_threshold = 3
1013 m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
1014                                        FLAG_MOD_REPLACE, "lockoutThreshold")
1015
1016 lockout_observation_window = 5
1017 lockout_observation_window_ticks = -int(lockout_observation_window * (1e7))
1018
1019 m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
1020                                                FLAG_MOD_REPLACE, "lockOutObservationWindow")
1021
1022 ldb.modify(m)
1023
1024 # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
1025 ldb.set_dsheuristics("000000001")
1026
1027 # Get the old "minPwdAge"
1028 minPwdAge = ldb.get_minPwdAge()
1029
1030 # Set it temporarely to "0"
1031 ldb.set_minPwdAge("0")
1032
1033 runner = SubunitTestRunner()
1034 rc = 0
1035 if not runner.run(unittest.makeSuite(PasswordTests)).wasSuccessful():
1036     rc = 1
1037
1038 # Reset the "dSHeuristics" as they were before
1039 ldb.set_dsheuristics(dsheuristics)
1040
1041 # Reset the "minPwdAge" as it was before
1042 ldb.set_minPwdAge(minPwdAge)
1043
1044 ldb.modify_ldif("""
1045 dn: """ + base_dn + """
1046 changetype: modify
1047 replace: lockoutDuration
1048 lockoutDuration: """ + str(lockoutDuration) + """
1049 replace: lockoutObservationWindow
1050 lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
1051 replace: lockoutThreshold
1052 lockoutThreshold: """ + str(lockoutThreshold) + """
1053 """)
1054
1055 sys.exit(rc)