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