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