pytests: heed assertEquals deprecation warning en-masse
[samba.git] / source4 / dsdb / tests / python / password_lockout_base.py
1 from __future__ import print_function
2 import samba
3
4 from samba.auth import system_session
5 from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS
6 from ldb import SCOPE_BASE, LdbError
7 from ldb import ERR_CONSTRAINT_VIOLATION
8 from ldb import ERR_INVALID_CREDENTIALS
9 from ldb import Message, MessageElement, Dn
10 from ldb import FLAG_MOD_REPLACE
11 from samba import gensec, dsdb
12 from samba.samdb import SamDB
13 import samba.tests
14 from samba.tests import delete_force
15 from samba.dcerpc import security, samr
16 from samba.ndr import ndr_unpack
17 from samba.tests.password_test import PasswordTestCase
18
19 import time
20
21
22 class BasePasswordTestCase(PasswordTestCase):
23     def _open_samr_user(self, res):
24         self.assertTrue("objectSid" in res[0])
25
26         (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split()
27         self.assertEqual(self.domain_sid, domain_sid)
28
29         return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid)
30
31     def _check_attribute(self, res, name, value):
32         if value is None:
33             self.assertTrue(name not in res[0],
34                             msg="attr[%s]=%r on dn[%s]" %
35                             (name, res[0], res[0].dn))
36             return
37
38         if isinstance(value, tuple):
39             (mode, value) = value
40         else:
41             mode = "equal"
42
43         if mode == "ignore":
44             return
45
46         if mode == "absent":
47             self.assertFalse(name in res[0],
48                              msg="attr[%s] not missing on dn[%s]" %
49                              (name, res[0].dn))
50             return
51
52         self.assertTrue(name in res[0],
53                         msg="attr[%s] missing on dn[%s]" %
54                         (name, res[0].dn))
55         self.assertTrue(len(res[0][name]) == 1,
56                         msg="attr[%s]=%r on dn[%s]" %
57                         (name, res[0][name], res[0].dn))
58
59         print("%s = '%s'" % (name, res[0][name][0]))
60
61         if mode == "present":
62             return
63
64         if mode == "equal":
65             v = int(res[0][name][0])
66             value = int(value)
67             msg = ("attr[%s]=[%s] != [%s] on dn[%s]\n"
68                    "(diff %d; actual value is %s than expected)"  %
69                    (name, v, value, res[0].dn, v - value,
70                     ('less' if v < value else 'greater')))
71
72             self.assertTrue(v == value, msg)
73             return
74
75         if mode == "greater":
76             v = int(res[0][name][0])
77             self.assertTrue(v > int(value),
78                             msg="attr[%s]=[%s] <= [%s] on dn[%s] (diff %d)" %
79                             (name, v, int(value), res[0].dn, v - int(value)))
80             return
81         if mode == "less":
82             v = int(res[0][name][0])
83             self.assertTrue(v < int(value),
84                             msg="attr[%s]=[%s] >= [%s] on dn[%s] (diff %d)" %
85                             (name, v, int(value), res[0].dn, v - int(value)))
86             return
87         self.assertEqual(mode, not mode, "Invalid Mode[%s]" % mode)
88
89     def _check_account_initial(self, userdn):
90         self._check_account(userdn,
91                             badPwdCount=0,
92                             badPasswordTime=0,
93                             logonCount=0,
94                             lastLogon=0,
95                             lastLogonTimestamp=("absent", None),
96                             userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
97                             msDSUserAccountControlComputed=0)
98
99     def _check_account(self, dn,
100                        badPwdCount=None,
101                        badPasswordTime=None,
102                        logonCount=None,
103                        lastLogon=None,
104                        lastLogonTimestamp=None,
105                        lockoutTime=None,
106                        userAccountControl=None,
107                        msDSUserAccountControlComputed=None,
108                        effective_bad_password_count=None,
109                        msg=None,
110                        badPwdCountOnly=False):
111         print('-=' * 36)
112         if msg is not None:
113             print("\033[01;32m %s \033[00m\n" % msg)
114         attrs = [
115             "objectSid",
116            "badPwdCount",
117            "badPasswordTime",
118            "lastLogon",
119            "lastLogonTimestamp",
120            "logonCount",
121            "lockoutTime",
122            "userAccountControl",
123            "msDS-User-Account-Control-Computed"
124         ]
125
126         # in order to prevent some time resolution problems we sleep for
127         # 10 micro second
128         time.sleep(0.01)
129
130         res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
131         self.assertTrue(len(res) == 1)
132         self._check_attribute(res, "badPwdCount", badPwdCount)
133         self._check_attribute(res, "lockoutTime", lockoutTime)
134         self._check_attribute(res, "badPasswordTime", badPasswordTime)
135         if not badPwdCountOnly:
136             self._check_attribute(res, "logonCount", logonCount)
137             self._check_attribute(res, "lastLogon", lastLogon)
138             self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp)
139             self._check_attribute(res, "userAccountControl", userAccountControl)
140             self._check_attribute(res, "msDS-User-Account-Control-Computed",
141                                   msDSUserAccountControlComputed)
142
143             lastLogon = int(res[0]["lastLogon"][0])
144             logonCount = int(res[0]["logonCount"][0])
145
146         samr_user = self._open_samr_user(res)
147         uinfo3 = self.samr.QueryUserInfo(samr_user, 3)
148         uinfo5 = self.samr.QueryUserInfo(samr_user, 5)
149         uinfo16 = self.samr.QueryUserInfo(samr_user, 16)
150         uinfo21 = self.samr.QueryUserInfo(samr_user, 21)
151         self.samr.Close(samr_user)
152
153         expected_acb_info = 0
154         if not badPwdCountOnly:
155             if userAccountControl & dsdb.UF_NORMAL_ACCOUNT:
156                 expected_acb_info |= samr.ACB_NORMAL
157             if userAccountControl & dsdb.UF_ACCOUNTDISABLE:
158                 expected_acb_info |= samr.ACB_DISABLED
159             if userAccountControl & dsdb.UF_PASSWD_NOTREQD:
160                 expected_acb_info |= samr.ACB_PWNOTREQ
161             if msDSUserAccountControlComputed & dsdb.UF_LOCKOUT:
162                 expected_acb_info |= samr.ACB_AUTOLOCK
163             if msDSUserAccountControlComputed & dsdb.UF_PASSWORD_EXPIRED:
164                 expected_acb_info |= samr.ACB_PW_EXPIRED
165
166             self.assertEqual(uinfo3.acct_flags, expected_acb_info)
167             self.assertEqual(uinfo3.last_logon, lastLogon)
168             self.assertEqual(uinfo3.logon_count, logonCount)
169
170         expected_bad_password_count = 0
171         if badPwdCount is not None:
172             expected_bad_password_count = badPwdCount
173         if effective_bad_password_count is None:
174             effective_bad_password_count = expected_bad_password_count
175
176         self.assertEqual(uinfo3.bad_password_count, expected_bad_password_count)
177
178         if not badPwdCountOnly:
179             self.assertEqual(uinfo5.acct_flags, expected_acb_info)
180             self.assertEqual(uinfo5.bad_password_count, effective_bad_password_count)
181             self.assertEqual(uinfo5.last_logon, lastLogon)
182             self.assertEqual(uinfo5.logon_count, logonCount)
183
184             self.assertEqual(uinfo16.acct_flags, expected_acb_info)
185
186             self.assertEqual(uinfo21.acct_flags, expected_acb_info)
187             self.assertEqual(uinfo21.bad_password_count, effective_bad_password_count)
188             self.assertEqual(uinfo21.last_logon, lastLogon)
189             self.assertEqual(uinfo21.logon_count, logonCount)
190
191         # check LDAP again and make sure the samr.QueryUserInfo
192         # doesn't have any impact.
193         res2 = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
194         self.assertEqual(res[0], res2[0])
195
196         # in order to prevent some time resolution problems we sleep for
197         # 10 micro second
198         time.sleep(0.01)
199         return res
200
201     def update_lockout_settings(self, threshold, duration, observation_window):
202         """Updates the global user lockout settings"""
203         m = Message()
204         m.dn = Dn(self.ldb, self.base_dn)
205         account_lockout_duration_ticks = -int(duration * (1e7))
206         m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
207                                               FLAG_MOD_REPLACE, "lockoutDuration")
208         m["lockoutThreshold"] = MessageElement(str(threshold),
209                                                FLAG_MOD_REPLACE, "lockoutThreshold")
210         lockout_observation_window_ticks = -int(observation_window * (1e7))
211         m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
212                                                        FLAG_MOD_REPLACE, "lockOutObservationWindow")
213         self.ldb.modify(m)
214
215     def _readd_user(self, creds, lockOutObservationWindow=0):
216         username = creds.get_username()
217         userpass = creds.get_password()
218         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
219
220         delete_force(self.ldb, userdn)
221         self.ldb.add({
222              "dn": userdn,
223              "objectclass": "user",
224              "sAMAccountName": username})
225
226         self.addCleanup(delete_force, self.ldb, userdn)
227
228         # Sets the initial user password with a "special" password change
229         # I think that this internally is a password set operation and it can
230         # only be performed by someone which has password set privileges on the
231         # account (at least in s4 we do handle it like that).
232         self.ldb.modify_ldif("""
233 dn: """ + userdn + """
234 changetype: modify
235 delete: userPassword
236 add: userPassword
237 userPassword: """ + userpass + """
238 """)
239         # Enables the user account
240         self.ldb.enable_account("(sAMAccountName=%s)" % username)
241
242         use_kerberos = creds.get_kerberos_state()
243         fail_creds = self.insta_creds(self.template_creds,
244                                       username=username,
245                                       userpass=userpass + "X",
246                                       kerberos_state=use_kerberos)
247         self._check_account_initial(userdn)
248
249         # Fail once to get a badPasswordTime
250         try:
251             ldb = SamDB(url=self.host_url, credentials=fail_creds, lp=self.lp)
252             self.fail()
253         except LdbError as e:
254             (num, msg) = e.args
255             self.assertEqual(num, ERR_INVALID_CREDENTIALS)
256
257         # Succeed to reset everything to 0
258         ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp)
259
260         return ldb
261
262     def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS):
263         try:
264             ldb = SamDB(url=url, credentials=creds, lp=lp)
265             self.fail("Login unexpectedly succeeded")
266         except LdbError as e1:
267             (num, msg) = e1.args
268             if errno is not None:
269                 self.assertEqual(num, errno, ("Login failed in the wrong way"
270                                                "(got err %d, expected %d)" %
271                                                (num, errno)))
272
273     def setUp(self):
274         super(BasePasswordTestCase, self).setUp()
275
276         self.global_creds.set_gensec_features(self.global_creds.get_gensec_features() |
277                                               gensec.FEATURE_SEAL)
278
279         self.template_creds = Credentials()
280         self.template_creds.set_username("testuser")
281         self.template_creds.set_password("thatsAcomplPASS1")
282         self.template_creds.set_domain(self.global_creds.get_domain())
283         self.template_creds.set_realm(self.global_creds.get_realm())
284         self.template_creds.set_workstation(self.global_creds.get_workstation())
285         self.template_creds.set_gensec_features(self.global_creds.get_gensec_features())
286         self.template_creds.set_kerberos_state(self.global_creds.get_kerberos_state())
287
288         # Gets back the basedn
289         base_dn = self.ldb.domain_dn()
290
291         # Gets back the configuration basedn
292         configuration_dn = self.ldb.get_config_basedn().get_linearized()
293
294         res = self.ldb.search(base_dn,
295                               scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
296
297         if "lockoutDuration" in res[0]:
298             lockoutDuration = res[0]["lockoutDuration"][0]
299         else:
300             lockoutDuration = 0
301
302         if "lockoutObservationWindow" in res[0]:
303             lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
304         else:
305             lockoutObservationWindow = 0
306
307         if "lockoutThreshold" in res[0]:
308             lockoutThreshold = res[0]["lockoutThreshold"][0]
309         else:
310             lockoutTreshold = 0
311
312         self.addCleanup(self.ldb.modify_ldif, """
313 dn: """ + base_dn + """
314 changetype: modify
315 replace: lockoutDuration
316 lockoutDuration: """ + str(lockoutDuration) + """
317 replace: lockoutObservationWindow
318 lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
319 replace: lockoutThreshold
320 lockoutThreshold: """ + str(lockoutThreshold) + """
321 """)
322
323         self.base_dn = self.ldb.domain_dn()
324
325         #
326         # Some test cases sleep() for self.account_lockout_duration
327         # so allow it to be controlled via the subclass
328         #
329         if not hasattr(self, 'account_lockout_duration'):
330             self.account_lockout_duration = 3
331         if not hasattr(self, 'lockout_observation_window'):
332             self.lockout_observation_window = 3
333         self.update_lockout_settings(threshold=3,
334                                      duration=self.account_lockout_duration,
335                                      observation_window=self.lockout_observation_window)
336
337         # update DC to allow password changes for the duration of this test
338         self.allow_password_changes()
339
340         self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
341         self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % self.host, self.lp, self.global_creds)
342         self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
343         self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
344
345         self.addCleanup(self.delete_ldb_connections)
346
347         # (Re)adds the test user accounts
348         self.lockout1krb5_creds = self.insta_creds(self.template_creds,
349                                                    username="lockout1krb5",
350                                                    userpass="thatsAcomplPASS0",
351                                                    kerberos_state=MUST_USE_KERBEROS)
352         self.lockout1krb5_ldb = self._readd_user(self.lockout1krb5_creds)
353         self.lockout1ntlm_creds = self.insta_creds(self.template_creds,
354                                                    username="lockout1ntlm",
355                                                    userpass="thatsAcomplPASS0",
356                                                    kerberos_state=DONT_USE_KERBEROS)
357         self.lockout1ntlm_ldb = self._readd_user(self.lockout1ntlm_creds)
358
359     def delete_ldb_connections(self):
360         del self.lockout1krb5_ldb
361         del self.lockout1ntlm_ldb
362         del self.ldb
363
364     def tearDown(self):
365         super(BasePasswordTestCase, self).tearDown()
366
367     def _test_login_lockout(self, creds, wait_lockout_duration=True):
368         username = creds.get_username()
369         userpass = creds.get_password()
370         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
371
372         use_kerberos = creds.get_kerberos_state()
373         # This unlocks by waiting for account_lockout_duration
374         if use_kerberos == MUST_USE_KERBEROS:
375             logoncount_relation = 'greater'
376             lastlogon_relation = 'greater'
377             print("Performs a lockout attempt against LDAP using Kerberos")
378         else:
379             logoncount_relation = 'equal'
380             lastlogon_relation = 'equal'
381             print("Performs a lockout attempt against LDAP using NTLM")
382
383         # Change password on a connection as another user
384         res = self._check_account(userdn,
385                                   badPwdCount=0,
386                                   badPasswordTime=("greater", 0),
387                                   logonCount=(logoncount_relation, 0),
388                                   lastLogon=("greater", 0),
389                                   lastLogonTimestamp=("greater", 0),
390                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
391                                   msDSUserAccountControlComputed=0)
392         badPasswordTime = int(res[0]["badPasswordTime"][0])
393         logonCount = int(res[0]["logonCount"][0])
394         lastLogon = int(res[0]["lastLogon"][0])
395         firstLogon = lastLogon
396         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
397         print(firstLogon)
398         print(lastLogonTimestamp)
399
400         self.assertGreater(lastLogon, badPasswordTime)
401         self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
402
403         # Open a second LDB connection with the user credentials. Use the
404         # command line credentials for information like the domain, the realm
405         # and the workstation.
406         creds_lockout = self.insta_creds(creds)
407
408         # The wrong password
409         creds_lockout.set_password("thatsAcomplPASS1x")
410
411         self.assertLoginFailure(self.host_url, creds_lockout, self.lp)
412
413         res = self._check_account(userdn,
414                                   badPwdCount=1,
415                                   badPasswordTime=("greater", badPasswordTime),
416                                   logonCount=logonCount,
417                                   lastLogon=lastLogon,
418                                   lastLogonTimestamp=lastLogonTimestamp,
419                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
420                                   msDSUserAccountControlComputed=0,
421                                   msg='lastlogontimestamp with wrong password')
422         badPasswordTime = int(res[0]["badPasswordTime"][0])
423
424         # Correct old password
425         creds_lockout.set_password(userpass)
426
427         ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
428
429         # lastLogonTimestamp should not change
430         # lastLogon increases if badPwdCount is non-zero (!)
431         res = self._check_account(userdn,
432                                   badPwdCount=0,
433                                   badPasswordTime=badPasswordTime,
434                                   logonCount=(logoncount_relation, logonCount),
435                                   lastLogon=('greater', lastLogon),
436                                   lastLogonTimestamp=lastLogonTimestamp,
437                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
438                                   msDSUserAccountControlComputed=0,
439                                   msg='LLTimestamp is updated to lastlogon')
440
441         logonCount = int(res[0]["logonCount"][0])
442         lastLogon = int(res[0]["lastLogon"][0])
443         self.assertGreater(lastLogon, badPasswordTime)
444         self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
445
446         # The wrong password
447         creds_lockout.set_password("thatsAcomplPASS1x")
448
449         self.assertLoginFailure(self.host_url, creds_lockout, self.lp)
450
451         res = self._check_account(userdn,
452                                   badPwdCount=1,
453                                   badPasswordTime=("greater", badPasswordTime),
454                                   logonCount=logonCount,
455                                   lastLogon=lastLogon,
456                                   lastLogonTimestamp=lastLogonTimestamp,
457                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
458                                   msDSUserAccountControlComputed=0)
459         badPasswordTime = int(res[0]["badPasswordTime"][0])
460
461         # The wrong password
462         creds_lockout.set_password("thatsAcomplPASS1x")
463
464         try:
465             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
466             self.fail()
467
468         except LdbError as e2:
469             (num, msg) = e2.args
470             self.assertEqual(num, ERR_INVALID_CREDENTIALS)
471
472         res = self._check_account(userdn,
473                                   badPwdCount=2,
474                                   badPasswordTime=("greater", badPasswordTime),
475                                   logonCount=logonCount,
476                                   lastLogon=lastLogon,
477                                   lastLogonTimestamp=lastLogonTimestamp,
478                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
479                                   msDSUserAccountControlComputed=0)
480         badPasswordTime = int(res[0]["badPasswordTime"][0])
481
482         print("two failed password change")
483
484         # The wrong password
485         creds_lockout.set_password("thatsAcomplPASS1x")
486
487         try:
488             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
489             self.fail()
490
491         except LdbError as e3:
492             (num, msg) = e3.args
493             self.assertEqual(num, ERR_INVALID_CREDENTIALS)
494
495         res = self._check_account(userdn,
496                                   badPwdCount=3,
497                                   badPasswordTime=("greater", badPasswordTime),
498                                   logonCount=logonCount,
499                                   lastLogon=lastLogon,
500                                   lastLogonTimestamp=lastLogonTimestamp,
501                                   lockoutTime=("greater", badPasswordTime),
502                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
503                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
504         badPasswordTime = int(res[0]["badPasswordTime"][0])
505         lockoutTime = int(res[0]["lockoutTime"][0])
506
507         # The wrong password
508         creds_lockout.set_password("thatsAcomplPASS1x")
509         try:
510             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
511             self.fail()
512         except LdbError as e4:
513             (num, msg) = e4.args
514             self.assertEqual(num, ERR_INVALID_CREDENTIALS)
515
516         res = self._check_account(userdn,
517                                   badPwdCount=3,
518                                   badPasswordTime=badPasswordTime,
519                                   logonCount=logonCount,
520                                   lastLogon=lastLogon,
521                                   lastLogonTimestamp=lastLogonTimestamp,
522                                   lockoutTime=lockoutTime,
523                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
524                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
525
526         # The wrong password
527         creds_lockout.set_password("thatsAcomplPASS1x")
528         try:
529             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
530             self.fail()
531         except LdbError as e5:
532             (num, msg) = e5.args
533             self.assertEqual(num, ERR_INVALID_CREDENTIALS)
534
535         res = self._check_account(userdn,
536                                   badPwdCount=3,
537                                   badPasswordTime=badPasswordTime,
538                                   logonCount=logonCount,
539                                   lastLogon=lastLogon,
540                                   lastLogonTimestamp=lastLogonTimestamp,
541                                   lockoutTime=lockoutTime,
542                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
543                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
544
545         # The correct password, but we are locked out
546         creds_lockout.set_password(userpass)
547         try:
548             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
549             self.fail()
550         except LdbError as e6:
551             (num, msg) = e6.args
552             self.assertEqual(num, ERR_INVALID_CREDENTIALS)
553
554         res = self._check_account(userdn,
555                                   badPwdCount=3,
556                                   badPasswordTime=badPasswordTime,
557                                   logonCount=logonCount,
558                                   lastLogon=lastLogon,
559                                   lastLogonTimestamp=lastLogonTimestamp,
560                                   lockoutTime=lockoutTime,
561                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
562                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
563
564         # if we're just checking the user gets locked out, we can stop here
565         if not wait_lockout_duration:
566             return
567
568         # wait for the lockout to end
569         time.sleep(self.account_lockout_duration + 1)
570         print(self.account_lockout_duration + 1)
571
572         res = self._check_account(userdn,
573                                   badPwdCount=3, effective_bad_password_count=0,
574                                   badPasswordTime=badPasswordTime,
575                                   logonCount=logonCount,
576                                   lockoutTime=lockoutTime,
577                                   lastLogon=lastLogon,
578                                   lastLogonTimestamp=lastLogonTimestamp,
579                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
580                                   msDSUserAccountControlComputed=0)
581
582         # The correct password after letting the timeout expire
583
584         creds_lockout.set_password(userpass)
585
586         creds_lockout2 = self.insta_creds(creds_lockout)
587
588         ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout2, lp=self.lp)
589         time.sleep(3)
590
591         res = self._check_account(userdn,
592                                   badPwdCount=0,
593                                   badPasswordTime=badPasswordTime,
594                                   logonCount=(logoncount_relation, logonCount),
595                                   lastLogon=(lastlogon_relation, lastLogon),
596                                   lastLogonTimestamp=lastLogonTimestamp,
597                                   lockoutTime=0,
598                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
599                                   msDSUserAccountControlComputed=0,
600                                   msg="lastLogon is way off")
601
602         logonCount = int(res[0]["logonCount"][0])
603         lastLogon = int(res[0]["lastLogon"][0])
604
605         # The wrong password
606         creds_lockout.set_password("thatsAcomplPASS1x")
607         try:
608             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
609             self.fail()
610         except LdbError as e7:
611             (num, msg) = e7.args
612             self.assertEqual(num, ERR_INVALID_CREDENTIALS)
613
614         res = self._check_account(userdn,
615                                   badPwdCount=1,
616                                   badPasswordTime=("greater", badPasswordTime),
617                                   logonCount=logonCount,
618                                   lockoutTime=0,
619                                   lastLogon=lastLogon,
620                                   lastLogonTimestamp=lastLogonTimestamp,
621                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
622                                   msDSUserAccountControlComputed=0)
623         badPasswordTime = int(res[0]["badPasswordTime"][0])
624
625         # The wrong password
626         creds_lockout.set_password("thatsAcomplPASS1x")
627         try:
628             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
629             self.fail()
630         except LdbError as e8:
631             (num, msg) = e8.args
632             self.assertEqual(num, ERR_INVALID_CREDENTIALS)
633
634         res = self._check_account(userdn,
635                                   badPwdCount=2,
636                                   badPasswordTime=("greater", badPasswordTime),
637                                   logonCount=logonCount,
638                                   lockoutTime=0,
639                                   lastLogon=lastLogon,
640                                   lastLogonTimestamp=lastLogonTimestamp,
641                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
642                                   msDSUserAccountControlComputed=0)
643         badPasswordTime = int(res[0]["badPasswordTime"][0])
644
645         time.sleep(self.lockout_observation_window + 1)
646
647         res = self._check_account(userdn,
648                                   badPwdCount=2, effective_bad_password_count=0,
649                                   badPasswordTime=badPasswordTime,
650                                   logonCount=logonCount,
651                                   lockoutTime=0,
652                                   lastLogon=lastLogon,
653                                   lastLogonTimestamp=lastLogonTimestamp,
654                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
655                                   msDSUserAccountControlComputed=0)
656
657         # The wrong password
658         creds_lockout.set_password("thatsAcomplPASS1x")
659         try:
660             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
661             self.fail()
662         except LdbError as e9:
663             (num, msg) = e9.args
664             self.assertEqual(num, ERR_INVALID_CREDENTIALS)
665
666         res = self._check_account(userdn,
667                                   badPwdCount=1,
668                                   badPasswordTime=("greater", badPasswordTime),
669                                   logonCount=logonCount,
670                                   lockoutTime=0,
671                                   lastLogon=lastLogon,
672                                   lastLogonTimestamp=lastLogonTimestamp,
673                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
674                                   msDSUserAccountControlComputed=0)
675         badPasswordTime = int(res[0]["badPasswordTime"][0])
676
677         # The correct password without letting the timeout expire
678         creds_lockout.set_password(userpass)
679         ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
680
681         res = self._check_account(userdn,
682                                   badPwdCount=0,
683                                   badPasswordTime=badPasswordTime,
684                                   logonCount=(logoncount_relation, logonCount),
685                                   lockoutTime=0,
686                                   lastLogon=("greater", lastLogon),
687                                   lastLogonTimestamp=lastLogonTimestamp,
688                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
689                                   msDSUserAccountControlComputed=0)
690
691     def _test_multiple_logon(self, creds):
692         # Test the happy case in which a user logs on correctly, then
693         # logs on correctly again, so that the bad password and
694         # lockout times are both zero the second time. The lastlogon
695         # time should increase.
696
697         # Open a second LDB connection with the user credentials. Use the
698         # command line credentials for information like the domain, the realm
699         # and the workstation.
700         username = creds.get_username()
701         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
702
703         use_kerberos = creds.get_kerberos_state()
704         if use_kerberos == MUST_USE_KERBEROS:
705             print("Testing multiple logon with Kerberos")
706             logoncount_relation = 'greater'
707             lastlogon_relation = 'greater'
708         else:
709             print("Testing multiple logon with NTLM")
710             logoncount_relation = 'equal'
711             lastlogon_relation = 'equal'
712
713         SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp)
714
715         res = self._check_account(userdn,
716                                   badPwdCount=0,
717                                   badPasswordTime=("greater", 0),
718                                   logonCount=(logoncount_relation, 0),
719                                   lastLogon=("greater", 0),
720                                   lastLogonTimestamp=("greater", 0),
721                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
722                                   msDSUserAccountControlComputed=0)
723         badPasswordTime = int(res[0]["badPasswordTime"][0])
724         logonCount = int(res[0]["logonCount"][0])
725         lastLogon = int(res[0]["lastLogon"][0])
726         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
727         firstLogon = lastLogon
728         print("last logon is %d" % lastLogon)
729         self.assertGreater(lastLogon, badPasswordTime)
730         self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
731
732         time.sleep(1)
733         SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp)
734
735         res = self._check_account(userdn,
736                                   badPwdCount=0,
737                                   badPasswordTime=badPasswordTime,
738                                   logonCount=(logoncount_relation, logonCount),
739                                   lastLogon=(lastlogon_relation, lastLogon),
740                                   lastLogonTimestamp=lastLogonTimestamp,
741                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
742                                   msDSUserAccountControlComputed=0,
743                                   msg=("second logon, firstlogon was %s" %
744                                        firstLogon))
745
746         lastLogon = int(res[0]["lastLogon"][0])
747
748         time.sleep(1)
749
750         SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp)
751
752         res = self._check_account(userdn,
753                                   badPwdCount=0,
754                                   badPasswordTime=badPasswordTime,
755                                   logonCount=(logoncount_relation, logonCount),
756                                   lastLogon=(lastlogon_relation, lastLogon),
757                                   lastLogonTimestamp=lastLogonTimestamp,
758                                   userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
759                                   msDSUserAccountControlComputed=0)