password_lockout: Move lockoutObservationWindow tests from setUp
[nivanova/samba-autobuild/.git] / source4 / dsdb / tests / python / password_lockout.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # This tests the password lockout behavior for AD implementations
4 #
5 # Copyright Matthias Dieter Wallnoefer 2010
6 # Copyright Andrew Bartlett 2013
7 # Copyright Stefan Metzmacher 2014
8 #
9
10 import optparse
11 import sys
12 import base64
13 import time
14
15 sys.path.insert(0, "bin/python")
16 import samba
17
18 from samba.tests.subunitrun import TestProgram, SubunitOptions
19
20 import samba.getopt as options
21
22 from samba.auth import system_session
23 from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS
24 from ldb import SCOPE_BASE, LdbError
25 from ldb import ERR_CONSTRAINT_VIOLATION
26 from ldb import ERR_INVALID_CREDENTIALS
27 from ldb import Message, MessageElement, Dn
28 from ldb import FLAG_MOD_REPLACE
29 from samba import gensec, dsdb
30 from samba.samdb import SamDB
31 import samba.tests
32 from samba.tests import delete_force
33 from samba.dcerpc import security, samr
34 from samba.ndr import ndr_unpack
35
36 parser = optparse.OptionParser("password_lockout.py [options] <host>")
37 sambaopts = options.SambaOptions(parser)
38 parser.add_option_group(sambaopts)
39 parser.add_option_group(options.VersionOptions(parser))
40 # use command line creds if available
41 credopts = options.CredentialsOptions(parser)
42 parser.add_option_group(credopts)
43 subunitopts = SubunitOptions(parser)
44 parser.add_option_group(subunitopts)
45 opts, args = parser.parse_args()
46
47 if len(args) < 1:
48     parser.print_usage()
49     sys.exit(1)
50
51 host = args[0]
52
53 lp = sambaopts.get_loadparm()
54 global_creds = credopts.get_credentials(lp)
55
56 import password_lockout_base
57
58 #
59 # Tests start here
60 #
61
62 class PasswordTests(password_lockout_base.BasePasswordTestCase):
63     def setUp(self):
64         self.host = host
65         self.host_url = host_url
66         self.lp = lp
67         self.global_creds = global_creds
68         self.ldb = SamDB(url=self.host_url, session_info=system_session(self.lp),
69                          credentials=self.global_creds, lp=self.lp)
70         super(PasswordTests, self).setUp()
71
72     def _reset_ldap_lockoutTime(self, res):
73         self.ldb.modify_ldif("""
74 dn: """ + str(res[0].dn) + """
75 changetype: modify
76 replace: lockoutTime
77 lockoutTime: 0
78 """)
79
80     def _reset_ldap_userAccountControl(self, res):
81         self.assertTrue("userAccountControl" in res[0])
82         self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
83
84         uac = int(res[0]["userAccountControl"][0])
85         uacc = int(res[0]["msDS-User-Account-Control-Computed"][0])
86
87         uac |= uacc
88         uac = uac & ~dsdb.UF_LOCKOUT
89
90         self.ldb.modify_ldif("""
91 dn: """ + str(res[0].dn) + """
92 changetype: modify
93 replace: userAccountControl
94 userAccountControl: %d
95 """ % uac)
96
97     def _reset_by_method(self, res, method):
98         if method is "ldap_userAccountControl":
99             self._reset_ldap_userAccountControl(res)
100         elif method is "ldap_lockoutTime":
101             self._reset_ldap_lockoutTime(res)
102         elif method is "samr":
103             self._reset_samr(res)
104         else:
105             self.assertTrue(False, msg="Invalid reset method[%s]" % method)
106
107     def _test_userPassword_lockout_with_clear_change(self, creds, other_ldb, method,
108                                                      initial_lastlogon_relation=None):
109         # Notice: This works only against Windows if "dSHeuristics" has been set
110         # properly
111         username = creds.get_username()
112         userpass = creds.get_password()
113         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
114
115         use_kerberos = creds.get_kerberos_state()
116         if use_kerberos == MUST_USE_KERBEROS:
117             logoncount_relation = 'greater'
118             lastlogon_relation = 'greater'
119             print "Performs a password cleartext change operation on 'userPassword' using Kerberos"
120         else:
121             logoncount_relation = 'equal'
122             lastlogon_relation = 'equal'
123             print "Performs a password cleartext change operation on 'userPassword' using NTLMSSP"
124
125         if initial_lastlogon_relation is not None:
126             lastlogon_relation = initial_lastlogon_relation
127
128         res = self._check_account(userdn,
129                                   badPwdCount=0,
130                                   badPasswordTime=("greater", 0),
131                                   logonCount=(logoncount_relation, 0),
132                                   lastLogon=(lastlogon_relation, 0),
133                                   lastLogonTimestamp=('greater', 0),
134                                   userAccountControl=
135                                     dsdb.UF_NORMAL_ACCOUNT,
136                                   msDSUserAccountControlComputed=0)
137         badPasswordTime = int(res[0]["badPasswordTime"][0])
138         logonCount = int(res[0]["logonCount"][0])
139         lastLogon = int(res[0]["lastLogon"][0])
140         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
141         if lastlogon_relation == 'greater':
142             self.assertGreater(lastLogon, badPasswordTime)
143             self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
144
145         # Change password on a connection as another user
146
147         # Wrong old password
148         try:
149             other_ldb.modify_ldif("""
150 dn: """ + userdn + """
151 changetype: modify
152 delete: userPassword
153 userPassword: thatsAcomplPASS1x
154 add: userPassword
155 userPassword: thatsAcomplPASS2
156 """)
157             self.fail()
158         except LdbError, (num, msg):
159             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
160             self.assertTrue('00000056' in msg, msg)
161
162         res = self._check_account(userdn,
163                                   badPwdCount=1,
164                                   badPasswordTime=("greater", badPasswordTime),
165                                   logonCount=logonCount,
166                                   lastLogon=lastLogon,
167                                   lastLogonTimestamp=lastLogonTimestamp,
168                                   userAccountControl=
169                                     dsdb.UF_NORMAL_ACCOUNT,
170                                   msDSUserAccountControlComputed=0)
171         badPasswordTime = int(res[0]["badPasswordTime"][0])
172
173         # Correct old password
174         other_ldb.modify_ldif("""
175 dn: """ + userdn + """
176 changetype: modify
177 delete: userPassword
178 userPassword: """ + userpass + """
179 add: userPassword
180 userPassword: thatsAcomplPASS2
181 """)
182
183         res = self._check_account(userdn,
184                                   badPwdCount=1,
185                                   badPasswordTime=badPasswordTime,
186                                   logonCount=logonCount,
187                                   lastLogon=lastLogon,
188                                   lastLogonTimestamp=lastLogonTimestamp,
189                                   userAccountControl=
190                                     dsdb.UF_NORMAL_ACCOUNT,
191                                   msDSUserAccountControlComputed=0)
192
193         # Wrong old password
194         try:
195             other_ldb.modify_ldif("""
196 dn: """ + userdn + """
197 changetype: modify
198 delete: userPassword
199 userPassword: thatsAcomplPASS1x
200 add: userPassword
201 userPassword: thatsAcomplPASS2
202 """)
203             self.fail()
204         except LdbError, (num, msg):
205             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
206             self.assertTrue('00000056' in msg, msg)
207
208         res = self._check_account(userdn,
209                                   badPwdCount=2,
210                                   badPasswordTime=("greater", badPasswordTime),
211                                   logonCount=logonCount,
212                                   lastLogon=lastLogon,
213                                   lastLogonTimestamp=lastLogonTimestamp,
214                                   userAccountControl=
215                                     dsdb.UF_NORMAL_ACCOUNT,
216                                   msDSUserAccountControlComputed=0)
217         badPasswordTime = int(res[0]["badPasswordTime"][0])
218
219         print "two failed password change"
220
221         # Wrong old password
222         try:
223             other_ldb.modify_ldif("""
224 dn: """ + userdn + """
225 changetype: modify
226 delete: userPassword
227 userPassword: thatsAcomplPASS1x
228 add: userPassword
229 userPassword: thatsAcomplPASS2
230 """)
231             self.fail()
232         except LdbError, (num, msg):
233             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
234             self.assertTrue('00000056' in msg, msg)
235
236         res = self._check_account(userdn,
237                                   badPwdCount=3,
238                                   badPasswordTime=("greater", badPasswordTime),
239                                   logonCount=logonCount,
240                                   lastLogon=lastLogon,
241                                   lastLogonTimestamp=lastLogonTimestamp,
242                                   lockoutTime=("greater", badPasswordTime),
243                                   userAccountControl=
244                                     dsdb.UF_NORMAL_ACCOUNT,
245                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
246         badPasswordTime = int(res[0]["badPasswordTime"][0])
247         lockoutTime = int(res[0]["lockoutTime"][0])
248
249         # Wrong old password
250         try:
251             other_ldb.modify_ldif("""
252 dn: """ + userdn + """
253 changetype: modify
254 delete: userPassword
255 userPassword: thatsAcomplPASS1x
256 add: userPassword
257 userPassword: thatsAcomplPASS2
258 """)
259             self.fail()
260         except LdbError, (num, msg):
261             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
262             self.assertTrue('00000775' in msg, msg)
263
264         res = self._check_account(userdn,
265                                   badPwdCount=3,
266                                   badPasswordTime=badPasswordTime,
267                                   logonCount=logonCount,
268                                   lastLogon=lastLogon,
269                                   lastLogonTimestamp=lastLogonTimestamp,
270                                   lockoutTime=lockoutTime,
271                                   userAccountControl=
272                                     dsdb.UF_NORMAL_ACCOUNT,
273                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
274
275         # Wrong old password
276         try:
277             other_ldb.modify_ldif("""
278 dn: """ + userdn + """
279 changetype: modify
280 delete: userPassword
281 userPassword: thatsAcomplPASS1x
282 add: userPassword
283 userPassword: thatsAcomplPASS2
284 """)
285             self.fail()
286         except LdbError, (num, msg):
287             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
288             self.assertTrue('00000775' in msg, msg)
289
290         res = self._check_account(userdn,
291                                   badPwdCount=3,
292                                   badPasswordTime=badPasswordTime,
293                                   logonCount=logonCount,
294                                   lockoutTime=lockoutTime,
295                                   lastLogon=lastLogon,
296                                   lastLogonTimestamp=lastLogonTimestamp,
297                                   userAccountControl=
298                                     dsdb.UF_NORMAL_ACCOUNT,
299                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
300
301         try:
302             # Correct old password
303             other_ldb.modify_ldif("""
304 dn: """ + userdn + """
305 changetype: modify
306 delete: userPassword
307 userPassword: thatsAcomplPASS2
308 add: userPassword
309 userPassword: thatsAcomplPASS2x
310 """)
311             self.fail()
312         except LdbError, (num, msg):
313             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
314             self.assertTrue('00000775' in msg, msg)
315
316         res = self._check_account(userdn,
317                                   badPwdCount=3,
318                                   badPasswordTime=badPasswordTime,
319                                   logonCount=logonCount,
320                                   lastLogon=lastLogon,
321                                   lastLogonTimestamp=lastLogonTimestamp,
322                                   lockoutTime=lockoutTime,
323                                   userAccountControl=
324                                     dsdb.UF_NORMAL_ACCOUNT,
325                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
326
327         # Now reset the password, which does NOT change the lockout!
328         self.ldb.modify_ldif("""
329 dn: """ + userdn + """
330 changetype: modify
331 replace: userPassword
332 userPassword: thatsAcomplPASS2
333 """)
334
335         res = self._check_account(userdn,
336                                   badPwdCount=3,
337                                   badPasswordTime=badPasswordTime,
338                                   logonCount=logonCount,
339                                   lastLogon=lastLogon,
340                                   lastLogonTimestamp=lastLogonTimestamp,
341                                   lockoutTime=lockoutTime,
342                                   userAccountControl=
343                                     dsdb.UF_NORMAL_ACCOUNT,
344                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
345
346         try:
347             # Correct old password
348             other_ldb.modify_ldif("""
349 dn: """ + userdn + """
350 changetype: modify
351 delete: userPassword
352 userPassword: thatsAcomplPASS2
353 add: userPassword
354 userPassword: thatsAcomplPASS2x
355 """)
356             self.fail()
357         except LdbError, (num, msg):
358             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
359             self.assertTrue('00000775' in msg, msg)
360
361         res = self._check_account(userdn,
362                                   badPwdCount=3,
363                                   badPasswordTime=badPasswordTime,
364                                   logonCount=logonCount,
365                                   lastLogon=lastLogon,
366                                   lastLogonTimestamp=lastLogonTimestamp,
367                                   lockoutTime=lockoutTime,
368                                   userAccountControl=
369                                     dsdb.UF_NORMAL_ACCOUNT,
370                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
371
372         m = Message()
373         m.dn = Dn(self.ldb, userdn)
374         m["userAccountControl"] = MessageElement(
375           str(dsdb.UF_LOCKOUT),
376           FLAG_MOD_REPLACE, "userAccountControl")
377
378         self.ldb.modify(m)
379
380         # This shows that setting the UF_LOCKOUT flag alone makes no difference
381         res = self._check_account(userdn,
382                                   badPwdCount=3,
383                                   badPasswordTime=badPasswordTime,
384                                   logonCount=logonCount,
385                                   lastLogon=lastLogon,
386                                   lastLogonTimestamp=lastLogonTimestamp,
387                                   lockoutTime=lockoutTime,
388                                   userAccountControl=
389                                     dsdb.UF_NORMAL_ACCOUNT,
390                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
391
392         # This shows that setting the UF_LOCKOUT flag makes no difference
393         try:
394             # Correct old password
395             other_ldb.modify_ldif("""
396 dn: """ + userdn + """
397 changetype: modify
398 delete: unicodePwd
399 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
400 add: unicodePwd
401 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
402 """)
403             self.fail()
404         except LdbError, (num, msg):
405             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
406             self.assertTrue('00000775' in msg, msg)
407
408         res = self._check_account(userdn,
409                                   badPwdCount=3,
410                                   badPasswordTime=badPasswordTime,
411                                   logonCount=logonCount,
412                                   lockoutTime=lockoutTime,
413                                   lastLogon=lastLogon,
414                                   lastLogonTimestamp=lastLogonTimestamp,
415                                   userAccountControl=
416                                     dsdb.UF_NORMAL_ACCOUNT,
417                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
418
419         self._reset_by_method(res, method)
420
421         # Here bad password counts are reset without logon success.
422         res = self._check_account(userdn,
423                                   badPwdCount=0,
424                                   badPasswordTime=badPasswordTime,
425                                   logonCount=logonCount,
426                                   lockoutTime=0,
427                                   lastLogon=lastLogon,
428                                   lastLogonTimestamp=lastLogonTimestamp,
429                                   userAccountControl=
430                                     dsdb.UF_NORMAL_ACCOUNT,
431                                   msDSUserAccountControlComputed=0)
432
433         # The correct password after doing the unlock
434
435         other_ldb.modify_ldif("""
436 dn: """ + userdn + """
437 changetype: modify
438 delete: unicodePwd
439 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
440 add: unicodePwd
441 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
442 """)
443         userpass = "thatsAcomplPASS2x"
444         creds.set_password(userpass)
445
446         res = self._check_account(userdn,
447                                   badPwdCount=0,
448                                   badPasswordTime=badPasswordTime,
449                                   logonCount=logonCount,
450                                   lockoutTime=0,
451                                   lastLogon=lastLogon,
452                                   lastLogonTimestamp=lastLogonTimestamp,
453                                   userAccountControl=
454                                     dsdb.UF_NORMAL_ACCOUNT,
455                                   msDSUserAccountControlComputed=0)
456
457         # Wrong old password
458         try:
459             other_ldb.modify_ldif("""
460 dn: """ + userdn + """
461 changetype: modify
462 delete: userPassword
463 userPassword: thatsAcomplPASS1xyz
464 add: userPassword
465 userPassword: thatsAcomplPASS2XYZ
466 """)
467             self.fail()
468         except LdbError, (num, msg):
469             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
470             self.assertTrue('00000056' in msg, msg)
471
472         res = self._check_account(userdn,
473                                   badPwdCount=1,
474                                   badPasswordTime=("greater", badPasswordTime),
475                                   logonCount=logonCount,
476                                   lockoutTime=0,
477                                   lastLogon=lastLogon,
478                                   lastLogonTimestamp=lastLogonTimestamp,
479                                   userAccountControl=
480                                     dsdb.UF_NORMAL_ACCOUNT,
481                                   msDSUserAccountControlComputed=0)
482         badPasswordTime = int(res[0]["badPasswordTime"][0])
483
484         # Wrong old password
485         try:
486             other_ldb.modify_ldif("""
487 dn: """ + userdn + """
488 changetype: modify
489 delete: userPassword
490 userPassword: thatsAcomplPASS1xyz
491 add: userPassword
492 userPassword: thatsAcomplPASS2XYZ
493 """)
494             self.fail()
495         except LdbError, (num, msg):
496             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
497             self.assertTrue('00000056' in msg, msg)
498
499         res = self._check_account(userdn,
500                                   badPwdCount=2,
501                                   badPasswordTime=("greater", badPasswordTime),
502                                   logonCount=logonCount,
503                                   lockoutTime=0,
504                                   lastLogon=lastLogon,
505                                   lastLogonTimestamp=lastLogonTimestamp,
506                                   userAccountControl=
507                                     dsdb.UF_NORMAL_ACCOUNT,
508                                   msDSUserAccountControlComputed=0)
509         badPasswordTime = int(res[0]["badPasswordTime"][0])
510
511         self._reset_ldap_lockoutTime(res)
512
513         res = self._check_account(userdn,
514                                   badPwdCount=0,
515                                   badPasswordTime=badPasswordTime,
516                                   logonCount=logonCount,
517                                   lastLogon=lastLogon,
518                                   lastLogonTimestamp=lastLogonTimestamp,
519                                   lockoutTime=0,
520                                   userAccountControl=
521                                     dsdb.UF_NORMAL_ACCOUNT,
522                                   msDSUserAccountControlComputed=0)
523
524     def test_userPassword_lockout_with_clear_change_krb5_ldap_userAccountControl(self):
525         self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
526                                                           self.lockout2krb5_ldb,
527                                                           "ldap_userAccountControl")
528
529     def test_userPassword_lockout_with_clear_change_krb5_ldap_lockoutTime(self):
530         self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
531                                                           self.lockout2krb5_ldb,
532                                                           "ldap_lockoutTime")
533
534     def test_userPassword_lockout_with_clear_change_krb5_samr(self):
535         self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
536                                                           self.lockout2krb5_ldb,
537                                                           "samr")
538
539     def test_userPassword_lockout_with_clear_change_ntlm_ldap_userAccountControl(self):
540         self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
541                                                           self.lockout2ntlm_ldb,
542                                                           "ldap_userAccountControl",
543                                                           initial_lastlogon_relation='greater')
544
545     def test_userPassword_lockout_with_clear_change_ntlm_ldap_lockoutTime(self):
546         self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
547                                                           self.lockout2ntlm_ldb,
548                                                           "ldap_lockoutTime",
549                                                           initial_lastlogon_relation='greater')
550
551     def test_userPassword_lockout_with_clear_change_ntlm_samr(self):
552         self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
553                                                           self.lockout2ntlm_ldb,
554                                                           "samr",
555                                                           initial_lastlogon_relation='greater')
556
557     def _test_unicodePwd_lockout_with_clear_change(self, creds, other_ldb,
558                                                    initial_logoncount_relation=None):
559         print "Performs a password cleartext change operation on 'unicodePwd'"
560         username = creds.get_username()
561         userpass = creds.get_password()
562         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
563         if initial_logoncount_relation is not None:
564             logoncount_relation = initial_logoncount_relation
565         else:
566             logoncount_relation = "greater"
567
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         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
581         self.assertGreater(lastLogonTimestamp, badPasswordTime)
582         self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
583
584         # Change password on a connection as another user
585
586         # Wrong old password
587         try:
588             other_ldb.modify_ldif("""
589 dn: """ + userdn + """
590 changetype: modify
591 delete: unicodePwd
592 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
593 add: unicodePwd
594 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
595 """)
596             self.fail()
597         except LdbError, (num, msg):
598             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
599             self.assertTrue('00000056' in msg, msg)
600
601         res = self._check_account(userdn,
602                                   badPwdCount=1,
603                                   badPasswordTime=("greater", badPasswordTime),
604                                   logonCount=logonCount,
605                                   lastLogon=lastLogon,
606                                   lastLogonTimestamp=lastLogonTimestamp,
607                                   userAccountControl=
608                                     dsdb.UF_NORMAL_ACCOUNT,
609                                   msDSUserAccountControlComputed=0)
610         badPasswordTime = int(res[0]["badPasswordTime"][0])
611
612         # Correct old password
613         old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
614         invalid_utf16 = "\"thatsAcomplPASSX\"".encode('utf-16-le')
615         userpass = "thatsAcomplPASS2"
616         creds.set_password(userpass)
617         new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
618
619         other_ldb.modify_ldif("""
620 dn: """ + userdn + """
621 changetype: modify
622 delete: unicodePwd
623 unicodePwd:: """ + base64.b64encode(old_utf16) + """
624 add: unicodePwd
625 unicodePwd:: """ + base64.b64encode(new_utf16) + """
626 """)
627
628         res = self._check_account(userdn,
629                                   badPwdCount=1,
630                                   badPasswordTime=badPasswordTime,
631                                   logonCount=logonCount,
632                                   lastLogon=lastLogon,
633                                   lastLogonTimestamp=lastLogonTimestamp,
634                                   userAccountControl=
635                                     dsdb.UF_NORMAL_ACCOUNT,
636                                   msDSUserAccountControlComputed=0)
637
638         # Wrong old password
639         try:
640             other_ldb.modify_ldif("""
641 dn: """ + userdn + """
642 changetype: modify
643 delete: unicodePwd
644 unicodePwd:: """ + base64.b64encode(old_utf16) + """
645 add: unicodePwd
646 unicodePwd:: """ + base64.b64encode(new_utf16) + """
647 """)
648             self.fail()
649         except LdbError, (num, msg):
650             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
651             self.assertTrue('00000056' in msg, msg)
652
653         res = self._check_account(userdn,
654                                   badPwdCount=2,
655                                   badPasswordTime=("greater", badPasswordTime),
656                                   logonCount=logonCount,
657                                   lastLogon=lastLogon,
658                                   lastLogonTimestamp=lastLogonTimestamp,
659                                   userAccountControl=
660                                     dsdb.UF_NORMAL_ACCOUNT,
661                                   msDSUserAccountControlComputed=0)
662         badPasswordTime = int(res[0]["badPasswordTime"][0])
663
664         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
665         # It doesn't create "lockoutTime" = 0 and doesn't
666         # reset "badPwdCount" = 0.
667         self._reset_samr(res)
668
669         res = self._check_account(userdn,
670                                   badPwdCount=2,
671                                   badPasswordTime=badPasswordTime,
672                                   logonCount=logonCount,
673                                   lastLogon=lastLogon,
674                                   lastLogonTimestamp=lastLogonTimestamp,
675                                   userAccountControl=
676                                     dsdb.UF_NORMAL_ACCOUNT,
677                                   msDSUserAccountControlComputed=0)
678
679         print "two failed password change"
680
681         # Wrong old password
682         try:
683             other_ldb.modify_ldif("""
684 dn: """ + userdn + """
685 changetype: modify
686 delete: unicodePwd
687 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
688 add: unicodePwd
689 unicodePwd:: """ + base64.b64encode(new_utf16) + """
690 """)
691             self.fail()
692         except LdbError, (num, msg):
693             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
694             self.assertTrue('00000056' in msg, msg)
695
696         # this is strange, why do we have lockoutTime=badPasswordTime here?
697         res = self._check_account(userdn,
698                                   badPwdCount=3,
699                                   badPasswordTime=("greater", badPasswordTime),
700                                   logonCount=logonCount,
701                                   lastLogon=lastLogon,
702                                   lastLogonTimestamp=lastLogonTimestamp,
703                                   lockoutTime=("greater", badPasswordTime),
704                                   userAccountControl=
705                                     dsdb.UF_NORMAL_ACCOUNT,
706                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
707         badPasswordTime = int(res[0]["badPasswordTime"][0])
708         lockoutTime = int(res[0]["lockoutTime"][0])
709
710         # Wrong old password
711         try:
712             other_ldb.modify_ldif("""
713 dn: """ + userdn + """
714 changetype: modify
715 delete: unicodePwd
716 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
717 add: unicodePwd
718 unicodePwd:: """ + base64.b64encode(new_utf16) + """
719 """)
720             self.fail()
721         except LdbError, (num, msg):
722             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
723             self.assertTrue('00000775' in msg, msg)
724
725         res = self._check_account(userdn,
726                                   badPwdCount=3,
727                                   badPasswordTime=badPasswordTime,
728                                   logonCount=logonCount,
729                                   lastLogon=lastLogon,
730                                   lastLogonTimestamp=lastLogonTimestamp,
731                                   lockoutTime=lockoutTime,
732                                   userAccountControl=
733                                     dsdb.UF_NORMAL_ACCOUNT,
734                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
735
736         # Wrong old password
737         try:
738             other_ldb.modify_ldif("""
739 dn: """ + userdn + """
740 changetype: modify
741 delete: unicodePwd
742 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
743 add: unicodePwd
744 unicodePwd:: """ + base64.b64encode(new_utf16) + """
745 """)
746             self.fail()
747         except LdbError, (num, msg):
748             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
749             self.assertTrue('00000775' in msg, msg)
750
751         res = self._check_account(userdn,
752                                   badPwdCount=3,
753                                   badPasswordTime=badPasswordTime,
754                                   logonCount=logonCount,
755                                   lastLogon=lastLogon,
756                                   lastLogonTimestamp=lastLogonTimestamp,
757                                   lockoutTime=lockoutTime,
758                                   userAccountControl=
759                                     dsdb.UF_NORMAL_ACCOUNT,
760                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
761
762         try:
763             # Correct old password
764             other_ldb.modify_ldif("""
765 dn: """ + userdn + """
766 changetype: modify
767 delete: unicodePwd
768 unicodePwd:: """ + base64.b64encode(new_utf16) + """
769 add: unicodePwd
770 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
771 """)
772             self.fail()
773         except LdbError, (num, msg):
774             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
775             self.assertTrue('00000775' in msg, msg)
776
777         res = self._check_account(userdn,
778                                   badPwdCount=3,
779                                   badPasswordTime=badPasswordTime,
780                                   logonCount=logonCount,
781                                   lastLogon=lastLogon,
782                                   lastLogonTimestamp=lastLogonTimestamp,
783                                   lockoutTime=lockoutTime,
784                                   userAccountControl=
785                                     dsdb.UF_NORMAL_ACCOUNT,
786                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
787
788         # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
789         self._reset_samr(res);
790
791         res = self._check_account(userdn,
792                                   badPwdCount=0,
793                                   badPasswordTime=badPasswordTime,
794                                   logonCount=logonCount,
795                                   lastLogon=lastLogon,
796                                   lastLogonTimestamp=lastLogonTimestamp,
797                                   lockoutTime=0,
798                                   userAccountControl=
799                                     dsdb.UF_NORMAL_ACCOUNT,
800                                   msDSUserAccountControlComputed=0)
801
802         # Correct old password
803         old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
804         invalid_utf16 = "\"thatsAcomplPASSiX\"".encode('utf-16-le')
805         userpass = "thatsAcomplPASS2x"
806         creds.set_password(userpass)
807         new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
808
809         other_ldb.modify_ldif("""
810 dn: """ + userdn + """
811 changetype: modify
812 delete: unicodePwd
813 unicodePwd:: """ + base64.b64encode(old_utf16) + """
814 add: unicodePwd
815 unicodePwd:: """ + base64.b64encode(new_utf16) + """
816 """)
817
818         res = self._check_account(userdn,
819                                   badPwdCount=0,
820                                   badPasswordTime=badPasswordTime,
821                                   logonCount=logonCount,
822                                   lastLogon=lastLogon,
823                                   lastLogonTimestamp=lastLogonTimestamp,
824                                   lockoutTime=0,
825                                   userAccountControl=
826                                     dsdb.UF_NORMAL_ACCOUNT,
827                                   msDSUserAccountControlComputed=0)
828
829         # Wrong old password
830         try:
831             other_ldb.modify_ldif("""
832 dn: """ + userdn + """
833 changetype: modify
834 delete: unicodePwd
835 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
836 add: unicodePwd
837 unicodePwd:: """ + base64.b64encode(new_utf16) + """
838 """)
839             self.fail()
840         except LdbError, (num, msg):
841             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
842             self.assertTrue('00000056' in msg, msg)
843
844         res = self._check_account(userdn,
845                                   badPwdCount=1,
846                                   badPasswordTime=("greater", badPasswordTime),
847                                   logonCount=logonCount,
848                                   lastLogon=lastLogon,
849                                   lastLogonTimestamp=lastLogonTimestamp,
850                                   lockoutTime=0,
851                                   userAccountControl=
852                                     dsdb.UF_NORMAL_ACCOUNT,
853                                   msDSUserAccountControlComputed=0)
854         badPasswordTime = int(res[0]["badPasswordTime"][0])
855
856         # Wrong old password
857         try:
858             other_ldb.modify_ldif("""
859 dn: """ + userdn + """
860 changetype: modify
861 delete: unicodePwd
862 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
863 add: unicodePwd
864 unicodePwd:: """ + base64.b64encode(new_utf16) + """
865 """)
866             self.fail()
867         except LdbError, (num, msg):
868             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
869             self.assertTrue('00000056' in msg, msg)
870
871         res = self._check_account(userdn,
872                                   badPwdCount=2,
873                                   badPasswordTime=("greater", badPasswordTime),
874                                   logonCount=logonCount,
875                                   lastLogon=lastLogon,
876                                   lastLogonTimestamp=lastLogonTimestamp,
877                                   lockoutTime=0,
878                                   userAccountControl=
879                                     dsdb.UF_NORMAL_ACCOUNT,
880                                   msDSUserAccountControlComputed=0)
881         badPasswordTime = int(res[0]["badPasswordTime"][0])
882
883         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
884         # It doesn't reset "badPwdCount" = 0.
885         self._reset_samr(res)
886
887         res = self._check_account(userdn,
888                                   badPwdCount=2,
889                                   badPasswordTime=badPasswordTime,
890                                   logonCount=logonCount,
891                                   lastLogon=lastLogon,
892                                   lastLogonTimestamp=lastLogonTimestamp,
893                                   lockoutTime=0,
894                                   userAccountControl=
895                                     dsdb.UF_NORMAL_ACCOUNT,
896                                   msDSUserAccountControlComputed=0)
897
898         # Wrong old password
899         try:
900             other_ldb.modify_ldif("""
901 dn: """ + userdn + """
902 changetype: modify
903 delete: unicodePwd
904 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
905 add: unicodePwd
906 unicodePwd:: """ + base64.b64encode(new_utf16) + """
907 """)
908             self.fail()
909         except LdbError, (num, msg):
910             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
911             self.assertTrue('00000056' in msg, msg)
912
913         res = self._check_account(userdn,
914                                   badPwdCount=3,
915                                   badPasswordTime=("greater", badPasswordTime),
916                                   logonCount=logonCount,
917                                   lastLogon=lastLogon,
918                                   lastLogonTimestamp=lastLogonTimestamp,
919                                   lockoutTime=("greater", badPasswordTime),
920                                   userAccountControl=
921                                     dsdb.UF_NORMAL_ACCOUNT,
922                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
923         badPasswordTime = int(res[0]["badPasswordTime"][0])
924         lockoutTime = int(res[0]["lockoutTime"][0])
925
926         time.sleep(self.account_lockout_duration + 1)
927
928         res = self._check_account(userdn,
929                                   badPwdCount=3, effective_bad_password_count=0,
930                                   badPasswordTime=badPasswordTime,
931                                   logonCount=logonCount,
932                                   lastLogon=lastLogon,
933                                   lastLogonTimestamp=lastLogonTimestamp,
934                                   lockoutTime=lockoutTime,
935                                   userAccountControl=
936                                     dsdb.UF_NORMAL_ACCOUNT,
937                                   msDSUserAccountControlComputed=0)
938
939         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
940         # It doesn't reset "lockoutTime" = 0 and doesn't
941         # reset "badPwdCount" = 0.
942         self._reset_samr(res)
943
944         res = self._check_account(userdn,
945                                   badPwdCount=3, effective_bad_password_count=0,
946                                   badPasswordTime=badPasswordTime,
947                                   logonCount=logonCount,
948                                   lockoutTime=lockoutTime,
949                                   lastLogon=lastLogon,
950                                   lastLogonTimestamp=lastLogonTimestamp,
951                                   userAccountControl=
952                                     dsdb.UF_NORMAL_ACCOUNT,
953                                   msDSUserAccountControlComputed=0)
954
955     def test_unicodePwd_lockout_with_clear_change_krb5(self):
956         self._test_unicodePwd_lockout_with_clear_change(self.lockout1krb5_creds,
957                                                         self.lockout2krb5_ldb)
958
959     def test_unicodePwd_lockout_with_clear_change_ntlm(self):
960         self._test_unicodePwd_lockout_with_clear_change(self.lockout1ntlm_creds,
961                                                         self.lockout2ntlm_ldb,
962                                                         initial_logoncount_relation="equal")
963
964     def test_login_lockout_krb5(self):
965         self._test_login_lockout(self.lockout1krb5_creds)
966
967     def test_login_lockout_ntlm(self):
968         self._test_login_lockout(self.lockout1ntlm_creds)
969
970     def test_multiple_logon_krb5(self):
971         self._test_multiple_logon(self.lockout1krb5_creds)
972
973     def test_multiple_logon_ntlm(self):
974         self._test_multiple_logon(self.lockout1ntlm_creds)
975
976     def test_lockout_observation_window(self):
977         lockout3krb5_creds = self.insta_creds(self.template_creds,
978                                               username="lockout3krb5",
979                                               userpass="thatsAcomplPASS0",
980                                               kerberos_state=MUST_USE_KERBEROS)
981         self._testing_add_user(lockout3krb5_creds)
982
983         lockout4krb5_creds = self.insta_creds(self.template_creds,
984                                               username="lockout4krb5",
985                                               userpass="thatsAcomplPASS0",
986                                               kerberos_state=MUST_USE_KERBEROS)
987         self._testing_add_user(lockout4krb5_creds,
988                                lockOutObservationWindow=self.lockout_observation_window)
989
990         lockout3ntlm_creds = self.insta_creds(self.template_creds,
991                                               username="lockout3ntlm",
992                                               userpass="thatsAcomplPASS0",
993                                               kerberos_state=DONT_USE_KERBEROS)
994         self._testing_add_user(lockout3ntlm_creds)
995         lockout4ntlm_creds = self.insta_creds(self.template_creds,
996                                               username="lockout4ntlm",
997                                               userpass="thatsAcomplPASS0",
998                                               kerberos_state=DONT_USE_KERBEROS)
999         self._testing_add_user(lockout4ntlm_creds,
1000                                lockOutObservationWindow=self.lockout_observation_window)
1001
1002 host_url = "ldap://%s" % host
1003
1004 TestProgram(module=__name__, opts=subunitopts)