s4:dsdb python tests - use "ldb.domain_dn"
[kai/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 import optparse
12 import sys
13 import base64
14 import time
15 import os
16
17 sys.path.append("bin/python")
18 import samba
19 samba.ensure_external_module("testtools", "testtools")
20 samba.ensure_external_module("subunit", "subunit/python")
21
22 import samba.getopt as options
23
24 from samba.auth import system_session
25 from samba.credentials import Credentials
26 from ldb import SCOPE_BASE, LdbError
27 from ldb import ERR_NO_SUCH_OBJECT, ERR_ATTRIBUTE_OR_VALUE_EXISTS
28 from ldb import ERR_UNWILLING_TO_PERFORM, ERR_INSUFFICIENT_ACCESS_RIGHTS
29 from ldb import ERR_NO_SUCH_ATTRIBUTE
30 from ldb import ERR_CONSTRAINT_VIOLATION
31 from ldb import Message, MessageElement, Dn
32 from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE
33 from samba import gensec
34 from samba.samdb import SamDB
35 import samba.tests
36 from subunit.run import SubunitTestRunner
37 import unittest
38
39 parser = optparse.OptionParser("passwords.py [options] <host>")
40 sambaopts = options.SambaOptions(parser)
41 parser.add_option_group(sambaopts)
42 parser.add_option_group(options.VersionOptions(parser))
43 # use command line creds if available
44 credopts = options.CredentialsOptions(parser)
45 parser.add_option_group(credopts)
46 opts, args = parser.parse_args()
47
48 if len(args) < 1:
49     parser.print_usage()
50     sys.exit(1)
51
52 host = args[0]
53
54 lp = sambaopts.get_loadparm()
55 creds = credopts.get_credentials(lp)
56
57 # Force an encrypted connection
58 creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
59
60 #
61 # Tests start here
62 #
63
64 class PasswordTests(samba.tests.TestCase):
65
66     def delete_force(self, ldb, dn):
67         try:
68             ldb.delete(dn)
69         except LdbError, (num, _):
70             self.assertEquals(num, ERR_NO_SUCH_OBJECT)
71
72     def setUp(self):
73         super(PasswordTests, self).setUp()
74         self.ldb = ldb
75         self.base_dn = ldb.domain_dn()
76
77         # (Re)adds the test user "testuser" with no password atm
78         self.delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
79         self.ldb.add({
80              "dn": "cn=testuser,cn=users," + self.base_dn,
81              "objectclass": "user",
82              "sAMAccountName": "testuser"})
83
84         # Tests a password change when we don't have any password yet with a
85         # wrong old password
86         try:
87             self.ldb.modify_ldif("""
88 dn: cn=testuser,cn=users,""" + self.base_dn + """
89 changetype: modify
90 delete: userPassword
91 userPassword: noPassword
92 add: userPassword
93 userPassword: thatsAcomplPASS2
94 """)
95             self.fail()
96         except LdbError, (num, msg):
97             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
98             # Windows (2008 at least) seems to have some small bug here: it
99             # returns "0000056A" on longer (always wrong) previous passwords.
100             self.assertTrue('00000056' in msg)
101
102         # Sets the initial user password with a "special" password change
103         # I think that this internally is a password set operation and it can
104         # only be performed by someone which has password set privileges on the
105         # account (at least in s4 we do handle it like that).
106         self.ldb.modify_ldif("""
107 dn: cn=testuser,cn=users,""" + self.base_dn + """
108 changetype: modify
109 delete: userPassword
110 add: userPassword
111 userPassword: thatsAcomplPASS1
112 """)
113
114         # But in the other way around this special syntax doesn't work
115         try:
116             self.ldb.modify_ldif("""
117 dn: cn=testuser,cn=users,""" + self.base_dn + """
118 changetype: modify
119 delete: userPassword
120 userPassword: thatsAcomplPASS1
121 add: userPassword
122 """)
123             self.fail()
124         except LdbError, (num, _):
125             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
126
127         # Enables the user account
128         self.ldb.enable_account("(sAMAccountName=testuser)")
129
130         # Open a second LDB connection with the user credentials. Use the
131         # command line credentials for informations like the domain, the realm
132         # and the workstation.
133         creds2 = Credentials()
134         creds2.set_username("testuser")
135         creds2.set_password("thatsAcomplPASS1")
136         creds2.set_domain(creds.get_domain())
137         creds2.set_realm(creds.get_realm())
138         creds2.set_workstation(creds.get_workstation())
139         creds2.set_gensec_features(creds2.get_gensec_features()
140                                                           | gensec.FEATURE_SEAL)
141         self.ldb2 = SamDB(url=host, credentials=creds2, lp=lp)
142
143     def test_unicodePwd_hash_set(self):
144         print "Performs a password hash set operation on 'unicodePwd' which should be prevented"
145         # Notice: Direct hash password sets should never work
146
147         m = Message()
148         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
149         m["unicodePwd"] = MessageElement("XXXXXXXXXXXXXXXX", FLAG_MOD_REPLACE,
150           "unicodePwd")
151         try:
152             ldb.modify(m)
153             self.fail()
154         except LdbError, (num, _):
155             self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
156
157     def test_unicodePwd_hash_change(self):
158         print "Performs a password hash change operation on 'unicodePwd' which should be prevented"
159         # Notice: Direct hash password changes should never work
160
161         # Hash password changes should never work
162         try:
163             self.ldb2.modify_ldif("""
164 dn: cn=testuser,cn=users,""" + self.base_dn + """
165 changetype: modify
166 delete: unicodePwd
167 unicodePwd: XXXXXXXXXXXXXXXX
168 add: unicodePwd
169 unicodePwd: YYYYYYYYYYYYYYYY
170 """)
171             self.fail()
172         except LdbError, (num, _):
173             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
174
175     def test_unicodePwd_clear_set(self):
176         print "Performs a password cleartext set operation on 'unicodePwd'"
177
178         m = Message()
179         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
180         m["unicodePwd"] = MessageElement("\"thatsAcomplPASS2\"".encode('utf-16-le'),
181           FLAG_MOD_REPLACE, "unicodePwd")
182         ldb.modify(m)
183
184     def test_unicodePwd_clear_change(self):
185         print "Performs a password cleartext change operation on 'unicodePwd'"
186
187         self.ldb2.modify_ldif("""
188 dn: cn=testuser,cn=users,""" + self.base_dn + """
189 changetype: modify
190 delete: unicodePwd
191 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """
192 add: unicodePwd
193 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
194 """)
195
196         # Wrong old password
197         try:
198             self.ldb2.modify_ldif("""
199 dn: cn=testuser,cn=users,""" + self.base_dn + """
200 changetype: modify
201 delete: unicodePwd
202 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')) + """
203 add: unicodePwd
204 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS4\"".encode('utf-16-le')) + """
205 """)
206             self.fail()
207         except LdbError, (num, msg):
208             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
209             self.assertTrue('00000056' in msg)
210
211         # A change to the same password again will not work (password history)
212         try:
213             self.ldb2.modify_ldif("""
214 dn: cn=testuser,cn=users,""" + self.base_dn + """
215 changetype: modify
216 delete: unicodePwd
217 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
218 add: unicodePwd
219 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
220 """)
221             self.fail()
222         except LdbError, (num, msg):
223             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
224             self.assertTrue('0000052D' in msg)
225
226     def test_dBCSPwd_hash_set(self):
227         print "Performs a password hash set operation on 'dBCSPwd' which should be prevented"
228         # Notice: Direct hash password sets should never work
229
230         m = Message()
231         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
232         m["dBCSPwd"] = MessageElement("XXXXXXXXXXXXXXXX", FLAG_MOD_REPLACE,
233           "dBCSPwd")
234         try:
235             ldb.modify(m)
236             self.fail()
237         except LdbError, (num, _):
238             self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
239
240     def test_dBCSPwd_hash_change(self):
241         print "Performs a password hash change operation on 'dBCSPwd' which should be prevented"
242         # Notice: Direct hash password changes should never work
243
244         try:
245             self.ldb2.modify_ldif("""
246 dn: cn=testuser,cn=users,""" + self.base_dn + """
247 changetype: modify
248 delete: dBCSPwd
249 dBCSPwd: XXXXXXXXXXXXXXXX
250 add: dBCSPwd
251 dBCSPwd: YYYYYYYYYYYYYYYY
252 """)
253             self.fail()
254         except LdbError, (num, _):
255             self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
256
257     def test_userPassword_clear_set(self):
258         print "Performs a password cleartext set operation on 'userPassword'"
259         # Notice: This works only against Windows if "dSHeuristics" has been set
260         # properly
261
262         m = Message()
263         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
264         m["userPassword"] = MessageElement("thatsAcomplPASS2", FLAG_MOD_REPLACE,
265           "userPassword")
266         ldb.modify(m)
267
268     def test_userPassword_clear_change(self):
269         print "Performs a password cleartext change operation on 'userPassword'"
270         # Notice: This works only against Windows if "dSHeuristics" has been set
271         # properly
272
273         self.ldb2.modify_ldif("""
274 dn: cn=testuser,cn=users,""" + self.base_dn + """
275 changetype: modify
276 delete: userPassword
277 userPassword: thatsAcomplPASS1
278 add: userPassword
279 userPassword: thatsAcomplPASS2
280 """)
281
282         # Wrong old password
283         try:
284             self.ldb2.modify_ldif("""
285 dn: cn=testuser,cn=users,""" + self.base_dn + """
286 changetype: modify
287 delete: userPassword
288 userPassword: thatsAcomplPASS3
289 add: userPassword
290 userPassword: thatsAcomplPASS4
291 """)
292             self.fail()
293         except LdbError, (num, msg):
294             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
295             self.assertTrue('00000056' in msg)
296
297         # A change to the same password again will not work (password history)
298         try:
299             self.ldb2.modify_ldif("""
300 dn: cn=testuser,cn=users,""" + self.base_dn + """
301 changetype: modify
302 delete: userPassword
303 userPassword: thatsAcomplPASS2
304 add: userPassword
305 userPassword: thatsAcomplPASS2
306 """)
307             self.fail()
308         except LdbError, (num, msg):
309             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
310             self.assertTrue('0000052D' in msg)
311
312     def test_clearTextPassword_clear_set(self):
313         print "Performs a password cleartext set operation on 'clearTextPassword'"
314         # Notice: This never works against Windows - only supported by us
315
316         try:
317             m = Message()
318             m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
319             m["clearTextPassword"] = MessageElement("thatsAcomplPASS2".encode('utf-16-le'),
320               FLAG_MOD_REPLACE, "clearTextPassword")
321             ldb.modify(m)
322             # this passes against s4
323         except LdbError, (num, msg):
324             # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it
325             if num != ERR_NO_SUCH_ATTRIBUTE:
326                 raise LdbError(num, msg)
327
328     def test_clearTextPassword_clear_change(self):
329         print "Performs a password cleartext change operation on 'clearTextPassword'"
330         # Notice: This never works against Windows - only supported by us
331
332         try:
333             self.ldb2.modify_ldif("""
334 dn: cn=testuser,cn=users,""" + self.base_dn + """
335 changetype: modify
336 delete: clearTextPassword
337 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS1".encode('utf-16-le')) + """
338 add: clearTextPassword
339 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')) + """
340 """)
341             # this passes against s4
342         except LdbError, (num, msg):
343             # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it
344             if num != ERR_NO_SUCH_ATTRIBUTE:
345                 raise LdbError(num, msg)
346
347         # Wrong old password
348         try:
349             self.ldb2.modify_ldif("""
350 dn: cn=testuser,cn=users,""" + self.base_dn + """
351 changetype: modify
352 delete: clearTextPassword
353 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS3".encode('utf-16-le')) + """
354 add: clearTextPassword
355 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS4".encode('utf-16-le')) + """
356 """)
357             self.fail()
358         except LdbError, (num, msg):
359             # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it
360             if num != ERR_NO_SUCH_ATTRIBUTE:
361                 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
362                 self.assertTrue('00000056' in msg)
363
364         # A change to the same password again will not work (password history)
365         try:
366             self.ldb2.modify_ldif("""
367 dn: cn=testuser,cn=users,""" + self.base_dn + """
368 changetype: modify
369 delete: clearTextPassword
370 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')) + """
371 add: clearTextPassword
372 clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')) + """
373 """)
374             self.fail()
375         except LdbError, (num, msg):
376             # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it
377             if num != ERR_NO_SUCH_ATTRIBUTE:
378                 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
379                 self.assertTrue('0000052D' in msg)
380
381     def test_failures(self):
382         print "Performs some failure testing"
383
384         try:
385             ldb.modify_ldif("""
386 dn: cn=testuser,cn=users,""" + self.base_dn + """
387 changetype: modify
388 delete: userPassword
389 userPassword: thatsAcomplPASS1
390 """)
391             self.fail()
392         except LdbError, (num, _):
393             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
394
395         try:
396             self.ldb2.modify_ldif("""
397 dn: cn=testuser,cn=users,""" + self.base_dn + """
398 changetype: modify
399 delete: userPassword
400 userPassword: thatsAcomplPASS1
401 """)
402             self.fail()
403         except LdbError, (num, _):
404             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
405
406         try:
407             ldb.modify_ldif("""
408 dn: cn=testuser,cn=users,""" + self.base_dn + """
409 changetype: modify
410 delete: userPassword
411 """)
412             self.fail()
413         except LdbError, (num, _):
414             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
415
416         try:
417             self.ldb2.modify_ldif("""
418 dn: cn=testuser,cn=users,""" + self.base_dn + """
419 changetype: modify
420 delete: userPassword
421 """)
422             self.fail()
423         except LdbError, (num, _):
424             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
425
426         try:
427             ldb.modify_ldif("""
428 dn: cn=testuser,cn=users,""" + self.base_dn + """
429 changetype: modify
430 add: userPassword
431 userPassword: thatsAcomplPASS1
432 """)
433             self.fail()
434         except LdbError, (num, _):
435             self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
436
437         try:
438             self.ldb2.modify_ldif("""
439 dn: cn=testuser,cn=users,""" + self.base_dn + """
440 changetype: modify
441 add: userPassword
442 userPassword: thatsAcomplPASS1
443 """)
444             self.fail()
445         except LdbError, (num, _):
446             self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
447
448         try:
449             ldb.modify_ldif("""
450 dn: cn=testuser,cn=users,""" + self.base_dn + """
451 changetype: modify
452 delete: userPassword
453 userPassword: thatsAcomplPASS1
454 add: userPassword
455 userPassword: thatsAcomplPASS2
456 userPassword: thatsAcomplPASS2
457 """)
458             self.fail()
459         except LdbError, (num, _):
460             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
461
462         try:
463             self.ldb2.modify_ldif("""
464 dn: cn=testuser,cn=users,""" + self.base_dn + """
465 changetype: modify
466 delete: userPassword
467 userPassword: thatsAcomplPASS1
468 add: userPassword
469 userPassword: thatsAcomplPASS2
470 userPassword: thatsAcomplPASS2
471 """)
472             self.fail()
473         except LdbError, (num, _):
474             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
475
476         try:
477             ldb.modify_ldif("""
478 dn: cn=testuser,cn=users,""" + self.base_dn + """
479 changetype: modify
480 delete: userPassword
481 userPassword: thatsAcomplPASS1
482 userPassword: thatsAcomplPASS1
483 add: userPassword
484 userPassword: thatsAcomplPASS2
485 """)
486             self.fail()
487         except LdbError, (num, _):
488             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
489
490         try:
491             self.ldb2.modify_ldif("""
492 dn: cn=testuser,cn=users,""" + self.base_dn + """
493 changetype: modify
494 delete: userPassword
495 userPassword: thatsAcomplPASS1
496 userPassword: thatsAcomplPASS1
497 add: userPassword
498 userPassword: thatsAcomplPASS2
499 """)
500             self.fail()
501         except LdbError, (num, _):
502             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
503
504         try:
505             ldb.modify_ldif("""
506 dn: cn=testuser,cn=users,""" + self.base_dn + """
507 changetype: modify
508 delete: userPassword
509 userPassword: thatsAcomplPASS1
510 add: userPassword
511 userPassword: thatsAcomplPASS2
512 add: userPassword
513 userPassword: thatsAcomplPASS2
514 """)
515             self.fail()
516         except LdbError, (num, _):
517             self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
518
519         try:
520             self.ldb2.modify_ldif("""
521 dn: cn=testuser,cn=users,""" + self.base_dn + """
522 changetype: modify
523 delete: userPassword
524 userPassword: thatsAcomplPASS1
525 add: userPassword
526 userPassword: thatsAcomplPASS2
527 add: userPassword
528 userPassword: thatsAcomplPASS2
529 """)
530             self.fail()
531         except LdbError, (num, _):
532             self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
533
534         try:
535             ldb.modify_ldif("""
536 dn: cn=testuser,cn=users,""" + self.base_dn + """
537 changetype: modify
538 delete: userPassword
539 userPassword: thatsAcomplPASS1
540 delete: userPassword
541 userPassword: thatsAcomplPASS1
542 add: userPassword
543 userPassword: thatsAcomplPASS2
544 """)
545             self.fail()
546         except LdbError, (num, _):
547             self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
548
549         try:
550             self.ldb2.modify_ldif("""
551 dn: cn=testuser,cn=users,""" + self.base_dn + """
552 changetype: modify
553 delete: userPassword
554 userPassword: thatsAcomplPASS1
555 delete: userPassword
556 userPassword: thatsAcomplPASS1
557 add: userPassword
558 userPassword: thatsAcomplPASS2
559 """)
560             self.fail()
561         except LdbError, (num, _):
562             self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
563
564         try:
565             ldb.modify_ldif("""
566 dn: cn=testuser,cn=users,""" + self.base_dn + """
567 changetype: modify
568 delete: userPassword
569 userPassword: thatsAcomplPASS1
570 add: userPassword
571 userPassword: thatsAcomplPASS2
572 replace: userPassword
573 userPassword: thatsAcomplPASS3
574 """)
575             self.fail()
576         except LdbError, (num, _):
577             self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
578
579         try:
580             self.ldb2.modify_ldif("""
581 dn: cn=testuser,cn=users,""" + self.base_dn + """
582 changetype: modify
583 delete: userPassword
584 userPassword: thatsAcomplPASS1
585 add: userPassword
586 userPassword: thatsAcomplPASS2
587 replace: userPassword
588 userPassword: thatsAcomplPASS3
589 """)
590             self.fail()
591         except LdbError, (num, _):
592             self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
593
594         # Reverse order does work
595         self.ldb2.modify_ldif("""
596 dn: cn=testuser,cn=users,""" + self.base_dn + """
597 changetype: modify
598 add: userPassword
599 userPassword: thatsAcomplPASS2
600 delete: userPassword
601 userPassword: thatsAcomplPASS1
602 """)
603
604         try:
605             self.ldb2.modify_ldif("""
606 dn: cn=testuser,cn=users,""" + self.base_dn + """
607 changetype: modify
608 delete: userPassword
609 userPassword: thatsAcomplPASS2
610 add: unicodePwd
611 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')) + """
612 """)
613              # this passes against s4
614         except LdbError, (num, _):
615             self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
616
617         try:
618             self.ldb2.modify_ldif("""
619 dn: cn=testuser,cn=users,""" + self.base_dn + """
620 changetype: modify
621 delete: unicodePwd
622 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')) + """
623 add: userPassword
624 userPassword: thatsAcomplPASS4
625 """)
626              # this passes against s4
627         except LdbError, (num, _):
628             self.assertEquals(num, ERR_NO_SUCH_ATTRIBUTE)
629
630         # Several password changes at once are allowed
631         ldb.modify_ldif("""
632 dn: cn=testuser,cn=users,""" + self.base_dn + """
633 changetype: modify
634 replace: userPassword
635 userPassword: thatsAcomplPASS1
636 userPassword: thatsAcomplPASS2
637 """)
638
639         # Several password changes at once are allowed
640         ldb.modify_ldif("""
641 dn: cn=testuser,cn=users,""" + self.base_dn + """
642 changetype: modify
643 replace: userPassword
644 userPassword: thatsAcomplPASS1
645 userPassword: thatsAcomplPASS2
646 replace: userPassword
647 userPassword: thatsAcomplPASS3
648 replace: userPassword
649 userPassword: thatsAcomplPASS4
650 """)
651
652         # This surprisingly should work
653         self.delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
654         self.ldb.add({
655              "dn": "cn=testuser2,cn=users," + self.base_dn,
656              "objectclass": "user",
657              "userPassword": ["thatsAcomplPASS1", "thatsAcomplPASS2"] })
658
659         # This surprisingly should work
660         self.delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
661         self.ldb.add({
662              "dn": "cn=testuser2,cn=users," + self.base_dn,
663              "objectclass": "user",
664              "userPassword": ["thatsAcomplPASS1", "thatsAcomplPASS1"] })
665
666     def test_empty_passwords(self):
667         print "Performs some empty passwords testing"
668
669         try:
670             self.ldb.add({
671                  "dn": "cn=testuser2,cn=users," + self.base_dn,
672                  "objectclass": "user",
673                  "unicodePwd": [] })
674             self.fail()
675         except LdbError, (num, _):
676             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
677
678         try:
679             self.ldb.add({
680                  "dn": "cn=testuser2,cn=users," + self.base_dn,
681                  "objectclass": "user",
682                  "dBCSPwd": [] })
683             self.fail()
684         except LdbError, (num, _):
685             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
686
687         try:
688             self.ldb.add({
689                  "dn": "cn=testuser2,cn=users," + self.base_dn,
690                  "objectclass": "user",
691                  "userPassword": [] })
692             self.fail()
693         except LdbError, (num, _):
694             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
695
696         try:
697             self.ldb.add({
698                  "dn": "cn=testuser2,cn=users," + self.base_dn,
699                  "objectclass": "user",
700                  "clearTextPassword": [] })
701             self.fail()
702         except LdbError, (num, _):
703             self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or
704                             num == ERR_NO_SUCH_ATTRIBUTE) # for Windows
705
706         self.delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
707
708         m = Message()
709         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
710         m["unicodePwd"] = MessageElement([], FLAG_MOD_ADD, "unicodePwd")
711         try:
712             ldb.modify(m)
713             self.fail()
714         except LdbError, (num, _):
715             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
716
717         m = Message()
718         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
719         m["dBCSPwd"] = MessageElement([], FLAG_MOD_ADD, "dBCSPwd")
720         try:
721             ldb.modify(m)
722             self.fail()
723         except LdbError, (num, _):
724             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
725
726         m = Message()
727         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
728         m["userPassword"] = MessageElement([], FLAG_MOD_ADD, "userPassword")
729         try:
730             ldb.modify(m)
731             self.fail()
732         except LdbError, (num, _):
733             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
734
735         m = Message()
736         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
737         m["clearTextPassword"] = MessageElement([], FLAG_MOD_ADD, "clearTextPassword")
738         try:
739             ldb.modify(m)
740             self.fail()
741         except LdbError, (num, _):
742             self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or
743                             num == ERR_NO_SUCH_ATTRIBUTE) # for Windows
744
745         m = Message()
746         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
747         m["unicodePwd"] = MessageElement([], FLAG_MOD_REPLACE, "unicodePwd")
748         try:
749             ldb.modify(m)
750             self.fail()
751         except LdbError, (num, _):
752             self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
753
754         m = Message()
755         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
756         m["dBCSPwd"] = MessageElement([], FLAG_MOD_REPLACE, "dBCSPwd")
757         try:
758             ldb.modify(m)
759             self.fail()
760         except LdbError, (num, _):
761             self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
762
763         m = Message()
764         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
765         m["userPassword"] = MessageElement([], FLAG_MOD_REPLACE, "userPassword")
766         try:
767             ldb.modify(m)
768             self.fail()
769         except LdbError, (num, _):
770             self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
771
772         m = Message()
773         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
774         m["clearTextPassword"] = MessageElement([], FLAG_MOD_REPLACE, "clearTextPassword")
775         try:
776             ldb.modify(m)
777             self.fail()
778         except LdbError, (num, _):
779             self.assertTrue(num == ERR_UNWILLING_TO_PERFORM or
780                             num == ERR_NO_SUCH_ATTRIBUTE) # for Windows
781
782         m = Message()
783         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
784         m["unicodePwd"] = MessageElement([], FLAG_MOD_DELETE, "unicodePwd")
785         try:
786             ldb.modify(m)
787             self.fail()
788         except LdbError, (num, _):
789             self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
790
791         m = Message()
792         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
793         m["dBCSPwd"] = MessageElement([], FLAG_MOD_DELETE, "dBCSPwd")
794         try:
795             ldb.modify(m)
796             self.fail()
797         except LdbError, (num, _):
798             self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
799
800         m = Message()
801         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
802         m["userPassword"] = MessageElement([], FLAG_MOD_DELETE, "userPassword")
803         try:
804             ldb.modify(m)
805             self.fail()
806         except LdbError, (num, _):
807             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
808
809         m = Message()
810         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
811         m["clearTextPassword"] = MessageElement([], FLAG_MOD_DELETE, "clearTextPassword")
812         try:
813             ldb.modify(m)
814             self.fail()
815         except LdbError, (num, _):
816             self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or
817                             num == ERR_NO_SUCH_ATTRIBUTE) # for Windows
818
819     def test_plain_userPassword(self):
820         print "Performs testing about the standard 'userPassword' behaviour"
821
822         # Delete the "dSHeuristics"
823         m = Message()
824         m.dn = Dn(ldb, "CN=Directory Service, CN=Windows NT, CN=Services, "
825           + configuration_dn)
826         m["dSHeuristics"] = MessageElement([], FLAG_MOD_DELETE, "dsHeuristics")
827         ldb.modify(m)
828
829         time.sleep(1) # This switching time is strictly needed!
830
831         m = Message()
832         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
833         m["userPassword"] = MessageElement("myPassword", FLAG_MOD_ADD,
834           "userPassword")
835         ldb.modify(m)
836
837         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
838                          scope=SCOPE_BASE, attrs=["userPassword"])
839         self.assertTrue(len(res) == 1)
840         self.assertTrue("userPassword" in res[0])
841         self.assertEquals(res[0]["userPassword"][0], "myPassword")
842
843         m = Message()
844         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
845         m["userPassword"] = MessageElement("myPassword2", FLAG_MOD_REPLACE,
846           "userPassword")
847         ldb.modify(m)
848
849         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
850                          scope=SCOPE_BASE, attrs=["userPassword"])
851         self.assertTrue(len(res) == 1)
852         self.assertTrue("userPassword" in res[0])
853         self.assertEquals(res[0]["userPassword"][0], "myPassword2")
854
855         m = Message()
856         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
857         m["userPassword"] = MessageElement([], FLAG_MOD_DELETE,
858           "userPassword")
859         ldb.modify(m)
860
861         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
862                          scope=SCOPE_BASE, attrs=["userPassword"])
863         self.assertTrue(len(res) == 1)
864         self.assertFalse("userPassword" in res[0])
865
866         # Set the test "dSHeuristics" to deactivate "userPassword" pwd changes
867         m = Message()
868         m.dn = Dn(ldb, "CN=Directory Service, CN=Windows NT, CN=Services, "
869           + configuration_dn)
870         m["dSHeuristics"] = MessageElement("000000000", FLAG_MOD_REPLACE,
871           "dSHeuristics")
872         ldb.modify(m)
873
874         m = Message()
875         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
876         m["userPassword"] = MessageElement("myPassword3", FLAG_MOD_REPLACE,
877           "userPassword")
878         ldb.modify(m)
879
880         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
881                          scope=SCOPE_BASE, attrs=["userPassword"])
882         self.assertTrue(len(res) == 1)
883         self.assertTrue("userPassword" in res[0])
884         self.assertEquals(res[0]["userPassword"][0], "myPassword3")
885
886         # Set the test "dSHeuristics" to deactivate "userPassword" pwd changes
887         m = Message()
888         m.dn = Dn(ldb, "CN=Directory Service, CN=Windows NT, CN=Services, "
889           + configuration_dn)
890         m["dSHeuristics"] = MessageElement("000000002", FLAG_MOD_REPLACE,
891           "dSHeuristics")
892         ldb.modify(m)
893
894         m = Message()
895         m.dn = Dn(ldb, "cn=testuser,cn=users," + self.base_dn)
896         m["userPassword"] = MessageElement("myPassword4", FLAG_MOD_REPLACE,
897           "userPassword")
898         ldb.modify(m)
899
900         res = ldb.search("cn=testuser,cn=users," + self.base_dn,
901                          scope=SCOPE_BASE, attrs=["userPassword"])
902         self.assertTrue(len(res) == 1)
903         self.assertTrue("userPassword" in res[0])
904         self.assertEquals(res[0]["userPassword"][0], "myPassword4")
905
906         # Reset the test "dSHeuristics" (reactivate "userPassword" pwd changes)
907         m = Message()
908         m.dn = Dn(ldb, "CN=Directory Service, CN=Windows NT, CN=Services, "
909           + configuration_dn)
910         m["dSHeuristics"] = MessageElement("000000001", FLAG_MOD_REPLACE,
911           "dSHeuristics")
912         ldb.modify(m)
913
914     def tearDown(self):
915         super(PasswordTests, self).tearDown()
916         self.delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn)
917         self.delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn)
918         # Close the second LDB connection (with the user credentials)
919         self.ldb2 = None
920
921 if not "://" in host:
922     if os.path.isfile(host):
923         host = "tdb://%s" % host
924     else:
925         host = "ldap://%s" % host
926
927 ldb = SamDB(url=host, session_info=system_session(), credentials=creds, lp=lp)
928
929 # Gets back the configuration basedn
930 res = ldb.search(base="", expression="", scope=SCOPE_BASE,
931                  attrs=["configurationNamingContext"])
932 configuration_dn = res[0]["configurationNamingContext"][0]
933
934 # Gets back the basedn
935 res = ldb.search(base="", expression="", scope=SCOPE_BASE,
936                  attrs=["defaultNamingContext"])
937 base_dn = res[0]["defaultNamingContext"][0]
938
939 # Get the old "dSHeuristics" if it was set
940 res = ldb.search("CN=Directory Service, CN=Windows NT, CN=Services, "
941                  + configuration_dn, scope=SCOPE_BASE, attrs=["dSHeuristics"])
942 if "dSHeuristics" in res[0]:
943   dsheuristics = res[0]["dSHeuristics"][0]
944 else:
945   dsheuristics = None
946
947 # Set the "dSHeuristics" to activate the correct "userPassword" behaviour
948 m = Message()
949 m.dn = Dn(ldb, "CN=Directory Service, CN=Windows NT, CN=Services, "
950   + configuration_dn)
951 m["dSHeuristics"] = MessageElement("000000001", FLAG_MOD_REPLACE,
952   "dSHeuristics")
953 ldb.modify(m)
954
955 # Get the old "minPwdAge"
956 res = ldb.search(base_dn, scope=SCOPE_BASE, attrs=["minPwdAge"])
957 minPwdAge = res[0]["minPwdAge"][0]
958
959 # Set it temporarely to "0"
960 m = Message()
961 m.dn = Dn(ldb, base_dn)
962 m["minPwdAge"] = MessageElement("0", FLAG_MOD_REPLACE, "minPwdAge")
963 ldb.modify(m)
964
965 runner = SubunitTestRunner()
966 rc = 0
967 if not runner.run(unittest.makeSuite(PasswordTests)).wasSuccessful():
968     rc = 1
969
970 # Reset the "dSHeuristics" as they were before
971 m = Message()
972 m.dn = Dn(ldb, "CN=Directory Service, CN=Windows NT, CN=Services, "
973   + configuration_dn)
974 if dsheuristics is not None:
975     m["dSHeuristics"] = MessageElement(dsheuristics, FLAG_MOD_REPLACE,
976       "dSHeuristics")
977 else:
978     m["dSHeuristics"] = MessageElement([], FLAG_MOD_DELETE, "dsHeuristics")
979 ldb.modify(m)
980
981 # Reset the "minPwdAge" as it was before
982 m = Message()
983 m.dn = Dn(ldb, base_dn)
984 m["minPwdAge"] = MessageElement(minPwdAge, FLAG_MOD_REPLACE, "minPwdAge")
985 ldb.modify(m)
986
987 sys.exit(rc)