password_lockout: Move lockoutObservationWindow tests from setUp
[nivanova/samba-autobuild/.git] / source4 / dsdb / tests / python / password_lockout_base.py
1 import samba
2
3 from samba.auth import system_session
4 from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS
5 from ldb import SCOPE_BASE, LdbError
6 from ldb import ERR_CONSTRAINT_VIOLATION
7 from ldb import ERR_INVALID_CREDENTIALS
8 from ldb import Message, MessageElement, Dn
9 from ldb import FLAG_MOD_REPLACE
10 from samba import gensec, dsdb
11 from samba.samdb import SamDB
12 import samba.tests
13 from samba.tests import delete_force
14 from samba.dcerpc import security, samr
15 from samba.ndr import ndr_unpack
16
17 import time
18
19 class BasePasswordTestCase(samba.tests.TestCase):
20     def _open_samr_user(self, res):
21         self.assertTrue("objectSid" in res[0])
22
23         (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split()
24         self.assertEquals(self.domain_sid, domain_sid)
25
26         return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid)
27
28     def _reset_samr(self, res):
29
30         # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
31         samr_user = self._open_samr_user(res)
32         acb_info = self.samr.QueryUserInfo(samr_user, 16)
33         acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
34         self.samr.SetUserInfo(samr_user, 16, acb_info)
35         self.samr.Close(samr_user)
36
37     def _check_attribute(self, res, name, value):
38         if value is None:
39             self.assertTrue(name not in res[0],
40                             msg="attr[%s]=%r on dn[%s]" %
41                             (name, res[0], res[0].dn))
42             return
43
44         if isinstance(value, tuple):
45             (mode, value) = value
46         else:
47             mode = "equal"
48
49         if mode == "ignore":
50             return
51
52         if mode == "absent":
53             self.assertFalse(name in res[0],
54                             msg="attr[%s] not missing on dn[%s]" %
55                             (name, res[0].dn))
56             return
57
58         self.assertTrue(name in res[0],
59                         msg="attr[%s] missing on dn[%s]" %
60                         (name, res[0].dn))
61         self.assertTrue(len(res[0][name]) == 1,
62                         msg="attr[%s]=%r on dn[%s]" %
63                         (name, res[0][name], res[0].dn))
64
65
66         print  "%s = '%s'" % (name, res[0][name][0])
67
68         if mode == "present":
69             return
70
71         if mode == "equal":
72             v = int(res[0][name][0])
73             value = int(value)
74             msg = ("attr[%s]=[%s] != [%s] on dn[%s]\n"
75                    "(diff %d; actual value is %s than expected)"  %
76                    (name, v, value, res[0].dn, v - value,
77                     ('less' if v < value else 'greater')))
78
79             self.assertTrue(v == value, msg)
80             return
81
82         if mode == "greater":
83             v = int(res[0][name][0])
84             self.assertTrue(v > int(value),
85                             msg="attr[%s]=[%s] <= [%s] on dn[%s] (diff %d)" %
86                             (name, v, int(value), res[0].dn, v - int(value)))
87             return
88         if mode == "less":
89             v = int(res[0][name][0])
90             self.assertTrue(v < int(value),
91                             msg="attr[%s]=[%s] >= [%s] on dn[%s] (diff %d)" %
92                             (name, v, int(value), res[0].dn, v - int(value)))
93             return
94         self.assertEqual(mode, not mode, "Invalid Mode[%s]" % mode)
95
96     def _check_account(self, dn,
97                        badPwdCount=None,
98                        badPasswordTime=None,
99                        logonCount=None,
100                        lastLogon=None,
101                        lastLogonTimestamp=None,
102                        lockoutTime=None,
103                        userAccountControl=None,
104                        msDSUserAccountControlComputed=None,
105                        effective_bad_password_count=None,
106                        msg=None):
107         print '-=' * 36
108         if msg is not None:
109             print  "\033[01;32m %s \033[00m\n" % msg
110         attrs = [
111            "objectSid",
112            "badPwdCount",
113            "badPasswordTime",
114            "lastLogon",
115            "lastLogonTimestamp",
116            "logonCount",
117            "lockoutTime",
118            "userAccountControl",
119            "msDS-User-Account-Control-Computed"
120         ]
121
122         # in order to prevent some time resolution problems we sleep for
123         # 10 micro second
124         time.sleep(0.01)
125
126         res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
127         self.assertTrue(len(res) == 1)
128         self._check_attribute(res, "badPwdCount", badPwdCount)
129         self._check_attribute(res, "badPasswordTime", badPasswordTime)
130         self._check_attribute(res, "logonCount", logonCount)
131         self._check_attribute(res, "lastLogon", lastLogon)
132         self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp)
133         self._check_attribute(res, "lockoutTime", lockoutTime)
134         self._check_attribute(res, "userAccountControl", userAccountControl)
135         self._check_attribute(res, "msDS-User-Account-Control-Computed",
136                               msDSUserAccountControlComputed)
137
138         lastLogon = int(res[0]["lastLogon"][0])
139         logonCount = int(res[0]["logonCount"][0])
140
141         samr_user = self._open_samr_user(res)
142         uinfo3 = self.samr.QueryUserInfo(samr_user, 3)
143         uinfo5 = self.samr.QueryUserInfo(samr_user, 5)
144         uinfo16 = self.samr.QueryUserInfo(samr_user, 16)
145         uinfo21 = self.samr.QueryUserInfo(samr_user, 21)
146         self.samr.Close(samr_user)
147
148         expected_acb_info = 0
149         if userAccountControl & dsdb.UF_NORMAL_ACCOUNT:
150             expected_acb_info |= samr.ACB_NORMAL
151         if userAccountControl & dsdb.UF_ACCOUNTDISABLE:
152             expected_acb_info |= samr.ACB_DISABLED
153         if userAccountControl & dsdb.UF_PASSWD_NOTREQD:
154             expected_acb_info |= samr.ACB_PWNOTREQ
155         if msDSUserAccountControlComputed & dsdb.UF_LOCKOUT:
156             expected_acb_info |= samr.ACB_AUTOLOCK
157         if msDSUserAccountControlComputed & dsdb.UF_PASSWORD_EXPIRED:
158             expected_acb_info |= samr.ACB_PW_EXPIRED
159
160         expected_bad_password_count = 0
161         if badPwdCount is not None:
162             expected_bad_password_count = badPwdCount
163         if effective_bad_password_count is None:
164             effective_bad_password_count = expected_bad_password_count
165
166         self.assertEquals(uinfo3.acct_flags, expected_acb_info)
167         self.assertEquals(uinfo3.bad_password_count, expected_bad_password_count)
168         self.assertEquals(uinfo3.last_logon, lastLogon)
169         self.assertEquals(uinfo3.logon_count, logonCount)
170
171         self.assertEquals(uinfo5.acct_flags, expected_acb_info)
172         self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count)
173         self.assertEquals(uinfo5.last_logon, lastLogon)
174         self.assertEquals(uinfo5.logon_count, logonCount)
175
176         self.assertEquals(uinfo16.acct_flags, expected_acb_info)
177
178         self.assertEquals(uinfo21.acct_flags, expected_acb_info)
179         self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count)
180         self.assertEquals(uinfo21.last_logon, lastLogon)
181         self.assertEquals(uinfo21.logon_count, logonCount)
182
183         # check LDAP again and make sure the samr.QueryUserInfo
184         # doesn't have any impact.
185         res2 = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
186         self.assertEquals(res[0], res2[0])
187
188         # in order to prevent some time resolution problems we sleep for
189         # 10 micro second
190         time.sleep(0.01)
191         return res
192
193     def _readd_user(self, creds, lockOutObservationWindow=0):
194         username = creds.get_username()
195         userpass = creds.get_password()
196         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
197
198         delete_force(self.ldb, userdn)
199         self.ldb.add({
200              "dn": userdn,
201              "objectclass": "user",
202              "sAMAccountName": username})
203
204         self.addCleanup(delete_force, self.ldb, userdn)
205
206         # Sets the initial user password with a "special" password change
207         # I think that this internally is a password set operation and it can
208         # only be performed by someone which has password set privileges on the
209         # account (at least in s4 we do handle it like that).
210         self.ldb.modify_ldif("""
211 dn: """ + userdn + """
212 changetype: modify
213 delete: userPassword
214 add: userPassword
215 userPassword: """ + userpass + """
216 """)
217         # Enables the user account
218         self.ldb.enable_account("(sAMAccountName=%s)" % username)
219
220         use_kerberos = creds.get_kerberos_state()
221         fail_creds = self.insta_creds(self.template_creds,
222                                       username=username,
223                                       userpass=userpass+"X",
224                                       kerberos_state=use_kerberos)
225
226         # Fail once to get a badPasswordTime
227         try:
228             ldb = SamDB(url=self.host_url, credentials=fail_creds, lp=self.lp)
229             self.fail()
230         except LdbError, (num, msg):
231             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
232
233         # Succeed to reset everything to 0
234         ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp)
235
236         return ldb
237
238     def _testing_add_user(self, creds, lockOutObservationWindow=0):
239         username = creds.get_username()
240         userpass = creds.get_password()
241         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
242
243         use_kerberos = creds.get_kerberos_state()
244         if use_kerberos == MUST_USE_KERBEROS:
245             logoncount_relation = 'greater'
246             lastlogon_relation = 'greater'
247         else:
248             logoncount_relation = 'equal'
249             if lockOutObservationWindow == 0:
250                 lastlogon_relation = 'greater'
251             else:
252                 lastlogon_relation = 'equal'
253
254         delete_force(self.ldb, userdn)
255         self.ldb.add({
256              "dn": userdn,
257              "objectclass": "user",
258              "sAMAccountName": username})
259
260         self.addCleanup(delete_force, self.ldb, userdn)
261
262         res = self._check_account(userdn,
263                                   badPwdCount=0,
264                                   badPasswordTime=0,
265                                   logonCount=0,
266                                   lastLogon=0,
267                                   lastLogonTimestamp=('absent', None),
268                                   userAccountControl=
269                                     dsdb.UF_NORMAL_ACCOUNT |
270                                     dsdb.UF_ACCOUNTDISABLE |
271                                     dsdb.UF_PASSWD_NOTREQD,
272                                   msDSUserAccountControlComputed=
273                                     dsdb.UF_PASSWORD_EXPIRED)
274
275         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
276         # It doesn't create "lockoutTime" = 0.
277         self._reset_samr(res)
278
279         res = self._check_account(userdn,
280                                   badPwdCount=0,
281                                   badPasswordTime=0,
282                                   logonCount=0,
283                                   lastLogon=0,
284                                   lastLogonTimestamp=('absent', None),
285                                   userAccountControl=
286                                     dsdb.UF_NORMAL_ACCOUNT |
287                                     dsdb.UF_ACCOUNTDISABLE |
288                                     dsdb.UF_PASSWD_NOTREQD,
289                                   msDSUserAccountControlComputed=
290                                     dsdb.UF_PASSWORD_EXPIRED)
291
292         # Tests a password change when we don't have any password yet with a
293         # wrong old password
294         try:
295             self.ldb.modify_ldif("""
296 dn: """ + userdn + """
297 changetype: modify
298 delete: userPassword
299 userPassword: noPassword
300 add: userPassword
301 userPassword: thatsAcomplPASS2
302 """)
303             self.fail()
304         except LdbError, (num, msg):
305             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
306             # Windows (2008 at least) seems to have some small bug here: it
307             # returns "0000056A" on longer (always wrong) previous passwords.
308             self.assertTrue('00000056' in msg, msg)
309
310         res = self._check_account(userdn,
311                                   badPwdCount=1,
312                                   badPasswordTime=("greater", 0),
313                                   logonCount=0,
314                                   lastLogon=0,
315                                   lastLogonTimestamp=('absent', None),
316                                   userAccountControl=
317                                     dsdb.UF_NORMAL_ACCOUNT |
318                                     dsdb.UF_ACCOUNTDISABLE |
319                                     dsdb.UF_PASSWD_NOTREQD,
320                                   msDSUserAccountControlComputed=
321                                     dsdb.UF_PASSWORD_EXPIRED)
322         badPwdCount = int(res[0]["badPwdCount"][0])
323         badPasswordTime = int(res[0]["badPasswordTime"][0])
324
325         # Sets the initial user password with a "special" password change
326         # I think that this internally is a password set operation and it can
327         # only be performed by someone which has password set privileges on the
328         # account (at least in s4 we do handle it like that).
329         self.ldb.modify_ldif("""
330 dn: """ + userdn + """
331 changetype: modify
332 delete: userPassword
333 add: userPassword
334 userPassword: """ + userpass + """
335 """)
336
337         res = self._check_account(userdn,
338                                   badPwdCount=badPwdCount,
339                                   badPasswordTime=badPasswordTime,
340                                   logonCount=0,
341                                   lastLogon=0,
342                                   lastLogonTimestamp=('absent', None),
343                                   userAccountControl=
344                                     dsdb.UF_NORMAL_ACCOUNT |
345                                     dsdb.UF_ACCOUNTDISABLE |
346                                     dsdb.UF_PASSWD_NOTREQD,
347                                   msDSUserAccountControlComputed=0)
348
349         # Enables the user account
350         self.ldb.enable_account("(sAMAccountName=%s)" % username)
351
352         res = self._check_account(userdn,
353                                   badPwdCount=badPwdCount,
354                                   badPasswordTime=badPasswordTime,
355                                   logonCount=0,
356                                   lastLogon=0,
357                                   lastLogonTimestamp=('absent', None),
358                                   userAccountControl=
359                                     dsdb.UF_NORMAL_ACCOUNT,
360                                   msDSUserAccountControlComputed=0)
361         if lockOutObservationWindow != 0:
362             time.sleep(lockOutObservationWindow + 1)
363             effective_bad_password_count = 0
364         else:
365             effective_bad_password_count = badPwdCount
366
367         res = self._check_account(userdn,
368                                   badPwdCount=badPwdCount,
369                                   effective_bad_password_count=effective_bad_password_count,
370                                   badPasswordTime=badPasswordTime,
371                                   logonCount=0,
372                                   lastLogon=0,
373                                   lastLogonTimestamp=('absent', None),
374                                   userAccountControl=
375                                     dsdb.UF_NORMAL_ACCOUNT,
376                                   msDSUserAccountControlComputed=0)
377
378         ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp)
379
380         if lockOutObservationWindow == 0:
381             badPwdCount = 0
382             effective_bad_password_count = 0
383         if use_kerberos == MUST_USE_KERBEROS:
384             badPwdCount = 0
385             effective_bad_password_count = 0
386
387         res = self._check_account(userdn,
388                                   badPwdCount=badPwdCount,
389                                   effective_bad_password_count=effective_bad_password_count,
390                                   badPasswordTime=badPasswordTime,
391                                   logonCount=(logoncount_relation, 0),
392                                   lastLogon=(lastlogon_relation, 0),
393                                   lastLogonTimestamp=('greater', badPasswordTime),
394                                   userAccountControl=
395                                     dsdb.UF_NORMAL_ACCOUNT,
396                                   msDSUserAccountControlComputed=0)
397
398         logonCount = int(res[0]["logonCount"][0])
399         lastLogon = int(res[0]["lastLogon"][0])
400         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
401         if lastlogon_relation == 'greater':
402             self.assertGreater(lastLogon, badPasswordTime)
403             self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
404
405         res = self._check_account(userdn,
406                                   badPwdCount=badPwdCount,
407                                   effective_bad_password_count=effective_bad_password_count,
408                                   badPasswordTime=badPasswordTime,
409                                   logonCount=logonCount,
410                                   lastLogon=lastLogon,
411                                   lastLogonTimestamp=lastLogonTimestamp,
412                                   userAccountControl=
413                                     dsdb.UF_NORMAL_ACCOUNT,
414                                   msDSUserAccountControlComputed=0)
415         return ldb
416
417     def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS):
418         try:
419             ldb = SamDB(url=url, credentials=creds, lp=lp)
420             self.fail("Login unexpectedly succeeded")
421         except LdbError, (num, msg):
422             if errno is not None:
423                 self.assertEquals(num, errno, ("Login failed in the wrong way"
424                                                "(got err %d, expected %d)" %
425                                                (num, errno)))
426
427     def setUp(self):
428         super(BasePasswordTestCase, self).setUp()
429
430         self.global_creds.set_gensec_features(self.global_creds.get_gensec_features() |
431                                               gensec.FEATURE_SEAL)
432
433         self.template_creds = Credentials()
434         self.template_creds.set_username("testuser")
435         self.template_creds.set_password("thatsAcomplPASS1")
436         self.template_creds.set_domain(self.global_creds.get_domain())
437         self.template_creds.set_realm(self.global_creds.get_realm())
438         self.template_creds.set_workstation(self.global_creds.get_workstation())
439         self.template_creds.set_gensec_features(self.global_creds.get_gensec_features())
440         self.template_creds.set_kerberos_state(self.global_creds.get_kerberos_state())
441
442
443         # Gets back the basedn
444         base_dn = self.ldb.domain_dn()
445
446         # Gets back the configuration basedn
447         configuration_dn = self.ldb.get_config_basedn().get_linearized()
448
449         # Get the old "dSHeuristics" if it was set
450         dsheuristics = self.ldb.get_dsheuristics()
451
452         # Reset the "dSHeuristics" as they were before
453         self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
454
455         res = self.ldb.search(base_dn,
456                          scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
457
458         if "lockoutDuration" in res[0]:
459             lockoutDuration = res[0]["lockoutDuration"][0]
460         else:
461             lockoutDuration = 0
462
463         if "lockoutObservationWindow" in res[0]:
464             lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
465         else:
466             lockoutObservationWindow = 0
467
468         if "lockoutThreshold" in res[0]:
469             lockoutThreshold = res[0]["lockoutThreshold"][0]
470         else:
471             lockoutTreshold = 0
472
473         self.addCleanup(self.ldb.modify_ldif, """
474 dn: """ + base_dn + """
475 changetype: modify
476 replace: lockoutDuration
477 lockoutDuration: """ + str(lockoutDuration) + """
478 replace: lockoutObservationWindow
479 lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
480 replace: lockoutThreshold
481 lockoutThreshold: """ + str(lockoutThreshold) + """
482 """)
483
484         m = Message()
485         m.dn = Dn(self.ldb, base_dn)
486
487         self.account_lockout_duration = 2
488         account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7))
489
490         m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
491                                               FLAG_MOD_REPLACE, "lockoutDuration")
492
493         account_lockout_threshold = 3
494         m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
495                                                FLAG_MOD_REPLACE, "lockoutThreshold")
496
497         self.lockout_observation_window = 2
498         lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7))
499
500         m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
501                                                        FLAG_MOD_REPLACE, "lockOutObservationWindow")
502
503         self.ldb.modify(m)
504
505         # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
506         self.ldb.set_dsheuristics("000000001")
507
508         # Get the old "minPwdAge"
509         minPwdAge = self.ldb.get_minPwdAge()
510
511         # Reset the "minPwdAge" as it was before
512         self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
513
514         # Set it temporarely to "0"
515         self.ldb.set_minPwdAge("0")
516
517         self.base_dn = self.ldb.domain_dn()
518
519         self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
520         self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % self.host, self.lp, self.global_creds)
521         self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
522         self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
523
524         # (Re)adds the test user accounts
525         self.lockout1krb5_creds = self.insta_creds(self.template_creds,
526                                                    username="lockout1krb5",
527                                                    userpass="thatsAcomplPASS0",
528                                                    kerberos_state=MUST_USE_KERBEROS)
529         self.lockout1krb5_ldb = self._readd_user(self.lockout1krb5_creds)
530         self.lockout2krb5_creds = self.insta_creds(self.template_creds,
531                                                    username="lockout2krb5",
532                                                    userpass="thatsAcomplPASS0",
533                                                    kerberos_state=MUST_USE_KERBEROS)
534         self.lockout2krb5_ldb = self._readd_user(self.lockout2krb5_creds,
535                                                  lockOutObservationWindow=self.lockout_observation_window)
536         self.lockout1ntlm_creds = self.insta_creds(self.template_creds,
537                                                    username="lockout1ntlm",
538                                                    userpass="thatsAcomplPASS0",
539                                                    kerberos_state=DONT_USE_KERBEROS)
540         self.lockout1ntlm_ldb = self._readd_user(self.lockout1ntlm_creds)
541         self.lockout2ntlm_creds = self.insta_creds(self.template_creds,
542                                                    username="lockout2ntlm",
543                                                    userpass="thatsAcomplPASS0",
544                                                    kerberos_state=DONT_USE_KERBEROS)
545         self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds,
546                                                  lockOutObservationWindow=self.lockout_observation_window)
547
548     def tearDown(self):
549         super(BasePasswordTestCase, self).tearDown()
550
551     def _test_login_lockout(self, creds):
552         username = creds.get_username()
553         userpass = creds.get_password()
554         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
555
556         use_kerberos = creds.get_kerberos_state()
557         # This unlocks by waiting for account_lockout_duration
558         if use_kerberos == MUST_USE_KERBEROS:
559             logoncount_relation = 'greater'
560             lastlogon_relation = 'greater'
561             print "Performs a lockout attempt against LDAP using Kerberos"
562         else:
563             logoncount_relation = 'equal'
564             lastlogon_relation = 'equal'
565             print "Performs a lockout attempt against LDAP using NTLM"
566
567         # Change password on a connection as another user
568         res = self._check_account(userdn,
569                                   badPwdCount=0,
570                                   badPasswordTime=("greater", 0),
571                                   logonCount=(logoncount_relation, 0),
572                                   lastLogon=("greater", 0),
573                                   lastLogonTimestamp=("greater", 0),
574                                   userAccountControl=
575                                     dsdb.UF_NORMAL_ACCOUNT,
576                                   msDSUserAccountControlComputed=0)
577         badPasswordTime = int(res[0]["badPasswordTime"][0])
578         logonCount = int(res[0]["logonCount"][0])
579         lastLogon = int(res[0]["lastLogon"][0])
580         firstLogon = lastLogon
581         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
582         print firstLogon
583         print lastLogonTimestamp
584
585
586         self.assertGreater(lastLogon, badPasswordTime)
587         self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
588
589         # Open a second LDB connection with the user credentials. Use the
590         # command line credentials for informations like the domain, the realm
591         # and the workstation.
592         creds_lockout = self.insta_creds(creds)
593
594         # The wrong password
595         creds_lockout.set_password("thatsAcomplPASS1x")
596
597         self.assertLoginFailure(self.host_url, creds_lockout, self.lp)
598
599         res = self._check_account(userdn,
600                                   badPwdCount=1,
601                                   badPasswordTime=("greater", badPasswordTime),
602                                   logonCount=logonCount,
603                                   lastLogon=lastLogon,
604                                   lastLogonTimestamp=lastLogonTimestamp,
605                                   userAccountControl=
606                                     dsdb.UF_NORMAL_ACCOUNT,
607                                   msDSUserAccountControlComputed=0,
608                                   msg='lastlogontimestamp with wrong password')
609         badPasswordTime = int(res[0]["badPasswordTime"][0])
610
611         # Correct old password
612         creds_lockout.set_password(userpass)
613
614         ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
615
616         # lastLogonTimestamp should not change
617         # lastLogon increases if badPwdCount is non-zero (!)
618         res = self._check_account(userdn,
619                                   badPwdCount=0,
620                                   badPasswordTime=badPasswordTime,
621                                   logonCount=(logoncount_relation, logonCount),
622                                   lastLogon=('greater', lastLogon),
623                                   lastLogonTimestamp=lastLogonTimestamp,
624                                   userAccountControl=
625                                     dsdb.UF_NORMAL_ACCOUNT,
626                                   msDSUserAccountControlComputed=0,
627                                   msg='LLTimestamp is updated to lastlogon')
628
629         logonCount = int(res[0]["logonCount"][0])
630         lastLogon = int(res[0]["lastLogon"][0])
631         self.assertGreater(lastLogon, badPasswordTime)
632         self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
633
634         # The wrong password
635         creds_lockout.set_password("thatsAcomplPASS1x")
636
637         self.assertLoginFailure(self.host_url, creds_lockout, self.lp)
638
639         res = self._check_account(userdn,
640                                   badPwdCount=1,
641                                   badPasswordTime=("greater", badPasswordTime),
642                                   logonCount=logonCount,
643                                   lastLogon=lastLogon,
644                                   lastLogonTimestamp=lastLogonTimestamp,
645                                   userAccountControl=
646                                     dsdb.UF_NORMAL_ACCOUNT,
647                                   msDSUserAccountControlComputed=0)
648         badPasswordTime = int(res[0]["badPasswordTime"][0])
649
650         # The wrong password
651         creds_lockout.set_password("thatsAcomplPASS1x")
652
653         try:
654             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
655             self.fail()
656
657         except LdbError, (num, msg):
658             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
659
660         res = self._check_account(userdn,
661                                   badPwdCount=2,
662                                   badPasswordTime=("greater", badPasswordTime),
663                                   logonCount=logonCount,
664                                   lastLogon=lastLogon,
665                                   lastLogonTimestamp=lastLogonTimestamp,
666                                   userAccountControl=
667                                     dsdb.UF_NORMAL_ACCOUNT,
668                                   msDSUserAccountControlComputed=0)
669         badPasswordTime = int(res[0]["badPasswordTime"][0])
670
671         print "two failed password change"
672
673         # The wrong password
674         creds_lockout.set_password("thatsAcomplPASS1x")
675
676         try:
677             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
678             self.fail()
679
680         except LdbError, (num, msg):
681             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
682
683         res = self._check_account(userdn,
684                                   badPwdCount=3,
685                                   badPasswordTime=("greater", badPasswordTime),
686                                   logonCount=logonCount,
687                                   lastLogon=lastLogon,
688                                   lastLogonTimestamp=lastLogonTimestamp,
689                                   lockoutTime=("greater", badPasswordTime),
690                                   userAccountControl=
691                                     dsdb.UF_NORMAL_ACCOUNT,
692                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
693         badPasswordTime = int(res[0]["badPasswordTime"][0])
694         lockoutTime = int(res[0]["lockoutTime"][0])
695
696         # The wrong password
697         creds_lockout.set_password("thatsAcomplPASS1x")
698         try:
699             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
700             self.fail()
701         except LdbError, (num, msg):
702             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
703
704         res = self._check_account(userdn,
705                                   badPwdCount=3,
706                                   badPasswordTime=badPasswordTime,
707                                   logonCount=logonCount,
708                                   lastLogon=lastLogon,
709                                   lastLogonTimestamp=lastLogonTimestamp,
710                                   lockoutTime=lockoutTime,
711                                   userAccountControl=
712                                     dsdb.UF_NORMAL_ACCOUNT,
713                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
714
715         # The wrong password
716         creds_lockout.set_password("thatsAcomplPASS1x")
717         try:
718             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
719             self.fail()
720         except LdbError, (num, msg):
721             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
722
723         res = self._check_account(userdn,
724                                   badPwdCount=3,
725                                   badPasswordTime=badPasswordTime,
726                                   logonCount=logonCount,
727                                   lastLogon=lastLogon,
728                                   lastLogonTimestamp=lastLogonTimestamp,
729                                   lockoutTime=lockoutTime,
730                                   userAccountControl=
731                                     dsdb.UF_NORMAL_ACCOUNT,
732                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
733
734         # The correct password, but we are locked out
735         creds_lockout.set_password(userpass)
736         try:
737             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
738             self.fail()
739         except LdbError, (num, msg):
740             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
741
742         res = self._check_account(userdn,
743                                   badPwdCount=3,
744                                   badPasswordTime=badPasswordTime,
745                                   logonCount=logonCount,
746                                   lastLogon=lastLogon,
747                                   lastLogonTimestamp=lastLogonTimestamp,
748                                   lockoutTime=lockoutTime,
749                                   userAccountControl=
750                                     dsdb.UF_NORMAL_ACCOUNT,
751                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
752
753         # wait for the lockout to end
754         time.sleep(self.account_lockout_duration + 1)
755         print self.account_lockout_duration + 1
756
757         res = self._check_account(userdn,
758                                   badPwdCount=3, effective_bad_password_count=0,
759                                   badPasswordTime=badPasswordTime,
760                                   logonCount=logonCount,
761                                   lockoutTime=lockoutTime,
762                                   lastLogon=lastLogon,
763                                   lastLogonTimestamp=lastLogonTimestamp,
764                                   userAccountControl=
765                                     dsdb.UF_NORMAL_ACCOUNT,
766                                   msDSUserAccountControlComputed=0)
767
768         # The correct password after letting the timeout expire
769
770         creds_lockout.set_password(userpass)
771
772         creds_lockout2 = self.insta_creds(creds_lockout)
773
774         ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout2, lp=self.lp)
775         time.sleep(3)
776
777         res = self._check_account(userdn,
778                                   badPwdCount=0,
779                                   badPasswordTime=badPasswordTime,
780                                   logonCount=(logoncount_relation, logonCount),
781                                   lastLogon=(lastlogon_relation, lastLogon),
782                                   lastLogonTimestamp=lastLogonTimestamp,
783                                   lockoutTime=0,
784                                   userAccountControl=
785                                     dsdb.UF_NORMAL_ACCOUNT,
786                                   msDSUserAccountControlComputed=0,
787                                   msg="lastLogon is way off")
788
789         logonCount = int(res[0]["logonCount"][0])
790         lastLogon = int(res[0]["lastLogon"][0])
791
792         # The wrong password
793         creds_lockout.set_password("thatsAcomplPASS1x")
794         try:
795             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
796             self.fail()
797         except LdbError, (num, msg):
798             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
799
800         res = self._check_account(userdn,
801                                   badPwdCount=1,
802                                   badPasswordTime=("greater", badPasswordTime),
803                                   logonCount=logonCount,
804                                   lockoutTime=0,
805                                   lastLogon=lastLogon,
806                                   lastLogonTimestamp=lastLogonTimestamp,
807                                   userAccountControl=
808                                     dsdb.UF_NORMAL_ACCOUNT,
809                                   msDSUserAccountControlComputed=0)
810         badPasswordTime = int(res[0]["badPasswordTime"][0])
811
812         # The wrong password
813         creds_lockout.set_password("thatsAcomplPASS1x")
814         try:
815             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
816             self.fail()
817         except LdbError, (num, msg):
818             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
819
820         res = self._check_account(userdn,
821                                   badPwdCount=2,
822                                   badPasswordTime=("greater", badPasswordTime),
823                                   logonCount=logonCount,
824                                   lockoutTime=0,
825                                   lastLogon=lastLogon,
826                                   lastLogonTimestamp=lastLogonTimestamp,
827                                   userAccountControl=
828                                     dsdb.UF_NORMAL_ACCOUNT,
829                                   msDSUserAccountControlComputed=0)
830         badPasswordTime = int(res[0]["badPasswordTime"][0])
831
832         time.sleep(self.lockout_observation_window + 1)
833
834         res = self._check_account(userdn,
835                                   badPwdCount=2, effective_bad_password_count=0,
836                                   badPasswordTime=badPasswordTime,
837                                   logonCount=logonCount,
838                                   lockoutTime=0,
839                                   lastLogon=lastLogon,
840                                   lastLogonTimestamp=lastLogonTimestamp,
841                                   userAccountControl=
842                                     dsdb.UF_NORMAL_ACCOUNT,
843                                   msDSUserAccountControlComputed=0)
844
845         # The wrong password
846         creds_lockout.set_password("thatsAcomplPASS1x")
847         try:
848             ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
849             self.fail()
850         except LdbError, (num, msg):
851             self.assertEquals(num, ERR_INVALID_CREDENTIALS)
852
853         res = self._check_account(userdn,
854                                   badPwdCount=1,
855                                   badPasswordTime=("greater", badPasswordTime),
856                                   logonCount=logonCount,
857                                   lockoutTime=0,
858                                   lastLogon=lastLogon,
859                                   lastLogonTimestamp=lastLogonTimestamp,
860                                   userAccountControl=
861                                     dsdb.UF_NORMAL_ACCOUNT,
862                                   msDSUserAccountControlComputed=0)
863         badPasswordTime = int(res[0]["badPasswordTime"][0])
864
865         # The correct password without letting the timeout expire
866         creds_lockout.set_password(userpass)
867         ldb_lockout = SamDB(url=self.host_url, credentials=creds_lockout, lp=self.lp)
868
869         res = self._check_account(userdn,
870                                   badPwdCount=0,
871                                   badPasswordTime=badPasswordTime,
872                                   logonCount=(logoncount_relation, logonCount),
873                                   lockoutTime=0,
874                                   lastLogon=("greater", lastLogon),
875                                   lastLogonTimestamp=lastLogonTimestamp,
876                                   userAccountControl=
877                                     dsdb.UF_NORMAL_ACCOUNT,
878                                   msDSUserAccountControlComputed=0)
879
880     def _test_multiple_logon(self, creds):
881         # Test the happy case in which a user logs on correctly, then
882         # logs on correctly again, so that the bad password and
883         # lockout times are both zero the second time. The lastlogon
884         # time should increase.
885
886         # Open a second LDB connection with the user credentials. Use the
887         # command line credentials for informations like the domain, the realm
888         # and the workstation.
889         username = creds.get_username()
890         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
891
892         use_kerberos = creds.get_kerberos_state()
893         if use_kerberos == MUST_USE_KERBEROS:
894             print "Testing multiple logon with Kerberos"
895             logoncount_relation = 'greater'
896             lastlogon_relation = 'greater'
897         else:
898             print "Testing multiple logon with NTLM"
899             logoncount_relation = 'equal'
900             lastlogon_relation = 'equal'
901
902         SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp)
903
904         res = self._check_account(userdn,
905                                   badPwdCount=0,
906                                   badPasswordTime=("greater", 0),
907                                   logonCount=(logoncount_relation, 0),
908                                   lastLogon=("greater", 0),
909                                   lastLogonTimestamp=("greater", 0),
910                                   userAccountControl=
911                                     dsdb.UF_NORMAL_ACCOUNT,
912                                   msDSUserAccountControlComputed=0)
913         badPasswordTime = int(res[0]["badPasswordTime"][0])
914         logonCount = int(res[0]["logonCount"][0])
915         lastLogon = int(res[0]["lastLogon"][0])
916         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
917         firstLogon = lastLogon
918         print "last logon is %d" % lastLogon
919         self.assertGreater(lastLogon, badPasswordTime)
920         self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
921
922         time.sleep(1)
923         SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp)
924
925         res = self._check_account(userdn,
926                                   badPwdCount=0,
927                                   badPasswordTime=badPasswordTime,
928                                   logonCount=(logoncount_relation, logonCount),
929                                   lastLogon=(lastlogon_relation, lastLogon),
930                                   lastLogonTimestamp=lastLogonTimestamp,
931                                   userAccountControl=
932                                   dsdb.UF_NORMAL_ACCOUNT,
933                                   msDSUserAccountControlComputed=0,
934                                   msg=("second logon, firstlogon was %s" %
935                                        firstLogon))
936
937
938         lastLogon = int(res[0]["lastLogon"][0])
939
940         time.sleep(1)
941
942         SamDB(url=self.host_url, credentials=self.insta_creds(creds), lp=self.lp)
943
944         res = self._check_account(userdn,
945                                   badPwdCount=0,
946                                   badPasswordTime=badPasswordTime,
947                                   logonCount=(logoncount_relation, logonCount),
948                                   lastLogon=(lastlogon_relation, lastLogon),
949                                   lastLogonTimestamp=lastLogonTimestamp,
950                                   userAccountControl=
951                                     dsdb.UF_NORMAL_ACCOUNT,
952                                   msDSUserAccountControlComputed=0)