s4:dsdb/tests: let password_lockout.py let _readd_user() return the ldb connection...
[garming/samba-autobuild/.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):
61     # get a copy of the global creds or a the passed in creds
62     c = Credentials()
63     c.set_username("testuser")
64     c.set_password("thatsAcomplPASS1")
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):
281         # (Re)adds the test user "testuser" with no password atm
282         delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
283         self.ldb.add({
284              "dn": "cn=testuser,cn=users," + self.base_dn,
285              "objectclass": "user",
286              "sAMAccountName": "testuser"})
287
288         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
289                                   badPwdCount=0,
290                                   badPasswordTime=0,
291                                   lastLogon=0,
292                                   lastLogonTimestamp=('absent', None),
293                                   userAccountControl=
294                                     dsdb.UF_NORMAL_ACCOUNT |
295                                     dsdb.UF_ACCOUNTDISABLE |
296                                     dsdb.UF_PASSWD_NOTREQD,
297                                   msDSUserAccountControlComputed=
298                                     dsdb.UF_PASSWORD_EXPIRED)
299
300         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
301         # It doesn't create "lockoutTime" = 0.
302         self._reset_samr(res)
303
304         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
305                                   badPwdCount=0,
306                                   badPasswordTime=0,
307                                   lastLogon=0,
308                                   lastLogonTimestamp=('absent', None),
309                                   userAccountControl=
310                                     dsdb.UF_NORMAL_ACCOUNT |
311                                     dsdb.UF_ACCOUNTDISABLE |
312                                     dsdb.UF_PASSWD_NOTREQD,
313                                   msDSUserAccountControlComputed=
314                                     dsdb.UF_PASSWORD_EXPIRED)
315
316         # Tests a password change when we don't have any password yet with a
317         # wrong old password
318         try:
319             self.ldb.modify_ldif("""
320 dn: cn=testuser,cn=users,""" + self.base_dn + """
321 changetype: modify
322 delete: userPassword
323 userPassword: noPassword
324 add: userPassword
325 userPassword: thatsAcomplPASS2
326 """)
327             self.fail()
328         except LdbError, (num, msg):
329             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
330             # Windows (2008 at least) seems to have some small bug here: it
331             # returns "0000056A" on longer (always wrong) previous passwords.
332             self.assertTrue('00000056' in msg, msg)
333
334         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
335                                   badPwdCount=1,
336                                   badPasswordTime=("greater", 0),
337                                   lastLogon=0,
338                                   lastLogonTimestamp=('absent', None),
339                                   userAccountControl=
340                                     dsdb.UF_NORMAL_ACCOUNT |
341                                     dsdb.UF_ACCOUNTDISABLE |
342                                     dsdb.UF_PASSWD_NOTREQD,
343                                   msDSUserAccountControlComputed=
344                                     dsdb.UF_PASSWORD_EXPIRED)
345         badPasswordTime = int(res[0]["badPasswordTime"][0])
346
347         # Sets the initial user password with a "special" password change
348         # I think that this internally is a password set operation and it can
349         # only be performed by someone which has password set privileges on the
350         # account (at least in s4 we do handle it like that).
351         self.ldb.modify_ldif("""
352 dn: cn=testuser,cn=users,""" + self.base_dn + """
353 changetype: modify
354 delete: userPassword
355 add: userPassword
356 userPassword: thatsAcomplPASS1
357 """)
358
359         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
360                                   badPwdCount=1,
361                                   badPasswordTime=badPasswordTime,
362                                   lastLogon=0,
363                                   lastLogonTimestamp=('absent', None),
364                                   userAccountControl=
365                                     dsdb.UF_NORMAL_ACCOUNT |
366                                     dsdb.UF_ACCOUNTDISABLE |
367                                     dsdb.UF_PASSWD_NOTREQD,
368                                   msDSUserAccountControlComputed=0)
369
370         # Enables the user account
371         self.ldb.enable_account("(sAMAccountName=testuser)")
372
373         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
374                                   badPwdCount=1,
375                                   badPasswordTime=badPasswordTime,
376                                   lastLogon=0,
377                                   lastLogonTimestamp=('absent', None),
378                                   userAccountControl=
379                                     dsdb.UF_NORMAL_ACCOUNT,
380                                   msDSUserAccountControlComputed=0)
381
382         # Open a second LDB connection with the user credentials. Use the
383         # command line credentials for informations like the domain, the realm
384         # and the workstation.
385         creds2 = insta_creds()
386
387         ldb = SamDB(url=host_url, credentials=creds2, lp=lp)
388
389         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
390                                   badPwdCount=0,
391                                   badPasswordTime=badPasswordTime,
392                                   lastLogon=('greater', 0),
393                                   lastLogonTimestamp=('greater', 0),
394                                   userAccountControl=
395                                     dsdb.UF_NORMAL_ACCOUNT,
396                                   msDSUserAccountControlComputed=0)
397
398         lastLogon = int(res[0]["lastLogon"][0])
399         self.assertGreater(lastLogon, badPasswordTime)
400         return ldb
401
402     def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS):
403         try:
404             ldb = SamDB(url=url, credentials=creds, lp=lp)
405             self.fail("Login unexpectedly succeeded")
406         except LdbError, (num, msg):
407             if errno is not None:
408                 self.assertEquals(num, errno, ("Login failed in the wrong way"
409                                                "(got err %d, expected %d)" %
410                                                (num, errno)))
411
412     def setUp(self):
413         super(PasswordTests, self).setUp()
414
415         self.ldb = SamDB(url=host_url, session_info=system_session(lp),
416                          credentials=global_creds, lp=lp)
417
418         # Gets back the basedn
419         base_dn = self.ldb.domain_dn()
420
421         # Gets back the configuration basedn
422         configuration_dn = self.ldb.get_config_basedn().get_linearized()
423
424         # Get the old "dSHeuristics" if it was set
425         dsheuristics = self.ldb.get_dsheuristics()
426
427         # Reset the "dSHeuristics" as they were before
428         self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
429
430         res = self.ldb.search(base_dn,
431                          scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
432
433         if "lockoutDuration" in res[0]:
434             lockoutDuration = res[0]["lockoutDuration"][0]
435         else:
436             lockoutDuration = 0
437
438         if "lockoutObservationWindow" in res[0]:
439             lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
440         else:
441             lockoutObservationWindow = 0
442
443         if "lockoutThreshold" in res[0]:
444             lockoutThreshold = res[0]["lockoutThreshold"][0]
445         else:
446             lockoutTreshold = 0
447
448         self.addCleanup(self.ldb.modify_ldif, """
449 dn: """ + base_dn + """
450 changetype: modify
451 replace: lockoutDuration
452 lockoutDuration: """ + str(lockoutDuration) + """
453 replace: lockoutObservationWindow
454 lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
455 replace: lockoutThreshold
456 lockoutThreshold: """ + str(lockoutThreshold) + """
457 """)
458
459         m = Message()
460         m.dn = Dn(self.ldb, base_dn)
461
462         self.account_lockout_duration = 2
463         account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7))
464
465         m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
466                                               FLAG_MOD_REPLACE, "lockoutDuration")
467
468         account_lockout_threshold = 3
469         m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
470                                                FLAG_MOD_REPLACE, "lockoutThreshold")
471
472         self.lockout_observation_window = 2
473         lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7))
474
475         m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
476                                                        FLAG_MOD_REPLACE, "lockOutObservationWindow")
477
478         self.ldb.modify(m)
479
480         # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
481         self.ldb.set_dsheuristics("000000001")
482
483         # Get the old "minPwdAge"
484         minPwdAge = self.ldb.get_minPwdAge()
485
486         # Reset the "minPwdAge" as it was before
487         self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
488
489         # Set it temporarely to "0"
490         self.ldb.set_minPwdAge("0")
491
492         self.base_dn = self.ldb.domain_dn()
493
494         self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
495         self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % host, lp, global_creds)
496         self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
497         self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
498
499         self.ldb2 = self._readd_user()
500
501      # (Re)adds the test user "testuser3" with no password atm
502         delete_force(self.ldb, "cn=testuser3,cn=users," + self.base_dn)
503         self.ldb.add({
504              "dn": "cn=testuser3,cn=users," + self.base_dn,
505              "objectclass": "user",
506              "sAMAccountName": "testuser3"})
507
508         res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
509                                   badPwdCount=0,
510                                   badPasswordTime=0,
511                                   lastLogon=0,
512                                   lastLogonTimestamp=('absent', None),
513                                   userAccountControl=
514                                     dsdb.UF_NORMAL_ACCOUNT |
515                                     dsdb.UF_ACCOUNTDISABLE |
516                                     dsdb.UF_PASSWD_NOTREQD,
517                                   msDSUserAccountControlComputed=
518                                     dsdb.UF_PASSWORD_EXPIRED)
519
520         # Tests a password change when we don't have any password yet with a
521         # wrong old password
522         try:
523             self.ldb.modify_ldif("""
524 dn: cn=testuser3,cn=users,""" + self.base_dn + """
525 changetype: modify
526 delete: userPassword
527 userPassword: noPassword
528 add: userPassword
529 userPassword: thatsAcomplPASS2
530 """)
531             self.fail()
532         except LdbError, (num, msg):
533             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
534             # Windows (2008 at least) seems to have some small bug here: it
535             # returns "0000056A" on longer (always wrong) previous passwords.
536             self.assertTrue('00000056' in msg, msg)
537
538         res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
539                                   badPwdCount=1,
540                                   badPasswordTime=("greater", 0),
541                                   lastLogon=0,
542                                   lastLogonTimestamp=('absent', None),
543                                   userAccountControl=
544                                     dsdb.UF_NORMAL_ACCOUNT |
545                                     dsdb.UF_ACCOUNTDISABLE |
546                                     dsdb.UF_PASSWD_NOTREQD,
547                                   msDSUserAccountControlComputed=
548                                     dsdb.UF_PASSWORD_EXPIRED)
549         badPasswordTime3 = int(res[0]["badPasswordTime"][0])
550
551         # Sets the initial user password with a "special" password change
552         # I think that this internally is a password set operation and it can
553         # only be performed by someone which has password set privileges on the
554         # account (at least in s4 we do handle it like that).
555         self.ldb.modify_ldif("""
556 dn: cn=testuser3,cn=users,""" + self.base_dn + """
557 changetype: modify
558 delete: userPassword
559 add: userPassword
560 userPassword: thatsAcomplPASS1
561 """)
562
563         res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
564                                   badPwdCount=1,
565                                   badPasswordTime=badPasswordTime3,
566                                   lastLogon=0,
567                                   lastLogonTimestamp=('absent', None),
568                                   userAccountControl=
569                                     dsdb.UF_NORMAL_ACCOUNT |
570                                     dsdb.UF_ACCOUNTDISABLE |
571                                     dsdb.UF_PASSWD_NOTREQD,
572                                   msDSUserAccountControlComputed=0)
573
574         # Enables the user account
575         self.ldb.enable_account("(sAMAccountName=testuser3)")
576
577         res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
578                                   badPwdCount=1,
579                                   badPasswordTime=badPasswordTime3,
580                                   lastLogon=0,
581                                   lastLogonTimestamp=('absent', None),
582                                   userAccountControl=
583                                     dsdb.UF_NORMAL_ACCOUNT,
584                                   msDSUserAccountControlComputed=0)
585
586         # Open a second LDB connection with the user credentials. Use the
587         # command line credentials for informations like the domain, the realm
588         # and the workstation.
589         creds3 = insta_creds()
590         creds3.set_username("testuser3")
591         creds3.set_password("thatsAcomplPASS1")
592         self.ldb3 = SamDB(url=host_url, credentials=creds3, lp=lp)
593
594         res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
595                                   badPwdCount=0,
596                                   badPasswordTime=badPasswordTime3,
597                                   lastLogon=('greater', badPasswordTime3),
598                                   lastLogonTimestamp=('greater', badPasswordTime3),
599                                   userAccountControl=
600                                     dsdb.UF_NORMAL_ACCOUNT,
601                                   msDSUserAccountControlComputed=0)
602
603     def _test_userPassword_lockout_with_clear_change(self, method):
604         print "Performs a password cleartext change operation on 'userPassword'"
605         # Notice: This works only against Windows if "dSHeuristics" has been set
606         # properly
607
608         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
609                                   badPwdCount=0,
610                                   badPasswordTime=("greater", 0),
611                                   lastLogon=('greater', 0),
612                                   lastLogonTimestamp=('greater', 0),
613                                   userAccountControl=
614                                     dsdb.UF_NORMAL_ACCOUNT,
615                                   msDSUserAccountControlComputed=0)
616         badPasswordTime = int(res[0]["badPasswordTime"][0])
617         lastLogon = int(res[0]["lastLogon"][0])
618         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
619
620         # Change password on a connection as another user
621
622         # Wrong old password
623         try:
624             self.ldb3.modify_ldif("""
625 dn: cn=testuser,cn=users,""" + self.base_dn + """
626 changetype: modify
627 delete: userPassword
628 userPassword: thatsAcomplPASS1x
629 add: userPassword
630 userPassword: thatsAcomplPASS2
631 """)
632             self.fail()
633         except LdbError, (num, msg):
634             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
635             self.assertTrue('00000056' in msg, msg)
636
637         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
638                                   badPwdCount=1,
639                                   badPasswordTime=("greater", badPasswordTime),
640                                   lastLogon=lastLogon,
641                                   lastLogonTimestamp=lastLogonTimestamp,
642                                   userAccountControl=
643                                     dsdb.UF_NORMAL_ACCOUNT,
644                                   msDSUserAccountControlComputed=0)
645         badPasswordTime = int(res[0]["badPasswordTime"][0])
646
647         # Correct old password
648         self.ldb3.modify_ldif("""
649 dn: cn=testuser,cn=users,""" + self.base_dn + """
650 changetype: modify
651 delete: userPassword
652 userPassword: thatsAcomplPASS1
653 add: userPassword
654 userPassword: thatsAcomplPASS2
655 """)
656
657         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
658                                   badPwdCount=1,
659                                   badPasswordTime=badPasswordTime,
660                                   lastLogon=lastLogon,
661                                   lastLogonTimestamp=lastLogonTimestamp,
662                                   userAccountControl=
663                                     dsdb.UF_NORMAL_ACCOUNT,
664                                   msDSUserAccountControlComputed=0)
665
666         # Wrong old password
667         try:
668             self.ldb3.modify_ldif("""
669 dn: cn=testuser,cn=users,""" + self.base_dn + """
670 changetype: modify
671 delete: userPassword
672 userPassword: thatsAcomplPASS1x
673 add: userPassword
674 userPassword: thatsAcomplPASS2
675 """)
676             self.fail()
677         except LdbError, (num, msg):
678             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
679             self.assertTrue('00000056' in msg, msg)
680
681         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
682                                   badPwdCount=2,
683                                   badPasswordTime=("greater", badPasswordTime),
684                                   lastLogon=lastLogon,
685                                   lastLogonTimestamp=lastLogonTimestamp,
686                                   userAccountControl=
687                                     dsdb.UF_NORMAL_ACCOUNT,
688                                   msDSUserAccountControlComputed=0)
689         badPasswordTime = int(res[0]["badPasswordTime"][0])
690
691         print "two failed password change"
692
693         # Wrong old password
694         try:
695             self.ldb3.modify_ldif("""
696 dn: cn=testuser,cn=users,""" + self.base_dn + """
697 changetype: modify
698 delete: userPassword
699 userPassword: thatsAcomplPASS1x
700 add: userPassword
701 userPassword: thatsAcomplPASS2
702 """)
703             self.fail()
704         except LdbError, (num, msg):
705             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
706             self.assertTrue('00000056' in msg, msg)
707
708         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
709                                   badPwdCount=3,
710                                   badPasswordTime=("greater", badPasswordTime),
711                                   lastLogon=lastLogon,
712                                   lastLogonTimestamp=lastLogonTimestamp,
713                                   lockoutTime=("greater", badPasswordTime),
714                                   userAccountControl=
715                                     dsdb.UF_NORMAL_ACCOUNT,
716                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
717         badPasswordTime = int(res[0]["badPasswordTime"][0])
718         lockoutTime = int(res[0]["lockoutTime"][0])
719
720         # Wrong old password
721         try:
722             self.ldb3.modify_ldif("""
723 dn: cn=testuser,cn=users,""" + self.base_dn + """
724 changetype: modify
725 delete: userPassword
726 userPassword: thatsAcomplPASS1x
727 add: userPassword
728 userPassword: thatsAcomplPASS2
729 """)
730             self.fail()
731         except LdbError, (num, msg):
732             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
733             self.assertTrue('00000775' in msg, msg)
734
735         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
736                                   badPwdCount=3,
737                                   badPasswordTime=badPasswordTime,
738                                   lastLogon=lastLogon,
739                                   lastLogonTimestamp=lastLogonTimestamp,
740                                   lockoutTime=lockoutTime,
741                                   userAccountControl=
742                                     dsdb.UF_NORMAL_ACCOUNT,
743                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
744
745         # Wrong old password
746         try:
747             self.ldb3.modify_ldif("""
748 dn: cn=testuser,cn=users,""" + self.base_dn + """
749 changetype: modify
750 delete: userPassword
751 userPassword: thatsAcomplPASS1x
752 add: userPassword
753 userPassword: thatsAcomplPASS2
754 """)
755             self.fail()
756         except LdbError, (num, msg):
757             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
758             self.assertTrue('00000775' in msg, msg)
759
760         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
761                                   badPwdCount=3,
762                                   badPasswordTime=badPasswordTime,
763                                   lockoutTime=lockoutTime,
764                                   lastLogon=lastLogon,
765                                   lastLogonTimestamp=lastLogonTimestamp,
766                                   userAccountControl=
767                                     dsdb.UF_NORMAL_ACCOUNT,
768                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
769
770         try:
771             # Correct old password
772             self.ldb3.modify_ldif("""
773 dn: cn=testuser,cn=users,""" + self.base_dn + """
774 changetype: modify
775 delete: userPassword
776 userPassword: thatsAcomplPASS2
777 add: userPassword
778 userPassword: thatsAcomplPASS2x
779 """)
780             self.fail()
781         except LdbError, (num, msg):
782             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
783             self.assertTrue('00000775' in msg, msg)
784
785         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
786                                   badPwdCount=3,
787                                   badPasswordTime=badPasswordTime,
788                                   lastLogon=lastLogon,
789                                   lastLogonTimestamp=lastLogonTimestamp,
790                                   lockoutTime=lockoutTime,
791                                   userAccountControl=
792                                     dsdb.UF_NORMAL_ACCOUNT,
793                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
794
795         # Now reset the password, which does NOT change the lockout!
796         self.ldb.modify_ldif("""
797 dn: cn=testuser,cn=users,""" + self.base_dn + """
798 changetype: modify
799 replace: userPassword
800 userPassword: thatsAcomplPASS2
801 """)
802
803         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
804                                   badPwdCount=3,
805                                   badPasswordTime=badPasswordTime,
806                                   lastLogon=lastLogon,
807                                   lastLogonTimestamp=lastLogonTimestamp,
808                                   lockoutTime=lockoutTime,
809                                   userAccountControl=
810                                     dsdb.UF_NORMAL_ACCOUNT,
811                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
812
813         try:
814             # Correct old password
815             self.ldb3.modify_ldif("""
816 dn: cn=testuser,cn=users,""" + self.base_dn + """
817 changetype: modify
818 delete: userPassword
819 userPassword: thatsAcomplPASS2
820 add: userPassword
821 userPassword: thatsAcomplPASS2x
822 """)
823             self.fail()
824         except LdbError, (num, msg):
825             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
826             self.assertTrue('00000775' in msg, msg)
827
828         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
829                                   badPwdCount=3,
830                                   badPasswordTime=badPasswordTime,
831                                   lastLogon=lastLogon,
832                                   lastLogonTimestamp=lastLogonTimestamp,
833                                   lockoutTime=lockoutTime,
834                                   userAccountControl=
835                                     dsdb.UF_NORMAL_ACCOUNT,
836                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
837
838         m = Message()
839         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
840         m["userAccountControl"] = MessageElement(
841           str(dsdb.UF_LOCKOUT),
842           FLAG_MOD_REPLACE, "userAccountControl")
843
844         self.ldb.modify(m)
845
846         # This shows that setting the UF_LOCKOUT flag alone makes no difference
847         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
848                                   badPwdCount=3,
849                                   badPasswordTime=badPasswordTime,
850                                   lastLogon=lastLogon,
851                                   lastLogonTimestamp=lastLogonTimestamp,
852                                   lockoutTime=lockoutTime,
853                                   userAccountControl=
854                                     dsdb.UF_NORMAL_ACCOUNT,
855                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
856
857         # This shows that setting the UF_LOCKOUT flag makes no difference
858         try:
859             # Correct old password
860             self.ldb3.modify_ldif("""
861 dn: cn=testuser,cn=users,""" + self.base_dn + """
862 changetype: modify
863 delete: unicodePwd
864 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
865 add: unicodePwd
866 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
867 """)
868             self.fail()
869         except LdbError, (num, msg):
870             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
871             self.assertTrue('00000775' in msg, msg)
872
873         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
874                                   badPwdCount=3,
875                                   badPasswordTime=badPasswordTime,
876                                   lockoutTime=lockoutTime,
877                                   lastLogon=lastLogon,
878                                   lastLogonTimestamp=lastLogonTimestamp,
879                                   userAccountControl=
880                                     dsdb.UF_NORMAL_ACCOUNT,
881                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
882
883         self._reset_by_method(res, method)
884
885         # Here bad password counts are reset without logon success.
886         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
887                                   badPwdCount=0,
888                                   badPasswordTime=badPasswordTime,
889                                   lockoutTime=0,
890                                   lastLogon=lastLogon,
891                                   lastLogonTimestamp=lastLogonTimestamp,
892                                   userAccountControl=
893                                     dsdb.UF_NORMAL_ACCOUNT,
894                                   msDSUserAccountControlComputed=0)
895
896         # The correct password after doing the unlock
897
898         self.ldb3.modify_ldif("""
899 dn: cn=testuser,cn=users,""" + self.base_dn + """
900 changetype: modify
901 delete: unicodePwd
902 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
903 add: unicodePwd
904 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
905 """)
906
907         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
908                                   badPwdCount=0,
909                                   badPasswordTime=badPasswordTime,
910                                   lockoutTime=0,
911                                   lastLogon=lastLogon,
912                                   lastLogonTimestamp=lastLogonTimestamp,
913                                   userAccountControl=
914                                     dsdb.UF_NORMAL_ACCOUNT,
915                                   msDSUserAccountControlComputed=0)
916
917         # Wrong old password
918         try:
919             self.ldb3.modify_ldif("""
920 dn: cn=testuser,cn=users,""" + self.base_dn + """
921 changetype: modify
922 delete: userPassword
923 userPassword: thatsAcomplPASS1xyz
924 add: userPassword
925 userPassword: thatsAcomplPASS2XYZ
926 """)
927             self.fail()
928         except LdbError, (num, msg):
929             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
930             self.assertTrue('00000056' in msg, msg)
931
932         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
933                                   badPwdCount=1,
934                                   badPasswordTime=("greater", badPasswordTime),
935                                   lockoutTime=0,
936                                   lastLogon=lastLogon,
937                                   lastLogonTimestamp=lastLogonTimestamp,
938                                   userAccountControl=
939                                     dsdb.UF_NORMAL_ACCOUNT,
940                                   msDSUserAccountControlComputed=0)
941         badPasswordTime = int(res[0]["badPasswordTime"][0])
942
943         # Wrong old password
944         try:
945             self.ldb3.modify_ldif("""
946 dn: cn=testuser,cn=users,""" + self.base_dn + """
947 changetype: modify
948 delete: userPassword
949 userPassword: thatsAcomplPASS1xyz
950 add: userPassword
951 userPassword: thatsAcomplPASS2XYZ
952 """)
953             self.fail()
954         except LdbError, (num, msg):
955             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
956             self.assertTrue('00000056' in msg, msg)
957
958         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
959                                   badPwdCount=2,
960                                   badPasswordTime=("greater", badPasswordTime),
961                                   lockoutTime=0,
962                                   lastLogon=lastLogon,
963                                   lastLogonTimestamp=lastLogonTimestamp,
964                                   userAccountControl=
965                                     dsdb.UF_NORMAL_ACCOUNT,
966                                   msDSUserAccountControlComputed=0)
967         badPasswordTime = int(res[0]["badPasswordTime"][0])
968
969         self._reset_ldap_lockoutTime(res)
970
971         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
972                                   badPwdCount=0,
973                                   badPasswordTime=badPasswordTime,
974                                   lastLogon=lastLogon,
975                                   lastLogonTimestamp=lastLogonTimestamp,
976                                   lockoutTime=0,
977                                   userAccountControl=
978                                     dsdb.UF_NORMAL_ACCOUNT,
979                                   msDSUserAccountControlComputed=0)
980
981     def test_userPassword_lockout_with_clear_change_ldap_userAccountControl(self):
982         self._test_userPassword_lockout_with_clear_change("ldap_userAccountControl")
983
984     def test_userPassword_lockout_with_clear_change_ldap_lockoutTime(self):
985         self._test_userPassword_lockout_with_clear_change("ldap_lockoutTime")
986
987     def test_userPassword_lockout_with_clear_change_samr(self):
988         self._test_userPassword_lockout_with_clear_change("samr")
989
990
991     def test_unicodePwd_lockout_with_clear_change(self):
992         print "Performs a password cleartext change operation on 'unicodePwd'"
993
994         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
995                                   badPwdCount=0,
996                                   badPasswordTime=("greater", 0),
997                                   lastLogon=("greater", 0),
998                                   lastLogonTimestamp=("greater", 0),
999                                   userAccountControl=
1000                                     dsdb.UF_NORMAL_ACCOUNT,
1001                                   msDSUserAccountControlComputed=0)
1002         badPasswordTime = int(res[0]["badPasswordTime"][0])
1003         lastLogon = int(res[0]["lastLogon"][0])
1004
1005         # Change password on a connection as another user
1006
1007         # Wrong old password
1008         try:
1009             self.ldb3.modify_ldif("""
1010 dn: cn=testuser,cn=users,""" + self.base_dn + """
1011 changetype: modify
1012 delete: unicodePwd
1013 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1014 add: unicodePwd
1015 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1016 """)
1017             self.fail()
1018         except LdbError, (num, msg):
1019             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1020             self.assertTrue('00000056' in msg, msg)
1021
1022         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1023                                   badPwdCount=1,
1024                                   badPasswordTime=("greater", badPasswordTime),
1025                                   lastLogon=lastLogon,
1026                                   lastLogonTimestamp=lastLogon,
1027                                   userAccountControl=
1028                                     dsdb.UF_NORMAL_ACCOUNT,
1029                                   msDSUserAccountControlComputed=0)
1030         badPasswordTime = int(res[0]["badPasswordTime"][0])
1031
1032         # Correct old password
1033         self.ldb3.modify_ldif("""
1034 dn: cn=testuser,cn=users,""" + self.base_dn + """
1035 changetype: modify
1036 delete: unicodePwd
1037 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
1038 add: unicodePwd
1039 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1040 """)
1041
1042         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1043                                   badPwdCount=1,
1044                                   badPasswordTime=badPasswordTime,
1045                                   lastLogon=lastLogon,
1046                                   lastLogonTimestamp=lastLogon,
1047                                   userAccountControl=
1048                                     dsdb.UF_NORMAL_ACCOUNT,
1049                                   msDSUserAccountControlComputed=0)
1050
1051         # Wrong old password
1052         try:
1053             self.ldb3.modify_ldif("""
1054 dn: cn=testuser,cn=users,""" + self.base_dn + """
1055 changetype: modify
1056 delete: unicodePwd
1057 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
1058 add: unicodePwd
1059 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1060 """)
1061             self.fail()
1062         except LdbError, (num, msg):
1063             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1064             self.assertTrue('00000056' in msg, msg)
1065
1066         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1067                                   badPwdCount=2,
1068                                   badPasswordTime=("greater", badPasswordTime),
1069                                   lastLogon=lastLogon,
1070                                   lastLogonTimestamp=lastLogon,
1071                                   userAccountControl=
1072                                     dsdb.UF_NORMAL_ACCOUNT,
1073                                   msDSUserAccountControlComputed=0)
1074         badPasswordTime = int(res[0]["badPasswordTime"][0])
1075
1076         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1077         # It doesn't create "lockoutTime" = 0 and doesn't
1078         # reset "badPwdCount" = 0.
1079         self._reset_samr(res)
1080
1081         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1082                                   badPwdCount=2,
1083                                   badPasswordTime=badPasswordTime,
1084                                   lastLogon=lastLogon,
1085                                   lastLogonTimestamp=lastLogon,
1086                                   userAccountControl=
1087                                     dsdb.UF_NORMAL_ACCOUNT,
1088                                   msDSUserAccountControlComputed=0)
1089
1090         print "two failed password change"
1091
1092         # Wrong old password
1093         try:
1094             self.ldb3.modify_ldif("""
1095 dn: cn=testuser,cn=users,""" + self.base_dn + """
1096 changetype: modify
1097 delete: unicodePwd
1098 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1099 add: unicodePwd
1100 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1101 """)
1102             self.fail()
1103         except LdbError, (num, msg):
1104             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1105             self.assertTrue('00000056' in msg, msg)
1106
1107         # this is strange, why do we have lockoutTime=badPasswordTime here?
1108         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1109                                   badPwdCount=3,
1110                                   badPasswordTime=("greater", badPasswordTime),
1111                                   lastLogon=lastLogon,
1112                                   lastLogonTimestamp=lastLogon,
1113                                   lockoutTime=("greater", badPasswordTime),
1114                                   userAccountControl=
1115                                     dsdb.UF_NORMAL_ACCOUNT,
1116                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1117         badPasswordTime = int(res[0]["badPasswordTime"][0])
1118         lockoutTime = int(res[0]["lockoutTime"][0])
1119
1120         # Wrong old password
1121         try:
1122             self.ldb3.modify_ldif("""
1123 dn: cn=testuser,cn=users,""" + self.base_dn + """
1124 changetype: modify
1125 delete: unicodePwd
1126 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1127 add: unicodePwd
1128 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1129 """)
1130             self.fail()
1131         except LdbError, (num, msg):
1132             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1133             self.assertTrue('00000775' in msg, msg)
1134
1135         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1136                                   badPwdCount=3,
1137                                   badPasswordTime=badPasswordTime,
1138                                   lastLogon=lastLogon,
1139                                   lastLogonTimestamp=lastLogon,
1140                                   lockoutTime=lockoutTime,
1141                                   userAccountControl=
1142                                     dsdb.UF_NORMAL_ACCOUNT,
1143                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1144
1145         # Wrong old password
1146         try:
1147             self.ldb3.modify_ldif("""
1148 dn: cn=testuser,cn=users,""" + self.base_dn + """
1149 changetype: modify
1150 delete: unicodePwd
1151 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1152 add: unicodePwd
1153 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1154 """)
1155             self.fail()
1156         except LdbError, (num, msg):
1157             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1158             self.assertTrue('00000775' in msg, msg)
1159
1160         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1161                                   badPwdCount=3,
1162                                   badPasswordTime=badPasswordTime,
1163                                   lastLogon=lastLogon,
1164                                   lastLogonTimestamp=lastLogon,
1165                                   lockoutTime=lockoutTime,
1166                                   userAccountControl=
1167                                     dsdb.UF_NORMAL_ACCOUNT,
1168                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1169
1170         try:
1171             # Correct old password
1172             self.ldb3.modify_ldif("""
1173 dn: cn=testuser,cn=users,""" + self.base_dn + """
1174 changetype: modify
1175 delete: unicodePwd
1176 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1177 add: unicodePwd
1178 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
1179 """)
1180             self.fail()
1181         except LdbError, (num, msg):
1182             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1183             self.assertTrue('00000775' in msg, msg)
1184
1185         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1186                                   badPwdCount=3,
1187                                   badPasswordTime=badPasswordTime,
1188                                   lastLogon=lastLogon,
1189                                   lastLogonTimestamp=lastLogon,
1190                                   lockoutTime=lockoutTime,
1191                                   userAccountControl=
1192                                     dsdb.UF_NORMAL_ACCOUNT,
1193                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1194
1195         # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
1196         self._reset_samr(res);
1197
1198         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1199                                   badPwdCount=0,
1200                                   badPasswordTime=badPasswordTime,
1201                                   lastLogon=lastLogon,
1202                                   lastLogonTimestamp=lastLogon,
1203                                   lockoutTime=0,
1204                                   userAccountControl=
1205                                     dsdb.UF_NORMAL_ACCOUNT,
1206                                   msDSUserAccountControlComputed=0)
1207
1208         # Correct old password
1209         self.ldb3.modify_ldif("""
1210 dn: cn=testuser,cn=users,""" + self.base_dn + """
1211 changetype: modify
1212 delete: unicodePwd
1213 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1214 add: unicodePwd
1215 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
1216 """)
1217
1218         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1219                                   badPwdCount=0,
1220                                   badPasswordTime=badPasswordTime,
1221                                   lastLogon=lastLogon,
1222                                   lastLogonTimestamp=lastLogon,
1223                                   lockoutTime=0,
1224                                   userAccountControl=
1225                                     dsdb.UF_NORMAL_ACCOUNT,
1226                                   msDSUserAccountControlComputed=0)
1227
1228         # Wrong old password
1229         try:
1230             self.ldb3.modify_ldif("""
1231 dn: cn=testuser,cn=users,""" + self.base_dn + """
1232 changetype: modify
1233 delete: unicodePwd
1234 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1235 add: unicodePwd
1236 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1237 """)
1238             self.fail()
1239         except LdbError, (num, msg):
1240             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1241             self.assertTrue('00000056' in msg, msg)
1242
1243         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1244                                   badPwdCount=1,
1245                                   badPasswordTime=("greater", badPasswordTime),
1246                                   lastLogon=lastLogon,
1247                                   lastLogonTimestamp=lastLogon,
1248                                   lockoutTime=0,
1249                                   userAccountControl=
1250                                     dsdb.UF_NORMAL_ACCOUNT,
1251                                   msDSUserAccountControlComputed=0)
1252         badPasswordTime = int(res[0]["badPasswordTime"][0])
1253
1254         # Wrong old password
1255         try:
1256             self.ldb3.modify_ldif("""
1257 dn: cn=testuser,cn=users,""" + self.base_dn + """
1258 changetype: modify
1259 delete: unicodePwd
1260 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1261 add: unicodePwd
1262 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1263 """)
1264             self.fail()
1265         except LdbError, (num, msg):
1266             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1267             self.assertTrue('00000056' in msg, msg)
1268
1269         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1270                                   badPwdCount=2,
1271                                   badPasswordTime=("greater", badPasswordTime),
1272                                   lastLogon=lastLogon,
1273                                   lastLogonTimestamp=lastLogon,
1274                                   lockoutTime=0,
1275                                   userAccountControl=
1276                                     dsdb.UF_NORMAL_ACCOUNT,
1277                                   msDSUserAccountControlComputed=0)
1278         badPasswordTime = int(res[0]["badPasswordTime"][0])
1279
1280         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1281         # It doesn't reset "badPwdCount" = 0.
1282         self._reset_samr(res)
1283
1284         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1285                                   badPwdCount=2,
1286                                   badPasswordTime=badPasswordTime,
1287                                   lastLogon=lastLogon,
1288                                   lastLogonTimestamp=lastLogon,
1289                                   lockoutTime=0,
1290                                   userAccountControl=
1291                                     dsdb.UF_NORMAL_ACCOUNT,
1292                                   msDSUserAccountControlComputed=0)
1293
1294         # Wrong old password
1295         try:
1296             self.ldb3.modify_ldif("""
1297 dn: cn=testuser,cn=users,""" + self.base_dn + """
1298 changetype: modify
1299 delete: unicodePwd
1300 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1301 add: unicodePwd
1302 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1303 """)
1304             self.fail()
1305         except LdbError, (num, msg):
1306             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1307             self.assertTrue('00000056' in msg, msg)
1308
1309         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1310                                   badPwdCount=3,
1311                                   badPasswordTime=("greater", badPasswordTime),
1312                                   lastLogon=lastLogon,
1313                                   lastLogonTimestamp=lastLogon,
1314                                   lockoutTime=("greater", badPasswordTime),
1315                                   userAccountControl=
1316                                     dsdb.UF_NORMAL_ACCOUNT,
1317                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1318         badPasswordTime = int(res[0]["badPasswordTime"][0])
1319         lockoutTime = int(res[0]["lockoutTime"][0])
1320
1321         time.sleep(self.account_lockout_duration + 1)
1322
1323         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1324                                   badPwdCount=3, effective_bad_password_count=0,
1325                                   badPasswordTime=badPasswordTime,
1326                                   lastLogon=lastLogon,
1327                                   lastLogonTimestamp=lastLogon,
1328                                   lockoutTime=lockoutTime,
1329                                   userAccountControl=
1330                                     dsdb.UF_NORMAL_ACCOUNT,
1331                                   msDSUserAccountControlComputed=0)
1332
1333         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1334         # It doesn't reset "lockoutTime" = 0 and doesn't
1335         # reset "badPwdCount" = 0.
1336         self._reset_samr(res)
1337
1338         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1339                                   badPwdCount=3, effective_bad_password_count=0,
1340                                   badPasswordTime=badPasswordTime,
1341                                   lockoutTime=lockoutTime,
1342                                   lastLogon=lastLogon,
1343                                   lastLogonTimestamp=lastLogon,
1344                                   userAccountControl=
1345                                     dsdb.UF_NORMAL_ACCOUNT,
1346                                   msDSUserAccountControlComputed=0)
1347
1348     def _test_login_lockout(self, use_kerberos):
1349         # This unlocks by waiting for account_lockout_duration
1350         if use_kerberos == MUST_USE_KERBEROS:
1351             lastlogon_relation = 'greater'
1352             print "Performs a lockout attempt against LDAP using Kerberos"
1353         else:
1354             lastlogon_relation = 'equal'
1355             print "Performs a lockout attempt against LDAP using NTLM"
1356
1357         # Change password on a connection as another user
1358         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1359                                   badPwdCount=0,
1360                                   badPasswordTime=("greater", 0),
1361                                   lastLogon=("greater", 0),
1362                                   lastLogonTimestamp=("greater", 0),
1363                                   userAccountControl=
1364                                     dsdb.UF_NORMAL_ACCOUNT,
1365                                   msDSUserAccountControlComputed=0)
1366         badPasswordTime = int(res[0]["badPasswordTime"][0])
1367         lastLogon = int(res[0]["lastLogon"][0])
1368         firstLogon = lastLogon
1369         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1370         print firstLogon
1371         print lastLogonTimestamp
1372
1373
1374         self.assertGreater(lastLogon, badPasswordTime)
1375
1376         # Open a second LDB connection with the user credentials. Use the
1377         # command line credentials for informations like the domain, the realm
1378         # and the workstation.
1379         creds_lockout = insta_creds()
1380         creds_lockout.set_kerberos_state(use_kerberos)
1381
1382         # The wrong password
1383         creds_lockout.set_password("thatsAcomplPASS1x")
1384
1385         self.assertLoginFailure(host_url, creds_lockout, lp)
1386
1387         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1388                                   badPwdCount=1,
1389                                   badPasswordTime=("greater", badPasswordTime),
1390                                   lastLogon=lastLogon,
1391                                   lastLogonTimestamp=lastLogonTimestamp,
1392                                   userAccountControl=
1393                                     dsdb.UF_NORMAL_ACCOUNT,
1394                                   msDSUserAccountControlComputed=0,
1395                                   msg='lastlogontimestamp with wrong password')
1396         badPasswordTime = int(res[0]["badPasswordTime"][0])
1397
1398         # Correct old password
1399         creds_lockout.set_password("thatsAcomplPASS1")
1400
1401         ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1402
1403         # lastLogonTimestamp should not change
1404         # lastLogon increases if badPwdCount is non-zero (!)
1405         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1406                                   badPwdCount=0,
1407                                   badPasswordTime=badPasswordTime,
1408                                   lastLogon=('greater', lastLogon),
1409                                   lastLogonTimestamp=lastLogonTimestamp,
1410                                   userAccountControl=
1411                                     dsdb.UF_NORMAL_ACCOUNT,
1412                                   msDSUserAccountControlComputed=0,
1413                                   msg='LLTimestamp is updated to lastlogon')
1414
1415         lastLogon = int(res[0]["lastLogon"][0])
1416         self.assertGreater(lastLogon, badPasswordTime)
1417
1418         # The wrong password
1419         creds_lockout.set_password("thatsAcomplPASS1x")
1420
1421         self.assertLoginFailure(host_url, creds_lockout, lp)
1422
1423         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1424                                   badPwdCount=1,
1425                                   badPasswordTime=("greater", badPasswordTime),
1426                                   lastLogon=lastLogon,
1427                                   lastLogonTimestamp=lastLogonTimestamp,
1428                                   userAccountControl=
1429                                     dsdb.UF_NORMAL_ACCOUNT,
1430                                   msDSUserAccountControlComputed=0)
1431         badPasswordTime = int(res[0]["badPasswordTime"][0])
1432
1433         # The wrong password
1434         creds_lockout.set_password("thatsAcomplPASS1x")
1435
1436         try:
1437             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1438             self.fail()
1439
1440         except LdbError, (num, msg):
1441             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1442
1443         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1444                                   badPwdCount=2,
1445                                   badPasswordTime=("greater", badPasswordTime),
1446                                   lastLogon=lastLogon,
1447                                   lastLogonTimestamp=lastLogonTimestamp,
1448                                   userAccountControl=
1449                                     dsdb.UF_NORMAL_ACCOUNT,
1450                                   msDSUserAccountControlComputed=0)
1451         badPasswordTime = int(res[0]["badPasswordTime"][0])
1452
1453         print "two failed password change"
1454
1455         # The wrong password
1456         creds_lockout.set_password("thatsAcomplPASS1x")
1457
1458         try:
1459             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1460             self.fail()
1461
1462         except LdbError, (num, msg):
1463             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1464
1465         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1466                                   badPwdCount=3,
1467                                   badPasswordTime=("greater", badPasswordTime),
1468                                   lastLogon=lastLogon,
1469                                   lastLogonTimestamp=lastLogonTimestamp,
1470                                   lockoutTime=("greater", badPasswordTime),
1471                                   userAccountControl=
1472                                     dsdb.UF_NORMAL_ACCOUNT,
1473                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1474         badPasswordTime = int(res[0]["badPasswordTime"][0])
1475         lockoutTime = int(res[0]["lockoutTime"][0])
1476
1477         # The wrong password
1478         creds_lockout.set_password("thatsAcomplPASS1x")
1479         try:
1480             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1481             self.fail()
1482         except LdbError, (num, msg):
1483             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1484
1485         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1486                                   badPwdCount=3,
1487                                   badPasswordTime=badPasswordTime,
1488                                   lastLogon=lastLogon,
1489                                   lastLogonTimestamp=lastLogonTimestamp,
1490                                   lockoutTime=lockoutTime,
1491                                   userAccountControl=
1492                                     dsdb.UF_NORMAL_ACCOUNT,
1493                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1494
1495         # The wrong password
1496         creds_lockout.set_password("thatsAcomplPASS1x")
1497         try:
1498             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1499             self.fail()
1500         except LdbError, (num, msg):
1501             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1502
1503         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1504                                   badPwdCount=3,
1505                                   badPasswordTime=badPasswordTime,
1506                                   lastLogon=lastLogon,
1507                                   lastLogonTimestamp=lastLogonTimestamp,
1508                                   lockoutTime=lockoutTime,
1509                                   userAccountControl=
1510                                     dsdb.UF_NORMAL_ACCOUNT,
1511                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1512
1513         # The correct password, but we are locked out
1514         creds_lockout.set_password("thatsAcomplPASS1")
1515         try:
1516             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1517             self.fail()
1518         except LdbError, (num, msg):
1519             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1520
1521         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1522                                   badPwdCount=3,
1523                                   badPasswordTime=badPasswordTime,
1524                                   lastLogon=lastLogon,
1525                                   lastLogonTimestamp=lastLogonTimestamp,
1526                                   lockoutTime=lockoutTime,
1527                                   userAccountControl=
1528                                     dsdb.UF_NORMAL_ACCOUNT,
1529                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1530
1531         # wait for the lockout to end
1532         time.sleep(self.account_lockout_duration + 1)
1533         print self.account_lockout_duration + 1
1534
1535         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1536                                   badPwdCount=3, effective_bad_password_count=0,
1537                                   badPasswordTime=badPasswordTime,
1538                                   lockoutTime=lockoutTime,
1539                                   lastLogon=lastLogon,
1540                                   lastLogonTimestamp=lastLogonTimestamp,
1541                                   userAccountControl=
1542                                     dsdb.UF_NORMAL_ACCOUNT,
1543                                   msDSUserAccountControlComputed=0)
1544
1545         lastLogon = int(res[0]["lastLogon"][0])
1546
1547         # The correct password after letting the timeout expire
1548
1549         creds_lockout.set_password("thatsAcomplPASS1")
1550
1551         creds_lockout2 = insta_creds(creds_lockout)
1552
1553         ldb_lockout = SamDB(url=host_url, credentials=creds_lockout2, lp=lp)
1554         time.sleep(3)
1555
1556         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1557                                   badPwdCount=0,
1558                                   badPasswordTime=badPasswordTime,
1559                                   lastLogon=(lastlogon_relation, lastLogon),
1560                                   lastLogonTimestamp=lastLogonTimestamp,
1561                                   lockoutTime=0,
1562                                   userAccountControl=
1563                                     dsdb.UF_NORMAL_ACCOUNT,
1564                                   msDSUserAccountControlComputed=0,
1565                                   msg="lastLogon is way off")
1566
1567         lastLogon = int(res[0]["lastLogon"][0])
1568
1569         # The wrong password
1570         creds_lockout.set_password("thatsAcomplPASS1x")
1571         try:
1572             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1573             self.fail()
1574         except LdbError, (num, msg):
1575             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1576
1577         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1578                                   badPwdCount=1,
1579                                   badPasswordTime=("greater", badPasswordTime),
1580                                   lockoutTime=0,
1581                                   lastLogon=lastLogon,
1582                                   lastLogonTimestamp=lastLogonTimestamp,
1583                                   userAccountControl=
1584                                     dsdb.UF_NORMAL_ACCOUNT,
1585                                   msDSUserAccountControlComputed=0)
1586         badPasswordTime = int(res[0]["badPasswordTime"][0])
1587
1588         # The wrong password
1589         creds_lockout.set_password("thatsAcomplPASS1x")
1590         try:
1591             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1592             self.fail()
1593         except LdbError, (num, msg):
1594             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1595
1596         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1597                                   badPwdCount=2,
1598                                   badPasswordTime=("greater", badPasswordTime),
1599                                   lockoutTime=0,
1600                                   lastLogon=lastLogon,
1601                                   lastLogonTimestamp=lastLogonTimestamp,
1602                                   userAccountControl=
1603                                     dsdb.UF_NORMAL_ACCOUNT,
1604                                   msDSUserAccountControlComputed=0)
1605         badPasswordTime = int(res[0]["badPasswordTime"][0])
1606
1607         time.sleep(self.lockout_observation_window + 1)
1608
1609         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1610                                   badPwdCount=2, effective_bad_password_count=0,
1611                                   badPasswordTime=badPasswordTime,
1612                                   lockoutTime=0,
1613                                   lastLogon=lastLogon,
1614                                   lastLogonTimestamp=lastLogonTimestamp,
1615                                   userAccountControl=
1616                                     dsdb.UF_NORMAL_ACCOUNT,
1617                                   msDSUserAccountControlComputed=0)
1618
1619         # The wrong password
1620         creds_lockout.set_password("thatsAcomplPASS1x")
1621         try:
1622             ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1623             self.fail()
1624         except LdbError, (num, msg):
1625             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1626
1627         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1628                                   badPwdCount=1,
1629                                   badPasswordTime=("greater", badPasswordTime),
1630                                   lockoutTime=0,
1631                                   lastLogon=lastLogon,
1632                                   lastLogonTimestamp=lastLogonTimestamp,
1633                                   userAccountControl=
1634                                     dsdb.UF_NORMAL_ACCOUNT,
1635                                   msDSUserAccountControlComputed=0)
1636         badPasswordTime = int(res[0]["badPasswordTime"][0])
1637
1638         # The correct password without letting the timeout expire
1639         creds_lockout.set_password("thatsAcomplPASS1")
1640         ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1641
1642         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1643                                   badPwdCount=0,
1644                                   badPasswordTime=badPasswordTime,
1645                                   lockoutTime=0,
1646                                   lastLogon=("greater", lastLogon),
1647                                   lastLogonTimestamp=lastLogonTimestamp,
1648                                   userAccountControl=
1649                                     dsdb.UF_NORMAL_ACCOUNT,
1650                                   msDSUserAccountControlComputed=0)
1651
1652     def test_login_lockout_ntlm(self):
1653         self._test_login_lockout(DONT_USE_KERBEROS)
1654
1655     def test_login_lockout_kerberos(self):
1656         self._test_login_lockout(MUST_USE_KERBEROS)
1657
1658     def _test_multiple_logon(self, use_kerberos):
1659         # Test the happy case in which a user logs on correctly, then
1660         # logs on correctly again, so that the bad password and
1661         # lockout times are both zero the second time. The lastlogon
1662         # time should increase.
1663
1664         # Open a second LDB connection with the user credentials. Use the
1665         # command line credentials for informations like the domain, the realm
1666         # and the workstation.
1667         creds2 = insta_creds()
1668         creds2.set_kerberos_state(use_kerberos)
1669         self.assertEqual(creds2.get_kerberos_state(), use_kerberos)
1670
1671         if use_kerberos == MUST_USE_KERBEROS:
1672             print "Testing multiple logon with Kerberos"
1673             lastlogon_relation = 'greater'
1674         else:
1675             print "Testing multiple logon with NTLM"
1676             lastlogon_relation = 'equal'
1677
1678         SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
1679
1680         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1681                                   badPwdCount=0,
1682                                   badPasswordTime=("greater", 0),
1683                                   lastLogon=("greater", 0),
1684                                   lastLogonTimestamp=("greater", 0),
1685                                   userAccountControl=
1686                                     dsdb.UF_NORMAL_ACCOUNT,
1687                                   msDSUserAccountControlComputed=0)
1688         badPasswordTime = int(res[0]["badPasswordTime"][0])
1689         lastLogon = int(res[0]["lastLogon"][0])
1690         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1691         firstLogon = lastLogon
1692         print "last logon is %d" % lastLogon
1693         self.assertGreater(lastLogon, badPasswordTime)
1694
1695         time.sleep(1)
1696         SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
1697
1698         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1699                                   badPwdCount=0,
1700                                   badPasswordTime=badPasswordTime,
1701                                   lastLogon=(lastlogon_relation, lastLogon),
1702                                   lastLogonTimestamp=lastLogonTimestamp,
1703                                   userAccountControl=
1704                                   dsdb.UF_NORMAL_ACCOUNT,
1705                                   msDSUserAccountControlComputed=0,
1706                                   msg=("second logon, firstlogon was %s" %
1707                                        firstLogon))
1708
1709
1710         lastLogon = int(res[0]["lastLogon"][0])
1711
1712         time.sleep(1)
1713
1714         SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
1715
1716         res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1717                                   badPwdCount=0,
1718                                   badPasswordTime=badPasswordTime,
1719                                   lastLogon=(lastlogon_relation, lastLogon),
1720                                   lastLogonTimestamp=lastLogonTimestamp,
1721                                   userAccountControl=
1722                                     dsdb.UF_NORMAL_ACCOUNT,
1723                                   msDSUserAccountControlComputed=0)
1724
1725     def test_multiple_logon_ntlm(self):
1726         self._test_multiple_logon(DONT_USE_KERBEROS)
1727
1728     def test_multiple_logon_kerberos(self):
1729         self._test_multiple_logon(MUST_USE_KERBEROS)
1730
1731     def tearDown(self):
1732         super(PasswordTests, self).tearDown()
1733         delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
1734         delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
1735         delete_force(self.ldb, "cn=testuser3,cn=users," + self.base_dn)
1736         # Close the second LDB connection (with the user credentials)
1737         self.ldb2 = None
1738
1739 host_url = "ldap://%s" % host
1740
1741 TestProgram(module=__name__, opts=subunitopts)