password_lockout: test creds.get_kerberos_state()
[Samba.git] / source4 / dsdb / tests / python / password_lockout.py
blobe6badbce4e611234e65f866dc28de07c5ab6f612
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # This tests the password lockout behavior for AD implementations
5 # Copyright Matthias Dieter Wallnoefer 2010
6 # Copyright Andrew Bartlett 2013
7 # Copyright Stefan Metzmacher 2014
10 import optparse
11 import sys
12 import base64
13 import time
15 sys.path.insert(0, "bin/python")
16 import samba
18 from samba.tests.subunitrun import TestProgram, SubunitOptions
20 import samba.getopt as options
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
36 parser = optparse.OptionParser("passwords.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()
47 if len(args) < 1:
48 parser.print_usage()
49 sys.exit(1)
51 host = args[0]
53 lp = sambaopts.get_loadparm()
54 global_creds = credopts.get_credentials(lp)
56 # Force an encrypted connection
57 global_creds.set_gensec_features(global_creds.get_gensec_features() |
58 gensec.FEATURE_SEAL)
60 def insta_creds(template=global_creds):
61 # get a copy of the global creds or a the passed in creds
62 c = Credentials()
63 c.set_username("testuser")
64 c.set_password("thatsAcomplPASS1")
65 c.set_domain(template.get_domain())
66 c.set_realm(template.get_realm())
67 c.set_workstation(template.get_workstation())
68 c.set_gensec_features(c.get_gensec_features()
69 | gensec.FEATURE_SEAL)
70 c.set_kerberos_state(template.get_kerberos_state())
71 return c
74 # Tests start here
77 class PasswordTests(samba.tests.TestCase):
79 def _open_samr_user(self, res):
80 self.assertTrue("objectSid" in res[0])
82 (domain_sid, rid) = ndr_unpack(security.dom_sid, res[0]["objectSid"][0]).split()
83 self.assertEquals(self.domain_sid, domain_sid)
85 return self.samr.OpenUser(self.samr_domain, security.SEC_FLAG_MAXIMUM_ALLOWED, rid)
87 def _reset_samr(self, res):
89 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
90 samr_user = self._open_samr_user(res)
91 acb_info = self.samr.QueryUserInfo(samr_user, 16)
92 acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
93 self.samr.SetUserInfo(samr_user, 16, acb_info)
94 self.samr.Close(samr_user)
96 def _reset_ldap_lockoutTime(self, res):
97 self.ldb.modify_ldif("""
98 dn: """ + str(res[0].dn) + """
99 changetype: modify
100 replace: lockoutTime
101 lockoutTime: 0
102 """)
104 def _reset_ldap_userAccountControl(self, res):
105 self.assertTrue("userAccountControl" in res[0])
106 self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
108 uac = int(res[0]["userAccountControl"][0])
109 uacc = int(res[0]["msDS-User-Account-Control-Computed"][0])
111 uac |= uacc
112 uac = uac & ~dsdb.UF_LOCKOUT
114 self.ldb.modify_ldif("""
115 dn: """ + str(res[0].dn) + """
116 changetype: modify
117 replace: userAccountControl
118 userAccountControl: %d
119 """ % uac)
121 def _reset_by_method(self, res, method):
122 if method is "ldap_userAccountControl":
123 self._reset_ldap_userAccountControl(res)
124 elif method is "ldap_lockoutTime":
125 self._reset_ldap_lockoutTime(res)
126 elif method is "samr":
127 self._reset_samr(res)
128 else:
129 self.assertTrue(False, msg="Invalid reset method[%s]" % method)
131 def _check_attribute(self, res, name, value):
132 if value is None:
133 self.assertTrue(name not in res[0],
134 msg="attr[%s]=%r on dn[%s]" %
135 (name, res[0], res[0].dn))
136 return
138 if isinstance(value, tuple):
139 (mode, value) = value
140 else:
141 mode = "equal"
143 if mode == "ignore":
144 return
146 if mode == "absent":
147 self.assertFalse(name in res[0],
148 msg="attr[%s] not missing on dn[%s]" %
149 (name, res[0].dn))
150 return
152 self.assertTrue(name in res[0],
153 msg="attr[%s] missing on dn[%s]" %
154 (name, res[0].dn))
155 self.assertTrue(len(res[0][name]) == 1,
156 msg="attr[%s]=%r on dn[%s]" %
157 (name, res[0][name], res[0].dn))
160 print "%s = '%s'" % (name, res[0][name][0])
162 if mode == "present":
163 return
165 if mode == "equal":
166 v = int(res[0][name][0])
167 value = int(value)
168 msg = ("attr[%s]=[%s] != [%s] on dn[%s]\n"
169 "(diff %d; actual value is %s than expected)" %
170 (name, v, value, res[0].dn, v - value,
171 ('less' if v < value else 'greater')))
173 self.assertTrue(v == value, msg)
174 return
176 if mode == "greater":
177 v = int(res[0][name][0])
178 self.assertTrue(v > int(value),
179 msg="attr[%s]=[%s] <= [%s] on dn[%s] (diff %d)" %
180 (name, v, int(value), res[0].dn, v - int(value)))
181 return
182 if mode == "less":
183 v = int(res[0][name][0])
184 self.assertTrue(v < int(value),
185 msg="attr[%s]=[%s] >= [%s] on dn[%s] (diff %d)" %
186 (name, v, int(value), res[0].dn, v - int(value)))
187 return
188 self.assertEqual(mode, not mode, "Invalid Mode[%s]" % mode)
190 def _check_account(self, dn,
191 badPwdCount=None,
192 badPasswordTime=None,
193 lastLogon=None,
194 lastLogonTimestamp=None,
195 lockoutTime=None,
196 userAccountControl=None,
197 msDSUserAccountControlComputed=None,
198 effective_bad_password_count=None,
199 msg=None):
200 print '-=' * 36
201 if msg is not None:
202 print "\033[01;32m %s \033[00m\n" % msg
203 attrs = [
204 "objectSid",
205 "badPwdCount",
206 "badPasswordTime",
207 "lastLogon",
208 "lastLogonTimestamp",
209 "lockoutTime",
210 "userAccountControl",
211 "msDS-User-Account-Control-Computed"
214 # in order to prevent some time resolution problems we sleep for
215 # 10 micro second
216 time.sleep(0.01)
218 res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
219 self.assertTrue(len(res) == 1)
220 self._check_attribute(res, "badPwdCount", badPwdCount)
221 self._check_attribute(res, "badPasswordTime", badPasswordTime)
222 self._check_attribute(res, "lastLogon", lastLogon)
223 self._check_attribute(res, "lastLogonTimestamp", lastLogonTimestamp)
224 self._check_attribute(res, "lockoutTime", lockoutTime)
225 self._check_attribute(res, "userAccountControl", userAccountControl)
226 self._check_attribute(res, "msDS-User-Account-Control-Computed",
227 msDSUserAccountControlComputed)
229 samr_user = self._open_samr_user(res)
230 uinfo3 = self.samr.QueryUserInfo(samr_user, 3)
231 uinfo5 = self.samr.QueryUserInfo(samr_user, 5)
232 uinfo16 = self.samr.QueryUserInfo(samr_user, 16)
233 uinfo21 = self.samr.QueryUserInfo(samr_user, 21)
234 self.samr.Close(samr_user)
236 expected_acb_info = 0
237 if userAccountControl & dsdb.UF_NORMAL_ACCOUNT:
238 expected_acb_info |= samr.ACB_NORMAL
239 if userAccountControl & dsdb.UF_ACCOUNTDISABLE:
240 expected_acb_info |= samr.ACB_DISABLED
241 if userAccountControl & dsdb.UF_PASSWD_NOTREQD:
242 expected_acb_info |= samr.ACB_PWNOTREQ
243 if msDSUserAccountControlComputed & dsdb.UF_LOCKOUT:
244 expected_acb_info |= samr.ACB_AUTOLOCK
245 if msDSUserAccountControlComputed & dsdb.UF_PASSWORD_EXPIRED:
246 expected_acb_info |= samr.ACB_PW_EXPIRED
248 expected_bad_password_count = 0
249 if badPwdCount is not None:
250 expected_bad_password_count = badPwdCount
251 if effective_bad_password_count is None:
252 effective_bad_password_count = expected_bad_password_count
254 self.assertEquals(uinfo3.acct_flags, expected_acb_info)
255 self.assertEquals(uinfo3.bad_password_count, expected_bad_password_count)
257 self.assertEquals(uinfo5.acct_flags, expected_acb_info)
258 self.assertEquals(uinfo5.bad_password_count, effective_bad_password_count)
260 self.assertEquals(uinfo16.acct_flags, expected_acb_info)
262 self.assertEquals(uinfo21.acct_flags, expected_acb_info)
263 self.assertEquals(uinfo21.bad_password_count, effective_bad_password_count)
265 # check LDAP again and make sure the samr.QueryUserInfo
266 # doesn't have any impact.
267 res2 = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
268 self.assertEquals(res[0], res2[0])
270 # in order to prevent some time resolution problems we sleep for
271 # 10 micro second
272 time.sleep(0.01)
273 return res
275 def assertLoginFailure(self, url, creds, lp, errno=ERR_INVALID_CREDENTIALS):
276 try:
277 ldb = SamDB(url=url, credentials=creds, lp=lp)
278 self.fail("Login unexpectedly succeeded")
279 except LdbError, (num, msg):
280 if errno is not None:
281 self.assertEquals(num, errno, ("Login failed in the wrong way"
282 "(got err %d, expected %d)" %
283 (num, errno)))
285 def setUp(self):
286 super(PasswordTests, self).setUp()
288 self.ldb = SamDB(url=host_url, session_info=system_session(lp),
289 credentials=global_creds, lp=lp)
291 # Gets back the basedn
292 base_dn = self.ldb.domain_dn()
294 # Gets back the configuration basedn
295 configuration_dn = self.ldb.get_config_basedn().get_linearized()
297 # Get the old "dSHeuristics" if it was set
298 dsheuristics = self.ldb.get_dsheuristics()
300 # Reset the "dSHeuristics" as they were before
301 self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
303 res = self.ldb.search(base_dn,
304 scope=SCOPE_BASE, attrs=["lockoutDuration", "lockOutObservationWindow", "lockoutThreshold"])
306 if "lockoutDuration" in res[0]:
307 lockoutDuration = res[0]["lockoutDuration"][0]
308 else:
309 lockoutDuration = 0
311 if "lockoutObservationWindow" in res[0]:
312 lockoutObservationWindow = res[0]["lockoutObservationWindow"][0]
313 else:
314 lockoutObservationWindow = 0
316 if "lockoutThreshold" in res[0]:
317 lockoutThreshold = res[0]["lockoutThreshold"][0]
318 else:
319 lockoutTreshold = 0
321 self.addCleanup(self.ldb.modify_ldif, """
322 dn: """ + base_dn + """
323 changetype: modify
324 replace: lockoutDuration
325 lockoutDuration: """ + str(lockoutDuration) + """
326 replace: lockoutObservationWindow
327 lockoutObservationWindow: """ + str(lockoutObservationWindow) + """
328 replace: lockoutThreshold
329 lockoutThreshold: """ + str(lockoutThreshold) + """
330 """)
332 m = Message()
333 m.dn = Dn(self.ldb, base_dn)
335 self.account_lockout_duration = 10
336 account_lockout_duration_ticks = -int(self.account_lockout_duration * (1e7))
338 m["lockoutDuration"] = MessageElement(str(account_lockout_duration_ticks),
339 FLAG_MOD_REPLACE, "lockoutDuration")
341 account_lockout_threshold = 3
342 m["lockoutThreshold"] = MessageElement(str(account_lockout_threshold),
343 FLAG_MOD_REPLACE, "lockoutThreshold")
345 self.lockout_observation_window = 5
346 lockout_observation_window_ticks = -int(self.lockout_observation_window * (1e7))
348 m["lockOutObservationWindow"] = MessageElement(str(lockout_observation_window_ticks),
349 FLAG_MOD_REPLACE, "lockOutObservationWindow")
351 self.ldb.modify(m)
353 # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
354 self.ldb.set_dsheuristics("000000001")
356 # Get the old "minPwdAge"
357 minPwdAge = self.ldb.get_minPwdAge()
359 # Reset the "minPwdAge" as it was before
360 self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
362 # Set it temporarely to "0"
363 self.ldb.set_minPwdAge("0")
365 self.base_dn = self.ldb.domain_dn()
367 self.domain_sid = security.dom_sid(self.ldb.get_domain_sid())
368 self.samr = samr.samr("ncacn_ip_tcp:%s[sign]" % host, lp, global_creds)
369 self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
370 self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
372 # (Re)adds the test user "testuser" with no password atm
373 delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
374 self.ldb.add({
375 "dn": "cn=testuser,cn=users," + self.base_dn,
376 "objectclass": "user",
377 "sAMAccountName": "testuser"})
379 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
380 badPwdCount=0,
381 badPasswordTime=0,
382 lastLogon=0,
383 lastLogonTimestamp=('absent', None),
384 userAccountControl=
385 dsdb.UF_NORMAL_ACCOUNT |
386 dsdb.UF_ACCOUNTDISABLE |
387 dsdb.UF_PASSWD_NOTREQD,
388 msDSUserAccountControlComputed=
389 dsdb.UF_PASSWORD_EXPIRED)
391 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
392 # It doesn't create "lockoutTime" = 0.
393 self._reset_samr(res)
395 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
396 badPwdCount=0,
397 badPasswordTime=0,
398 lastLogon=0,
399 lastLogonTimestamp=('absent', None),
400 userAccountControl=
401 dsdb.UF_NORMAL_ACCOUNT |
402 dsdb.UF_ACCOUNTDISABLE |
403 dsdb.UF_PASSWD_NOTREQD,
404 msDSUserAccountControlComputed=
405 dsdb.UF_PASSWORD_EXPIRED)
407 # Tests a password change when we don't have any password yet with a
408 # wrong old password
409 try:
410 self.ldb.modify_ldif("""
411 dn: cn=testuser,cn=users,""" + self.base_dn + """
412 changetype: modify
413 delete: userPassword
414 userPassword: noPassword
415 add: userPassword
416 userPassword: thatsAcomplPASS2
417 """)
418 self.fail()
419 except LdbError, (num, msg):
420 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
421 # Windows (2008 at least) seems to have some small bug here: it
422 # returns "0000056A" on longer (always wrong) previous passwords.
423 self.assertTrue('00000056' in msg)
425 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
426 badPwdCount=1,
427 badPasswordTime=("greater", 0),
428 lastLogon=0,
429 lastLogonTimestamp=('absent', None),
430 userAccountControl=
431 dsdb.UF_NORMAL_ACCOUNT |
432 dsdb.UF_ACCOUNTDISABLE |
433 dsdb.UF_PASSWD_NOTREQD,
434 msDSUserAccountControlComputed=
435 dsdb.UF_PASSWORD_EXPIRED)
436 badPasswordTime = int(res[0]["badPasswordTime"][0])
438 # Sets the initial user password with a "special" password change
439 # I think that this internally is a password set operation and it can
440 # only be performed by someone which has password set privileges on the
441 # account (at least in s4 we do handle it like that).
442 self.ldb.modify_ldif("""
443 dn: cn=testuser,cn=users,""" + self.base_dn + """
444 changetype: modify
445 delete: userPassword
446 add: userPassword
447 userPassword: thatsAcomplPASS1
448 """)
450 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
451 badPwdCount=1,
452 badPasswordTime=badPasswordTime,
453 lastLogon=0,
454 lastLogonTimestamp=('absent', None),
455 userAccountControl=
456 dsdb.UF_NORMAL_ACCOUNT |
457 dsdb.UF_ACCOUNTDISABLE |
458 dsdb.UF_PASSWD_NOTREQD,
459 msDSUserAccountControlComputed=0)
461 # Enables the user account
462 self.ldb.enable_account("(sAMAccountName=testuser)")
464 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
465 badPwdCount=1,
466 badPasswordTime=badPasswordTime,
467 lastLogon=0,
468 lastLogonTimestamp=('absent', None),
469 userAccountControl=
470 dsdb.UF_NORMAL_ACCOUNT,
471 msDSUserAccountControlComputed=0)
473 # Open a second LDB connection with the user credentials. Use the
474 # command line credentials for informations like the domain, the realm
475 # and the workstation.
476 creds2 = insta_creds()
478 self.ldb2 = SamDB(url=host_url, credentials=creds2, lp=lp)
480 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
481 badPwdCount=0,
482 badPasswordTime=badPasswordTime,
483 lastLogon=('greater', 0),
484 lastLogonTimestamp=('greater', 0),
485 userAccountControl=
486 dsdb.UF_NORMAL_ACCOUNT,
487 msDSUserAccountControlComputed=0)
489 lastLogon = int(res[0]["lastLogon"][0])
490 self.assertGreater(lastLogon, badPasswordTime)
492 # (Re)adds the test user "testuser3" with no password atm
493 delete_force(self.ldb, "cn=testuser3,cn=users," + self.base_dn)
494 self.ldb.add({
495 "dn": "cn=testuser3,cn=users," + self.base_dn,
496 "objectclass": "user",
497 "sAMAccountName": "testuser3"})
499 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
500 badPwdCount=0,
501 badPasswordTime=0,
502 lastLogon=0,
503 lastLogonTimestamp=('absent', None),
504 userAccountControl=
505 dsdb.UF_NORMAL_ACCOUNT |
506 dsdb.UF_ACCOUNTDISABLE |
507 dsdb.UF_PASSWD_NOTREQD,
508 msDSUserAccountControlComputed=
509 dsdb.UF_PASSWORD_EXPIRED)
511 # Tests a password change when we don't have any password yet with a
512 # wrong old password
513 try:
514 self.ldb.modify_ldif("""
515 dn: cn=testuser3,cn=users,""" + self.base_dn + """
516 changetype: modify
517 delete: userPassword
518 userPassword: noPassword
519 add: userPassword
520 userPassword: thatsAcomplPASS2
521 """)
522 self.fail()
523 except LdbError, (num, msg):
524 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
525 # Windows (2008 at least) seems to have some small bug here: it
526 # returns "0000056A" on longer (always wrong) previous passwords.
527 self.assertTrue('00000056' in msg)
529 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
530 badPwdCount=1,
531 badPasswordTime=("greater", 0),
532 lastLogon=0,
533 lastLogonTimestamp=('absent', None),
534 userAccountControl=
535 dsdb.UF_NORMAL_ACCOUNT |
536 dsdb.UF_ACCOUNTDISABLE |
537 dsdb.UF_PASSWD_NOTREQD,
538 msDSUserAccountControlComputed=
539 dsdb.UF_PASSWORD_EXPIRED)
540 badPasswordTime3 = int(res[0]["badPasswordTime"][0])
542 # Sets the initial user password with a "special" password change
543 # I think that this internally is a password set operation and it can
544 # only be performed by someone which has password set privileges on the
545 # account (at least in s4 we do handle it like that).
546 self.ldb.modify_ldif("""
547 dn: cn=testuser3,cn=users,""" + self.base_dn + """
548 changetype: modify
549 delete: userPassword
550 add: userPassword
551 userPassword: thatsAcomplPASS1
552 """)
554 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
555 badPwdCount=1,
556 badPasswordTime=badPasswordTime3,
557 lastLogon=0,
558 lastLogonTimestamp=('absent', None),
559 userAccountControl=
560 dsdb.UF_NORMAL_ACCOUNT |
561 dsdb.UF_ACCOUNTDISABLE |
562 dsdb.UF_PASSWD_NOTREQD,
563 msDSUserAccountControlComputed=0)
565 # Enables the user account
566 self.ldb.enable_account("(sAMAccountName=testuser3)")
568 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
569 badPwdCount=1,
570 badPasswordTime=badPasswordTime3,
571 lastLogon=0,
572 lastLogonTimestamp=('absent', None),
573 userAccountControl=
574 dsdb.UF_NORMAL_ACCOUNT,
575 msDSUserAccountControlComputed=0)
577 # Open a second LDB connection with the user credentials. Use the
578 # command line credentials for informations like the domain, the realm
579 # and the workstation.
580 creds3 = insta_creds()
581 creds3.set_username("testuser3")
582 creds3.set_password("thatsAcomplPASS1")
583 self.ldb3 = SamDB(url=host_url, credentials=creds3, lp=lp)
585 res = self._check_account("cn=testuser3,cn=users," + self.base_dn,
586 badPwdCount=0,
587 badPasswordTime=badPasswordTime3,
588 lastLogon=('greater', badPasswordTime3),
589 lastLogonTimestamp=('greater', badPasswordTime3),
590 userAccountControl=
591 dsdb.UF_NORMAL_ACCOUNT,
592 msDSUserAccountControlComputed=0)
594 def _test_userPassword_lockout_with_clear_change(self, method):
595 print "Performs a password cleartext change operation on 'userPassword'"
596 # Notice: This works only against Windows if "dSHeuristics" has been set
597 # properly
599 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
600 badPwdCount=0,
601 badPasswordTime=("greater", 0),
602 lastLogon=('greater', 0),
603 lastLogonTimestamp=('greater', 0),
604 userAccountControl=
605 dsdb.UF_NORMAL_ACCOUNT,
606 msDSUserAccountControlComputed=0)
607 badPasswordTime = int(res[0]["badPasswordTime"][0])
608 lastLogon = int(res[0]["lastLogon"][0])
609 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
611 # Change password on a connection as another user
613 # Wrong old password
614 try:
615 self.ldb3.modify_ldif("""
616 dn: cn=testuser,cn=users,""" + self.base_dn + """
617 changetype: modify
618 delete: userPassword
619 userPassword: thatsAcomplPASS1x
620 add: userPassword
621 userPassword: thatsAcomplPASS2
622 """)
623 self.fail()
624 except LdbError, (num, msg):
625 self.assertTrue('00000056' in msg)
626 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
628 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
629 badPwdCount=1,
630 badPasswordTime=("greater", badPasswordTime),
631 lastLogon=lastLogon,
632 lastLogonTimestamp=lastLogonTimestamp,
633 userAccountControl=
634 dsdb.UF_NORMAL_ACCOUNT,
635 msDSUserAccountControlComputed=0)
636 badPasswordTime = int(res[0]["badPasswordTime"][0])
638 # Correct old password
639 self.ldb3.modify_ldif("""
640 dn: cn=testuser,cn=users,""" + self.base_dn + """
641 changetype: modify
642 delete: userPassword
643 userPassword: thatsAcomplPASS1
644 add: userPassword
645 userPassword: thatsAcomplPASS2
646 """)
648 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
649 badPwdCount=1,
650 badPasswordTime=badPasswordTime,
651 lastLogon=lastLogon,
652 lastLogonTimestamp=lastLogonTimestamp,
653 userAccountControl=
654 dsdb.UF_NORMAL_ACCOUNT,
655 msDSUserAccountControlComputed=0)
657 # Wrong old password
658 try:
659 self.ldb3.modify_ldif("""
660 dn: cn=testuser,cn=users,""" + self.base_dn + """
661 changetype: modify
662 delete: userPassword
663 userPassword: thatsAcomplPASS1x
664 add: userPassword
665 userPassword: thatsAcomplPASS2
666 """)
667 self.fail()
668 except LdbError, (num, msg):
669 self.assertTrue('00000056' in msg)
670 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
672 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
673 badPwdCount=2,
674 badPasswordTime=("greater", badPasswordTime),
675 lastLogon=lastLogon,
676 lastLogonTimestamp=lastLogonTimestamp,
677 userAccountControl=
678 dsdb.UF_NORMAL_ACCOUNT,
679 msDSUserAccountControlComputed=0)
680 badPasswordTime = int(res[0]["badPasswordTime"][0])
682 print "two failed password change"
684 # Wrong old password
685 try:
686 self.ldb3.modify_ldif("""
687 dn: cn=testuser,cn=users,""" + self.base_dn + """
688 changetype: modify
689 delete: userPassword
690 userPassword: thatsAcomplPASS1x
691 add: userPassword
692 userPassword: thatsAcomplPASS2
693 """)
694 self.fail()
695 except LdbError, (num, msg):
696 self.assertTrue('00000056' in msg)
697 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
699 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
700 badPwdCount=3,
701 badPasswordTime=("greater", badPasswordTime),
702 lastLogon=lastLogon,
703 lastLogonTimestamp=lastLogonTimestamp,
704 lockoutTime=("greater", badPasswordTime),
705 userAccountControl=
706 dsdb.UF_NORMAL_ACCOUNT,
707 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
708 badPasswordTime = int(res[0]["badPasswordTime"][0])
709 lockoutTime = int(res[0]["lockoutTime"][0])
711 # Wrong old password
712 try:
713 self.ldb3.modify_ldif("""
714 dn: cn=testuser,cn=users,""" + self.base_dn + """
715 changetype: modify
716 delete: userPassword
717 userPassword: thatsAcomplPASS1x
718 add: userPassword
719 userPassword: thatsAcomplPASS2
720 """)
721 self.fail()
722 except LdbError, (num, msg):
723 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
724 self.assertTrue('00000775' in msg)
726 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
727 badPwdCount=3,
728 badPasswordTime=badPasswordTime,
729 lastLogon=lastLogon,
730 lastLogonTimestamp=lastLogonTimestamp,
731 lockoutTime=lockoutTime,
732 userAccountControl=
733 dsdb.UF_NORMAL_ACCOUNT,
734 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
736 # Wrong old password
737 try:
738 self.ldb3.modify_ldif("""
739 dn: cn=testuser,cn=users,""" + self.base_dn + """
740 changetype: modify
741 delete: userPassword
742 userPassword: thatsAcomplPASS1x
743 add: userPassword
744 userPassword: thatsAcomplPASS2
745 """)
746 self.fail()
747 except LdbError, (num, msg):
748 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
749 self.assertTrue('00000775' in msg)
751 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
752 badPwdCount=3,
753 badPasswordTime=badPasswordTime,
754 lockoutTime=lockoutTime,
755 lastLogon=lastLogon,
756 lastLogonTimestamp=lastLogonTimestamp,
757 userAccountControl=
758 dsdb.UF_NORMAL_ACCOUNT,
759 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
761 try:
762 # Correct old password
763 self.ldb3.modify_ldif("""
764 dn: cn=testuser,cn=users,""" + self.base_dn + """
765 changetype: modify
766 delete: userPassword
767 userPassword: thatsAcomplPASS2
768 add: userPassword
769 userPassword: thatsAcomplPASS2x
770 """)
771 self.fail()
772 except LdbError, (num, msg):
773 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
774 self.assertTrue('0000775' in msg)
776 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
777 badPwdCount=3,
778 badPasswordTime=badPasswordTime,
779 lastLogon=lastLogon,
780 lastLogonTimestamp=lastLogonTimestamp,
781 lockoutTime=lockoutTime,
782 userAccountControl=
783 dsdb.UF_NORMAL_ACCOUNT,
784 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
786 # Now reset the password, which does NOT change the lockout!
787 self.ldb.modify_ldif("""
788 dn: cn=testuser,cn=users,""" + self.base_dn + """
789 changetype: modify
790 replace: userPassword
791 userPassword: thatsAcomplPASS2
792 """)
794 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
795 badPwdCount=3,
796 badPasswordTime=badPasswordTime,
797 lastLogon=lastLogon,
798 lastLogonTimestamp=lastLogonTimestamp,
799 lockoutTime=lockoutTime,
800 userAccountControl=
801 dsdb.UF_NORMAL_ACCOUNT,
802 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
804 try:
805 # Correct old password
806 self.ldb3.modify_ldif("""
807 dn: cn=testuser,cn=users,""" + self.base_dn + """
808 changetype: modify
809 delete: userPassword
810 userPassword: thatsAcomplPASS2
811 add: userPassword
812 userPassword: thatsAcomplPASS2x
813 """)
814 self.fail()
815 except LdbError, (num, msg):
816 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
817 self.assertTrue('0000775' in msg)
819 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
820 badPwdCount=3,
821 badPasswordTime=badPasswordTime,
822 lastLogon=lastLogon,
823 lastLogonTimestamp=lastLogonTimestamp,
824 lockoutTime=lockoutTime,
825 userAccountControl=
826 dsdb.UF_NORMAL_ACCOUNT,
827 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
829 m = Message()
830 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
831 m["userAccountControl"] = MessageElement(
832 str(dsdb.UF_LOCKOUT),
833 FLAG_MOD_REPLACE, "userAccountControl")
835 self.ldb.modify(m)
837 # This shows that setting the UF_LOCKOUT flag alone makes no difference
838 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
839 badPwdCount=3,
840 badPasswordTime=badPasswordTime,
841 lastLogon=lastLogon,
842 lastLogonTimestamp=lastLogonTimestamp,
843 lockoutTime=lockoutTime,
844 userAccountControl=
845 dsdb.UF_NORMAL_ACCOUNT,
846 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
848 # This shows that setting the UF_LOCKOUT flag makes no difference
849 try:
850 # Correct old password
851 self.ldb3.modify_ldif("""
852 dn: cn=testuser,cn=users,""" + self.base_dn + """
853 changetype: modify
854 delete: unicodePwd
855 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
856 add: unicodePwd
857 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
858 """)
859 self.fail()
860 except LdbError, (num, msg):
861 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
862 self.assertTrue('0000775' in msg)
864 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
865 badPwdCount=3,
866 badPasswordTime=badPasswordTime,
867 lockoutTime=lockoutTime,
868 lastLogon=lastLogon,
869 lastLogonTimestamp=lastLogonTimestamp,
870 userAccountControl=
871 dsdb.UF_NORMAL_ACCOUNT,
872 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
874 self._reset_by_method(res, method)
876 # Here bad password counts are reset without logon success.
877 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
878 badPwdCount=0,
879 badPasswordTime=badPasswordTime,
880 lockoutTime=0,
881 lastLogon=lastLogon,
882 lastLogonTimestamp=lastLogonTimestamp,
883 userAccountControl=
884 dsdb.UF_NORMAL_ACCOUNT,
885 msDSUserAccountControlComputed=0)
887 # The correct password after doing the unlock
889 self.ldb3.modify_ldif("""
890 dn: cn=testuser,cn=users,""" + self.base_dn + """
891 changetype: modify
892 delete: unicodePwd
893 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
894 add: unicodePwd
895 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
896 """)
898 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
899 badPwdCount=0,
900 badPasswordTime=badPasswordTime,
901 lockoutTime=0,
902 lastLogon=lastLogon,
903 lastLogonTimestamp=lastLogonTimestamp,
904 userAccountControl=
905 dsdb.UF_NORMAL_ACCOUNT,
906 msDSUserAccountControlComputed=0)
908 # Wrong old password
909 try:
910 self.ldb3.modify_ldif("""
911 dn: cn=testuser,cn=users,""" + self.base_dn + """
912 changetype: modify
913 delete: userPassword
914 userPassword: thatsAcomplPASS1xyz
915 add: userPassword
916 userPassword: thatsAcomplPASS2XYZ
917 """)
918 self.fail()
919 except LdbError, (num, msg):
920 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
921 self.assertTrue('00000056' in msg)
923 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
924 badPwdCount=1,
925 badPasswordTime=("greater", badPasswordTime),
926 lockoutTime=0,
927 lastLogon=lastLogon,
928 lastLogonTimestamp=lastLogonTimestamp,
929 userAccountControl=
930 dsdb.UF_NORMAL_ACCOUNT,
931 msDSUserAccountControlComputed=0)
932 badPasswordTime = int(res[0]["badPasswordTime"][0])
934 # Wrong old password
935 try:
936 self.ldb3.modify_ldif("""
937 dn: cn=testuser,cn=users,""" + self.base_dn + """
938 changetype: modify
939 delete: userPassword
940 userPassword: thatsAcomplPASS1xyz
941 add: userPassword
942 userPassword: thatsAcomplPASS2XYZ
943 """)
944 self.fail()
945 except LdbError, (num, msg):
946 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
947 self.assertTrue('00000056' in msg)
949 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
950 badPwdCount=2,
951 badPasswordTime=("greater", badPasswordTime),
952 lockoutTime=0,
953 lastLogon=lastLogon,
954 lastLogonTimestamp=lastLogonTimestamp,
955 userAccountControl=
956 dsdb.UF_NORMAL_ACCOUNT,
957 msDSUserAccountControlComputed=0)
958 badPasswordTime = int(res[0]["badPasswordTime"][0])
960 self._reset_ldap_lockoutTime(res)
962 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
963 badPwdCount=0,
964 badPasswordTime=badPasswordTime,
965 lastLogon=lastLogon,
966 lastLogonTimestamp=lastLogonTimestamp,
967 lockoutTime=0,
968 userAccountControl=
969 dsdb.UF_NORMAL_ACCOUNT,
970 msDSUserAccountControlComputed=0)
972 def test_userPassword_lockout_with_clear_change_ldap_userAccountControl(self):
973 self._test_userPassword_lockout_with_clear_change("ldap_userAccountControl")
975 def test_userPassword_lockout_with_clear_change_ldap_lockoutTime(self):
976 self._test_userPassword_lockout_with_clear_change("ldap_lockoutTime")
978 def test_userPassword_lockout_with_clear_change_samr(self):
979 self._test_userPassword_lockout_with_clear_change("samr")
982 def test_unicodePwd_lockout_with_clear_change(self):
983 print "Performs a password cleartext change operation on 'unicodePwd'"
985 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
986 badPwdCount=0,
987 badPasswordTime=("greater", 0),
988 lastLogon=("greater", 0),
989 lastLogonTimestamp=("greater", 0),
990 userAccountControl=
991 dsdb.UF_NORMAL_ACCOUNT,
992 msDSUserAccountControlComputed=0)
993 badPasswordTime = int(res[0]["badPasswordTime"][0])
994 lastLogon = int(res[0]["lastLogon"][0])
996 # Change password on a connection as another user
998 # Wrong old password
999 try:
1000 self.ldb3.modify_ldif("""
1001 dn: cn=testuser,cn=users,""" + self.base_dn + """
1002 changetype: modify
1003 delete: unicodePwd
1004 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1005 add: unicodePwd
1006 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1007 """)
1008 self.fail()
1009 except LdbError, (num, msg):
1010 self.assertTrue('00000056' in msg)
1011 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1013 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1014 badPwdCount=1,
1015 badPasswordTime=("greater", badPasswordTime),
1016 lastLogon=lastLogon,
1017 lastLogonTimestamp=lastLogon,
1018 userAccountControl=
1019 dsdb.UF_NORMAL_ACCOUNT,
1020 msDSUserAccountControlComputed=0)
1021 badPasswordTime = int(res[0]["badPasswordTime"][0])
1023 # Correct old password
1024 self.ldb3.modify_ldif("""
1025 dn: cn=testuser,cn=users,""" + self.base_dn + """
1026 changetype: modify
1027 delete: unicodePwd
1028 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
1029 add: unicodePwd
1030 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1031 """)
1033 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1034 badPwdCount=1,
1035 badPasswordTime=badPasswordTime,
1036 lastLogon=lastLogon,
1037 lastLogonTimestamp=lastLogon,
1038 userAccountControl=
1039 dsdb.UF_NORMAL_ACCOUNT,
1040 msDSUserAccountControlComputed=0)
1042 # Wrong old password
1043 try:
1044 self.ldb3.modify_ldif("""
1045 dn: cn=testuser,cn=users,""" + self.base_dn + """
1046 changetype: modify
1047 delete: unicodePwd
1048 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
1049 add: unicodePwd
1050 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1051 """)
1052 self.fail()
1053 except LdbError, (num, msg):
1054 self.assertTrue('00000056' in msg)
1055 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1057 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1058 badPwdCount=2,
1059 badPasswordTime=("greater", badPasswordTime),
1060 lastLogon=lastLogon,
1061 lastLogonTimestamp=lastLogon,
1062 userAccountControl=
1063 dsdb.UF_NORMAL_ACCOUNT,
1064 msDSUserAccountControlComputed=0)
1065 badPasswordTime = int(res[0]["badPasswordTime"][0])
1067 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1068 # It doesn't create "lockoutTime" = 0 and doesn't
1069 # reset "badPwdCount" = 0.
1070 self._reset_samr(res)
1072 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1073 badPwdCount=2,
1074 badPasswordTime=badPasswordTime,
1075 lastLogon=lastLogon,
1076 lastLogonTimestamp=lastLogon,
1077 userAccountControl=
1078 dsdb.UF_NORMAL_ACCOUNT,
1079 msDSUserAccountControlComputed=0)
1081 print "two failed password change"
1083 # Wrong old password
1084 try:
1085 self.ldb3.modify_ldif("""
1086 dn: cn=testuser,cn=users,""" + self.base_dn + """
1087 changetype: modify
1088 delete: unicodePwd
1089 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1090 add: unicodePwd
1091 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1092 """)
1093 self.fail()
1094 except LdbError, (num, msg):
1095 self.assertTrue('00000056' in msg)
1096 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1098 # this is strange, why do we have lockoutTime=badPasswordTime here?
1099 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1100 badPwdCount=3,
1101 badPasswordTime=("greater", badPasswordTime),
1102 lastLogon=lastLogon,
1103 lastLogonTimestamp=lastLogon,
1104 lockoutTime=("greater", badPasswordTime),
1105 userAccountControl=
1106 dsdb.UF_NORMAL_ACCOUNT,
1107 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1108 badPasswordTime = int(res[0]["badPasswordTime"][0])
1109 lockoutTime = int(res[0]["lockoutTime"][0])
1111 # Wrong old password
1112 try:
1113 self.ldb3.modify_ldif("""
1114 dn: cn=testuser,cn=users,""" + self.base_dn + """
1115 changetype: modify
1116 delete: unicodePwd
1117 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1118 add: unicodePwd
1119 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1120 """)
1121 self.fail()
1122 except LdbError, (num, msg):
1123 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1124 self.assertTrue('00000775' in msg)
1126 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1127 badPwdCount=3,
1128 badPasswordTime=badPasswordTime,
1129 lastLogon=lastLogon,
1130 lastLogonTimestamp=lastLogon,
1131 lockoutTime=lockoutTime,
1132 userAccountControl=
1133 dsdb.UF_NORMAL_ACCOUNT,
1134 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1136 # Wrong old password
1137 try:
1138 self.ldb3.modify_ldif("""
1139 dn: cn=testuser,cn=users,""" + self.base_dn + """
1140 changetype: modify
1141 delete: unicodePwd
1142 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1143 add: unicodePwd
1144 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1145 """)
1146 self.fail()
1147 except LdbError, (num, msg):
1148 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1149 self.assertTrue('00000775' in msg)
1151 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1152 badPwdCount=3,
1153 badPasswordTime=badPasswordTime,
1154 lastLogon=lastLogon,
1155 lastLogonTimestamp=lastLogon,
1156 lockoutTime=lockoutTime,
1157 userAccountControl=
1158 dsdb.UF_NORMAL_ACCOUNT,
1159 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1161 try:
1162 # Correct old password
1163 self.ldb3.modify_ldif("""
1164 dn: cn=testuser,cn=users,""" + self.base_dn + """
1165 changetype: modify
1166 delete: unicodePwd
1167 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1168 add: unicodePwd
1169 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
1170 """)
1171 self.fail()
1172 except LdbError, (num, msg):
1173 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1174 self.assertTrue('0000775' in msg)
1176 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1177 badPwdCount=3,
1178 badPasswordTime=badPasswordTime,
1179 lastLogon=lastLogon,
1180 lastLogonTimestamp=lastLogon,
1181 lockoutTime=lockoutTime,
1182 userAccountControl=
1183 dsdb.UF_NORMAL_ACCOUNT,
1184 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1186 # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
1187 self._reset_samr(res);
1189 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1190 badPwdCount=0,
1191 badPasswordTime=badPasswordTime,
1192 lastLogon=lastLogon,
1193 lastLogonTimestamp=lastLogon,
1194 lockoutTime=0,
1195 userAccountControl=
1196 dsdb.UF_NORMAL_ACCOUNT,
1197 msDSUserAccountControlComputed=0)
1199 # Correct old password
1200 self.ldb3.modify_ldif("""
1201 dn: cn=testuser,cn=users,""" + self.base_dn + """
1202 changetype: modify
1203 delete: unicodePwd
1204 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1205 add: unicodePwd
1206 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
1207 """)
1209 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1210 badPwdCount=0,
1211 badPasswordTime=badPasswordTime,
1212 lastLogon=lastLogon,
1213 lastLogonTimestamp=lastLogon,
1214 lockoutTime=0,
1215 userAccountControl=
1216 dsdb.UF_NORMAL_ACCOUNT,
1217 msDSUserAccountControlComputed=0)
1219 # Wrong old password
1220 try:
1221 self.ldb3.modify_ldif("""
1222 dn: cn=testuser,cn=users,""" + self.base_dn + """
1223 changetype: modify
1224 delete: unicodePwd
1225 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1226 add: unicodePwd
1227 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1228 """)
1229 self.fail()
1230 except LdbError, (num, msg):
1231 self.assertTrue('00000056' in msg)
1232 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1234 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1235 badPwdCount=1,
1236 badPasswordTime=("greater", badPasswordTime),
1237 lastLogon=lastLogon,
1238 lastLogonTimestamp=lastLogon,
1239 lockoutTime=0,
1240 userAccountControl=
1241 dsdb.UF_NORMAL_ACCOUNT,
1242 msDSUserAccountControlComputed=0)
1243 badPasswordTime = int(res[0]["badPasswordTime"][0])
1245 # Wrong old password
1246 try:
1247 self.ldb3.modify_ldif("""
1248 dn: cn=testuser,cn=users,""" + self.base_dn + """
1249 changetype: modify
1250 delete: unicodePwd
1251 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1252 add: unicodePwd
1253 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1254 """)
1255 self.fail()
1256 except LdbError, (num, msg):
1257 self.assertTrue('00000056' in msg)
1258 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1260 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1261 badPwdCount=2,
1262 badPasswordTime=("greater", badPasswordTime),
1263 lastLogon=lastLogon,
1264 lastLogonTimestamp=lastLogon,
1265 lockoutTime=0,
1266 userAccountControl=
1267 dsdb.UF_NORMAL_ACCOUNT,
1268 msDSUserAccountControlComputed=0)
1269 badPasswordTime = int(res[0]["badPasswordTime"][0])
1271 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1272 # It doesn't reset "badPwdCount" = 0.
1273 self._reset_samr(res)
1275 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1276 badPwdCount=2,
1277 badPasswordTime=badPasswordTime,
1278 lastLogon=lastLogon,
1279 lastLogonTimestamp=lastLogon,
1280 lockoutTime=0,
1281 userAccountControl=
1282 dsdb.UF_NORMAL_ACCOUNT,
1283 msDSUserAccountControlComputed=0)
1285 # Wrong old password
1286 try:
1287 self.ldb3.modify_ldif("""
1288 dn: cn=testuser,cn=users,""" + self.base_dn + """
1289 changetype: modify
1290 delete: unicodePwd
1291 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
1292 add: unicodePwd
1293 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
1294 """)
1295 self.fail()
1296 except LdbError, (num, msg):
1297 self.assertTrue('00000056' in msg)
1298 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1300 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1301 badPwdCount=3,
1302 badPasswordTime=("greater", badPasswordTime),
1303 lastLogon=lastLogon,
1304 lastLogonTimestamp=lastLogon,
1305 lockoutTime=("greater", badPasswordTime),
1306 userAccountControl=
1307 dsdb.UF_NORMAL_ACCOUNT,
1308 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1309 badPasswordTime = int(res[0]["badPasswordTime"][0])
1310 lockoutTime = int(res[0]["lockoutTime"][0])
1312 time.sleep(self.account_lockout_duration + 1)
1314 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1315 badPwdCount=3, effective_bad_password_count=0,
1316 badPasswordTime=badPasswordTime,
1317 lastLogon=lastLogon,
1318 lastLogonTimestamp=lastLogon,
1319 lockoutTime=lockoutTime,
1320 userAccountControl=
1321 dsdb.UF_NORMAL_ACCOUNT,
1322 msDSUserAccountControlComputed=0)
1324 # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1325 # It doesn't reset "lockoutTime" = 0 and doesn't
1326 # reset "badPwdCount" = 0.
1327 self._reset_samr(res)
1329 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1330 badPwdCount=3, effective_bad_password_count=0,
1331 badPasswordTime=badPasswordTime,
1332 lockoutTime=lockoutTime,
1333 lastLogon=lastLogon,
1334 lastLogonTimestamp=lastLogon,
1335 userAccountControl=
1336 dsdb.UF_NORMAL_ACCOUNT,
1337 msDSUserAccountControlComputed=0)
1339 def _test_login_lockout(self, use_kerberos):
1340 # This unlocks by waiting for account_lockout_duration
1341 if use_kerberos == MUST_USE_KERBEROS:
1342 lastlogon_relation = 'greater'
1343 print "Performs a lockout attempt against LDAP using Kerberos"
1344 else:
1345 lastlogon_relation = 'equal'
1346 print "Performs a lockout attempt against LDAP using NTLM"
1348 # Change password on a connection as another user
1349 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1350 badPwdCount=0,
1351 badPasswordTime=("greater", 0),
1352 lastLogon=("greater", 0),
1353 lastLogonTimestamp=("greater", 0),
1354 userAccountControl=
1355 dsdb.UF_NORMAL_ACCOUNT,
1356 msDSUserAccountControlComputed=0)
1357 badPasswordTime = int(res[0]["badPasswordTime"][0])
1358 lastLogon = int(res[0]["lastLogon"][0])
1359 firstLogon = lastLogon
1360 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1361 print firstLogon
1362 print lastLogonTimestamp
1365 self.assertGreater(lastLogon, badPasswordTime)
1367 # Open a second LDB connection with the user credentials. Use the
1368 # command line credentials for informations like the domain, the realm
1369 # and the workstation.
1370 creds_lockout = insta_creds()
1371 creds_lockout.set_kerberos_state(use_kerberos)
1373 # The wrong password
1374 creds_lockout.set_password("thatsAcomplPASS1x")
1376 self.assertLoginFailure(host_url, creds_lockout, lp)
1378 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1379 badPwdCount=1,
1380 badPasswordTime=("greater", badPasswordTime),
1381 lastLogon=lastLogon,
1382 lastLogonTimestamp=lastLogonTimestamp,
1383 userAccountControl=
1384 dsdb.UF_NORMAL_ACCOUNT,
1385 msDSUserAccountControlComputed=0,
1386 msg='lastlogontimestamp with wrong password')
1387 badPasswordTime = int(res[0]["badPasswordTime"][0])
1389 # Correct old password
1390 creds_lockout.set_password("thatsAcomplPASS1")
1392 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1394 # lastLogonTimestamp should not change
1395 # lastLogon increases if badPwdCount is non-zero (!)
1396 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1397 badPwdCount=0,
1398 badPasswordTime=badPasswordTime,
1399 lastLogon=('greater', lastLogon),
1400 lastLogonTimestamp=lastLogonTimestamp,
1401 userAccountControl=
1402 dsdb.UF_NORMAL_ACCOUNT,
1403 msDSUserAccountControlComputed=0,
1404 msg='LLTimestamp is updated to lastlogon')
1406 lastLogon = int(res[0]["lastLogon"][0])
1407 self.assertGreater(lastLogon, badPasswordTime)
1409 # The wrong password
1410 creds_lockout.set_password("thatsAcomplPASS1x")
1412 self.assertLoginFailure(host_url, creds_lockout, lp)
1414 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1415 badPwdCount=1,
1416 badPasswordTime=("greater", badPasswordTime),
1417 lastLogon=lastLogon,
1418 lastLogonTimestamp=lastLogonTimestamp,
1419 userAccountControl=
1420 dsdb.UF_NORMAL_ACCOUNT,
1421 msDSUserAccountControlComputed=0)
1422 badPasswordTime = int(res[0]["badPasswordTime"][0])
1424 # The wrong password
1425 creds_lockout.set_password("thatsAcomplPASS1x")
1427 try:
1428 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1429 self.fail()
1431 except LdbError, (num, msg):
1432 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1434 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1435 badPwdCount=2,
1436 badPasswordTime=("greater", badPasswordTime),
1437 lastLogon=lastLogon,
1438 lastLogonTimestamp=lastLogonTimestamp,
1439 userAccountControl=
1440 dsdb.UF_NORMAL_ACCOUNT,
1441 msDSUserAccountControlComputed=0)
1442 badPasswordTime = int(res[0]["badPasswordTime"][0])
1444 print "two failed password change"
1446 # The wrong password
1447 creds_lockout.set_password("thatsAcomplPASS1x")
1449 try:
1450 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1451 self.fail()
1453 except LdbError, (num, msg):
1454 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1456 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1457 badPwdCount=3,
1458 badPasswordTime=("greater", badPasswordTime),
1459 lastLogon=lastLogon,
1460 lastLogonTimestamp=lastLogonTimestamp,
1461 lockoutTime=("greater", badPasswordTime),
1462 userAccountControl=
1463 dsdb.UF_NORMAL_ACCOUNT,
1464 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1465 badPasswordTime = int(res[0]["badPasswordTime"][0])
1466 lockoutTime = int(res[0]["lockoutTime"][0])
1468 # The wrong password
1469 creds_lockout.set_password("thatsAcomplPASS1x")
1470 try:
1471 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1472 self.fail()
1473 except LdbError, (num, msg):
1474 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1476 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1477 badPwdCount=3,
1478 badPasswordTime=badPasswordTime,
1479 lastLogon=lastLogon,
1480 lastLogonTimestamp=lastLogonTimestamp,
1481 lockoutTime=lockoutTime,
1482 userAccountControl=
1483 dsdb.UF_NORMAL_ACCOUNT,
1484 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1486 # The wrong password
1487 creds_lockout.set_password("thatsAcomplPASS1x")
1488 try:
1489 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1490 self.fail()
1491 except LdbError, (num, msg):
1492 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1494 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1495 badPwdCount=3,
1496 badPasswordTime=badPasswordTime,
1497 lastLogon=lastLogon,
1498 lastLogonTimestamp=lastLogonTimestamp,
1499 lockoutTime=lockoutTime,
1500 userAccountControl=
1501 dsdb.UF_NORMAL_ACCOUNT,
1502 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1504 # The correct password, but we are locked out
1505 creds_lockout.set_password("thatsAcomplPASS1")
1506 try:
1507 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1508 self.fail()
1509 except LdbError, (num, msg):
1510 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1512 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1513 badPwdCount=3,
1514 badPasswordTime=badPasswordTime,
1515 lastLogon=lastLogon,
1516 lastLogonTimestamp=lastLogonTimestamp,
1517 lockoutTime=lockoutTime,
1518 userAccountControl=
1519 dsdb.UF_NORMAL_ACCOUNT,
1520 msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
1522 # wait for the lockout to end
1523 time.sleep(self.account_lockout_duration + 1)
1524 print self.account_lockout_duration + 1
1526 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1527 badPwdCount=3, effective_bad_password_count=0,
1528 badPasswordTime=badPasswordTime,
1529 lockoutTime=lockoutTime,
1530 lastLogon=lastLogon,
1531 lastLogonTimestamp=lastLogonTimestamp,
1532 userAccountControl=
1533 dsdb.UF_NORMAL_ACCOUNT,
1534 msDSUserAccountControlComputed=0)
1536 lastLogon = int(res[0]["lastLogon"][0])
1538 # The correct password after letting the timeout expire
1540 creds_lockout.set_password("thatsAcomplPASS1")
1542 creds_lockout2 = insta_creds(creds_lockout)
1544 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout2, lp=lp)
1545 time.sleep(3)
1547 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1548 badPwdCount=0,
1549 badPasswordTime=badPasswordTime,
1550 lastLogon=(lastlogon_relation, lastLogon),
1551 lastLogonTimestamp=lastLogonTimestamp,
1552 lockoutTime=0,
1553 userAccountControl=
1554 dsdb.UF_NORMAL_ACCOUNT,
1555 msDSUserAccountControlComputed=0,
1556 msg="lastLogon is way off")
1558 lastLogon = int(res[0]["lastLogon"][0])
1560 # The wrong password
1561 creds_lockout.set_password("thatsAcomplPASS1x")
1562 try:
1563 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1564 self.fail()
1565 except LdbError, (num, msg):
1566 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1568 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1569 badPwdCount=1,
1570 badPasswordTime=("greater", badPasswordTime),
1571 lockoutTime=0,
1572 lastLogon=lastLogon,
1573 lastLogonTimestamp=lastLogonTimestamp,
1574 userAccountControl=
1575 dsdb.UF_NORMAL_ACCOUNT,
1576 msDSUserAccountControlComputed=0)
1577 badPasswordTime = int(res[0]["badPasswordTime"][0])
1579 # The wrong password
1580 creds_lockout.set_password("thatsAcomplPASS1x")
1581 try:
1582 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1583 self.fail()
1584 except LdbError, (num, msg):
1585 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1587 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1588 badPwdCount=2,
1589 badPasswordTime=("greater", badPasswordTime),
1590 lockoutTime=0,
1591 lastLogon=lastLogon,
1592 lastLogonTimestamp=lastLogonTimestamp,
1593 userAccountControl=
1594 dsdb.UF_NORMAL_ACCOUNT,
1595 msDSUserAccountControlComputed=0)
1596 badPasswordTime = int(res[0]["badPasswordTime"][0])
1598 time.sleep(self.lockout_observation_window + 1)
1600 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1601 badPwdCount=2, effective_bad_password_count=0,
1602 badPasswordTime=badPasswordTime,
1603 lockoutTime=0,
1604 lastLogon=lastLogon,
1605 lastLogonTimestamp=lastLogonTimestamp,
1606 userAccountControl=
1607 dsdb.UF_NORMAL_ACCOUNT,
1608 msDSUserAccountControlComputed=0)
1610 # The wrong password
1611 creds_lockout.set_password("thatsAcomplPASS1x")
1612 try:
1613 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1614 self.fail()
1615 except LdbError, (num, msg):
1616 self.assertEquals(num, ERR_INVALID_CREDENTIALS)
1618 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1619 badPwdCount=1,
1620 badPasswordTime=("greater", badPasswordTime),
1621 lockoutTime=0,
1622 lastLogon=lastLogon,
1623 lastLogonTimestamp=lastLogonTimestamp,
1624 userAccountControl=
1625 dsdb.UF_NORMAL_ACCOUNT,
1626 msDSUserAccountControlComputed=0)
1627 badPasswordTime = int(res[0]["badPasswordTime"][0])
1629 # The correct password without letting the timeout expire
1630 creds_lockout.set_password("thatsAcomplPASS1")
1631 ldb_lockout = SamDB(url=host_url, credentials=creds_lockout, lp=lp)
1633 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1634 badPwdCount=0,
1635 badPasswordTime=badPasswordTime,
1636 lockoutTime=0,
1637 lastLogon=("greater", lastLogon),
1638 lastLogonTimestamp=lastLogonTimestamp,
1639 userAccountControl=
1640 dsdb.UF_NORMAL_ACCOUNT,
1641 msDSUserAccountControlComputed=0)
1643 def test_login_lockout_ntlm(self):
1644 self._test_login_lockout(DONT_USE_KERBEROS)
1646 def test_login_lockout_kerberos(self):
1647 self._test_login_lockout(MUST_USE_KERBEROS)
1649 def _test_multiple_logon(self, use_kerberos):
1650 # Test the happy case in which a user logs on correctly, then
1651 # logs on correctly again, so that the bad password and
1652 # lockout times are both zero the second time. The lastlogon
1653 # time should increase.
1655 # Open a second LDB connection with the user credentials. Use the
1656 # command line credentials for informations like the domain, the realm
1657 # and the workstation.
1658 creds2 = insta_creds()
1659 creds2.set_kerberos_state(use_kerberos)
1660 self.assertEqual(creds2.get_kerberos_state(), use_kerberos)
1662 if use_kerberos == MUST_USE_KERBEROS:
1663 print "Testing multiple logon with Kerberos"
1664 lastlogon_relation = 'greater'
1665 else:
1666 print "Testing multiple logon with NTLM"
1667 lastlogon_relation = 'equal'
1669 SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
1671 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1672 badPwdCount=0,
1673 badPasswordTime=("greater", 0),
1674 lastLogon=("greater", 0),
1675 lastLogonTimestamp=("greater", 0),
1676 userAccountControl=
1677 dsdb.UF_NORMAL_ACCOUNT,
1678 msDSUserAccountControlComputed=0)
1679 badPasswordTime = int(res[0]["badPasswordTime"][0])
1680 lastLogon = int(res[0]["lastLogon"][0])
1681 lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1682 firstLogon = lastLogon
1683 print "last logon is %d" % lastLogon
1684 self.assertGreater(lastLogon, badPasswordTime)
1686 time.sleep(1)
1687 SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
1689 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1690 badPwdCount=0,
1691 badPasswordTime=badPasswordTime,
1692 lastLogon=(lastlogon_relation, lastLogon),
1693 lastLogonTimestamp=lastLogonTimestamp,
1694 userAccountControl=
1695 dsdb.UF_NORMAL_ACCOUNT,
1696 msDSUserAccountControlComputed=0,
1697 msg=("second logon, firstlogon was %s" %
1698 firstLogon))
1701 lastLogon = int(res[0]["lastLogon"][0])
1703 time.sleep(1)
1705 SamDB(url=host_url, credentials=insta_creds(creds2), lp=lp)
1707 res = self._check_account("cn=testuser,cn=users," + self.base_dn,
1708 badPwdCount=0,
1709 badPasswordTime=badPasswordTime,
1710 lastLogon=(lastlogon_relation, lastLogon),
1711 lastLogonTimestamp=lastLogonTimestamp,
1712 userAccountControl=
1713 dsdb.UF_NORMAL_ACCOUNT,
1714 msDSUserAccountControlComputed=0)
1716 def test_multiple_logon_ntlm(self):
1717 self._test_multiple_logon(DONT_USE_KERBEROS)
1719 def test_multiple_logon_kerberos(self):
1720 self._test_multiple_logon(MUST_USE_KERBEROS)
1722 def tearDown(self):
1723 super(PasswordTests, self).tearDown()
1724 delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
1725 delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
1726 delete_force(self.ldb, "cn=testuser3,cn=users," + self.base_dn)
1727 # Close the second LDB connection (with the user credentials)
1728 self.ldb2 = None
1730 host_url = "ldap://%s" % host
1732 TestProgram(module=__name__, opts=subunitopts)