selftest: Add test for password lockout
[samba.git] / source4 / dsdb / tests / python / password_lockout.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # This tests the password lockout behavior for AD implementations
4 #
5 # Copyright Matthias Dieter Wallnoefer 2010
6 # Copyright Andrew Bartlett 2013
7 # Copyright Stefan Metzmacher 2014
8 #
9
10 import optparse
11 import sys
12 import base64
13 import time
14 import os
15
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")
20
21 import samba.getopt as options
22
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
41
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()
50
51 if len(args) < 1:
52     parser.print_usage()
53     sys.exit(1)
54
55 host = args[0]
56
57 lp = sambaopts.get_loadparm()
58 creds = credopts.get_credentials(lp)
59
60 # Force an encrypted connection
61 creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
62
63 #
64 # Tests start here
65 #
66
67 class PasswordTests(samba.tests.TestCase):
68
69     def _open_samr_user(self, res):
70         self.assertTrue("objectSid" in res[0])
71
72         (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split()
73         self.assertEquals(self.domain_sid, domain_sid)
74
75         return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid)
76
77     def _reset_samr(self, res):
78
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)
85
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 """)
93
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])
97
98         uac = int(res[0]["userAccountControl"][0])
99         uacc = int(res[0]["msDS-User-Account-Control-Computed"][0])
100
101         uac |= uacc
102         uac = uac & ~dsdb.UF_LOCKOUT
103
104         self.ldb.modify_ldif("""
105 dn: """ + str(res[0].dn) + """
106 changetype: modify
107 replace: userAccountControl
108 userAccountControl: %d
109 """ % uac)
110
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)
120
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
127
128         if isinstance(value, tuple):
129             (mode, value) = value
130         else:
131             mode = "equal"
132
133         if mode == "ignore":
134             return
135
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))
142
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)
163
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):
171
172         attrs = [
173            "objectSid",
174            "badPwdCount",
175            "badPasswordTime",
176            "lockoutTime",
177            "userAccountControl",
178            "msDS-User-Account-Control-Computed"
179         ]
180
181         # in order to prevent some time resolution problems we sleep for
182         # 10 micro second
183         time.sleep(0.01)
184
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)
193
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)
200
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
212
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
218
219         self.assertEquals(uinfo3.acct_flags, expected_acb_info)
220         self.assertEquals(uinfo3.bad_password_count, expected_bad_password_count)
221
222         self.assertEquals(uinfo5.acct_flags, expected_acb_info)
223         self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count)
224
225         self.assertEquals(uinfo16.acct_flags, expected_acb_info)
226
227         self.assertEquals(uinfo21.acct_flags, expected_acb_info)
228         self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count)
229
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])
234
235         # in order to prevent some time resolution problems we sleep for
236         # 10 micro second
237         time.sleep(0.01)
238         return res
239
240     def setUp(self):
241         super(PasswordTests, self).setUp()
242         self.ldb = ldb
243         self.base_dn = ldb.domain_dn()
244
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)
249
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"})
256
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)
266
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)
270
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)
280
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)
298
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])
309
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 """)
321
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)
330
331         # Enables the user account
332         self.ldb.enable_account("(sAMAccountName=testuser)")
333
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)
340
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)
352
353         self.ldb2 = SamDB(url=host_url, credentials=creds2, lp=lp)
354
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)
361
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"})
368
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)
378
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)
396
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])
407
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 """)
419
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)
428
429         # Enables the user account
430         self.ldb.enable_account("(sAMAccountName=testuser3)")
431
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)
438
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)
451
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)
458
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
463
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])
471
472         # Change password on a connection as another user
473
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)
488
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])
496
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 """)
506
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)
513
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)
528
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])
536
537         print "two failed password change"
538
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)
553
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])
563
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)
578
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)
586
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)
601
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)
609
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)
624
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)
632
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 """)
640
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)
648
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)
663
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)
671
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")
677
678         self.ldb.modify(m)
679
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)
688
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)
704
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)
712
713         self._reset_by_method(res, method)
714
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)
722
723         # The correct password after doing the unlock
724
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 """)
733
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)
741
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)
756
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])
765
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)
780
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])
789
790         self._reset_ldap_lockoutTime(res)
791
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)
799
800     def test_userPassword_lockout_with_clear_change_ldap_userAccountControl(self):
801         self._test_userPassword_lockout_with_clear_change("ldap_userAccountControl")
802
803     def test_userPassword_lockout_with_clear_change_ldap_lockoutTime(self):
804         self._test_userPassword_lockout_with_clear_change("ldap_lockoutTime")
805
806     def test_userPassword_lockout_with_clear_change_samr(self):
807         self._test_userPassword_lockout_with_clear_change("samr")
808
809
810     def test_unicodePwd_lockout_with_clear_change(self):
811         print "Performs a password cleartext change operation on 'unicodePwd'"
812
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])
820
821         # Change password on a connection as another user
822
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)
837
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])
845
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 """)
855
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)
862
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)
877
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])
885
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)
890
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)
897
898         print "two failed password change"
899
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)
914
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])
925
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)
940
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)
948
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)
963
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)
971
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)
986
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)
994
995         # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
996         self._reset_samr(res);
997
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)
1005
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 """)
1015
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)
1023
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)
1038
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])
1047
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)
1062
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])
1071
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)
1075
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)
1083
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)
1098
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])
1108
1109         time.sleep(account_lockout_duration + 1)
1110
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)
1118
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)
1123
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)
1131
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"
1135
1136         # Change password on a connection as another user
1137
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])
1145
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)
1157
1158         # The wrong password
1159         creds_lockout.set_password("thatsAcomplPASS1x")
1160
1161         try:
1162             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1163
1164         except LdbError, (num, msg):
1165             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1166
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])
1174
1175         # Correct old password
1176         creds_lockout.set_password("thatsAcomplPASS1")
1177
1178         ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1179
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)
1186
1187         # The wrong password
1188         creds_lockout.set_password("thatsAcomplPASS1x")
1189
1190         try:
1191             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1192
1193         except LdbError, (num, msg):
1194             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1195
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])
1203
1204         # The wrong password
1205         creds_lockout.set_password("thatsAcomplPASS1x")
1206
1207         try:
1208             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1209             self.fail()
1210
1211         except LdbError, (num, msg):
1212             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1213
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])
1221
1222         print "two failed password change"
1223
1224         # The wrong password
1225         creds_lockout.set_password("thatsAcomplPASS1x")
1226
1227         try:
1228             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1229             self.fail()
1230
1231         except LdbError, (num, msg):
1232             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1233
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])
1243
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)
1251
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)
1259
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)
1267
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)
1275
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)
1283
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)
1291
1292         time.sleep(account_lockout_duration + 1)
1293
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)
1301
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)
1305
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)
1313
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)
1321
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])
1330
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)
1338
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])
1347
1348         time.sleep(lockout_observation_window + 1)
1349
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)
1357
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)
1365
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])
1374
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)
1378
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)
1386
1387     def test_login_lockout_ntlm(self):
1388         self._test_login_lockout(DONT_USE_KERBEROS)
1389
1390     def test_login_lockout_kerberos(self):
1391         self._test_login_lockout(MUST_USE_KERBEROS)
1392
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
1400
1401 host_url = "ldap://%s" % host
1402
1403 ldb = SamDB(url=host_url, session_info=system_session(lp), credentials=creds, lp=lp)
1404
1405 # Gets back the basedn
1406 base_dn = ldb.domain_dn()
1407
1408 # Gets back the configuration basedn
1409 configuration_dn = ldb.get_config_basedn().get_linearized()
1410
1411 # Get the old "dSHeuristics" if it was set
1412 dsheuristics = ldb.get_dsheuristics()
1413
1414 res = ldb.search(base_dn,
1415                  scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
1416
1417 if "lockoutDuration" in res[0]:
1418     lockoutDuration = res[0]["lockoutDuration"][0]
1419 else:
1420     lockoutDuration = 0
1421
1422 if "lockoutObservationWindow" in res[0]:
1423     lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
1424 else:
1425     lockoutObservationWindow = 0
1426
1427 if "lockoutThreshold" in res[0]:
1428     lockoutThreshold = res[0]["lockoutThreshold"][0]
1429 else:
1430     lockoutTreshold = 0
1431
1432 m = Message()
1433 m.dn = Dn(ldb, base_dn)
1434
1435 account_lockout_duration = 10
1436 account_lockout_duration_ticks = -int(account_lockout_duration * (1e7))
1437
1438 m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
1439                                       FLAG_MOD_REPLACE, "lockoutDuration")
1440
1441 account_lockout_threshold = 3
1442 m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
1443                                        FLAG_MOD_REPLACE, "lockoutThreshold")
1444
1445 lockout_observation_window = 5
1446 lockout_observation_window_ticks = -int(lockout_observation_window * (1e7))
1447
1448 m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
1449                                                FLAG_MOD_REPLACE, "lockOutObservationWindow")
1450
1451 ldb.modify(m)
1452
1453 # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
1454 ldb.set_dsheuristics("000000001")
1455
1456 # Get the old "minPwdAge"
1457 minPwdAge = ldb.get_minPwdAge()
1458
1459 # Set it temporarely to "0"
1460 ldb.set_minPwdAge("0")
1461
1462 runner = SubunitTestRunner()
1463 rc = 0
1464 if not runner.run(unittest.makeSuite(PasswordTests)).wasSuccessful():
1465     rc = 1
1466
1467 # Reset the "dSHeuristics" as they were before
1468 ldb.set_dsheuristics(dsheuristics)
1469
1470 # Reset the "minPwdAge" as it was before
1471 ldb.set_minPwdAge(minPwdAge)
1472
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 """)
1483
1484 sys.exit(rc)