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