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