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)
69 class PasswordTests(PasswordTestCase):
72 super(PasswordTests, self).setUp()
73 self.ldb = SamDB(url=host, session_info=system_session(lp), credentials=creds, lp=lp)
75 # Gets back the basedn
76 base_dn = self.ldb.domain_dn()
78 # Gets back the configuration basedn
79 configuration_dn = self.ldb.get_config_basedn().get_linearized()
81 # permit password changes during this test
82 self.allow_password_changes()
84 self.base_dn = self.ldb.domain_dn()
86 # (Re)adds the test user "testuser" with no password atm
87 delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
89 "dn": "cn=testuser,cn=users," + self.base_dn,
90 "objectclass": "user",
91 "sAMAccountName": "testuser"})
93 # Tests a password change when we don't have any password yet with a
96 self.ldb.modify_ldif("""
97 dn: cn=testuser,cn=users,""" + self.base_dn + """
100 userPassword: noPassword
102 userPassword: thatsAcomplPASS2
105 except LdbError as e:
107 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
108 # Windows (2008 at least) seems to have some small bug here: it
109 # returns "0000056A" on longer (always wrong) previous passwords.
110 self.assertTrue('00000056' in msg)
112 # Sets the initial user password with a "special" password change
113 # I think that this internally is a password set operation and it can
114 # only be performed by someone which has password set privileges on the
115 # account (at least in s4 we do handle it like that).
116 self.ldb.modify_ldif("""
117 dn: cn=testuser,cn=users,""" + self.base_dn + """
121 userPassword: thatsAcomplPASS1
124 # But in the other way around this special syntax doesn't work
126 self.ldb.modify_ldif("""
127 dn: cn=testuser,cn=users,""" + self.base_dn + """
130 userPassword: thatsAcomplPASS1
134 except LdbError as e1:
136 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
138 # Enables the user account
139 self.ldb.enable_account("(sAMAccountName=testuser)")
141 # Open a second LDB connection with the user credentials. Use the
142 # command line credentials for informations like the domain, the realm
143 # and the workstation.
144 creds2 = Credentials()
145 creds2.set_username("testuser")
146 creds2.set_password("thatsAcomplPASS1")
147 creds2.set_domain(creds.get_domain())
148 creds2.set_realm(creds.get_realm())
149 creds2.set_workstation(creds.get_workstation())
150 creds2.set_gensec_features(creds2.get_gensec_features()
151 | gensec.FEATURE_SEAL)
152 self.ldb2 = SamDB(url=host, credentials=creds2, lp=lp)
154 def test_unicodePwd_hash_set(self):
155 """Performs a password hash set operation on 'unicodePwd' which should be prevented"""
156 # Notice: Direct hash password sets should never work
159 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
160 m["unicodePwd"] = MessageElement("XXXXXXXXXXXXXXXX", FLAG_MOD_REPLACE,
165 except LdbError as e2:
167 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
169 def test_unicodePwd_hash_change(self):
170 """Performs a password hash change operation on 'unicodePwd' which should be prevented"""
171 # Notice: Direct hash password changes should never work
173 # Hash password changes should never work
175 self.ldb2.modify_ldif("""
176 dn: cn=testuser,cn=users,""" + self.base_dn + """
179 unicodePwd: XXXXXXXXXXXXXXXX
181 unicodePwd: YYYYYYYYYYYYYYYY
184 except LdbError as e3:
186 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
188 def test_unicodePwd_clear_set(self):
189 """Performs a password cleartext set operation on 'unicodePwd'"""
192 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
193 m["unicodePwd"] = MessageElement("\"thatsAcomplPASS2\"".encode('utf-16-le'),
194 FLAG_MOD_REPLACE, "unicodePwd")
197 def test_unicodePwd_clear_change(self):
198 """Performs a password cleartext change operation on 'unicodePwd'"""
200 self.ldb2.modify_ldif("""
201 dn: cn=testuser,cn=users,""" + self.base_dn + """
204 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')).decode('utf8') + """
206 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
211 self.ldb2.modify_ldif("""
212 dn: cn=testuser,cn=users,""" + self.base_dn + """
215 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """
217 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS4\"".encode('utf-16-le')).decode('utf8') + """
220 except LdbError as e4:
222 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
223 self.assertTrue('00000056' in msg)
225 # A change to the same password again will not work (password history)
227 self.ldb2.modify_ldif("""
228 dn: cn=testuser,cn=users,""" + self.base_dn + """
231 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
233 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
236 except LdbError as e5:
238 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
239 self.assertTrue('0000052D' in msg)
241 def test_dBCSPwd_hash_set(self):
242 """Performs a password hash set operation on 'dBCSPwd' which should be prevented"""
243 # Notice: Direct hash password sets should never work
246 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
247 m["dBCSPwd"] = MessageElement("XXXXXXXXXXXXXXXX", FLAG_MOD_REPLACE,
252 except LdbError as e6:
254 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
256 def test_dBCSPwd_hash_change(self):
257 """Performs a password hash change operation on 'dBCSPwd' which should be prevented"""
258 # Notice: Direct hash password changes should never work
261 self.ldb2.modify_ldif("""
262 dn: cn=testuser,cn=users,""" + self.base_dn + """
265 dBCSPwd: XXXXXXXXXXXXXXXX
267 dBCSPwd: YYYYYYYYYYYYYYYY
270 except LdbError as e7:
272 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
274 def test_userPassword_clear_set(self):
275 """Performs a password cleartext set operation on 'userPassword'"""
276 # Notice: This works only against Windows if "dSHeuristics" has been set
280 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
281 m["userPassword"] = MessageElement("thatsAcomplPASS2", FLAG_MOD_REPLACE,
285 def test_userPassword_clear_change(self):
286 """Performs a password cleartext change operation on 'userPassword'"""
287 # Notice: This works only against Windows if "dSHeuristics" has been set
290 self.ldb2.modify_ldif("""
291 dn: cn=testuser,cn=users,""" + self.base_dn + """
294 userPassword: thatsAcomplPASS1
296 userPassword: thatsAcomplPASS2
301 self.ldb2.modify_ldif("""
302 dn: cn=testuser,cn=users,""" + self.base_dn + """
305 userPassword: thatsAcomplPASS3
307 userPassword: thatsAcomplPASS4
310 except LdbError as e8:
312 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
313 self.assertTrue('00000056' in msg)
315 # A change to the same password again will not work (password history)
317 self.ldb2.modify_ldif("""
318 dn: cn=testuser,cn=users,""" + self.base_dn + """
321 userPassword: thatsAcomplPASS2
323 userPassword: thatsAcomplPASS2
326 except LdbError as e9:
328 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
329 self.assertTrue('0000052D' in msg)
331 def test_clearTextPassword_clear_set(self):
332 """Performs a password cleartext set operation on 'clearTextPassword'"""
333 # Notice: This never works against Windows - only supported by us
337 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
338 m["clearTextPassword"] = MessageElement("thatsAcomplPASS2".encode('utf-16-le'),
339 FLAG_MOD_REPLACE, "clearTextPassword")
341 # this passes against s4
342 except LdbError as e10:
343 (num, msg) = e10.args
344 # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it
345 if num != ERR_NO_SUCH_ATTRIBUTE:
346 raise LdbError(num, msg)
348 def test_clearTextPassword_clear_change(self):
349 """Performs a password cleartext change operation on 'clearTextPassword'"""
350 # Notice: This never works against Windows - only supported by us
353 self.ldb2.modify_ldif("""
354 dn: cn=testuser,cn=users,""" + self.base_dn + """
356 delete: clearTextPassword
357 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS1".encode('utf-16-le')).decode('utf8') + """
358 add: clearTextPassword
359 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')).decode('utf8') + """
361 # this passes against s4
362 except LdbError as e11:
363 (num, msg) = e11.args
364 # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it
365 if num != ERR_NO_SUCH_ATTRIBUTE:
366 raise LdbError(num, msg)
370 self.ldb2.modify_ldif("""
371 dn: cn=testuser,cn=users,""" + self.base_dn + """
373 delete: clearTextPassword
374 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS3".encode('utf-16-le')).decode('utf8') + """
375 add: clearTextPassword
376 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS4".encode('utf-16-le')).decode('utf8') + """
379 except LdbError as e12:
380 (num, msg) = e12.args
381 # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it
382 if num != ERR_NO_SUCH_ATTRIBUTE:
383 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
384 self.assertTrue('00000056' in msg)
386 # A change to the same password again will not work (password history)
388 self.ldb2.modify_ldif("""
389 dn: cn=testuser,cn=users,""" + self.base_dn + """
391 delete: clearTextPassword
392 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')).decode('utf8') + """
393 add: clearTextPassword
394 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')).decode('utf8') + """
397 except LdbError as e13:
398 (num, msg) = e13.args
399 # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it
400 if num != ERR_NO_SUCH_ATTRIBUTE:
401 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
402 self.assertTrue('0000052D' in msg)
404 def test_failures(self):
405 """Performs some failure testing"""
408 self.ldb.modify_ldif("""
409 dn: cn=testuser,cn=users,""" + self.base_dn + """
412 userPassword: thatsAcomplPASS1
415 except LdbError as e14:
417 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
420 self.ldb2.modify_ldif("""
421 dn: cn=testuser,cn=users,""" + self.base_dn + """
424 userPassword: thatsAcomplPASS1
427 except LdbError as e15:
429 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
432 self.ldb.modify_ldif("""
433 dn: cn=testuser,cn=users,""" + self.base_dn + """
438 except LdbError as e16:
440 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
443 self.ldb2.modify_ldif("""
444 dn: cn=testuser,cn=users,""" + self.base_dn + """
449 except LdbError as e17:
451 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
454 self.ldb.modify_ldif("""
455 dn: cn=testuser,cn=users,""" + self.base_dn + """
458 userPassword: thatsAcomplPASS1
461 except LdbError as e18:
463 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
466 self.ldb2.modify_ldif("""
467 dn: cn=testuser,cn=users,""" + self.base_dn + """
470 userPassword: thatsAcomplPASS1
473 except LdbError as e19:
475 self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
478 self.ldb.modify_ldif("""
479 dn: cn=testuser,cn=users,""" + self.base_dn + """
482 userPassword: thatsAcomplPASS1
484 userPassword: thatsAcomplPASS2
485 userPassword: thatsAcomplPASS2
488 except LdbError as e20:
490 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
493 self.ldb2.modify_ldif("""
494 dn: cn=testuser,cn=users,""" + self.base_dn + """
497 userPassword: thatsAcomplPASS1
499 userPassword: thatsAcomplPASS2
500 userPassword: thatsAcomplPASS2
503 except LdbError as e21:
505 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
508 self.ldb.modify_ldif("""
509 dn: cn=testuser,cn=users,""" + self.base_dn + """
512 userPassword: thatsAcomplPASS1
513 userPassword: thatsAcomplPASS1
515 userPassword: thatsAcomplPASS2
518 except LdbError as e22:
520 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
523 self.ldb2.modify_ldif("""
524 dn: cn=testuser,cn=users,""" + self.base_dn + """
527 userPassword: thatsAcomplPASS1
528 userPassword: thatsAcomplPASS1
530 userPassword: thatsAcomplPASS2
533 except LdbError as e23:
535 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
538 self.ldb.modify_ldif("""
539 dn: cn=testuser,cn=users,""" + self.base_dn + """
542 userPassword: thatsAcomplPASS1
544 userPassword: thatsAcomplPASS2
546 userPassword: thatsAcomplPASS2
549 except LdbError as e24:
551 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
554 self.ldb2.modify_ldif("""
555 dn: cn=testuser,cn=users,""" + self.base_dn + """
558 userPassword: thatsAcomplPASS1
560 userPassword: thatsAcomplPASS2
562 userPassword: thatsAcomplPASS2
565 except LdbError as e25:
567 self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
570 self.ldb.modify_ldif("""
571 dn: cn=testuser,cn=users,""" + self.base_dn + """
574 userPassword: thatsAcomplPASS1
576 userPassword: thatsAcomplPASS1
578 userPassword: thatsAcomplPASS2
581 except LdbError as e26:
583 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
586 self.ldb2.modify_ldif("""
587 dn: cn=testuser,cn=users,""" + self.base_dn + """
590 userPassword: thatsAcomplPASS1
592 userPassword: thatsAcomplPASS1
594 userPassword: thatsAcomplPASS2
597 except LdbError as e27:
599 self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
602 self.ldb.modify_ldif("""
603 dn: cn=testuser,cn=users,""" + self.base_dn + """
606 userPassword: thatsAcomplPASS1
608 userPassword: thatsAcomplPASS2
609 replace: userPassword
610 userPassword: thatsAcomplPASS3
613 except LdbError as e28:
615 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
618 self.ldb2.modify_ldif("""
619 dn: cn=testuser,cn=users,""" + self.base_dn + """
622 userPassword: thatsAcomplPASS1
624 userPassword: thatsAcomplPASS2
625 replace: userPassword
626 userPassword: thatsAcomplPASS3
629 except LdbError as e29:
631 self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
633 # Reverse order does work
634 self.ldb2.modify_ldif("""
635 dn: cn=testuser,cn=users,""" + self.base_dn + """
638 userPassword: thatsAcomplPASS2
640 userPassword: thatsAcomplPASS1
644 self.ldb2.modify_ldif("""
645 dn: cn=testuser,cn=users,""" + self.base_dn + """
648 userPassword: thatsAcomplPASS2
650 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """
652 # this passes against s4
653 except LdbError as e30:
655 self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
658 self.ldb2.modify_ldif("""
659 dn: cn=testuser,cn=users,""" + self.base_dn + """
662 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """
664 userPassword: thatsAcomplPASS4
666 # this passes against s4
667 except LdbError as e31:
669 self.assertEquals(num, ERR_NO_SUCH_ATTRIBUTE)
671 # Several password changes at once are allowed
672 self.ldb.modify_ldif("""
673 dn: cn=testuser,cn=users,""" + self.base_dn + """
675 replace: userPassword
676 userPassword: thatsAcomplPASS1
677 userPassword: thatsAcomplPASS2
680 # Several password changes at once are allowed
681 self.ldb.modify_ldif("""
682 dn: cn=testuser,cn=users,""" + self.base_dn + """
684 replace: userPassword
685 userPassword: thatsAcomplPASS1
686 userPassword: thatsAcomplPASS2
687 replace: userPassword
688 userPassword: thatsAcomplPASS3
689 replace: userPassword
690 userPassword: thatsAcomplPASS4
693 # This surprisingly should work
694 delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
696 "dn": "cn=testuser2,cn=users," + self.base_dn,
697 "objectclass": "user",
698 "userPassword": ["thatsAcomplPASS1", "thatsAcomplPASS2"] })
700 # This surprisingly should work
701 delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
703 "dn": "cn=testuser2,cn=users," + self.base_dn,
704 "objectclass": "user",
705 "userPassword": ["thatsAcomplPASS1", "thatsAcomplPASS1"] })
707 def test_empty_passwords(self):
708 print("Performs some empty passwords testing")
712 "dn": "cn=testuser2,cn=users," + self.base_dn,
713 "objectclass": "user",
716 except LdbError as e32:
718 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
722 "dn": "cn=testuser2,cn=users," + self.base_dn,
723 "objectclass": "user",
726 except LdbError as e33:
728 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
732 "dn": "cn=testuser2,cn=users," + self.base_dn,
733 "objectclass": "user",
734 "userPassword": [] })
736 except LdbError as e34:
738 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
742 "dn": "cn=testuser2,cn=users," + self.base_dn,
743 "objectclass": "user",
744 "clearTextPassword": [] })
746 except LdbError as e35:
748 self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or
749 num == ERR_NO_SUCH_ATTRIBUTE) # for Windows
751 delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
754 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
755 m["unicodePwd"] = MessageElement([], FLAG_MOD_ADD, "unicodePwd")
759 except LdbError as e36:
761 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
764 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
765 m["dBCSPwd"] = MessageElement([], FLAG_MOD_ADD, "dBCSPwd")
769 except LdbError as e37:
771 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
774 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
775 m["userPassword"] = MessageElement([], FLAG_MOD_ADD, "userPassword")
779 except LdbError as e38:
781 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
784 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
785 m["clearTextPassword"] = MessageElement([], FLAG_MOD_ADD, "clearTextPassword")
789 except LdbError as e39:
791 self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or
792 num == ERR_NO_SUCH_ATTRIBUTE) # for Windows
795 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
796 m["unicodePwd"] = MessageElement([], FLAG_MOD_REPLACE, "unicodePwd")
800 except LdbError as e40:
802 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
805 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
806 m["dBCSPwd"] = MessageElement([], FLAG_MOD_REPLACE, "dBCSPwd")
810 except LdbError as e41:
812 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
815 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
816 m["userPassword"] = MessageElement([], FLAG_MOD_REPLACE, "userPassword")
820 except LdbError as e42:
822 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
825 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
826 m["clearTextPassword"] = MessageElement([], FLAG_MOD_REPLACE, "clearTextPassword")
830 except LdbError as e43:
832 self.assertTrue(num == ERR_UNWILLING_TO_PERFORM or
833 num == ERR_NO_SUCH_ATTRIBUTE) # for Windows
836 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
837 m["unicodePwd"] = MessageElement([], FLAG_MOD_DELETE, "unicodePwd")
841 except LdbError as e44:
843 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
846 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
847 m["dBCSPwd"] = MessageElement([], FLAG_MOD_DELETE, "dBCSPwd")
851 except LdbError as e45:
853 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
856 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
857 m["userPassword"] = MessageElement([], FLAG_MOD_DELETE, "userPassword")
861 except LdbError as e46:
863 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
866 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
867 m["clearTextPassword"] = MessageElement([], FLAG_MOD_DELETE, "clearTextPassword")
871 except LdbError as e47:
873 self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or
874 num == ERR_NO_SUCH_ATTRIBUTE) # for Windows
876 def test_plain_userPassword(self):
877 print("Performs testing about the standard 'userPassword' behaviour")
879 # Delete the "dSHeuristics"
880 self.ldb.set_dsheuristics(None)
882 time.sleep(1) # This switching time is strictly needed!
885 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
886 m["userPassword"] = MessageElement("myPassword", FLAG_MOD_ADD,
890 res = self.ldb.search("cn=testuser,cn=users," + self.base_dn,
891 scope=SCOPE_BASE, attrs=["userPassword"])
892 self.assertTrue(len(res) == 1)
893 self.assertTrue("userPassword" in res[0])
894 self.assertEquals(res[0]["userPassword"][0], "myPassword")
897 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
898 m["userPassword"] = MessageElement("myPassword2", FLAG_MOD_REPLACE,
902 res = self.ldb.search("cn=testuser,cn=users," + self.base_dn,
903 scope=SCOPE_BASE, attrs=["userPassword"])
904 self.assertTrue(len(res) == 1)
905 self.assertTrue("userPassword" in res[0])
906 self.assertEquals(res[0]["userPassword"][0], "myPassword2")
909 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
910 m["userPassword"] = MessageElement([], FLAG_MOD_DELETE,
914 res = self.ldb.search("cn=testuser,cn=users," + self.base_dn,
915 scope=SCOPE_BASE, attrs=["userPassword"])
916 self.assertTrue(len(res) == 1)
917 self.assertFalse("userPassword" in res[0])
919 # Set the test "dSHeuristics" to deactivate "userPassword" pwd changes
920 self.ldb.set_dsheuristics("000000000")
923 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
924 m["userPassword"] = MessageElement("myPassword3", FLAG_MOD_REPLACE,
928 res = self.ldb.search("cn=testuser,cn=users," + self.base_dn,
929 scope=SCOPE_BASE, attrs=["userPassword"])
930 self.assertTrue(len(res) == 1)
931 self.assertTrue("userPassword" in res[0])
932 self.assertEquals(res[0]["userPassword"][0], "myPassword3")
934 # Set the test "dSHeuristics" to deactivate "userPassword" pwd changes
935 self.ldb.set_dsheuristics("000000002")
938 m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
939 m["userPassword"] = MessageElement("myPassword4", FLAG_MOD_REPLACE,
943 res = self.ldb.search("cn=testuser,cn=users," + self.base_dn,
944 scope=SCOPE_BASE, attrs=["userPassword"])
945 self.assertTrue(len(res) == 1)
946 self.assertTrue("userPassword" in res[0])
947 self.assertEquals(res[0]["userPassword"][0], "myPassword4")
949 # Reset the test "dSHeuristics" (reactivate "userPassword" pwd changes)
950 self.ldb.set_dsheuristics("000000001")
952 def test_modify_dsheuristics_userPassword(self):
953 print("Performs testing about reading userPassword between dsHeuristic modifies")
955 # Make sure userPassword cannot be read
956 self.ldb.set_dsheuristics("000000000")
958 # Open a new connection (with dsHeuristic=000000000)
959 ldb1 = SamDB(url=host, session_info=system_session(lp),
960 credentials=creds, lp=lp)
962 # Set userPassword to be read
963 # This setting only affects newer connections (ldb2)
964 ldb1.set_dsheuristics("000000001")
968 m.dn = Dn(ldb1, "cn=testuser,cn=users," + self.base_dn)
969 m["userPassword"] = MessageElement("thatsAcomplPASS1", FLAG_MOD_REPLACE,
973 res = ldb1.search("cn=testuser,cn=users," + self.base_dn,
974 scope=SCOPE_BASE, attrs=["userPassword"])
976 # userPassword cannot be read, it wasn't set, instead the
978 self.assertTrue(len(res) == 1)
979 self.assertFalse("userPassword" in res[0])
981 # Open another new connection (with dsHeuristic=000000001)
982 ldb2 = SamDB(url=host, session_info=system_session(lp),
983 credentials=creds, lp=lp)
985 res = ldb2.search("cn=testuser,cn=users," + self.base_dn,
986 scope=SCOPE_BASE, attrs=["userPassword"])
988 # Check on the new connection that userPassword was not stored
989 # from ldb1 or is not readable
990 self.assertTrue(len(res) == 1)
991 self.assertFalse("userPassword" in res[0])
993 # Set userPassword to be readable
994 # This setting does not affect this connection
995 ldb2.set_dsheuristics("000000000")
998 res = ldb2.search("cn=testuser,cn=users," + self.base_dn,
999 scope=SCOPE_BASE, attrs=["userPassword"])
1001 # Check that userPassword was not stored from ldb1
1002 self.assertTrue(len(res) == 1)
1003 self.assertFalse("userPassword" in res[0])
1006 m.dn = Dn(ldb2, "cn=testuser,cn=users," + self.base_dn)
1007 m["userPassword"] = MessageElement("thatsAcomplPASS2", FLAG_MOD_REPLACE,
1011 res = ldb2.search("cn=testuser,cn=users," + self.base_dn,
1012 scope=SCOPE_BASE, attrs=["userPassword"])
1014 # Check despite setting it with userPassword support disabled
1015 # on this connection it should still not be readable
1016 self.assertTrue(len(res) == 1)
1017 self.assertFalse("userPassword" in res[0])
1019 # Only password from ldb1 is the user's password
1020 creds2 = Credentials()
1021 creds2.set_username("testuser")
1022 creds2.set_password("thatsAcomplPASS1")
1023 creds2.set_domain(creds.get_domain())
1024 creds2.set_realm(creds.get_realm())
1025 creds2.set_workstation(creds.get_workstation())
1026 creds2.set_gensec_features(creds2.get_gensec_features()
1027 | gensec.FEATURE_SEAL)
1030 SamDB(url=host, credentials=creds2, lp=lp)
1032 self.fail("testuser used the wrong password")
1034 ldb3 = SamDB(url=host, session_info=system_session(lp),
1035 credentials=creds, lp=lp)
1037 # Check that userPassword was stored from ldb2
1038 res = ldb3.search("cn=testuser,cn=users," + self.base_dn,
1039 scope=SCOPE_BASE, attrs=["userPassword"])
1041 # userPassword can be read
1042 self.assertTrue(len(res) == 1)
1043 self.assertTrue("userPassword" in res[0])
1044 self.assertEquals(res[0]["userPassword"][0], "thatsAcomplPASS2")
1046 # Reset the test "dSHeuristics" (reactivate "userPassword" pwd changes)
1047 self.ldb.set_dsheuristics("000000001")
1049 ldb4 = SamDB(url=host, session_info=system_session(lp),
1050 credentials=creds, lp=lp)
1052 # Check that userPassword that was stored from ldb2
1053 res = ldb4.search("cn=testuser,cn=users," + self.base_dn,
1054 scope=SCOPE_BASE, attrs=["userPassword"])
1056 # userPassword can be not be read
1057 self.assertTrue(len(res) == 1)
1058 self.assertFalse("userPassword" in res[0])
1060 def test_zero_length(self):
1061 # Get the old "minPwdLength"
1062 minPwdLength = self.ldb.get_minPwdLength()
1063 # Set it temporarely to "0"
1064 self.ldb.set_minPwdLength("0")
1066 # Get the old "pwdProperties"
1067 pwdProperties = self.ldb.get_pwdProperties()
1068 # Set them temporarely to "0" (to deactivate eventually the complexity)
1069 self.ldb.set_pwdProperties("0")
1071 self.ldb.setpassword("(sAMAccountName=testuser)", "")
1073 # Reset the "pwdProperties" as they were before
1074 self.ldb.set_pwdProperties(pwdProperties)
1076 # Reset the "minPwdLength" as it was before
1077 self.ldb.set_minPwdLength(minPwdLength)
1079 def test_pw_change_delete_no_value_userPassword(self):
1080 """Test password change with userPassword where the delete attribute doesn't have a value"""
1083 self.ldb2.modify_ldif("""
1084 dn: cn=testuser,cn=users,""" + self.base_dn + """
1086 delete: userPassword
1088 userPassword: thatsAcomplPASS1
1090 except LdbError, (num, msg):
1091 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1095 def test_pw_change_delete_no_value_clearTextPassword(self):
1096 """Test password change with clearTextPassword where the delete attribute doesn't have a value"""
1099 self.ldb2.modify_ldif("""
1100 dn: cn=testuser,cn=users,""" + self.base_dn + """
1102 delete: clearTextPassword
1103 add: clearTextPassword
1104 clearTextPassword: thatsAcomplPASS2
1106 except LdbError, (num, msg):
1107 self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or
1108 num == ERR_NO_SUCH_ATTRIBUTE) # for Windows
1112 def test_pw_change_delete_no_value_unicodePwd(self):
1113 """Test password change with unicodePwd where the delete attribute doesn't have a value"""
1116 self.ldb2.modify_ldif("""
1117 dn: cn=testuser,cn=users,""" + self.base_dn + """
1121 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """
1123 except LdbError, (num, msg):
1124 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1129 super(PasswordTests, self).tearDown()
1130 delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
1131 delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
1132 # Close the second LDB connection (with the user credentials)
1135 if not "://" in host:
1136 if os.path.isfile(host):
1137 host = "tdb://%s" % host
1139 host = "ldap://%s" % host
1141 TestProgram(module=__name__, opts=subunitopts)