pytests: heed assertEquals deprecation warning en-masse
[samba.git] / source4 / dsdb / tests / python / passwords.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 # This tests the password changes over LDAP for AD implementations
4 #
5 # Copyright Matthias Dieter Wallnoefer 2010
6 #
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
9 # MS-ADTS 3.1.1.3.1.5
10
11 from __future__ import print_function
12 import optparse
13 import sys
14 import base64
15 import time
16 import os
17
18 sys.path.insert(0, "bin/python")
19 import samba
20
21 from samba.tests.subunitrun import SubunitOptions, TestProgram
22 from samba.tests.password_test import PasswordTestCase
23
24 import samba.getopt as options
25
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
37 import samba.tests
38 from samba.tests import delete_force
39 from password_lockout_base import BasePasswordTestCase
40
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)
50
51 opts, args = parser.parse_args()
52
53 if len(args) < 1:
54     parser.print_usage()
55     sys.exit(1)
56
57 host = args[0]
58
59 lp = sambaopts.get_loadparm()
60 creds = credopts.get_credentials(lp)
61
62 # Force an encrypted connection
63 creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
64
65 #
66 # Tests start here
67 #
68
69
70 class PasswordTests(PasswordTestCase):
71
72     def setUp(self):
73         super(PasswordTests, self).setUp()
74         self.ldb = SamDB(url=host, session_info=system_session(lp), credentials=creds, lp=lp)
75
76         # Gets back the basedn
77         base_dn = self.ldb.domain_dn()
78
79         # Gets back the configuration basedn
80         configuration_dn = self.ldb.get_config_basedn().get_linearized()
81
82         # permit password changes during this test
83         self.allow_password_changes()
84
85         self.base_dn = self.ldb.domain_dn()
86
87         # (Re)adds the test user "testuser" with no password atm
88         delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
89         self.ldb.add({
90              "dn": "cn=testuser,cn=users," + self.base_dn,
91              "objectclass": "user",
92              "sAMAccountName": "testuser"})
93
94         # Tests a password change when we don't have any password yet with a
95         # wrong old password
96         try:
97             self.ldb.modify_ldif("""
98 dn: cn=testuser,cn=users,""" + self.base_dn + """
99 changetype: modify
100 delete: userPassword
101 userPassword: noPassword
102 add: userPassword
103 userPassword: thatsAcomplPASS2
104 """)
105             self.fail()
106         except LdbError as e:
107             (num, msg) = e.args
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)
112
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 + """
119 changetype: modify
120 delete: userPassword
121 add: userPassword
122 userPassword: thatsAcomplPASS1
123 """)
124
125         # But in the other way around this special syntax doesn't work
126         try:
127             self.ldb.modify_ldif("""
128 dn: cn=testuser,cn=users,""" + self.base_dn + """
129 changetype: modify
130 delete: userPassword
131 userPassword: thatsAcomplPASS1
132 add: userPassword
133 """)
134             self.fail()
135         except LdbError as e1:
136             (num, _) = e1.args
137             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
138
139         # Enables the user account
140         self.ldb.enable_account("(sAMAccountName=testuser)")
141
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)
154
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
158
159         m = Message()
160         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
161         m["unicodePwd"] = MessageElement("XXXXXXXXXXXXXXXX", FLAG_MOD_REPLACE,
162                                          "unicodePwd")
163         try:
164             self.ldb.modify(m)
165             self.fail()
166         except LdbError as e2:
167             (num, _) = e2.args
168             self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
169
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
173
174         # Hash password changes should never work
175         try:
176             self.ldb2.modify_ldif("""
177 dn: cn=testuser,cn=users,""" + self.base_dn + """
178 changetype: modify
179 delete: unicodePwd
180 unicodePwd: XXXXXXXXXXXXXXXX
181 add: unicodePwd
182 unicodePwd: YYYYYYYYYYYYYYYY
183 """)
184             self.fail()
185         except LdbError as e3:
186             (num, _) = e3.args
187             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
188
189     def test_unicodePwd_clear_set(self):
190         """Performs a password cleartext set operation on 'unicodePwd'"""
191
192         m = Message()
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")
196         self.ldb.modify(m)
197
198     def test_unicodePwd_clear_change(self):
199         """Performs a password cleartext change operation on 'unicodePwd'"""
200
201         self.ldb2.modify_ldif("""
202 dn: cn=testuser,cn=users,""" + self.base_dn + """
203 changetype: modify
204 delete: unicodePwd
205 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')).decode('utf8') + """
206 add: unicodePwd
207 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
208 """)
209
210         # Wrong old password
211         try:
212             self.ldb2.modify_ldif("""
213 dn: cn=testuser,cn=users,""" + self.base_dn + """
214 changetype: modify
215 delete: unicodePwd
216 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """
217 add: unicodePwd
218 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS4\"".encode('utf-16-le')).decode('utf8') + """
219 """)
220             self.fail()
221         except LdbError as e4:
222             (num, msg) = e4.args
223             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
224             self.assertTrue('00000056' in msg)
225
226         # A change to the same password again will not work (password history)
227         try:
228             self.ldb2.modify_ldif("""
229 dn: cn=testuser,cn=users,""" + self.base_dn + """
230 changetype: modify
231 delete: unicodePwd
232 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
233 add: unicodePwd
234 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """
235 """)
236             self.fail()
237         except LdbError as e5:
238             (num, msg) = e5.args
239             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
240             self.assertTrue('0000052D' in msg)
241
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
245
246         m = Message()
247         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
248         m["dBCSPwd"] = MessageElement("XXXXXXXXXXXXXXXX", FLAG_MOD_REPLACE,
249                                       "dBCSPwd")
250         try:
251             self.ldb.modify(m)
252             self.fail()
253         except LdbError as e6:
254             (num, _) = e6.args
255             self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
256
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
260
261         try:
262             self.ldb2.modify_ldif("""
263 dn: cn=testuser,cn=users,""" + self.base_dn + """
264 changetype: modify
265 delete: dBCSPwd
266 dBCSPwd: XXXXXXXXXXXXXXXX
267 add: dBCSPwd
268 dBCSPwd: YYYYYYYYYYYYYYYY
269 """)
270             self.fail()
271         except LdbError as e7:
272             (num, _) = e7.args
273             self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
274
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
278         # properly
279
280         m = Message()
281         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
282         m["userPassword"] = MessageElement("thatsAcomplPASS2", FLAG_MOD_REPLACE,
283                                            "userPassword")
284         self.ldb.modify(m)
285
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
289         # properly
290
291         self.ldb2.modify_ldif("""
292 dn: cn=testuser,cn=users,""" + self.base_dn + """
293 changetype: modify
294 delete: userPassword
295 userPassword: thatsAcomplPASS1
296 add: userPassword
297 userPassword: thatsAcomplPASS2
298 """)
299
300         # Wrong old password
301         try:
302             self.ldb2.modify_ldif("""
303 dn: cn=testuser,cn=users,""" + self.base_dn + """
304 changetype: modify
305 delete: userPassword
306 userPassword: thatsAcomplPASS3
307 add: userPassword
308 userPassword: thatsAcomplPASS4
309 """)
310             self.fail()
311         except LdbError as e8:
312             (num, msg) = e8.args
313             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
314             self.assertTrue('00000056' in msg)
315
316         # A change to the same password again will not work (password history)
317         try:
318             self.ldb2.modify_ldif("""
319 dn: cn=testuser,cn=users,""" + self.base_dn + """
320 changetype: modify
321 delete: userPassword
322 userPassword: thatsAcomplPASS2
323 add: userPassword
324 userPassword: thatsAcomplPASS2
325 """)
326             self.fail()
327         except LdbError as e9:
328             (num, msg) = e9.args
329             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
330             self.assertTrue('0000052D' in msg)
331
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
335
336         try:
337             m = Message()
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")
341             self.ldb.modify(m)
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)
348
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
352
353         try:
354             self.ldb2.modify_ldif("""
355 dn: cn=testuser,cn=users,""" + self.base_dn + """
356 changetype: modify
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') + """
361 """)
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)
368
369         # Wrong old password
370         try:
371             self.ldb2.modify_ldif("""
372 dn: cn=testuser,cn=users,""" + self.base_dn + """
373 changetype: modify
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') + """
378 """)
379             self.fail()
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)
386
387         # A change to the same password again will not work (password history)
388         try:
389             self.ldb2.modify_ldif("""
390 dn: cn=testuser,cn=users,""" + self.base_dn + """
391 changetype: modify
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') + """
396 """)
397             self.fail()
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)
404
405     def test_failures(self):
406         """Performs some failure testing"""
407
408         try:
409             self.ldb.modify_ldif("""
410 dn: cn=testuser,cn=users,""" + self.base_dn + """
411 changetype: modify
412 delete: userPassword
413 userPassword: thatsAcomplPASS1
414 """)
415             self.fail()
416         except LdbError as e14:
417             (num, _) = e14.args
418             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
419
420         try:
421             self.ldb2.modify_ldif("""
422 dn: cn=testuser,cn=users,""" + self.base_dn + """
423 changetype: modify
424 delete: userPassword
425 userPassword: thatsAcomplPASS1
426 """)
427             self.fail()
428         except LdbError as e15:
429             (num, _) = e15.args
430             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
431
432         try:
433             self.ldb.modify_ldif("""
434 dn: cn=testuser,cn=users,""" + self.base_dn + """
435 changetype: modify
436 delete: userPassword
437 """)
438             self.fail()
439         except LdbError as e16:
440             (num, _) = e16.args
441             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
442
443         try:
444             self.ldb2.modify_ldif("""
445 dn: cn=testuser,cn=users,""" + self.base_dn + """
446 changetype: modify
447 delete: userPassword
448 """)
449             self.fail()
450         except LdbError as e17:
451             (num, _) = e17.args
452             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
453
454         try:
455             self.ldb.modify_ldif("""
456 dn: cn=testuser,cn=users,""" + self.base_dn + """
457 changetype: modify
458 add: userPassword
459 userPassword: thatsAcomplPASS1
460 """)
461             self.fail()
462         except LdbError as e18:
463             (num, _) = e18.args
464             self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
465
466         try:
467             self.ldb2.modify_ldif("""
468 dn: cn=testuser,cn=users,""" + self.base_dn + """
469 changetype: modify
470 add: userPassword
471 userPassword: thatsAcomplPASS1
472 """)
473             self.fail()
474         except LdbError as e19:
475             (num, _) = e19.args
476             self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
477
478         try:
479             self.ldb.modify_ldif("""
480 dn: cn=testuser,cn=users,""" + self.base_dn + """
481 changetype: modify
482 delete: userPassword
483 userPassword: thatsAcomplPASS1
484 add: userPassword
485 userPassword: thatsAcomplPASS2
486 userPassword: thatsAcomplPASS2
487 """)
488             self.fail()
489         except LdbError as e20:
490             (num, _) = e20.args
491             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
492
493         try:
494             self.ldb2.modify_ldif("""
495 dn: cn=testuser,cn=users,""" + self.base_dn + """
496 changetype: modify
497 delete: userPassword
498 userPassword: thatsAcomplPASS1
499 add: userPassword
500 userPassword: thatsAcomplPASS2
501 userPassword: thatsAcomplPASS2
502 """)
503             self.fail()
504         except LdbError as e21:
505             (num, _) = e21.args
506             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
507
508         try:
509             self.ldb.modify_ldif("""
510 dn: cn=testuser,cn=users,""" + self.base_dn + """
511 changetype: modify
512 delete: userPassword
513 userPassword: thatsAcomplPASS1
514 userPassword: thatsAcomplPASS1
515 add: userPassword
516 userPassword: thatsAcomplPASS2
517 """)
518             self.fail()
519         except LdbError as e22:
520             (num, _) = e22.args
521             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
522
523         try:
524             self.ldb2.modify_ldif("""
525 dn: cn=testuser,cn=users,""" + self.base_dn + """
526 changetype: modify
527 delete: userPassword
528 userPassword: thatsAcomplPASS1
529 userPassword: thatsAcomplPASS1
530 add: userPassword
531 userPassword: thatsAcomplPASS2
532 """)
533             self.fail()
534         except LdbError as e23:
535             (num, _) = e23.args
536             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
537
538         try:
539             self.ldb.modify_ldif("""
540 dn: cn=testuser,cn=users,""" + self.base_dn + """
541 changetype: modify
542 delete: userPassword
543 userPassword: thatsAcomplPASS1
544 add: userPassword
545 userPassword: thatsAcomplPASS2
546 add: userPassword
547 userPassword: thatsAcomplPASS2
548 """)
549             self.fail()
550         except LdbError as e24:
551             (num, _) = e24.args
552             self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
553
554         try:
555             self.ldb2.modify_ldif("""
556 dn: cn=testuser,cn=users,""" + self.base_dn + """
557 changetype: modify
558 delete: userPassword
559 userPassword: thatsAcomplPASS1
560 add: userPassword
561 userPassword: thatsAcomplPASS2
562 add: userPassword
563 userPassword: thatsAcomplPASS2
564 """)
565             self.fail()
566         except LdbError as e25:
567             (num, _) = e25.args
568             self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
569
570         try:
571             self.ldb.modify_ldif("""
572 dn: cn=testuser,cn=users,""" + self.base_dn + """
573 changetype: modify
574 delete: userPassword
575 userPassword: thatsAcomplPASS1
576 delete: userPassword
577 userPassword: thatsAcomplPASS1
578 add: userPassword
579 userPassword: thatsAcomplPASS2
580 """)
581             self.fail()
582         except LdbError as e26:
583             (num, _) = e26.args
584             self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
585
586         try:
587             self.ldb2.modify_ldif("""
588 dn: cn=testuser,cn=users,""" + self.base_dn + """
589 changetype: modify
590 delete: userPassword
591 userPassword: thatsAcomplPASS1
592 delete: userPassword
593 userPassword: thatsAcomplPASS1
594 add: userPassword
595 userPassword: thatsAcomplPASS2
596 """)
597             self.fail()
598         except LdbError as e27:
599             (num, _) = e27.args
600             self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
601
602         try:
603             self.ldb.modify_ldif("""
604 dn: cn=testuser,cn=users,""" + self.base_dn + """
605 changetype: modify
606 delete: userPassword
607 userPassword: thatsAcomplPASS1
608 add: userPassword
609 userPassword: thatsAcomplPASS2
610 replace: userPassword
611 userPassword: thatsAcomplPASS3
612 """)
613             self.fail()
614         except LdbError as e28:
615             (num, _) = e28.args
616             self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
617
618         try:
619             self.ldb2.modify_ldif("""
620 dn: cn=testuser,cn=users,""" + self.base_dn + """
621 changetype: modify
622 delete: userPassword
623 userPassword: thatsAcomplPASS1
624 add: userPassword
625 userPassword: thatsAcomplPASS2
626 replace: userPassword
627 userPassword: thatsAcomplPASS3
628 """)
629             self.fail()
630         except LdbError as e29:
631             (num, _) = e29.args
632             self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
633
634         # Reverse order does work
635         self.ldb2.modify_ldif("""
636 dn: cn=testuser,cn=users,""" + self.base_dn + """
637 changetype: modify
638 add: userPassword
639 userPassword: thatsAcomplPASS2
640 delete: userPassword
641 userPassword: thatsAcomplPASS1
642 """)
643
644         try:
645             self.ldb2.modify_ldif("""
646 dn: cn=testuser,cn=users,""" + self.base_dn + """
647 changetype: modify
648 delete: userPassword
649 userPassword: thatsAcomplPASS2
650 add: unicodePwd
651 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """
652 """)
653             # this passes against s4
654         except LdbError as e30:
655             (num, _) = e30.args
656             self.assertEqual(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
657
658         try:
659             self.ldb2.modify_ldif("""
660 dn: cn=testuser,cn=users,""" + self.base_dn + """
661 changetype: modify
662 delete: unicodePwd
663 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """
664 add: userPassword
665 userPassword: thatsAcomplPASS4
666 """)
667             # this passes against s4
668         except LdbError as e31:
669             (num, _) = e31.args
670             self.assertEqual(num, ERR_NO_SUCH_ATTRIBUTE)
671
672         # Several password changes at once are allowed
673         self.ldb.modify_ldif("""
674 dn: cn=testuser,cn=users,""" + self.base_dn + """
675 changetype: modify
676 replace: userPassword
677 userPassword: thatsAcomplPASS1
678 userPassword: thatsAcomplPASS2
679 """)
680
681         # Several password changes at once are allowed
682         self.ldb.modify_ldif("""
683 dn: cn=testuser,cn=users,""" + self.base_dn + """
684 changetype: modify
685 replace: userPassword
686 userPassword: thatsAcomplPASS1
687 userPassword: thatsAcomplPASS2
688 replace: userPassword
689 userPassword: thatsAcomplPASS3
690 replace: userPassword
691 userPassword: thatsAcomplPASS4
692 """)
693
694         # This surprisingly should work
695         delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
696         self.ldb.add({
697              "dn": "cn=testuser2,cn=users," + self.base_dn,
698              "objectclass": "user",
699              "userPassword": ["thatsAcomplPASS1", "thatsAcomplPASS2"]})
700
701         # This surprisingly should work
702         delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
703         self.ldb.add({
704              "dn": "cn=testuser2,cn=users," + self.base_dn,
705              "objectclass": "user",
706              "userPassword": ["thatsAcomplPASS1", "thatsAcomplPASS1"]})
707
708     def test_empty_passwords(self):
709         print("Performs some empty passwords testing")
710
711         try:
712             self.ldb.add({
713                  "dn": "cn=testuser2,cn=users," + self.base_dn,
714                  "objectclass": "user",
715                  "unicodePwd": []})
716             self.fail()
717         except LdbError as e32:
718             (num, _) = e32.args
719             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
720
721         try:
722             self.ldb.add({
723                  "dn": "cn=testuser2,cn=users," + self.base_dn,
724                  "objectclass": "user",
725                  "dBCSPwd": []})
726             self.fail()
727         except LdbError as e33:
728             (num, _) = e33.args
729             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
730
731         try:
732             self.ldb.add({
733                  "dn": "cn=testuser2,cn=users," + self.base_dn,
734                  "objectclass": "user",
735                  "userPassword": []})
736             self.fail()
737         except LdbError as e34:
738             (num, _) = e34.args
739             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
740
741         try:
742             self.ldb.add({
743                  "dn": "cn=testuser2,cn=users," + self.base_dn,
744                  "objectclass": "user",
745                  "clearTextPassword": []})
746             self.fail()
747         except LdbError as e35:
748             (num, _) = e35.args
749             self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or
750                             num == ERR_NO_SUCH_ATTRIBUTE)  # for Windows
751
752         delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
753
754         m = Message()
755         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
756         m["unicodePwd"] = MessageElement([], FLAG_MOD_ADD, "unicodePwd")
757         try:
758             self.ldb.modify(m)
759             self.fail()
760         except LdbError as e36:
761             (num, _) = e36.args
762             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
763
764         m = Message()
765         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
766         m["dBCSPwd"] = MessageElement([], FLAG_MOD_ADD, "dBCSPwd")
767         try:
768             self.ldb.modify(m)
769             self.fail()
770         except LdbError as e37:
771             (num, _) = e37.args
772             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
773
774         m = Message()
775         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
776         m["userPassword"] = MessageElement([], FLAG_MOD_ADD, "userPassword")
777         try:
778             self.ldb.modify(m)
779             self.fail()
780         except LdbError as e38:
781             (num, _) = e38.args
782             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
783
784         m = Message()
785         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
786         m["clearTextPassword"] = MessageElement([], FLAG_MOD_ADD, "clearTextPassword")
787         try:
788             self.ldb.modify(m)
789             self.fail()
790         except LdbError as e39:
791             (num, _) = e39.args
792             self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or
793                             num == ERR_NO_SUCH_ATTRIBUTE)  # for Windows
794
795         m = Message()
796         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
797         m["unicodePwd"] = MessageElement([], FLAG_MOD_REPLACE, "unicodePwd")
798         try:
799             self.ldb.modify(m)
800             self.fail()
801         except LdbError as e40:
802             (num, _) = e40.args
803             self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
804
805         m = Message()
806         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
807         m["dBCSPwd"] = MessageElement([], FLAG_MOD_REPLACE, "dBCSPwd")
808         try:
809             self.ldb.modify(m)
810             self.fail()
811         except LdbError as e41:
812             (num, _) = e41.args
813             self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
814
815         m = Message()
816         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
817         m["userPassword"] = MessageElement([], FLAG_MOD_REPLACE, "userPassword")
818         try:
819             self.ldb.modify(m)
820             self.fail()
821         except LdbError as e42:
822             (num, _) = e42.args
823             self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
824
825         m = Message()
826         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
827         m["clearTextPassword"] = MessageElement([], FLAG_MOD_REPLACE, "clearTextPassword")
828         try:
829             self.ldb.modify(m)
830             self.fail()
831         except LdbError as e43:
832             (num, _) = e43.args
833             self.assertTrue(num == ERR_UNWILLING_TO_PERFORM or
834                             num == ERR_NO_SUCH_ATTRIBUTE)  # for Windows
835
836         m = Message()
837         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
838         m["unicodePwd"] = MessageElement([], FLAG_MOD_DELETE, "unicodePwd")
839         try:
840             self.ldb.modify(m)
841             self.fail()
842         except LdbError as e44:
843             (num, _) = e44.args
844             self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
845
846         m = Message()
847         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
848         m["dBCSPwd"] = MessageElement([], FLAG_MOD_DELETE, "dBCSPwd")
849         try:
850             self.ldb.modify(m)
851             self.fail()
852         except LdbError as e45:
853             (num, _) = e45.args
854             self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
855
856         m = Message()
857         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
858         m["userPassword"] = MessageElement([], FLAG_MOD_DELETE, "userPassword")
859         try:
860             self.ldb.modify(m)
861             self.fail()
862         except LdbError as e46:
863             (num, _) = e46.args
864             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
865
866         m = Message()
867         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
868         m["clearTextPassword"] = MessageElement([], FLAG_MOD_DELETE, "clearTextPassword")
869         try:
870             self.ldb.modify(m)
871             self.fail()
872         except LdbError as e47:
873             (num, _) = e47.args
874             self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or
875                             num == ERR_NO_SUCH_ATTRIBUTE)  # for Windows
876
877     def test_plain_userPassword(self):
878         print("Performs testing about the standard 'userPassword' behaviour")
879
880         # Delete the "dSHeuristics"
881         self.ldb.set_dsheuristics(None)
882
883         time.sleep(1)  # This switching time is strictly needed!
884
885         m = Message()
886         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
887         m["userPassword"] = MessageElement("myPassword", FLAG_MOD_ADD,
888                                            "userPassword")
889         self.ldb.modify(m)
890
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")
896
897         m = Message()
898         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
899         m["userPassword"] = MessageElement("myPassword2", FLAG_MOD_REPLACE,
900                                            "userPassword")
901         self.ldb.modify(m)
902
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")
908
909         m = Message()
910         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
911         m["userPassword"] = MessageElement([], FLAG_MOD_DELETE,
912                                            "userPassword")
913         self.ldb.modify(m)
914
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])
919
920         # Set the test "dSHeuristics" to deactivate "userPassword" pwd changes
921         self.ldb.set_dsheuristics("000000000")
922
923         m = Message()
924         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
925         m["userPassword"] = MessageElement("myPassword3", FLAG_MOD_REPLACE,
926                                            "userPassword")
927         self.ldb.modify(m)
928
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")
934
935         # Set the test "dSHeuristics" to deactivate "userPassword" pwd changes
936         self.ldb.set_dsheuristics("000000002")
937
938         m = Message()
939         m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn)
940         m["userPassword"] = MessageElement("myPassword4", FLAG_MOD_REPLACE,
941                                            "userPassword")
942         self.ldb.modify(m)
943
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")
949
950         # Reset the test "dSHeuristics" (reactivate "userPassword" pwd changes)
951         self.ldb.set_dsheuristics("000000001")
952
953     def test_modify_dsheuristics_userPassword(self):
954         print("Performs testing about reading userPassword between dsHeuristic modifies")
955
956         # Make sure userPassword cannot be read
957         self.ldb.set_dsheuristics("000000000")
958
959         # Open a new connection (with dsHeuristic=000000000)
960         ldb1 = SamDB(url=host, session_info=system_session(lp),
961                      credentials=creds, lp=lp)
962
963         # Set userPassword to be read
964         # This setting only affects newer connections (ldb2)
965         ldb1.set_dsheuristics("000000001")
966         time.sleep(1)
967
968         m = Message()
969         m.dn = Dn(ldb1, "cn=testuser,cn=users," + self.base_dn)
970         m["userPassword"] = MessageElement("thatsAcomplPASS1", FLAG_MOD_REPLACE,
971                                            "userPassword")
972         ldb1.modify(m)
973
974         res = ldb1.search("cn=testuser,cn=users," + self.base_dn,
975                           scope=SCOPE_BASE, attrs=["userPassword"])
976
977         # userPassword cannot be read, it wasn't set, instead the
978         # password was
979         self.assertTrue(len(res) == 1)
980         self.assertFalse("userPassword" in res[0])
981
982         # Open another new connection (with dsHeuristic=000000001)
983         ldb2 = SamDB(url=host, session_info=system_session(lp),
984                      credentials=creds, lp=lp)
985
986         res = ldb2.search("cn=testuser,cn=users," + self.base_dn,
987                           scope=SCOPE_BASE, attrs=["userPassword"])
988
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])
993
994         # Set userPassword to be readable
995         # This setting does not affect this connection
996         ldb2.set_dsheuristics("000000000")
997         time.sleep(1)
998
999         res = ldb2.search("cn=testuser,cn=users," + self.base_dn,
1000                           scope=SCOPE_BASE, attrs=["userPassword"])
1001
1002         # Check that userPassword was not stored from ldb1
1003         self.assertTrue(len(res) == 1)
1004         self.assertFalse("userPassword" in res[0])
1005
1006         m = Message()
1007         m.dn = Dn(ldb2, "cn=testuser,cn=users," + self.base_dn)
1008         m["userPassword"] = MessageElement("thatsAcomplPASS2", FLAG_MOD_REPLACE,
1009                                            "userPassword")
1010         ldb2.modify(m)
1011
1012         res = ldb2.search("cn=testuser,cn=users," + self.base_dn,
1013                           scope=SCOPE_BASE, attrs=["userPassword"])
1014
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])
1019
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)
1029
1030         try:
1031             SamDB(url=host, credentials=creds2, lp=lp)
1032         except:
1033             self.fail("testuser used the wrong password")
1034
1035         ldb3 = SamDB(url=host, session_info=system_session(lp),
1036                      credentials=creds, lp=lp)
1037
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"])
1041
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")
1046
1047         # Reset the test "dSHeuristics" (reactivate "userPassword" pwd changes)
1048         self.ldb.set_dsheuristics("000000001")
1049
1050         ldb4 = SamDB(url=host, session_info=system_session(lp),
1051                      credentials=creds, lp=lp)
1052
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"])
1056
1057         # userPassword can be not be read
1058         self.assertTrue(len(res) == 1)
1059         self.assertFalse("userPassword" in res[0])
1060
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")
1066
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")
1071
1072         self.ldb.setpassword("(sAMAccountName=testuser)", "")
1073
1074         # Reset the "pwdProperties" as they were before
1075         self.ldb.set_pwdProperties(pwdProperties)
1076
1077         # Reset the "minPwdLength" as it was before
1078         self.ldb.set_minPwdLength(minPwdLength)
1079
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"""
1082
1083         try:
1084             self.ldb2.modify_ldif("""
1085 dn: cn=testuser,cn=users,""" + self.base_dn + """
1086 changetype: modify
1087 delete: userPassword
1088 add: userPassword
1089 userPassword: thatsAcomplPASS1
1090 """)
1091         except LdbError as e:
1092             (num, msg) = e.args
1093             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
1094         else:
1095             self.fail()
1096
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"""
1099
1100         try:
1101             self.ldb2.modify_ldif("""
1102 dn: cn=testuser,cn=users,""" + self.base_dn + """
1103 changetype: modify
1104 delete: clearTextPassword
1105 add: clearTextPassword
1106 clearTextPassword: thatsAcomplPASS2
1107 """)
1108         except LdbError as e:
1109             (num, msg) = e.args
1110             self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or
1111                             num == ERR_NO_SUCH_ATTRIBUTE)  # for Windows
1112         else:
1113             self.fail()
1114
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"""
1117
1118         try:
1119             self.ldb2.modify_ldif("""
1120 dn: cn=testuser,cn=users,""" + self.base_dn + """
1121 changetype: modify
1122 delete: unicodePwd
1123 add: unicodePwd
1124 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """
1125 """)
1126         except LdbError as e:
1127             (num, msg) = e.args
1128             self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
1129         else:
1130             self.fail()
1131
1132     def tearDown(self):
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)
1137         self.ldb2 = None
1138
1139
1140 if "://" not in host:
1141     if os.path.isfile(host):
1142         host = "tdb://%s" % host
1143     else:
1144         host = "ldap://%s" % host
1145
1146 TestProgram(module=__name__, opts=subunitopts)