s4:dsdb/tests: let password_lockout.py use creds and other_ldb as function arguments
[obnox/samba/samba-obnox.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
15 sys.path.insert(0, "bin/python")
16 import samba
17
18 from samba.tests.subunitrun import TestProgram, SubunitOptions
19
20 import samba.getopt as options
21
22 from samba.auth import system_session
23 from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS
24 from ldb import SCOPE_BASE, LdbError
25 from ldb import ERR_CONSTRAINT_VIOLATION
26 from ldb import ERR_INVALID_CREDENTIALS
27 from ldb import Message, MessageElement, Dn
28 from ldb import FLAG_MOD_REPLACE
29 from samba import gensec, dsdb
30 from samba.samdb import SamDB
31 import samba.tests
32 from samba.tests import delete_force
33 from samba.dcerpc import security, samr
34 from samba.ndr import ndr_unpack
35
36 parser = optparse.OptionParser("password_lockout.py [options] <host>")
37 sambaopts = options.SambaOptions(parser)
38 parser.add_option_group(sambaopts)
39 parser.add_option_group(options.VersionOptions(parser))
40 # use command line creds if available
41 credopts = options.CredentialsOptions(parser)
42 parser.add_option_group(credopts)
43 subunitopts = SubunitOptions(parser)
44 parser.add_option_group(subunitopts)
45 opts, args = parser.parse_args()
46
47 if len(args) < 1:
48     parser.print_usage()
49     sys.exit(1)
50
51 host = args[0]
52
53 lp = sambaopts.get_loadparm()
54 global_creds = credopts.get_credentials(lp)
55
56 # Force an encrypted connection
57 global_creds.set_gensec_features(global_creds.get_gensec_features() |
58                                  gensec.FEATURE_SEAL)
59
60 def insta_creds(template=global_creds, username="testuser", userpass="thatsAcomplPASS1"):
61     # get a copy of the global creds or a the passed in creds
62     c = Credentials()
63     c.set_username(username)
64     c.set_password(userpass)
65     c.set_domain(template.get_domain())
66     c.set_realm(template.get_realm())
67     c.set_workstation(template.get_workstation())
68     c.set_gensec_features(c.get_gensec_features()
69                           | gensec.FEATURE_SEAL)
70     c.set_kerberos_state(template.get_kerberos_state())
71     return c
72
73 #
74 # Tests start here
75 #
76
77 class PasswordTests(samba.tests.TestCase):
78
79     def _open_samr_user(self, res):
80         self.assertTrue("objectSid" in res[0])
81
82         (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split()
83         self.assertEquals(self.domain_sid, domain_sid)
84
85         return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid)
86
87     def _reset_samr(self, res):
88
89         # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
90         samr_user = self._open_samr_user(res)
91         acb_info = self.samr.QueryUserInfo(samr_user, 16)
92         acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
93         self.samr.SetUserInfo(samr_user, 16, acb_info)
94         self.samr.Close(samr_user)
95
96     def _reset_ldap_lockoutTime(self, res):
97         self.ldb.modify_ldif("""
98 dn: """ + str(res[0].dn) + """
99 changetype: modify
100 replace: lockoutTime
101 lockoutTime: 0
102 """)
103
104     def _reset_ldap_userAccountControl(self, res):
105         self.assertTrue("userAccountControl" in res[0])
106         self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
107
108         uac = int(res[0]["userAccountControl"][0])
109         uacc = int(res[0]["msDS-User-Account-Control-Computed"][0])
110
111         uac |= uacc
112         uac = uac & ~dsdb.UF_LOCKOUT
113
114         self.ldb.modify_ldif("""
115 dn: """ + str(res[0].dn) + """
116 changetype: modify
117 replace: userAccountControl
118 userAccountControl: %d
119 """ % uac)
120
121     def _reset_by_method(self, res, method):
122         if method is "ldap_userAccountControl":
123             self._reset_ldap_userAccountControl(res)
124         elif method is "ldap_lockoutTime":
125             self._reset_ldap_lockoutTime(res)
126         elif method is "samr":
127             self._reset_samr(res)
128         else:
129             self.assertTrue(False, msg="Invalid reset method[%s]" % method)
130
131     def _check_attribute(self, res, name, value):
132         if value is None:
133             self.assertTrue(name not in res[0],
134                             msg="attr[%s]=%r on dn[%s]" %
135                             (name, res[0], res[0].dn))
136             return
137
138         if isinstance(value, tuple):
139             (mode, value) = value
140         else:
141             mode = "equal"
142
143         if mode == "ignore":
144             return
145
146         if mode == "absent":
147             self.assertFalse(name in res[0],
148                             msg="attr[%s] not missing on dn[%s]" %
149                             (name, res[0].dn))
150             return
151
152         self.assertTrue(name in res[0],
153                         msg="attr[%s] missing on dn[%s]" %
154                         (name, res[0].dn))
155         self.assertTrue(len(res[0][name]) == 1,
156                         msg="attr[%s]=%r on dn[%s]" %
157                         (name, res[0][name], res[0].dn))
158
159
160         print  "%s = '%s'" % (name, res[0][name][0])
161
162         if mode == "present":
163             return
164
165         if mode == "equal":
166             v = int(res[0][name][0])
167             value = int(value)
168             msg = ("attr[%s]=[%s] != [%s] on dn[%s]\n"
169                    "(diff %d; actual value is %s than expected)"  %
170                    (name, v, value, res[0].dn, v - value,
171                     ('less' if v < value else 'greater')))
172
173             self.assertTrue(v == value, msg)
174             return
175
176         if mode == "greater":
177             v = int(res[0][name][0])
178             self.assertTrue(v > int(value),
179                             msg="attr[%s]=[%s] <= [%s] on dn[%s] (diff %d)" %
180                             (name, v, int(value), res[0].dn, v - int(value)))
181             return
182         if mode == "less":
183             v = int(res[0][name][0])
184             self.assertTrue(v < int(value),
185                             msg="attr[%s]=[%s] >= [%s] on dn[%s] (diff %d)" %
186                             (name, v, int(value), res[0].dn, v - int(value)))
187             return
188         self.assertEqual(mode, not mode, "Invalid Mode[%s]" % mode)
189
190     def _check_account(self, dn,
191                        badPwdCount=None,
192                        badPasswordTime=None,
193                        lastLogon=None,
194                        lastLogonTimestamp=None,
195                        lockoutTime=None,
196                        userAccountControl=None,
197                        msDSUserAccountControlComputed=None,
198                        effective_bad_password_count=None,
199                        msg=None):
200         print '-=' * 36
201         if msg is not None:
202             print  "\033[01;32m %s \033[00m\n" % msg
203         attrs = [
204            "objectSid",
205            "badPwdCount",
206            "badPasswordTime",
207            "lastLogon",
208            "lastLogonTimestamp",
209            "lockoutTime",
210            "userAccountControl",
211            "msDS-User-Account-Control-Computed"
212         ]
213
214         # in order to prevent some time resolution problems we sleep for
215         # 10 micro second
216         time.sleep(0.01)
217
218         res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
219         self.assertTrue(len(res) == 1)
220         self._check_attribute(res, "badPwdCount", badPwdCount)
221         self._check_attribute(res, "badPasswordTime", badPasswordTime)
222         self._check_attribute(res, "lastLogon", lastLogon)
223         self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp)
224         self._check_attribute(res, "lockoutTime", lockoutTime)
225         self._check_attribute(res, "userAccountControl", userAccountControl)
226         self._check_attribute(res, "msDS-User-Account-Control-Computed",
227                               msDSUserAccountControlComputed)
228
229         lastLogon = int(res[0]["lastLogon"][0])
230
231         samr_user = self._open_samr_user(res)
232         uinfo3 = self.samr.QueryUserInfo(samr_user, 3)
233         uinfo5 = self.samr.QueryUserInfo(samr_user, 5)
234         uinfo16 = self.samr.QueryUserInfo(samr_user, 16)
235         uinfo21 = self.samr.QueryUserInfo(samr_user, 21)
236         self.samr.Close(samr_user)
237
238         expected_acb_info = 0
239         if userAccountControl & dsdb.UF_NORMAL_ACCOUNT:
240             expected_acb_info |= samr.ACB_NORMAL
241         if userAccountControl & dsdb.UF_ACCOUNTDISABLE:
242             expected_acb_info |= samr.ACB_DISABLED
243         if userAccountControl & dsdb.UF_PASSWD_NOTREQD:
244             expected_acb_info |= samr.ACB_PWNOTREQ
245         if msDSUserAccountControlComputed & dsdb.UF_LOCKOUT:
246             expected_acb_info |= samr.ACB_AUTOLOCK
247         if msDSUserAccountControlComputed & dsdb.UF_PASSWORD_EXPIRED:
248             expected_acb_info |= samr.ACB_PW_EXPIRED
249
250         expected_bad_password_count = 0
251         if badPwdCount is not None:
252             expected_bad_password_count = badPwdCount
253         if effective_bad_password_count is None:
254             effective_bad_password_count = expected_bad_password_count
255
256         self.assertEquals(uinfo3.acct_flags, expected_acb_info)
257         self.assertEquals(uinfo3.bad_password_count, expected_bad_password_count)
258         self.assertEquals(uinfo3.last_logon, lastLogon)
259
260         self.assertEquals(uinfo5.acct_flags, expected_acb_info)
261         self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count)
262         self.assertEquals(uinfo5.last_logon, lastLogon)
263
264         self.assertEquals(uinfo16.acct_flags, expected_acb_info)
265
266         self.assertEquals(uinfo21.acct_flags, expected_acb_info)
267         self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count)
268         self.assertEquals(uinfo21.last_logon, lastLogon)
269
270         # check LDAP again and make sure the samr.QueryUserInfo
271         # doesn't have any impact.
272         res2 = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
273         self.assertEquals(res[0], res2[0])
274
275         # in order to prevent some time resolution problems we sleep for
276         # 10 micro second
277         time.sleep(0.01)
278         return res
279
280     def _readd_user(self, creds):
281         username = creds.get_username()
282         userpass = creds.get_password()
283         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
284
285         # (Re)adds the test user "testuser" with no password atm
286         delete_force(self.ldb, userdn)
287         self.ldb.add({
288              "dn": userdn,
289              "objectclass": "user",
290              "sAMAccountName": username})
291
292         self.addCleanup(delete_force, self.ldb, userdn)
293
294         res = self._check_account(userdn,
295                                   badPwdCount=0,
296                                   badPasswordTime=0,
297                                   lastLogon=0,
298                                   lastLogonTimestamp=('absent', None),
299                                   userAccountControl=
300                                     dsdb.UF_NORMAL_ACCOUNT |
301                                     dsdb.UF_ACCOUNTDISABLE |
302                                     dsdb.UF_PASSWD_NOTREQD,
303                                   msDSUserAccountControlComputed=
304                                     dsdb.UF_PASSWORD_EXPIRED)
305
306         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
307         # It doesn't create "lockoutTime" = 0.
308         self._reset_samr(res)
309
310         res = self._check_account(userdn,
311                                   badPwdCount=0,
312                                   badPasswordTime=0,
313                                   lastLogon=0,
314                                   lastLogonTimestamp=('absent', None),
315                                   userAccountControl=
316                                     dsdb.UF_NORMAL_ACCOUNT |
317                                     dsdb.UF_ACCOUNTDISABLE |
318                                     dsdb.UF_PASSWD_NOTREQD,
319                                   msDSUserAccountControlComputed=
320                                     dsdb.UF_PASSWORD_EXPIRED)
321
322         # Tests a password change when we don't have any password yet with a
323         # wrong old password
324         try:
325             self.ldb.modify_ldif("""
326 dn: """ + userdn + """
327 changetype: modify
328 delete: userPassword
329 userPassword: noPassword
330 add: userPassword
331 userPassword: thatsAcomplPASS2
332 """)
333             self.fail()
334         except LdbError, (num, msg):
335             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
336             # Windows (2008 at least) seems to have some small bug here: it
337             # returns "0000056A" on longer (always wrong) previous passwords.
338             self.assertTrue('00000056' in msg, msg)
339
340         res = self._check_account(userdn,
341                                   badPwdCount=1,
342                                   badPasswordTime=("greater", 0),
343                                   lastLogon=0,
344                                   lastLogonTimestamp=('absent', None),
345                                   userAccountControl=
346                                     dsdb.UF_NORMAL_ACCOUNT |
347                                     dsdb.UF_ACCOUNTDISABLE |
348                                     dsdb.UF_PASSWD_NOTREQD,
349                                   msDSUserAccountControlComputed=
350                                     dsdb.UF_PASSWORD_EXPIRED)
351         badPasswordTime = int(res[0]["badPasswordTime"][0])
352
353         # Sets the initial user password with a "special" password change
354         # I think that this internally is a password set operation and it can
355         # only be performed by someone which has password set privileges on the
356         # account (at least in s4 we do handle it like that).
357         self.ldb.modify_ldif("""
358 dn: """ + userdn + """
359 changetype: modify
360 delete: userPassword
361 add: userPassword
362 userPassword: """ + userpass + """
363 """)
364
365         res = self._check_account(userdn,
366                                   badPwdCount=1,
367                                   badPasswordTime=badPasswordTime,
368                                   lastLogon=0,
369                                   lastLogonTimestamp=('absent', None),
370                                   userAccountControl=
371                                     dsdb.UF_NORMAL_ACCOUNT |
372                                     dsdb.UF_ACCOUNTDISABLE |
373                                     dsdb.UF_PASSWD_NOTREQD,
374                                   msDSUserAccountControlComputed=0)
375
376         # Enables the user account
377         self.ldb.enable_account("(sAMAccountName=%s)" % username)
378
379         res = self._check_account(userdn,
380                                   badPwdCount=1,
381                                   badPasswordTime=badPasswordTime,
382                                   lastLogon=0,
383                                   lastLogonTimestamp=('absent', None),
384                                   userAccountControl=
385                                     dsdb.UF_NORMAL_ACCOUNT,
386                                   msDSUserAccountControlComputed=0)
387
388         # Open a second LDB connection with the user credentials. Use the
389         # command line credentials for informations like the domain, the realm
390         # and the workstation.
391
392         ldb = SamDB(url=host_url, credentials=creds, lp=lp)
393
394         res = self._check_account(userdn,
395                                   badPwdCount=0,
396                                   badPasswordTime=badPasswordTime,
397                                   lastLogon=('greater', 0),
398                                   lastLogonTimestamp=('greater', 0),
399                                   userAccountControl=
400                                     dsdb.UF_NORMAL_ACCOUNT,
401                                   msDSUserAccountControlComputed=0)
402
403         lastLogon = int(res[0]["lastLogon"][0])
404         self.assertGreater(lastLogon, badPasswordTime)
405         return ldb
406
407     def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS):
408         try:
409             ldb = SamDB(url=url, credentials=creds, lp=lp)
410             self.fail("Login unexpectedly succeeded")
411         except LdbError, (num, msg):
412             if errno is not None:
413                 self.assertEquals(num, errno, ("Login failed in the wrong way"
414                                                "(got err %d, expected %d)" %
415                                                (num, errno)))
416
417     def setUp(self):
418         super(PasswordTests, self).setUp()
419
420         self.ldb = SamDB(url=host_url, session_info=system_session(lp),
421                          credentials=global_creds, lp=lp)
422
423         # Gets back the basedn
424         base_dn = self.ldb.domain_dn()
425
426         # Gets back the configuration basedn
427         configuration_dn = self.ldb.get_config_basedn().get_linearized()
428
429         # Get the old "dSHeuristics" if it was set
430         dsheuristics = self.ldb.get_dsheuristics()
431
432         # Reset the "dSHeuristics" as they were before
433         self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
434
435         res = self.ldb.search(base_dn,
436                          scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
437
438         if "lockoutDuration" in res[0]:
439             lockoutDuration = res[0]["lockoutDuration"][0]
440         else:
441             lockoutDuration = 0
442
443         if "lockoutObservationWindow" in res[0]:
444             lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
445         else:
446             lockoutObservationWindow = 0
447
448         if "lockoutThreshold" in res[0]:
449             lockoutThreshold = res[0]["lockoutThreshold"][0]
450         else:
451             lockoutTreshold = 0
452
453         self.addCleanup(self.ldb.modify_ldif, """
454 dn: """ + base_dn + """
455 changetype: modify
456 replace: lockoutDuration
457 lockoutDuration: """ + str(lockoutDuration) + """
458 replace: lockoutObservationWindow
459 lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
460 replace: lockoutThreshold
461 lockoutThreshold: """ + str(lockoutThreshold) + """
462 """)
463
464         m = Message()
465         m.dn = Dn(self.ldb, base_dn)
466
467         self.account_lockout_duration = 2
468         account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7))
469
470         m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
471                                               FLAG_MOD_REPLACE, "lockoutDuration")
472
473         account_lockout_threshold = 3
474         m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
475                                                FLAG_MOD_REPLACE, "lockoutThreshold")
476
477         self.lockout_observation_window = 2
478         lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7))
479
480         m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
481                                                        FLAG_MOD_REPLACE, "lockOutObservationWindow")
482
483         self.ldb.modify(m)
484
485         # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
486         self.ldb.set_dsheuristics("000000001")
487
488         # Get the old "minPwdAge"
489         minPwdAge = self.ldb.get_minPwdAge()
490
491         # Reset the "minPwdAge" as it was before
492         self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
493
494         # Set it temporarely to "0"
495         self.ldb.set_minPwdAge("0")
496
497         self.base_dn = self.ldb.domain_dn()
498
499         self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
500         self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % host, lp, global_creds)
501         self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
502         self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
503
504         self.creds2 = insta_creds()
505         self.ldb2 = self._readd_user(self.creds2)
506
507         self.creds3 = insta_creds(username="testuser3", userpass="thatsAcomplPASS1")
508         self.ldb3 = self._readd_user(self.creds3)
509
510     def _test_userPassword_lockout_with_clear_change(self, creds, other_ldb, method):
511         print "Performs a password cleartext change operation on 'userPassword'"
512         # Notice: This works only against Windows if "dSHeuristics" has been set
513         # properly
514         username = creds.get_username()
515         userpass = creds.get_password()
516         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
517
518         res = self._check_account(userdn,
519                                   badPwdCount=0,
520                                   badPasswordTime=("greater", 0),
521                                   lastLogon=('greater', 0),
522                                   lastLogonTimestamp=('greater', 0),
523                                   userAccountControl=
524                                     dsdb.UF_NORMAL_ACCOUNT,
525                                   msDSUserAccountControlComputed=0)
526         badPasswordTime = int(res[0]["badPasswordTime"][0])
527         lastLogon = int(res[0]["lastLogon"][0])
528         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
529
530         # Change password on a connection as another user
531
532         # Wrong old password
533         try:
534             other_ldb.modify_ldif("""
535 dn: """ + userdn + """
536 changetype: modify
537 delete: userPassword
538 userPassword: thatsAcomplPASS1x
539 add: userPassword
540 userPassword: thatsAcomplPASS2
541 """)
542             self.fail()
543         except LdbError, (num, msg):
544             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
545             self.assertTrue('00000056' in msg, msg)
546
547         res = self._check_account(userdn,
548                                   badPwdCount=1,
549                                   badPasswordTime=("greater", badPasswordTime),
550                                   lastLogon=lastLogon,
551                                   lastLogonTimestamp=lastLogonTimestamp,
552                                   userAccountControl=
553                                     dsdb.UF_NORMAL_ACCOUNT,
554                                   msDSUserAccountControlComputed=0)
555         badPasswordTime = int(res[0]["badPasswordTime"][0])
556
557         # Correct old password
558         other_ldb.modify_ldif("""
559 dn: """ + userdn + """
560 changetype: modify
561 delete: userPassword
562 userPassword: """ + userpass + """
563 add: userPassword
564 userPassword: thatsAcomplPASS2
565 """)
566
567         res = self._check_account(userdn,
568                                   badPwdCount=1,
569                                   badPasswordTime=badPasswordTime,
570                                   lastLogon=lastLogon,
571                                   lastLogonTimestamp=lastLogonTimestamp,
572                                   userAccountControl=
573                                     dsdb.UF_NORMAL_ACCOUNT,
574                                   msDSUserAccountControlComputed=0)
575
576         # Wrong old password
577         try:
578             other_ldb.modify_ldif("""
579 dn: """ + userdn + """
580 changetype: modify
581 delete: userPassword
582 userPassword: thatsAcomplPASS1x
583 add: userPassword
584 userPassword: thatsAcomplPASS2
585 """)
586             self.fail()
587         except LdbError, (num, msg):
588             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
589             self.assertTrue('00000056' in msg, msg)
590
591         res = self._check_account(userdn,
592                                   badPwdCount=2,
593                                   badPasswordTime=("greater", badPasswordTime),
594                                   lastLogon=lastLogon,
595                                   lastLogonTimestamp=lastLogonTimestamp,
596                                   userAccountControl=
597                                     dsdb.UF_NORMAL_ACCOUNT,
598                                   msDSUserAccountControlComputed=0)
599         badPasswordTime = int(res[0]["badPasswordTime"][0])
600
601         print "two failed password change"
602
603         # Wrong old password
604         try:
605             other_ldb.modify_ldif("""
606 dn: """ + userdn + """
607 changetype: modify
608 delete: userPassword
609 userPassword: thatsAcomplPASS1x
610 add: userPassword
611 userPassword: thatsAcomplPASS2
612 """)
613             self.fail()
614         except LdbError, (num, msg):
615             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
616             self.assertTrue('00000056' in msg, msg)
617
618         res = self._check_account(userdn,
619                                   badPwdCount=3,
620                                   badPasswordTime=("greater", badPasswordTime),
621                                   lastLogon=lastLogon,
622                                   lastLogonTimestamp=lastLogonTimestamp,
623                                   lockoutTime=("greater", badPasswordTime),
624                                   userAccountControl=
625                                     dsdb.UF_NORMAL_ACCOUNT,
626                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
627         badPasswordTime = int(res[0]["badPasswordTime"][0])
628         lockoutTime = int(res[0]["lockoutTime"][0])
629
630         # Wrong old password
631         try:
632             other_ldb.modify_ldif("""
633 dn: """ + userdn + """
634 changetype: modify
635 delete: userPassword
636 userPassword: thatsAcomplPASS1x
637 add: userPassword
638 userPassword: thatsAcomplPASS2
639 """)
640             self.fail()
641         except LdbError, (num, msg):
642             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
643             self.assertTrue('00000775' in msg, msg)
644
645         res = self._check_account(userdn,
646                                   badPwdCount=3,
647                                   badPasswordTime=badPasswordTime,
648                                   lastLogon=lastLogon,
649                                   lastLogonTimestamp=lastLogonTimestamp,
650                                   lockoutTime=lockoutTime,
651                                   userAccountControl=
652                                     dsdb.UF_NORMAL_ACCOUNT,
653                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
654
655         # Wrong old password
656         try:
657             other_ldb.modify_ldif("""
658 dn: """ + userdn + """
659 changetype: modify
660 delete: userPassword
661 userPassword: thatsAcomplPASS1x
662 add: userPassword
663 userPassword: thatsAcomplPASS2
664 """)
665             self.fail()
666         except LdbError, (num, msg):
667             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
668             self.assertTrue('00000775' in msg, msg)
669
670         res = self._check_account(userdn,
671                                   badPwdCount=3,
672                                   badPasswordTime=badPasswordTime,
673                                   lockoutTime=lockoutTime,
674                                   lastLogon=lastLogon,
675                                   lastLogonTimestamp=lastLogonTimestamp,
676                                   userAccountControl=
677                                     dsdb.UF_NORMAL_ACCOUNT,
678                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
679
680         try:
681             # Correct old password
682             other_ldb.modify_ldif("""
683 dn: """ + userdn + """
684 changetype: modify
685 delete: userPassword
686 userPassword: thatsAcomplPASS2
687 add: userPassword
688 userPassword: thatsAcomplPASS2x
689 """)
690             self.fail()
691         except LdbError, (num, msg):
692             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
693             self.assertTrue('00000775' in msg, msg)
694
695         res = self._check_account(userdn,
696                                   badPwdCount=3,
697                                   badPasswordTime=badPasswordTime,
698                                   lastLogon=lastLogon,
699                                   lastLogonTimestamp=lastLogonTimestamp,
700                                   lockoutTime=lockoutTime,
701                                   userAccountControl=
702                                     dsdb.UF_NORMAL_ACCOUNT,
703                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
704
705         # Now reset the password, which does NOT change the lockout!
706         self.ldb.modify_ldif("""
707 dn: """ + userdn + """
708 changetype: modify
709 replace: userPassword
710 userPassword: thatsAcomplPASS2
711 """)
712
713         res = self._check_account(userdn,
714                                   badPwdCount=3,
715                                   badPasswordTime=badPasswordTime,
716                                   lastLogon=lastLogon,
717                                   lastLogonTimestamp=lastLogonTimestamp,
718                                   lockoutTime=lockoutTime,
719                                   userAccountControl=
720                                     dsdb.UF_NORMAL_ACCOUNT,
721                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
722
723         try:
724             # Correct old password
725             other_ldb.modify_ldif("""
726 dn: """ + userdn + """
727 changetype: modify
728 delete: userPassword
729 userPassword: thatsAcomplPASS2
730 add: userPassword
731 userPassword: thatsAcomplPASS2x
732 """)
733             self.fail()
734         except LdbError, (num, msg):
735             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
736             self.assertTrue('00000775' in msg, msg)
737
738         res = self._check_account(userdn,
739                                   badPwdCount=3,
740                                   badPasswordTime=badPasswordTime,
741                                   lastLogon=lastLogon,
742                                   lastLogonTimestamp=lastLogonTimestamp,
743                                   lockoutTime=lockoutTime,
744                                   userAccountControl=
745                                     dsdb.UF_NORMAL_ACCOUNT,
746                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
747
748         m = Message()
749         m.dn = Dn(self.ldb, userdn)
750         m["userAccountControl"] = MessageElement(
751           str(dsdb.UF_LOCKOUT),
752           FLAG_MOD_REPLACE, "userAccountControl")
753
754         self.ldb.modify(m)
755
756         # This shows that setting the UF_LOCKOUT flag alone makes no difference
757         res = self._check_account(userdn,
758                                   badPwdCount=3,
759                                   badPasswordTime=badPasswordTime,
760                                   lastLogon=lastLogon,
761                                   lastLogonTimestamp=lastLogonTimestamp,
762                                   lockoutTime=lockoutTime,
763                                   userAccountControl=
764                                     dsdb.UF_NORMAL_ACCOUNT,
765                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
766
767         # This shows that setting the UF_LOCKOUT flag makes no difference
768         try:
769             # Correct old password
770             other_ldb.modify_ldif("""
771 dn: """ + userdn + """
772 changetype: modify
773 delete: unicodePwd
774 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
775 add: unicodePwd
776 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
777 """)
778             self.fail()
779         except LdbError, (num, msg):
780             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
781             self.assertTrue('00000775' in msg, msg)
782
783         res = self._check_account(userdn,
784                                   badPwdCount=3,
785                                   badPasswordTime=badPasswordTime,
786                                   lockoutTime=lockoutTime,
787                                   lastLogon=lastLogon,
788                                   lastLogonTimestamp=lastLogonTimestamp,
789                                   userAccountControl=
790                                     dsdb.UF_NORMAL_ACCOUNT,
791                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
792
793         self._reset_by_method(res, method)
794
795         # Here bad password counts are reset without logon success.
796         res = self._check_account(userdn,
797                                   badPwdCount=0,
798                                   badPasswordTime=badPasswordTime,
799                                   lockoutTime=0,
800                                   lastLogon=lastLogon,
801                                   lastLogonTimestamp=lastLogonTimestamp,
802                                   userAccountControl=
803                                     dsdb.UF_NORMAL_ACCOUNT,
804                                   msDSUserAccountControlComputed=0)
805
806         # The correct password after doing the unlock
807
808         other_ldb.modify_ldif("""
809 dn: """ + userdn + """
810 changetype: modify
811 delete: unicodePwd
812 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
813 add: unicodePwd
814 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
815 """)
816         userpass = "thatsAcomplPASS2x"
817         creds.set_password(userpass)
818
819         res = self._check_account(userdn,
820                                   badPwdCount=0,
821                                   badPasswordTime=badPasswordTime,
822                                   lockoutTime=0,
823                                   lastLogon=lastLogon,
824                                   lastLogonTimestamp=lastLogonTimestamp,
825                                   userAccountControl=
826                                     dsdb.UF_NORMAL_ACCOUNT,
827                                   msDSUserAccountControlComputed=0)
828
829         # Wrong old password
830         try:
831             other_ldb.modify_ldif("""
832 dn: """ + userdn + """
833 changetype: modify
834 delete: userPassword
835 userPassword: thatsAcomplPASS1xyz
836 add: userPassword
837 userPassword: thatsAcomplPASS2XYZ
838 """)
839             self.fail()
840         except LdbError, (num, msg):
841             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
842             self.assertTrue('00000056' in msg, msg)
843
844         res = self._check_account(userdn,
845                                   badPwdCount=1,
846                                   badPasswordTime=("greater", badPasswordTime),
847                                   lockoutTime=0,
848                                   lastLogon=lastLogon,
849                                   lastLogonTimestamp=lastLogonTimestamp,
850                                   userAccountControl=
851                                     dsdb.UF_NORMAL_ACCOUNT,
852                                   msDSUserAccountControlComputed=0)
853         badPasswordTime = int(res[0]["badPasswordTime"][0])
854
855         # Wrong old password
856         try:
857             other_ldb.modify_ldif("""
858 dn: """ + userdn + """
859 changetype: modify
860 delete: userPassword
861 userPassword: thatsAcomplPASS1xyz
862 add: userPassword
863 userPassword: thatsAcomplPASS2XYZ
864 """)
865             self.fail()
866         except LdbError, (num, msg):
867             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
868             self.assertTrue('00000056' in msg, msg)
869
870         res = self._check_account(userdn,
871                                   badPwdCount=2,
872                                   badPasswordTime=("greater", badPasswordTime),
873                                   lockoutTime=0,
874                                   lastLogon=lastLogon,
875                                   lastLogonTimestamp=lastLogonTimestamp,
876                                   userAccountControl=
877                                     dsdb.UF_NORMAL_ACCOUNT,
878                                   msDSUserAccountControlComputed=0)
879         badPasswordTime = int(res[0]["badPasswordTime"][0])
880
881         self._reset_ldap_lockoutTime(res)
882
883         res = self._check_account(userdn,
884                                   badPwdCount=0,
885                                   badPasswordTime=badPasswordTime,
886                                   lastLogon=lastLogon,
887                                   lastLogonTimestamp=lastLogonTimestamp,
888                                   lockoutTime=0,
889                                   userAccountControl=
890                                     dsdb.UF_NORMAL_ACCOUNT,
891                                   msDSUserAccountControlComputed=0)
892
893     def test_userPassword_lockout_with_clear_change_ldap_userAccountControl(self):
894         self._test_userPassword_lockout_with_clear_change(self.creds2,
895                                                           self.ldb3,
896                                                           "ldap_userAccountControl")
897
898     def test_userPassword_lockout_with_clear_change_ldap_lockoutTime(self):
899         self._test_userPassword_lockout_with_clear_change(self.creds2,
900                                                           self.ldb3,
901                                                           "ldap_lockoutTime")
902
903     def test_userPassword_lockout_with_clear_change_samr(self):
904         self._test_userPassword_lockout_with_clear_change(self.creds2,
905                                                           self.ldb3,
906                                                           "samr")
907
908     def _test_unicodePwd_lockout_with_clear_change(self, creds, other_ldb):
909         print "Performs a password cleartext change operation on 'unicodePwd'"
910         username = creds.get_username()
911         userpass = creds.get_password()
912         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
913
914         res = self._check_account(userdn,
915                                   badPwdCount=0,
916                                   badPasswordTime=("greater", 0),
917                                   lastLogon=("greater", 0),
918                                   lastLogonTimestamp=("greater", 0),
919                                   userAccountControl=
920                                     dsdb.UF_NORMAL_ACCOUNT,
921                                   msDSUserAccountControlComputed=0)
922         badPasswordTime = int(res[0]["badPasswordTime"][0])
923         lastLogon = int(res[0]["lastLogon"][0])
924
925         # Change password on a connection as another user
926
927         # Wrong old password
928         try:
929             other_ldb.modify_ldif("""
930 dn: """ + userdn + """
931 changetype: modify
932 delete: unicodePwd
933 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
934 add: unicodePwd
935 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
936 """)
937             self.fail()
938         except LdbError, (num, msg):
939             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
940             self.assertTrue('00000056' in msg, msg)
941
942         res = self._check_account(userdn,
943                                   badPwdCount=1,
944                                   badPasswordTime=("greater", badPasswordTime),
945                                   lastLogon=lastLogon,
946                                   lastLogonTimestamp=lastLogon,
947                                   userAccountControl=
948                                     dsdb.UF_NORMAL_ACCOUNT,
949                                   msDSUserAccountControlComputed=0)
950         badPasswordTime = int(res[0]["badPasswordTime"][0])
951
952         # Correct old password
953         old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
954         invalid_utf16 = "\"thatsAcomplPASSX\"".encode('utf-16-le')
955         userpass = "thatsAcomplPASS2"
956         creds.set_password(userpass)
957         new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
958
959         other_ldb.modify_ldif("""
960 dn: """ + userdn + """
961 changetype: modify
962 delete: unicodePwd
963 unicodePwd:: """ + base64.b64encode(old_utf16) + """
964 add: unicodePwd
965 unicodePwd:: """ + base64.b64encode(new_utf16) + """
966 """)
967
968         res = self._check_account(userdn,
969                                   badPwdCount=1,
970                                   badPasswordTime=badPasswordTime,
971                                   lastLogon=lastLogon,
972                                   lastLogonTimestamp=lastLogon,
973                                   userAccountControl=
974                                     dsdb.UF_NORMAL_ACCOUNT,
975                                   msDSUserAccountControlComputed=0)
976
977         # Wrong old password
978         try:
979             other_ldb.modify_ldif("""
980 dn: """ + userdn + """
981 changetype: modify
982 delete: unicodePwd
983 unicodePwd:: """ + base64.b64encode(old_utf16) + """
984 add: unicodePwd
985 unicodePwd:: """ + base64.b64encode(new_utf16) + """
986 """)
987             self.fail()
988         except LdbError, (num, msg):
989             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
990             self.assertTrue('00000056' in msg, msg)
991
992         res = self._check_account(userdn,
993                                   badPwdCount=2,
994                                   badPasswordTime=("greater", badPasswordTime),
995                                   lastLogon=lastLogon,
996                                   lastLogonTimestamp=lastLogon,
997                                   userAccountControl=
998                                     dsdb.UF_NORMAL_ACCOUNT,
999                                   msDSUserAccountControlComputed=0)
1000         badPasswordTime = int(res[0]["badPasswordTime"][0])
1001
1002         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1003         # It doesn't create "lockoutTime" = 0 and doesn't
1004         # reset "badPwdCount" = 0.
1005         self._reset_samr(res)
1006
1007         res = self._check_account(userdn,
1008                                   badPwdCount=2,
1009                                   badPasswordTime=badPasswordTime,
1010                                   lastLogon=lastLogon,
1011                                   lastLogonTimestamp=lastLogon,
1012                                   userAccountControl=
1013                                     dsdb.UF_NORMAL_ACCOUNT,
1014                                   msDSUserAccountControlComputed=0)
1015
1016         print "two failed password change"
1017
1018         # Wrong old password
1019         try:
1020             other_ldb.modify_ldif("""
1021 dn: """ + userdn + """
1022 changetype: modify
1023 delete: unicodePwd
1024 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1025 add: unicodePwd
1026 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1027 """)
1028             self.fail()
1029         except LdbError, (num, msg):
1030             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1031             self.assertTrue('00000056' in msg, msg)
1032
1033         # this is strange, why do we have lockoutTime=badPasswordTime here?
1034         res = self._check_account(userdn,
1035                                   badPwdCount=3,
1036                                   badPasswordTime=("greater", badPasswordTime),
1037                                   lastLogon=lastLogon,
1038                                   lastLogonTimestamp=lastLogon,
1039                                   lockoutTime=("greater", badPasswordTime),
1040                                   userAccountControl=
1041                                     dsdb.UF_NORMAL_ACCOUNT,
1042                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1043         badPasswordTime = int(res[0]["badPasswordTime"][0])
1044         lockoutTime = int(res[0]["lockoutTime"][0])
1045
1046         # Wrong old password
1047         try:
1048             other_ldb.modify_ldif("""
1049 dn: """ + userdn + """
1050 changetype: modify
1051 delete: unicodePwd
1052 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1053 add: unicodePwd
1054 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1055 """)
1056             self.fail()
1057         except LdbError, (num, msg):
1058             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1059             self.assertTrue('00000775' in msg, msg)
1060
1061         res = self._check_account(userdn,
1062                                   badPwdCount=3,
1063                                   badPasswordTime=badPasswordTime,
1064                                   lastLogon=lastLogon,
1065                                   lastLogonTimestamp=lastLogon,
1066                                   lockoutTime=lockoutTime,
1067                                   userAccountControl=
1068                                     dsdb.UF_NORMAL_ACCOUNT,
1069                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1070
1071         # Wrong old password
1072         try:
1073             other_ldb.modify_ldif("""
1074 dn: """ + userdn + """
1075 changetype: modify
1076 delete: unicodePwd
1077 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1078 add: unicodePwd
1079 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1080 """)
1081             self.fail()
1082         except LdbError, (num, msg):
1083             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1084             self.assertTrue('00000775' in msg, msg)
1085
1086         res = self._check_account(userdn,
1087                                   badPwdCount=3,
1088                                   badPasswordTime=badPasswordTime,
1089                                   lastLogon=lastLogon,
1090                                   lastLogonTimestamp=lastLogon,
1091                                   lockoutTime=lockoutTime,
1092                                   userAccountControl=
1093                                     dsdb.UF_NORMAL_ACCOUNT,
1094                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1095
1096         try:
1097             # Correct old password
1098             other_ldb.modify_ldif("""
1099 dn: """ + userdn + """
1100 changetype: modify
1101 delete: unicodePwd
1102 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1103 add: unicodePwd
1104 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1105 """)
1106             self.fail()
1107         except LdbError, (num, msg):
1108             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1109             self.assertTrue('00000775' in msg, msg)
1110
1111         res = self._check_account(userdn,
1112                                   badPwdCount=3,
1113                                   badPasswordTime=badPasswordTime,
1114                                   lastLogon=lastLogon,
1115                                   lastLogonTimestamp=lastLogon,
1116                                   lockoutTime=lockoutTime,
1117                                   userAccountControl=
1118                                     dsdb.UF_NORMAL_ACCOUNT,
1119                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1120
1121         # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
1122         self._reset_samr(res);
1123
1124         res = self._check_account(userdn,
1125                                   badPwdCount=0,
1126                                   badPasswordTime=badPasswordTime,
1127                                   lastLogon=lastLogon,
1128                                   lastLogonTimestamp=lastLogon,
1129                                   lockoutTime=0,
1130                                   userAccountControl=
1131                                     dsdb.UF_NORMAL_ACCOUNT,
1132                                   msDSUserAccountControlComputed=0)
1133
1134         # Correct old password
1135         old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
1136         invalid_utf16 = "\"thatsAcomplPASSiX\"".encode('utf-16-le')
1137         userpass = "thatsAcomplPASS2x"
1138         creds.set_password(userpass)
1139         new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
1140
1141         other_ldb.modify_ldif("""
1142 dn: """ + userdn + """
1143 changetype: modify
1144 delete: unicodePwd
1145 unicodePwd:: """ + base64.b64encode(old_utf16) + """
1146 add: unicodePwd
1147 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1148 """)
1149
1150         res = self._check_account(userdn,
1151                                   badPwdCount=0,
1152                                   badPasswordTime=badPasswordTime,
1153                                   lastLogon=lastLogon,
1154                                   lastLogonTimestamp=lastLogon,
1155                                   lockoutTime=0,
1156                                   userAccountControl=
1157                                     dsdb.UF_NORMAL_ACCOUNT,
1158                                   msDSUserAccountControlComputed=0)
1159
1160         # Wrong old password
1161         try:
1162             other_ldb.modify_ldif("""
1163 dn: """ + userdn + """
1164 changetype: modify
1165 delete: unicodePwd
1166 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1167 add: unicodePwd
1168 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1169 """)
1170             self.fail()
1171         except LdbError, (num, msg):
1172             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1173             self.assertTrue('00000056' in msg, msg)
1174
1175         res = self._check_account(userdn,
1176                                   badPwdCount=1,
1177                                   badPasswordTime=("greater", badPasswordTime),
1178                                   lastLogon=lastLogon,
1179                                   lastLogonTimestamp=lastLogon,
1180                                   lockoutTime=0,
1181                                   userAccountControl=
1182                                     dsdb.UF_NORMAL_ACCOUNT,
1183                                   msDSUserAccountControlComputed=0)
1184         badPasswordTime = int(res[0]["badPasswordTime"][0])
1185
1186         # Wrong old password
1187         try:
1188             other_ldb.modify_ldif("""
1189 dn: """ + userdn + """
1190 changetype: modify
1191 delete: unicodePwd
1192 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1193 add: unicodePwd
1194 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1195 """)
1196             self.fail()
1197         except LdbError, (num, msg):
1198             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1199             self.assertTrue('00000056' in msg, msg)
1200
1201         res = self._check_account(userdn,
1202                                   badPwdCount=2,
1203                                   badPasswordTime=("greater", badPasswordTime),
1204                                   lastLogon=lastLogon,
1205                                   lastLogonTimestamp=lastLogon,
1206                                   lockoutTime=0,
1207                                   userAccountControl=
1208                                     dsdb.UF_NORMAL_ACCOUNT,
1209                                   msDSUserAccountControlComputed=0)
1210         badPasswordTime = int(res[0]["badPasswordTime"][0])
1211
1212         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1213         # It doesn't reset "badPwdCount" = 0.
1214         self._reset_samr(res)
1215
1216         res = self._check_account(userdn,
1217                                   badPwdCount=2,
1218                                   badPasswordTime=badPasswordTime,
1219                                   lastLogon=lastLogon,
1220                                   lastLogonTimestamp=lastLogon,
1221                                   lockoutTime=0,
1222                                   userAccountControl=
1223                                     dsdb.UF_NORMAL_ACCOUNT,
1224                                   msDSUserAccountControlComputed=0)
1225
1226         # Wrong old password
1227         try:
1228             other_ldb.modify_ldif("""
1229 dn: """ + userdn + """
1230 changetype: modify
1231 delete: unicodePwd
1232 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
1233 add: unicodePwd
1234 unicodePwd:: """ + base64.b64encode(new_utf16) + """
1235 """)
1236             self.fail()
1237         except LdbError, (num, msg):
1238             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1239             self.assertTrue('00000056' in msg, msg)
1240
1241         res = self._check_account(userdn,
1242                                   badPwdCount=3,
1243                                   badPasswordTime=("greater", badPasswordTime),
1244                                   lastLogon=lastLogon,
1245                                   lastLogonTimestamp=lastLogon,
1246                                   lockoutTime=("greater", badPasswordTime),
1247                                   userAccountControl=
1248                                     dsdb.UF_NORMAL_ACCOUNT,
1249                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1250         badPasswordTime = int(res[0]["badPasswordTime"][0])
1251         lockoutTime = int(res[0]["lockoutTime"][0])
1252
1253         time.sleep(self.account_lockout_duration + 1)
1254
1255         res = self._check_account(userdn,
1256                                   badPwdCount=3, effective_bad_password_count=0,
1257                                   badPasswordTime=badPasswordTime,
1258                                   lastLogon=lastLogon,
1259                                   lastLogonTimestamp=lastLogon,
1260                                   lockoutTime=lockoutTime,
1261                                   userAccountControl=
1262                                     dsdb.UF_NORMAL_ACCOUNT,
1263                                   msDSUserAccountControlComputed=0)
1264
1265         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1266         # It doesn't reset "lockoutTime" = 0 and doesn't
1267         # reset "badPwdCount" = 0.
1268         self._reset_samr(res)
1269
1270         res = self._check_account(userdn,
1271                                   badPwdCount=3, effective_bad_password_count=0,
1272                                   badPasswordTime=badPasswordTime,
1273                                   lockoutTime=lockoutTime,
1274                                   lastLogon=lastLogon,
1275                                   lastLogonTimestamp=lastLogon,
1276                                   userAccountControl=
1277                                     dsdb.UF_NORMAL_ACCOUNT,
1278                                   msDSUserAccountControlComputed=0)
1279
1280     def test_unicodePwd_lockout_with_clear_change(self):
1281         return self._test_unicodePwd_lockout_with_clear_change(self.creds2, self.ldb3)
1282
1283     def _test_login_lockout(self, creds, use_kerberos):
1284         username = creds.get_username()
1285         userpass = creds.get_password()
1286         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
1287
1288         # This unlocks by waiting for account_lockout_duration
1289         if use_kerberos == MUST_USE_KERBEROS:
1290             lastlogon_relation = 'greater'
1291             print "Performs a lockout attempt against LDAP using Kerberos"
1292         else:
1293             lastlogon_relation = 'equal'
1294             print "Performs a lockout attempt against LDAP using NTLM"
1295
1296         # Change password on a connection as another user
1297         res = self._check_account(userdn,
1298                                   badPwdCount=0,
1299                                   badPasswordTime=("greater", 0),
1300                                   lastLogon=("greater", 0),
1301                                   lastLogonTimestamp=("greater", 0),
1302                                   userAccountControl=
1303                                     dsdb.UF_NORMAL_ACCOUNT,
1304                                   msDSUserAccountControlComputed=0)
1305         badPasswordTime = int(res[0]["badPasswordTime"][0])
1306         lastLogon = int(res[0]["lastLogon"][0])
1307         firstLogon = lastLogon
1308         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1309         print firstLogon
1310         print lastLogonTimestamp
1311
1312
1313         self.assertGreater(lastLogon, badPasswordTime)
1314
1315         # Open a second LDB connection with the user credentials. Use the
1316         # command line credentials for informations like the domain, the realm
1317         # and the workstation.
1318         creds_lockout = insta_creds(template=creds)
1319         creds_lockout.set_kerberos_state(use_kerberos)
1320
1321         # The wrong password
1322         creds_lockout.set_password("thatsAcomplPASS1x")
1323
1324         self.assertLoginFailure(host_url, creds_lockout, lp)
1325
1326         res = self._check_account(userdn,
1327                                   badPwdCount=1,
1328                                   badPasswordTime=("greater", badPasswordTime),
1329                                   lastLogon=lastLogon,
1330                                   lastLogonTimestamp=lastLogonTimestamp,
1331                                   userAccountControl=
1332                                     dsdb.UF_NORMAL_ACCOUNT,
1333                                   msDSUserAccountControlComputed=0,
1334                                   msg='lastlogontimestamp with wrong password')
1335         badPasswordTime = int(res[0]["badPasswordTime"][0])
1336
1337         # Correct old password
1338         creds_lockout.set_password(userpass)
1339
1340         ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1341
1342         # lastLogonTimestamp should not change
1343         # lastLogon increases if badPwdCount is non-zero (!)
1344         res = self._check_account(userdn,
1345                                   badPwdCount=0,
1346                                   badPasswordTime=badPasswordTime,
1347                                   lastLogon=('greater', lastLogon),
1348                                   lastLogonTimestamp=lastLogonTimestamp,
1349                                   userAccountControl=
1350                                     dsdb.UF_NORMAL_ACCOUNT,
1351                                   msDSUserAccountControlComputed=0,
1352                                   msg='LLTimestamp is updated to lastlogon')
1353
1354         lastLogon = int(res[0]["lastLogon"][0])
1355         self.assertGreater(lastLogon, badPasswordTime)
1356
1357         # The wrong password
1358         creds_lockout.set_password("thatsAcomplPASS1x")
1359
1360         self.assertLoginFailure(host_url, creds_lockout, lp)
1361
1362         res = self._check_account(userdn,
1363                                   badPwdCount=1,
1364                                   badPasswordTime=("greater", badPasswordTime),
1365                                   lastLogon=lastLogon,
1366                                   lastLogonTimestamp=lastLogonTimestamp,
1367                                   userAccountControl=
1368                                     dsdb.UF_NORMAL_ACCOUNT,
1369                                   msDSUserAccountControlComputed=0)
1370         badPasswordTime = int(res[0]["badPasswordTime"][0])
1371
1372         # The wrong password
1373         creds_lockout.set_password("thatsAcomplPASS1x")
1374
1375         try:
1376             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1377             self.fail()
1378
1379         except LdbError, (num, msg):
1380             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1381
1382         res = self._check_account(userdn,
1383                                   badPwdCount=2,
1384                                   badPasswordTime=("greater", badPasswordTime),
1385                                   lastLogon=lastLogon,
1386                                   lastLogonTimestamp=lastLogonTimestamp,
1387                                   userAccountControl=
1388                                     dsdb.UF_NORMAL_ACCOUNT,
1389                                   msDSUserAccountControlComputed=0)
1390         badPasswordTime = int(res[0]["badPasswordTime"][0])
1391
1392         print "two failed password change"
1393
1394         # The wrong password
1395         creds_lockout.set_password("thatsAcomplPASS1x")
1396
1397         try:
1398             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1399             self.fail()
1400
1401         except LdbError, (num, msg):
1402             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1403
1404         res = self._check_account(userdn,
1405                                   badPwdCount=3,
1406                                   badPasswordTime=("greater", badPasswordTime),
1407                                   lastLogon=lastLogon,
1408                                   lastLogonTimestamp=lastLogonTimestamp,
1409                                   lockoutTime=("greater", badPasswordTime),
1410                                   userAccountControl=
1411                                     dsdb.UF_NORMAL_ACCOUNT,
1412                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1413         badPasswordTime = int(res[0]["badPasswordTime"][0])
1414         lockoutTime = int(res[0]["lockoutTime"][0])
1415
1416         # The wrong password
1417         creds_lockout.set_password("thatsAcomplPASS1x")
1418         try:
1419             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1420             self.fail()
1421         except LdbError, (num, msg):
1422             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1423
1424         res = self._check_account(userdn,
1425                                   badPwdCount=3,
1426                                   badPasswordTime=badPasswordTime,
1427                                   lastLogon=lastLogon,
1428                                   lastLogonTimestamp=lastLogonTimestamp,
1429                                   lockoutTime=lockoutTime,
1430                                   userAccountControl=
1431                                     dsdb.UF_NORMAL_ACCOUNT,
1432                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1433
1434         # The wrong password
1435         creds_lockout.set_password("thatsAcomplPASS1x")
1436         try:
1437             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1438             self.fail()
1439         except LdbError, (num, msg):
1440             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1441
1442         res = self._check_account(userdn,
1443                                   badPwdCount=3,
1444                                   badPasswordTime=badPasswordTime,
1445                                   lastLogon=lastLogon,
1446                                   lastLogonTimestamp=lastLogonTimestamp,
1447                                   lockoutTime=lockoutTime,
1448                                   userAccountControl=
1449                                     dsdb.UF_NORMAL_ACCOUNT,
1450                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1451
1452         # The correct password, but we are locked out
1453         creds_lockout.set_password(userpass)
1454         try:
1455             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1456             self.fail()
1457         except LdbError, (num, msg):
1458             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1459
1460         res = self._check_account(userdn,
1461                                   badPwdCount=3,
1462                                   badPasswordTime=badPasswordTime,
1463                                   lastLogon=lastLogon,
1464                                   lastLogonTimestamp=lastLogonTimestamp,
1465                                   lockoutTime=lockoutTime,
1466                                   userAccountControl=
1467                                     dsdb.UF_NORMAL_ACCOUNT,
1468                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1469
1470         # wait for the lockout to end
1471         time.sleep(self.account_lockout_duration + 1)
1472         print self.account_lockout_duration + 1
1473
1474         res = self._check_account(userdn,
1475                                   badPwdCount=3, effective_bad_password_count=0,
1476                                   badPasswordTime=badPasswordTime,
1477                                   lockoutTime=lockoutTime,
1478                                   lastLogon=lastLogon,
1479                                   lastLogonTimestamp=lastLogonTimestamp,
1480                                   userAccountControl=
1481                                     dsdb.UF_NORMAL_ACCOUNT,
1482                                   msDSUserAccountControlComputed=0)
1483
1484         lastLogon = int(res[0]["lastLogon"][0])
1485
1486         # The correct password after letting the timeout expire
1487
1488         creds_lockout.set_password(userpass)
1489
1490         creds_lockout2 = insta_creds(creds_lockout)
1491
1492         ldb_lockout = SamDB(url=host_url, credentials=creds_lockout2, lp=lp)
1493         time.sleep(3)
1494
1495         res = self._check_account(userdn,
1496                                   badPwdCount=0,
1497                                   badPasswordTime=badPasswordTime,
1498                                   lastLogon=(lastlogon_relation, lastLogon),
1499                                   lastLogonTimestamp=lastLogonTimestamp,
1500                                   lockoutTime=0,
1501                                   userAccountControl=
1502                                     dsdb.UF_NORMAL_ACCOUNT,
1503                                   msDSUserAccountControlComputed=0,
1504                                   msg="lastLogon is way off")
1505
1506         lastLogon = int(res[0]["lastLogon"][0])
1507
1508         # The wrong password
1509         creds_lockout.set_password("thatsAcomplPASS1x")
1510         try:
1511             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1512             self.fail()
1513         except LdbError, (num, msg):
1514             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1515
1516         res = self._check_account(userdn,
1517                                   badPwdCount=1,
1518                                   badPasswordTime=("greater", badPasswordTime),
1519                                   lockoutTime=0,
1520                                   lastLogon=lastLogon,
1521                                   lastLogonTimestamp=lastLogonTimestamp,
1522                                   userAccountControl=
1523                                     dsdb.UF_NORMAL_ACCOUNT,
1524                                   msDSUserAccountControlComputed=0)
1525         badPasswordTime = int(res[0]["badPasswordTime"][0])
1526
1527         # The wrong password
1528         creds_lockout.set_password("thatsAcomplPASS1x")
1529         try:
1530             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1531             self.fail()
1532         except LdbError, (num, msg):
1533             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1534
1535         res = self._check_account(userdn,
1536                                   badPwdCount=2,
1537                                   badPasswordTime=("greater", badPasswordTime),
1538                                   lockoutTime=0,
1539                                   lastLogon=lastLogon,
1540                                   lastLogonTimestamp=lastLogonTimestamp,
1541                                   userAccountControl=
1542                                     dsdb.UF_NORMAL_ACCOUNT,
1543                                   msDSUserAccountControlComputed=0)
1544         badPasswordTime = int(res[0]["badPasswordTime"][0])
1545
1546         time.sleep(self.lockout_observation_window + 1)
1547
1548         res = self._check_account(userdn,
1549                                   badPwdCount=2, effective_bad_password_count=0,
1550                                   badPasswordTime=badPasswordTime,
1551                                   lockoutTime=0,
1552                                   lastLogon=lastLogon,
1553                                   lastLogonTimestamp=lastLogonTimestamp,
1554                                   userAccountControl=
1555                                     dsdb.UF_NORMAL_ACCOUNT,
1556                                   msDSUserAccountControlComputed=0)
1557
1558         # The wrong password
1559         creds_lockout.set_password("thatsAcomplPASS1x")
1560         try:
1561             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1562             self.fail()
1563         except LdbError, (num, msg):
1564             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1565
1566         res = self._check_account(userdn,
1567                                   badPwdCount=1,
1568                                   badPasswordTime=("greater", badPasswordTime),
1569                                   lockoutTime=0,
1570                                   lastLogon=lastLogon,
1571                                   lastLogonTimestamp=lastLogonTimestamp,
1572                                   userAccountControl=
1573                                     dsdb.UF_NORMAL_ACCOUNT,
1574                                   msDSUserAccountControlComputed=0)
1575         badPasswordTime = int(res[0]["badPasswordTime"][0])
1576
1577         # The correct password without letting the timeout expire
1578         creds_lockout.set_password(userpass)
1579         ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1580
1581         res = self._check_account(userdn,
1582                                   badPwdCount=0,
1583                                   badPasswordTime=badPasswordTime,
1584                                   lockoutTime=0,
1585                                   lastLogon=("greater", lastLogon),
1586                                   lastLogonTimestamp=lastLogonTimestamp,
1587                                   userAccountControl=
1588                                     dsdb.UF_NORMAL_ACCOUNT,
1589                                   msDSUserAccountControlComputed=0)
1590
1591     def test_login_lockout_ntlm(self):
1592         self._test_login_lockout(self.creds2, DONT_USE_KERBEROS)
1593
1594     def test_login_lockout_kerberos(self):
1595         self._test_login_lockout(self.creds2, MUST_USE_KERBEROS)
1596
1597     def _test_multiple_logon(self, creds, use_kerberos):
1598         # Test the happy case in which a user logs on correctly, then
1599         # logs on correctly again, so that the bad password and
1600         # lockout times are both zero the second time. The lastlogon
1601         # time should increase.
1602
1603         # Open a second LDB connection with the user credentials. Use the
1604         # command line credentials for informations like the domain, the realm
1605         # and the workstation.
1606         username = creds.get_username()
1607         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
1608         creds2 = insta_creds(template=creds)
1609         creds2.set_kerberos_state(use_kerberos)
1610         self.assertEqual(creds2.get_kerberos_state(), use_kerberos)
1611
1612         if use_kerberos == MUST_USE_KERBEROS:
1613             print "Testing multiple logon with Kerberos"
1614             lastlogon_relation = 'greater'
1615         else:
1616             print "Testing multiple logon with NTLM"
1617             lastlogon_relation = 'equal'
1618
1619         SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
1620
1621         res = self._check_account(userdn,
1622                                   badPwdCount=0,
1623                                   badPasswordTime=("greater", 0),
1624                                   lastLogon=("greater", 0),
1625                                   lastLogonTimestamp=("greater", 0),
1626                                   userAccountControl=
1627                                     dsdb.UF_NORMAL_ACCOUNT,
1628                                   msDSUserAccountControlComputed=0)
1629         badPasswordTime = int(res[0]["badPasswordTime"][0])
1630         lastLogon = int(res[0]["lastLogon"][0])
1631         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1632         firstLogon = lastLogon
1633         print "last logon is %d" % lastLogon
1634         self.assertGreater(lastLogon, badPasswordTime)
1635
1636         time.sleep(1)
1637         SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
1638
1639         res = self._check_account(userdn,
1640                                   badPwdCount=0,
1641                                   badPasswordTime=badPasswordTime,
1642                                   lastLogon=(lastlogon_relation, lastLogon),
1643                                   lastLogonTimestamp=lastLogonTimestamp,
1644                                   userAccountControl=
1645                                   dsdb.UF_NORMAL_ACCOUNT,
1646                                   msDSUserAccountControlComputed=0,
1647                                   msg=("second logon, firstlogon was %s" %
1648                                        firstLogon))
1649
1650
1651         lastLogon = int(res[0]["lastLogon"][0])
1652
1653         time.sleep(1)
1654
1655         SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
1656
1657         res = self._check_account(userdn,
1658                                   badPwdCount=0,
1659                                   badPasswordTime=badPasswordTime,
1660                                   lastLogon=(lastlogon_relation, lastLogon),
1661                                   lastLogonTimestamp=lastLogonTimestamp,
1662                                   userAccountControl=
1663                                     dsdb.UF_NORMAL_ACCOUNT,
1664                                   msDSUserAccountControlComputed=0)
1665
1666     def test_multiple_logon_ntlm(self):
1667         self._test_multiple_logon(self.creds2, DONT_USE_KERBEROS)
1668
1669     def test_multiple_logon_kerberos(self):
1670         self._test_multiple_logon(self.creds2, MUST_USE_KERBEROS)
1671
1672     def tearDown(self):
1673         super(PasswordTests, self).tearDown()
1674
1675 host_url = "ldap://%s" % host
1676
1677 TestProgram(module=__name__, opts=subunitopts)