2 # -*- coding: utf-8 -*-
3 # This tests the password changes over LDAP for AD implementations
5 # Copyright Matthias Dieter Wallnoefer 2010
7 # Notice: This tests will also work against Windows Server if the connection is
8 # secured enough (SASL with a minimum of 128 Bit encryption) - consider
11 from __future__ import print_function
18 sys.path.insert(0, "bin/python")
21 from samba.tests.subunitrun import SubunitOptions, TestProgram
22 from samba.tests.password_test import PasswordTestCase
24 import samba.getopt as options
26 from samba.auth import system_session
27 from samba.credentials import Credentials
28 from ldb import SCOPE_BASE, LdbError
29 from ldb import ERR_ATTRIBUTE_OR_VALUE_EXISTS
30 from ldb import ERR_UNWILLING_TO_PERFORM, ERR_INSUFFICIENT_ACCESS_RIGHTS
31 from ldb import ERR_NO_SUCH_ATTRIBUTE
32 from ldb import ERR_CONSTRAINT_VIOLATION
33 from ldb import Message, MessageElement, Dn
34 from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE
35 from samba import gensec
36 from samba.samdb import SamDB
38 from samba.tests import delete_force
39 from password_lockout_base import BasePasswordTestCase
41 parser = optparse.OptionParser("passwords.py [options] <host>")
42 sambaopts = options.SambaOptions(parser)
43 parser.add_option_group(sambaopts)
44 parser.add_option_group(options.VersionOptions(parser))
45 # use command line creds if available
46 credopts = options.CredentialsOptions(parser)
47 parser.add_option_group(credopts)
48 subunitopts = SubunitOptions(parser)
49 parser.add_option_group(subunitopts)
51 opts, args = parser.parse_args()
59 lp = sambaopts.get_loadparm()
60 creds = credopts.get_credentials(lp)
62 # Force an encrypted connection
63 creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
70 class PasswordTests(PasswordTestCase):
73 super(PasswordTests, self).setUp()
74 self.ldb = SamDB(url=host, session_info=system_session(lp), credentials=creds, lp=lp)
76 # Gets back the basedn
77 base_dn = self.ldb.domain_dn()
79 # Gets back the configuration basedn
80 configuration_dn = self.ldb.get_config_basedn().get_linearized()
82 # permit password changes during this test
83 self.allow_password_changes()
85 self.base_dn = self.ldb.domain_dn()
87 # (Re)adds the test user "testuser" with no password atm
88 delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
90 "dn": "cn=testuser,cn=users," + self.base_dn,
91 "objectclass": "user",
92 "sAMAccountName": "testuser"})
94 # Tests a password change when we don't have any password yet with a
97 self.ldb.modify_ldif("""
98 dn: cn=testuser,cn=users,""" + self.base_dn + """
101 userPassword: noPassword
103 userPassword: thatsAcomplPASS2
106 except LdbError as e:
108 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
109 # Windows (2008 at least) seems to have some small bug here: it
110 # returns "0000056A" on longer (always wrong) previous passwords.
111 self.assertTrue('00000056' in msg)
113 # Sets the initial user password with a "special" password change
114 # I think that this internally is a password set operation and it can
115 # only be performed by someone which has password set privileges on the
116 # account (at least in s4 we do handle it like that).
117 self.ldb.modify_ldif("""
118 dn: cn=testuser,cn=users,""" + self.base_dn + """
122 userPassword: thatsAcomplPASS1
125 # But in the other way around this special syntax doesn't work
127 self.ldb.modify_ldif("""
128 dn: cn=testuser,cn=users,""" + self.base_dn + """
131 userPassword: thatsAcomplPASS1
135 except LdbError as e1:
137 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
139 # Enables the user account
140 self.ldb.enable_account("(sAMAccountName=testuser)")
142 # Open a second LDB connection with the user credentials. Use the
143 # command line credentials for information like the domain, the realm
144 # and the workstation.
145 creds2 = Credentials()
146 creds2.set_username("testuser")
147 creds2.set_password("thatsAcomplPASS1")
148 creds2.set_domain(creds.get_domain())
149 creds2.set_realm(creds.get_realm())
150 creds2.set_workstation(creds.get_workstation())
151 creds2.set_gensec_features(creds2.get_gensec_features()
152 | gensec.FEATURE_SEAL)
153 self.ldb2 = SamDB(url=host, credentials=creds2, lp=lp)
155 def test_unicodePwd_hash_set(self):
156 """Performs a password hash set operation on 'unicodePwd' which should be prevented"""
157 # Notice: Direct hash password sets should never work
160 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
161 m["unicodePwd"] = MessageElement("XXXXXXXXXXXXXXXX", FLAG_MOD_REPLACE,
166 except LdbError as e2:
168 self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
170 def test_unicodePwd_hash_change(self):
171 """Performs a password hash change operation on 'unicodePwd' which should be prevented"""
172 # Notice: Direct hash password changes should never work
174 # Hash password changes should never work
176 self.ldb2.modify_ldif("""
177 dn: cn=testuser,cn=users,""" + self.base_dn + """
180 unicodePwd: XXXXXXXXXXXXXXXX
182 unicodePwd: YYYYYYYYYYYYYYYY
185 except LdbError as e3:
187 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
189 def test_unicodePwd_clear_set(self):
190 """Performs a password cleartext set operation on 'unicodePwd'"""
193 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
194 m["unicodePwd"] = MessageElement("\"thatsAcomplPASS2\"".encode('utf-16-le'),
195 FLAG_MOD_REPLACE, "unicodePwd")
198 def test_unicodePwd_clear_change(self):
199 """Performs a password cleartext change operation on 'unicodePwd'"""
201 self.ldb2.modify_ldif("""
202 dn: cn=testuser,cn=users,""" + self.base_dn + """
205 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')).decode('utf8') + """
207 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
212 self.ldb2.modify_ldif("""
213 dn: cn=testuser,cn=users,""" + self.base_dn + """
216 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """
218 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS4\"".encode('utf-16-le')).decode('utf8') + """
221 except LdbError as e4:
223 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
224 self.assertTrue('00000056' in msg)
226 # A change to the same password again will not work (password history)
228 self.ldb2.modify_ldif("""
229 dn: cn=testuser,cn=users,""" + self.base_dn + """
232 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
234 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
237 except LdbError as e5:
239 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
240 self.assertTrue('0000052D' in msg)
242 def test_dBCSPwd_hash_set(self):
243 """Performs a password hash set operation on 'dBCSPwd' which should be prevented"""
244 # Notice: Direct hash password sets should never work
247 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
248 m["dBCSPwd"] = MessageElement("XXXXXXXXXXXXXXXX", FLAG_MOD_REPLACE,
253 except LdbError as e6:
255 self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
257 def test_dBCSPwd_hash_change(self):
258 """Performs a password hash change operation on 'dBCSPwd' which should be prevented"""
259 # Notice: Direct hash password changes should never work
262 self.ldb2.modify_ldif("""
263 dn: cn=testuser,cn=users,""" + self.base_dn + """
266 dBCSPwd: XXXXXXXXXXXXXXXX
268 dBCSPwd: YYYYYYYYYYYYYYYY
271 except LdbError as e7:
273 self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
275 def test_userPassword_clear_set(self):
276 """Performs a password cleartext set operation on 'userPassword'"""
277 # Notice: This works only against Windows if "dSHeuristics" has been set
281 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
282 m["userPassword"] = MessageElement("thatsAcomplPASS2", FLAG_MOD_REPLACE,
286 def test_userPassword_clear_change(self):
287 """Performs a password cleartext change operation on 'userPassword'"""
288 # Notice: This works only against Windows if "dSHeuristics" has been set
291 self.ldb2.modify_ldif("""
292 dn: cn=testuser,cn=users,""" + self.base_dn + """
295 userPassword: thatsAcomplPASS1
297 userPassword: thatsAcomplPASS2
302 self.ldb2.modify_ldif("""
303 dn: cn=testuser,cn=users,""" + self.base_dn + """
306 userPassword: thatsAcomplPASS3
308 userPassword: thatsAcomplPASS4
311 except LdbError as e8:
313 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
314 self.assertTrue('00000056' in msg)
316 # A change to the same password again will not work (password history)
318 self.ldb2.modify_ldif("""
319 dn: cn=testuser,cn=users,""" + self.base_dn + """
322 userPassword: thatsAcomplPASS2
324 userPassword: thatsAcomplPASS2
327 except LdbError as e9:
329 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
330 self.assertTrue('0000052D' in msg)
332 def test_clearTextPassword_clear_set(self):
333 """Performs a password cleartext set operation on 'clearTextPassword'"""
334 # Notice: This never works against Windows - only supported by us
338 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
339 m["clearTextPassword"] = MessageElement("thatsAcomplPASS2".encode('utf-16-le'),
340 FLAG_MOD_REPLACE, "clearTextPassword")
342 # this passes against s4
343 except LdbError as e10:
344 (num, msg) = e10.args
345 # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it
346 if num != ERR_NO_SUCH_ATTRIBUTE:
347 raise LdbError(num, msg)
349 def test_clearTextPassword_clear_change(self):
350 """Performs a password cleartext change operation on 'clearTextPassword'"""
351 # Notice: This never works against Windows - only supported by us
354 self.ldb2.modify_ldif("""
355 dn: cn=testuser,cn=users,""" + self.base_dn + """
357 delete: clearTextPassword
358 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS1".encode('utf-16-le')).decode('utf8') + """
359 add: clearTextPassword
360 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')).decode('utf8') + """
362 # this passes against s4
363 except LdbError as e11:
364 (num, msg) = e11.args
365 # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it
366 if num != ERR_NO_SUCH_ATTRIBUTE:
367 raise LdbError(num, msg)
371 self.ldb2.modify_ldif("""
372 dn: cn=testuser,cn=users,""" + self.base_dn + """
374 delete: clearTextPassword
375 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS3".encode('utf-16-le')).decode('utf8') + """
376 add: clearTextPassword
377 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS4".encode('utf-16-le')).decode('utf8') + """
380 except LdbError as e12:
381 (num, msg) = e12.args
382 # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it
383 if num != ERR_NO_SUCH_ATTRIBUTE:
384 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
385 self.assertTrue('00000056' in msg)
387 # A change to the same password again will not work (password history)
389 self.ldb2.modify_ldif("""
390 dn: cn=testuser,cn=users,""" + self.base_dn + """
392 delete: clearTextPassword
393 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')).decode('utf8') + """
394 add: clearTextPassword
395 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')).decode('utf8') + """
398 except LdbError as e13:
399 (num, msg) = e13.args
400 # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it
401 if num != ERR_NO_SUCH_ATTRIBUTE:
402 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
403 self.assertTrue('0000052D' in msg)
405 def test_failures(self):
406 """Performs some failure testing"""
409 self.ldb.modify_ldif("""
410 dn: cn=testuser,cn=users,""" + self.base_dn + """
413 userPassword: thatsAcomplPASS1
416 except LdbError as e14:
418 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
421 self.ldb2.modify_ldif("""
422 dn: cn=testuser,cn=users,""" + self.base_dn + """
425 userPassword: thatsAcomplPASS1
428 except LdbError as e15:
430 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
433 self.ldb.modify_ldif("""
434 dn: cn=testuser,cn=users,""" + self.base_dn + """
439 except LdbError as e16:
441 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
444 self.ldb2.modify_ldif("""
445 dn: cn=testuser,cn=users,""" + self.base_dn + """
450 except LdbError as e17:
452 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
455 self.ldb.modify_ldif("""
456 dn: cn=testuser,cn=users,""" + self.base_dn + """
459 userPassword: thatsAcomplPASS1
462 except LdbError as e18:
464 self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
467 self.ldb2.modify_ldif("""
468 dn: cn=testuser,cn=users,""" + self.base_dn + """
471 userPassword: thatsAcomplPASS1
474 except LdbError as e19:
476 self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
479 self.ldb.modify_ldif("""
480 dn: cn=testuser,cn=users,""" + self.base_dn + """
483 userPassword: thatsAcomplPASS1
485 userPassword: thatsAcomplPASS2
486 userPassword: thatsAcomplPASS2
489 except LdbError as e20:
491 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
494 self.ldb2.modify_ldif("""
495 dn: cn=testuser,cn=users,""" + self.base_dn + """
498 userPassword: thatsAcomplPASS1
500 userPassword: thatsAcomplPASS2
501 userPassword: thatsAcomplPASS2
504 except LdbError as e21:
506 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
509 self.ldb.modify_ldif("""
510 dn: cn=testuser,cn=users,""" + self.base_dn + """
513 userPassword: thatsAcomplPASS1
514 userPassword: thatsAcomplPASS1
516 userPassword: thatsAcomplPASS2
519 except LdbError as e22:
521 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
524 self.ldb2.modify_ldif("""
525 dn: cn=testuser,cn=users,""" + self.base_dn + """
528 userPassword: thatsAcomplPASS1
529 userPassword: thatsAcomplPASS1
531 userPassword: thatsAcomplPASS2
534 except LdbError as e23:
536 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
539 self.ldb.modify_ldif("""
540 dn: cn=testuser,cn=users,""" + self.base_dn + """
543 userPassword: thatsAcomplPASS1
545 userPassword: thatsAcomplPASS2
547 userPassword: thatsAcomplPASS2
550 except LdbError as e24:
552 self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
555 self.ldb2.modify_ldif("""
556 dn: cn=testuser,cn=users,""" + self.base_dn + """
559 userPassword: thatsAcomplPASS1
561 userPassword: thatsAcomplPASS2
563 userPassword: thatsAcomplPASS2
566 except LdbError as e25:
568 self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
571 self.ldb.modify_ldif("""
572 dn: cn=testuser,cn=users,""" + self.base_dn + """
575 userPassword: thatsAcomplPASS1
577 userPassword: thatsAcomplPASS1
579 userPassword: thatsAcomplPASS2
582 except LdbError as e26:
584 self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
587 self.ldb2.modify_ldif("""
588 dn: cn=testuser,cn=users,""" + self.base_dn + """
591 userPassword: thatsAcomplPASS1
593 userPassword: thatsAcomplPASS1
595 userPassword: thatsAcomplPASS2
598 except LdbError as e27:
600 self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
603 self.ldb.modify_ldif("""
604 dn: cn=testuser,cn=users,""" + self.base_dn + """
607 userPassword: thatsAcomplPASS1
609 userPassword: thatsAcomplPASS2
610 replace: userPassword
611 userPassword: thatsAcomplPASS3
614 except LdbError as e28:
616 self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
619 self.ldb2.modify_ldif("""
620 dn: cn=testuser,cn=users,""" + self.base_dn + """
623 userPassword: thatsAcomplPASS1
625 userPassword: thatsAcomplPASS2
626 replace: userPassword
627 userPassword: thatsAcomplPASS3
630 except LdbError as e29:
632 self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
634 # Reverse order does work
635 self.ldb2.modify_ldif("""
636 dn: cn=testuser,cn=users,""" + self.base_dn + """
639 userPassword: thatsAcomplPASS2
641 userPassword: thatsAcomplPASS1
645 self.ldb2.modify_ldif("""
646 dn: cn=testuser,cn=users,""" + self.base_dn + """
649 userPassword: thatsAcomplPASS2
651 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """
653 # this passes against s4
654 except LdbError as e30:
656 self.assertEqual(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
659 self.ldb2.modify_ldif("""
660 dn: cn=testuser,cn=users,""" + self.base_dn + """
663 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """
665 userPassword: thatsAcomplPASS4
667 # this passes against s4
668 except LdbError as e31:
670 self.assertEqual(num, ERR_NO_SUCH_ATTRIBUTE)
672 # Several password changes at once are allowed
673 self.ldb.modify_ldif("""
674 dn: cn=testuser,cn=users,""" + self.base_dn + """
676 replace: userPassword
677 userPassword: thatsAcomplPASS1
678 userPassword: thatsAcomplPASS2
681 # Several password changes at once are allowed
682 self.ldb.modify_ldif("""
683 dn: cn=testuser,cn=users,""" + self.base_dn + """
685 replace: userPassword
686 userPassword: thatsAcomplPASS1
687 userPassword: thatsAcomplPASS2
688 replace: userPassword
689 userPassword: thatsAcomplPASS3
690 replace: userPassword
691 userPassword: thatsAcomplPASS4
694 # This surprisingly should work
695 delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
697 "dn": "cn=testuser2,cn=users," + self.base_dn,
698 "objectclass": "user",
699 "userPassword": ["thatsAcomplPASS1", "thatsAcomplPASS2"]})
701 # This surprisingly should work
702 delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
704 "dn": "cn=testuser2,cn=users," + self.base_dn,
705 "objectclass": "user",
706 "userPassword": ["thatsAcomplPASS1", "thatsAcomplPASS1"]})
708 def test_empty_passwords(self):
709 print("Performs some empty passwords testing")
713 "dn": "cn=testuser2,cn=users," + self.base_dn,
714 "objectclass": "user",
717 except LdbError as e32:
719 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
723 "dn": "cn=testuser2,cn=users," + self.base_dn,
724 "objectclass": "user",
727 except LdbError as e33:
729 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
733 "dn": "cn=testuser2,cn=users," + self.base_dn,
734 "objectclass": "user",
737 except LdbError as e34:
739 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
743 "dn": "cn=testuser2,cn=users," + self.base_dn,
744 "objectclass": "user",
745 "clearTextPassword": []})
747 except LdbError as e35:
749 self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or
750 num == ERR_NO_SUCH_ATTRIBUTE) # for Windows
752 delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
755 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
756 m["unicodePwd"] = MessageElement([], FLAG_MOD_ADD, "unicodePwd")
760 except LdbError as e36:
762 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
765 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
766 m["dBCSPwd"] = MessageElement([], FLAG_MOD_ADD, "dBCSPwd")
770 except LdbError as e37:
772 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
775 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
776 m["userPassword"] = MessageElement([], FLAG_MOD_ADD, "userPassword")
780 except LdbError as e38:
782 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
785 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
786 m["clearTextPassword"] = MessageElement([], FLAG_MOD_ADD, "clearTextPassword")
790 except LdbError as e39:
792 self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or
793 num == ERR_NO_SUCH_ATTRIBUTE) # for Windows
796 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
797 m["unicodePwd"] = MessageElement([], FLAG_MOD_REPLACE, "unicodePwd")
801 except LdbError as e40:
803 self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
806 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
807 m["dBCSPwd"] = MessageElement([], FLAG_MOD_REPLACE, "dBCSPwd")
811 except LdbError as e41:
813 self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
816 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
817 m["userPassword"] = MessageElement([], FLAG_MOD_REPLACE, "userPassword")
821 except LdbError as e42:
823 self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
826 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
827 m["clearTextPassword"] = MessageElement([], FLAG_MOD_REPLACE, "clearTextPassword")
831 except LdbError as e43:
833 self.assertTrue(num == ERR_UNWILLING_TO_PERFORM or
834 num == ERR_NO_SUCH_ATTRIBUTE) # for Windows
837 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
838 m["unicodePwd"] = MessageElement([], FLAG_MOD_DELETE, "unicodePwd")
842 except LdbError as e44:
844 self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
847 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
848 m["dBCSPwd"] = MessageElement([], FLAG_MOD_DELETE, "dBCSPwd")
852 except LdbError as e45:
854 self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
857 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
858 m["userPassword"] = MessageElement([], FLAG_MOD_DELETE, "userPassword")
862 except LdbError as e46:
864 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
867 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
868 m["clearTextPassword"] = MessageElement([], FLAG_MOD_DELETE, "clearTextPassword")
872 except LdbError as e47:
874 self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or
875 num == ERR_NO_SUCH_ATTRIBUTE) # for Windows
877 def test_plain_userPassword(self):
878 print("Performs testing about the standard 'userPassword' behaviour")
880 # Delete the "dSHeuristics"
881 self.ldb.set_dsheuristics(None)
883 time.sleep(1) # This switching time is strictly needed!
886 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
887 m["userPassword"] = MessageElement("myPassword", FLAG_MOD_ADD,
891 res = self.ldb.search("cn=testuser,cn=users," + self.base_dn,
892 scope=SCOPE_BASE, attrs=["userPassword"])
893 self.assertTrue(len(res) == 1)
894 self.assertTrue("userPassword" in res[0])
895 self.assertEqual(str(res[0]["userPassword"][0]), "myPassword")
898 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
899 m["userPassword"] = MessageElement("myPassword2", FLAG_MOD_REPLACE,
903 res = self.ldb.search("cn=testuser,cn=users," + self.base_dn,
904 scope=SCOPE_BASE, attrs=["userPassword"])
905 self.assertTrue(len(res) == 1)
906 self.assertTrue("userPassword" in res[0])
907 self.assertEqual(str(res[0]["userPassword"][0]), "myPassword2")
910 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
911 m["userPassword"] = MessageElement([], FLAG_MOD_DELETE,
915 res = self.ldb.search("cn=testuser,cn=users," + self.base_dn,
916 scope=SCOPE_BASE, attrs=["userPassword"])
917 self.assertTrue(len(res) == 1)
918 self.assertFalse("userPassword" in res[0])
920 # Set the test "dSHeuristics" to deactivate "userPassword" pwd changes
921 self.ldb.set_dsheuristics("000000000")
924 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
925 m["userPassword"] = MessageElement("myPassword3", FLAG_MOD_REPLACE,
929 res = self.ldb.search("cn=testuser,cn=users," + self.base_dn,
930 scope=SCOPE_BASE, attrs=["userPassword"])
931 self.assertTrue(len(res) == 1)
932 self.assertTrue("userPassword" in res[0])
933 self.assertEqual(str(res[0]["userPassword"][0]), "myPassword3")
935 # Set the test "dSHeuristics" to deactivate "userPassword" pwd changes
936 self.ldb.set_dsheuristics("000000002")
939 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
940 m["userPassword"] = MessageElement("myPassword4", FLAG_MOD_REPLACE,
944 res = self.ldb.search("cn=testuser,cn=users," + self.base_dn,
945 scope=SCOPE_BASE, attrs=["userPassword"])
946 self.assertTrue(len(res) == 1)
947 self.assertTrue("userPassword" in res[0])
948 self.assertEqual(str(res[0]["userPassword"][0]), "myPassword4")
950 # Reset the test "dSHeuristics" (reactivate "userPassword" pwd changes)
951 self.ldb.set_dsheuristics("000000001")
953 def test_modify_dsheuristics_userPassword(self):
954 print("Performs testing about reading userPassword between dsHeuristic modifies")
956 # Make sure userPassword cannot be read
957 self.ldb.set_dsheuristics("000000000")
959 # Open a new connection (with dsHeuristic=000000000)
960 ldb1 = SamDB(url=host, session_info=system_session(lp),
961 credentials=creds, lp=lp)
963 # Set userPassword to be read
964 # This setting only affects newer connections (ldb2)
965 ldb1.set_dsheuristics("000000001")
969 m.dn = Dn(ldb1, "cn=testuser,cn=users," + self.base_dn)
970 m["userPassword"] = MessageElement("thatsAcomplPASS1", FLAG_MOD_REPLACE,
974 res = ldb1.search("cn=testuser,cn=users," + self.base_dn,
975 scope=SCOPE_BASE, attrs=["userPassword"])
977 # userPassword cannot be read, it wasn't set, instead the
979 self.assertTrue(len(res) == 1)
980 self.assertFalse("userPassword" in res[0])
982 # Open another new connection (with dsHeuristic=000000001)
983 ldb2 = SamDB(url=host, session_info=system_session(lp),
984 credentials=creds, lp=lp)
986 res = ldb2.search("cn=testuser,cn=users," + self.base_dn,
987 scope=SCOPE_BASE, attrs=["userPassword"])
989 # Check on the new connection that userPassword was not stored
990 # from ldb1 or is not readable
991 self.assertTrue(len(res) == 1)
992 self.assertFalse("userPassword" in res[0])
994 # Set userPassword to be readable
995 # This setting does not affect this connection
996 ldb2.set_dsheuristics("000000000")
999 res = ldb2.search("cn=testuser,cn=users," + self.base_dn,
1000 scope=SCOPE_BASE, attrs=["userPassword"])
1002 # Check that userPassword was not stored from ldb1
1003 self.assertTrue(len(res) == 1)
1004 self.assertFalse("userPassword" in res[0])
1007 m.dn = Dn(ldb2, "cn=testuser,cn=users," + self.base_dn)
1008 m["userPassword"] = MessageElement("thatsAcomplPASS2", FLAG_MOD_REPLACE,
1012 res = ldb2.search("cn=testuser,cn=users," + self.base_dn,
1013 scope=SCOPE_BASE, attrs=["userPassword"])
1015 # Check despite setting it with userPassword support disabled
1016 # on this connection it should still not be readable
1017 self.assertTrue(len(res) == 1)
1018 self.assertFalse("userPassword" in res[0])
1020 # Only password from ldb1 is the user's password
1021 creds2 = Credentials()
1022 creds2.set_username("testuser")
1023 creds2.set_password("thatsAcomplPASS1")
1024 creds2.set_domain(creds.get_domain())
1025 creds2.set_realm(creds.get_realm())
1026 creds2.set_workstation(creds.get_workstation())
1027 creds2.set_gensec_features(creds2.get_gensec_features()
1028 | gensec.FEATURE_SEAL)
1031 SamDB(url=host, credentials=creds2, lp=lp)
1033 self.fail("testuser used the wrong password")
1035 ldb3 = SamDB(url=host, session_info=system_session(lp),
1036 credentials=creds, lp=lp)
1038 # Check that userPassword was stored from ldb2
1039 res = ldb3.search("cn=testuser,cn=users," + self.base_dn,
1040 scope=SCOPE_BASE, attrs=["userPassword"])
1042 # userPassword can be read
1043 self.assertTrue(len(res) == 1)
1044 self.assertTrue("userPassword" in res[0])
1045 self.assertEqual(str(res[0]["userPassword"][0]), "thatsAcomplPASS2")
1047 # Reset the test "dSHeuristics" (reactivate "userPassword" pwd changes)
1048 self.ldb.set_dsheuristics("000000001")
1050 ldb4 = SamDB(url=host, session_info=system_session(lp),
1051 credentials=creds, lp=lp)
1053 # Check that userPassword that was stored from ldb2
1054 res = ldb4.search("cn=testuser,cn=users," + self.base_dn,
1055 scope=SCOPE_BASE, attrs=["userPassword"])
1057 # userPassword can be not be read
1058 self.assertTrue(len(res) == 1)
1059 self.assertFalse("userPassword" in res[0])
1061 def test_zero_length(self):
1062 # Get the old "minPwdLength"
1063 minPwdLength = self.ldb.get_minPwdLength()
1064 # Set it temporarely to "0"
1065 self.ldb.set_minPwdLength("0")
1067 # Get the old "pwdProperties"
1068 pwdProperties = self.ldb.get_pwdProperties()
1069 # Set them temporarely to "0" (to deactivate eventually the complexity)
1070 self.ldb.set_pwdProperties("0")
1072 self.ldb.setpassword("(sAMAccountName=testuser)", "")
1074 # Reset the "pwdProperties" as they were before
1075 self.ldb.set_pwdProperties(pwdProperties)
1077 # Reset the "minPwdLength" as it was before
1078 self.ldb.set_minPwdLength(minPwdLength)
1080 def test_pw_change_delete_no_value_userPassword(self):
1081 """Test password change with userPassword where the delete attribute doesn't have a value"""
1084 self.ldb2.modify_ldif("""
1085 dn: cn=testuser,cn=users,""" + self.base_dn + """
1087 delete: userPassword
1089 userPassword: thatsAcomplPASS1
1091 except LdbError as e:
1093 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
1097 def test_pw_change_delete_no_value_clearTextPassword(self):
1098 """Test password change with clearTextPassword where the delete attribute doesn't have a value"""
1101 self.ldb2.modify_ldif("""
1102 dn: cn=testuser,cn=users,""" + self.base_dn + """
1104 delete: clearTextPassword
1105 add: clearTextPassword
1106 clearTextPassword: thatsAcomplPASS2
1108 except LdbError as e:
1110 self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or
1111 num == ERR_NO_SUCH_ATTRIBUTE) # for Windows
1115 def test_pw_change_delete_no_value_unicodePwd(self):
1116 """Test password change with unicodePwd where the delete attribute doesn't have a value"""
1119 self.ldb2.modify_ldif("""
1120 dn: cn=testuser,cn=users,""" + self.base_dn + """
1124 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """
1126 except LdbError as e:
1128 self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
1133 super(PasswordTests, self).tearDown()
1134 delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
1135 delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
1136 # Close the second LDB connection (with the user credentials)
1140 if "://" not in host:
1141 if os.path.isfile(host):
1142 host = "tdb://%s" % host
1144 host = "ldap://%s" % host
1146 TestProgram(module=__name__, opts=subunitopts)