PEP8: fix E303: too many blank lines (2)
[sfrench/samba-autobuild/.git] / source4 / dsdb / tests / python / ldap_schema.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # This is a port of the original in testprogs/ejs/ldap.js
4
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2008-2011
6 # Copyright (C) Catalyst.Net Ltd 2017
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21
22 from __future__ import print_function
23 import optparse
24 import sys
25 import time
26 import random
27 import os
28
29 sys.path.insert(0, "bin/python")
30 import samba
31 from samba.tests.subunitrun import TestProgram, SubunitOptions
32
33 import samba.getopt as options
34
35 from samba.auth import system_session
36 from ldb import SCOPE_ONELEVEL, SCOPE_BASE, LdbError
37 from ldb import ERR_NO_SUCH_OBJECT
38 from ldb import ERR_UNWILLING_TO_PERFORM
39 from ldb import ERR_ENTRY_ALREADY_EXISTS
40 from ldb import ERR_CONSTRAINT_VIOLATION
41 from ldb import ERR_OBJECT_CLASS_VIOLATION
42 from ldb import Message, MessageElement, Dn
43 from ldb import FLAG_MOD_REPLACE
44 from samba.samdb import SamDB
45 from samba.dsdb import DS_DOMAIN_FUNCTION_2003
46 from samba.tests import delete_force
47 from samba.ndr import ndr_unpack
48 from samba.dcerpc import drsblobs
49
50 parser = optparse.OptionParser("ldap_schema.py [options] <host>")
51 sambaopts = options.SambaOptions(parser)
52 parser.add_option_group(sambaopts)
53 parser.add_option_group(options.VersionOptions(parser))
54 # use command line creds if available
55 credopts = options.CredentialsOptions(parser)
56 parser.add_option_group(credopts)
57 subunitopts = SubunitOptions(parser)
58 parser.add_option_group(subunitopts)
59 opts, args = parser.parse_args()
60
61 if len(args) < 1:
62     parser.print_usage()
63     sys.exit(1)
64
65 host = args[0]
66
67 lp = sambaopts.get_loadparm()
68 creds = credopts.get_credentials(lp)
69
70
71 class SchemaTests(samba.tests.TestCase):
72
73     def setUp(self):
74         super(SchemaTests, self).setUp()
75         self.ldb = SamDB(host, credentials=creds,
76                          session_info=system_session(lp), lp=lp, options=ldb_options)
77         self.base_dn = self.ldb.domain_dn()
78         self.schema_dn = self.ldb.get_schema_basedn().get_linearized()
79
80     def test_generated_schema(self):
81         """Testing we can read the generated schema via LDAP"""
82         res = self.ldb.search("cn=aggregate," + self.schema_dn, scope=SCOPE_BASE,
83                               attrs=["objectClasses", "attributeTypes", "dITContentRules"])
84         self.assertEquals(len(res), 1)
85         self.assertTrue("dITContentRules" in res[0])
86         self.assertTrue("objectClasses" in res[0])
87         self.assertTrue("attributeTypes" in res[0])
88
89     def test_generated_schema_is_operational(self):
90         """Testing we don't get the generated schema via LDAP by default"""
91         # Must keep the "*" form
92         res = self.ldb.search("cn=aggregate," + self.schema_dn, scope=SCOPE_BASE,
93                               attrs=["*"])
94         self.assertEquals(len(res), 1)
95         self.assertFalse("dITContentRules" in res[0])
96         self.assertFalse("objectClasses" in res[0])
97         self.assertFalse("attributeTypes" in res[0])
98
99     def test_schemaUpdateNow(self):
100         """Testing schemaUpdateNow"""
101         rand = str(random.randint(1, 100000))
102         attr_name = "test-Attr" + time.strftime("%s", time.gmtime()) + "-" + rand
103         attr_ldap_display_name = attr_name.replace("-", "")
104
105         ldif = """
106 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
107 objectClass: top
108 objectClass: attributeSchema
109 adminDescription: """ + attr_name + """
110 adminDisplayName: """ + attr_name + """
111 cn: """ + attr_name + """
112 attributeId: 1.3.6.1.4.1.7165.4.6.1.6.1.""" + rand + """
113 attributeSyntax: 2.5.5.12
114 omSyntax: 64
115 instanceType: 4
116 isSingleValued: TRUE
117 systemOnly: FALSE
118 """
119         self.ldb.add_ldif(ldif)
120         # We must do a schemaUpdateNow otherwise it's not 100% sure that the schema
121         # will contain the new attribute
122         ldif = """
123 dn:
124 changetype: modify
125 add: schemaUpdateNow
126 schemaUpdateNow: 1
127 """
128         self.ldb.modify_ldif(ldif)
129
130         # Search for created attribute
131         res = []
132         res = self.ldb.search("cn=%s,%s" % (attr_name, self.schema_dn), scope=SCOPE_BASE,
133                               attrs=["lDAPDisplayName", "schemaIDGUID", "msDS-IntID"])
134         self.assertEquals(len(res), 1)
135         self.assertEquals(res[0]["lDAPDisplayName"][0], attr_ldap_display_name)
136         self.assertTrue("schemaIDGUID" in res[0])
137         if "msDS-IntId" in res[0]:
138             msDS_IntId = int(res[0]["msDS-IntId"][0])
139             if msDS_IntId < 0:
140                 msDS_IntId += (1 << 32)
141         else:
142             msDS_IntId = None
143
144         class_name = "test-Class" + time.strftime("%s", time.gmtime())
145         class_ldap_display_name = class_name.replace("-", "")
146
147         # First try to create a class with a wrong "defaultObjectCategory"
148         ldif = """
149 dn: CN=%s,%s""" % (class_name, self.schema_dn) + """
150 objectClass: top
151 objectClass: classSchema
152 defaultObjectCategory: CN=_
153 adminDescription: """ + class_name + """
154 adminDisplayName: """ + class_name + """
155 cn: """ + class_name + """
156 governsId: 1.3.6.1.4.1.7165.4.6.2.6.1.""" + str(random.randint(1, 100000)) + """
157 instanceType: 4
158 objectClassCategory: 1
159 subClassOf: organizationalPerson
160 systemFlags: 16
161 rDNAttID: cn
162 systemMustContain: cn
163 systemMustContain: """ + attr_ldap_display_name + """
164 systemOnly: FALSE
165 """
166         try:
167             self.ldb.add_ldif(ldif)
168             self.fail()
169         except LdbError as e1:
170             (num, _) = e1.args
171             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
172
173         ldif = """
174 dn: CN=%s,%s""" % (class_name, self.schema_dn) + """
175 objectClass: top
176 objectClass: classSchema
177 adminDescription: """ + class_name + """
178 adminDisplayName: """ + class_name + """
179 cn: """ + class_name + """
180 governsId: 1.3.6.1.4.1.7165.4.6.2.6.2.""" + str(random.randint(1, 100000)) + """
181 instanceType: 4
182 objectClassCategory: 1
183 subClassOf: organizationalPerson
184 systemFlags: 16
185 rDNAttID: cn
186 systemMustContain: cn
187 systemMustContain: """ + attr_ldap_display_name + """
188 systemOnly: FALSE
189 """
190         self.ldb.add_ldif(ldif)
191
192         # Search for created objectclass
193         res = []
194         res = self.ldb.search("cn=%s,%s" % (class_name, self.schema_dn), scope=SCOPE_BASE,
195                               attrs=["lDAPDisplayName", "defaultObjectCategory", "schemaIDGUID", "distinguishedName"])
196         self.assertEquals(len(res), 1)
197         self.assertEquals(res[0]["lDAPDisplayName"][0], class_ldap_display_name)
198         self.assertEquals(res[0]["defaultObjectCategory"][0], res[0]["distinguishedName"][0])
199         self.assertTrue("schemaIDGUID" in res[0])
200
201         ldif = """
202 dn:
203 changetype: modify
204 add: schemaUpdateNow
205 schemaUpdateNow: 1
206 """
207         self.ldb.modify_ldif(ldif)
208
209         object_name = "obj" + time.strftime("%s", time.gmtime())
210
211         ldif = """
212 dn: CN=%s,CN=Users,%s""" % (object_name, self.base_dn) + """
213 objectClass: organizationalPerson
214 objectClass: person
215 objectClass: """ + class_ldap_display_name + """
216 objectClass: top
217 cn: """ + object_name + """
218 instanceType: 4
219 objectCategory: CN=%s,%s""" % (class_name, self.schema_dn) + """
220 distinguishedName: CN=%s,CN=Users,%s""" % (object_name, self.base_dn) + """
221 name: """ + object_name + """
222 """ + attr_ldap_display_name + """: test
223 """
224         self.ldb.add_ldif(ldif)
225
226         # Search for created object
227         obj_res = self.ldb.search("cn=%s,cn=Users,%s" % (object_name, self.base_dn), scope=SCOPE_BASE, attrs=["replPropertyMetaData"])
228
229         self.assertEquals(len(obj_res), 1)
230         self.assertTrue("replPropertyMetaData" in obj_res[0])
231         val = obj_res[0]["replPropertyMetaData"][0]
232         repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(val))
233         obj = repl.ctr
234
235         # Windows 2000 functional level won't have this.  It is too
236         # hard to work it out from the prefixmap however, so we skip
237         # this test in that case.
238         if msDS_IntId is not None:
239             found = False
240             for o in repl.ctr.array:
241                 if o.attid == msDS_IntId:
242                     found = True
243                     break
244             self.assertTrue(found, "Did not find 0x%08x in replPropertyMetaData" % msDS_IntId)
245         # Delete the object
246         delete_force(self.ldb, "cn=%s,cn=Users,%s" % (object_name, self.base_dn))
247
248     def test_subClassOf(self):
249         """ Testing usage of custom child schamaClass
250         """
251
252         class_name = "my-Class" + time.strftime("%s", time.gmtime())
253         class_ldap_display_name = class_name.replace("-", "")
254
255         ldif = """
256 dn: CN=%s,%s""" % (class_name, self.schema_dn) + """
257 objectClass: top
258 objectClass: classSchema
259 adminDescription: """ + class_name + """
260 adminDisplayName: """ + class_name + """
261 cn: """ + class_name + """
262 governsId: 1.3.6.1.4.1.7165.4.6.2.6.3.""" + str(random.randint(1, 100000)) + """
263 instanceType: 4
264 objectClassCategory: 1
265 subClassOf: organizationalUnit
266 systemFlags: 16
267 systemOnly: FALSE
268 """
269         self.ldb.add_ldif(ldif)
270
271         # Search for created objectclass
272         res = []
273         res = self.ldb.search("cn=%s,%s" % (class_name, self.schema_dn), scope=SCOPE_BASE,
274                               attrs=["lDAPDisplayName", "defaultObjectCategory",
275                                      "schemaIDGUID", "distinguishedName"])
276         self.assertEquals(len(res), 1)
277         self.assertEquals(res[0]["lDAPDisplayName"][0], class_ldap_display_name)
278         self.assertEquals(res[0]["defaultObjectCategory"][0], res[0]["distinguishedName"][0])
279         self.assertTrue("schemaIDGUID" in res[0])
280
281         ldif = """
282 dn:
283 changetype: modify
284 add: schemaUpdateNow
285 schemaUpdateNow: 1
286 """
287         self.ldb.modify_ldif(ldif)
288
289         object_name = "org" + time.strftime("%s", time.gmtime())
290
291         ldif = """
292 dn: OU=%s,%s""" % (object_name, self.base_dn) + """
293 objectClass: """ + class_ldap_display_name + """
294 ou: """ + object_name + """
295 instanceType: 4
296 """
297         self.ldb.add_ldif(ldif)
298
299         # Search for created object
300         res = []
301         res = self.ldb.search("ou=%s,%s" % (object_name, self.base_dn), scope=SCOPE_BASE, attrs=["dn"])
302         self.assertEquals(len(res), 1)
303         # Delete the object
304         delete_force(self.ldb, "ou=%s,%s" % (object_name, self.base_dn))
305
306     def test_duplicate_attributeID(self):
307         """Testing creating a duplicate attribute"""
308         rand = str(random.randint(1, 100000))
309         attr_name = "test-Attr" + time.strftime("%s", time.gmtime()) + "-" + rand
310         attr_ldap_display_name = attr_name.replace("-", "")
311         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.2." + rand
312         ldif = """
313 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
314 objectClass: top
315 objectClass: attributeSchema
316 adminDescription: """ + attr_name + """
317 adminDisplayName: """ + attr_name + """
318 cn: """ + attr_name + """
319 attributeId: """ + attributeID + """
320 attributeSyntax: 2.5.5.12
321 omSyntax: 64
322 instanceType: 4
323 isSingleValued: TRUE
324 systemOnly: FALSE
325 """
326         self.ldb.add_ldif(ldif)
327
328         ldif = """
329 dn: CN=%s-dup,%s""" % (attr_name, self.schema_dn) + """
330 objectClass: top
331 objectClass: attributeSchema
332 adminDescription: """ + attr_name + """dup
333 adminDisplayName: """ + attr_name + """dup
334 cn: """ + attr_name + """-dup
335 attributeId: """ + attributeID + """
336 attributeSyntax: 2.5.5.12
337 omSyntax: 64
338 instanceType: 4
339 isSingleValued: TRUE
340 systemOnly: FALSE
341 """
342         try:
343             self.ldb.add_ldif(ldif)
344             self.fail("Should have failed to add duplicate attributeID value")
345         except LdbError as e2:
346             (enum, estr) = e2.args
347             self.assertEquals(enum, ERR_UNWILLING_TO_PERFORM)
348
349     def test_duplicate_attributeID_governsID(self):
350         """Testing creating a duplicate attribute and class"""
351         rand = str(random.randint(1, 100000))
352         attr_name = "test-Attr" + time.strftime("%s", time.gmtime()) + "-" + rand
353         attr_ldap_display_name = attr_name.replace("-", "")
354         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.3." + rand
355         ldif = """
356 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
357 objectClass: top
358 objectClass: attributeSchema
359 adminDescription: """ + attr_name + """
360 adminDisplayName: """ + attr_name + """
361 cn: """ + attr_name + """
362 attributeId: """ + attributeID + """
363 attributeSyntax: 2.5.5.12
364 omSyntax: 64
365 instanceType: 4
366 isSingleValued: TRUE
367 systemOnly: FALSE
368 """
369         self.ldb.add_ldif(ldif)
370
371         ldif = """
372 dn: CN=%s-dup,%s""" % (attr_name, self.schema_dn) + """
373 objectClass: top
374 objectClass: classSchema
375 adminDescription: """ + attr_name + """dup
376 adminDisplayName: """ + attr_name + """dup
377 cn: """ + attr_name + """-dup
378 governsId: """ + attributeID + """
379 instanceType: 4
380 objectClassCategory: 1
381 subClassOf: organizationalPerson
382 rDNAttID: cn
383 systemMustContain: cn
384 systemOnly: FALSE
385 """
386         try:
387             self.ldb.add_ldif(ldif)
388             self.fail("Should have failed to add duplicate governsID conflicting with attributeID value")
389         except LdbError as e3:
390             (enum, estr) = e3.args
391             self.assertEquals(enum, ERR_UNWILLING_TO_PERFORM)
392
393     def test_duplicate_cn(self):
394         """Testing creating a duplicate attribute"""
395         rand = str(random.randint(1, 100000))
396         attr_name = "test-Attr" + time.strftime("%s", time.gmtime()) + "-" + rand
397         attr_ldap_display_name = attr_name.replace("-", "")
398         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.4." + rand
399         ldif = """
400 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
401 objectClass: top
402 objectClass: attributeSchema
403 adminDescription: """ + attr_name + """
404 adminDisplayName: """ + attr_name + """
405 cn: """ + attr_name + """
406 attributeId: """ + attributeID + """
407 attributeSyntax: 2.5.5.12
408 omSyntax: 64
409 instanceType: 4
410 isSingleValued: TRUE
411 systemOnly: FALSE
412 """
413         self.ldb.add_ldif(ldif)
414
415         ldif = """
416 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
417 objectClass: top
418 objectClass: attributeSchema
419 adminDescription: """ + attr_name + """dup
420 adminDisplayName: """ + attr_name + """dup
421 cn: """ + attr_name + """
422 attributeId: """ + attributeID + """.1
423 attributeSyntax: 2.5.5.12
424 omSyntax: 64
425 instanceType: 4
426 isSingleValued: TRUE
427 systemOnly: FALSE
428 """
429         try:
430             self.ldb.add_ldif(ldif)
431             self.fail("Should have failed to add attribute with duplicate CN")
432         except LdbError as e4:
433             (enum, estr) = e4.args
434             self.assertEquals(enum, ERR_ENTRY_ALREADY_EXISTS)
435
436     def test_duplicate_implicit_ldapdisplayname(self):
437         """Testing creating a duplicate attribute ldapdisplayname"""
438         rand = str(random.randint(1, 100000))
439         attr_name = "test-Attr" + time.strftime("%s", time.gmtime()) + "-" + rand
440         attr_ldap_display_name = attr_name.replace("-", "")
441         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.5." + rand
442         ldif = """
443 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
444 objectClass: top
445 objectClass: attributeSchema
446 adminDescription: """ + attr_name + """
447 adminDisplayName: """ + attr_name + """
448 cn: """ + attr_name + """
449 attributeId: """ + attributeID + """
450 attributeSyntax: 2.5.5.12
451 omSyntax: 64
452 instanceType: 4
453 isSingleValued: TRUE
454 systemOnly: FALSE
455 """
456         self.ldb.add_ldif(ldif)
457
458         ldif = """
459 dn: CN=%s-dup,%s""" % (attr_name, self.schema_dn) + """
460 objectClass: top
461 objectClass: attributeSchema
462 adminDescription: """ + attr_name + """dup
463 adminDisplayName: """ + attr_name + """dup
464 cn: """ + attr_name + """-dup
465 ldapDisplayName: """ + attr_ldap_display_name + """
466 attributeId: """ + attributeID + """.1
467 attributeSyntax: 2.5.5.12
468 omSyntax: 64
469 instanceType: 4
470 isSingleValued: TRUE
471 systemOnly: FALSE
472 """
473         try:
474             self.ldb.add_ldif(ldif)
475             self.fail("Should have failed to add attribute with duplicate of the implicit ldapDisplayName")
476         except LdbError as e5:
477             (enum, estr) = e5.args
478             self.assertEquals(enum, ERR_UNWILLING_TO_PERFORM)
479
480     def test_duplicate_explicit_ldapdisplayname(self):
481         """Testing creating a duplicate attribute ldapdisplayname"""
482         rand = str(random.randint(1, 100000))
483         attr_name = "test-Attr" + time.strftime("%s", time.gmtime()) + "-" + rand
484         attr_ldap_display_name = attr_name.replace("-", "")
485         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.6." + rand
486         ldif = """
487 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
488 objectClass: top
489 objectClass: attributeSchema
490 adminDescription: """ + attr_name + """
491 adminDisplayName: """ + attr_name + """
492 cn: """ + attr_name + """
493 attributeId: """ + attributeID + """
494 attributeSyntax: 2.5.5.12
495 ldapDisplayName: """ + attr_ldap_display_name + """
496 omSyntax: 64
497 instanceType: 4
498 isSingleValued: TRUE
499 systemOnly: FALSE
500 """
501         self.ldb.add_ldif(ldif)
502
503         ldif = """
504 dn: CN=%s-dup,%s""" % (attr_name, self.schema_dn) + """
505 objectClass: top
506 objectClass: attributeSchema
507 adminDescription: """ + attr_name + """dup
508 adminDisplayName: """ + attr_name + """dup
509 cn: """ + attr_name + """-dup
510 ldapDisplayName: """ + attr_ldap_display_name + """
511 attributeId: """ + attributeID + """.1
512 attributeSyntax: 2.5.5.12
513 omSyntax: 64
514 instanceType: 4
515 isSingleValued: TRUE
516 systemOnly: FALSE
517 """
518         try:
519             self.ldb.add_ldif(ldif)
520             self.fail("Should have failed to add attribute with duplicate ldapDisplayName")
521         except LdbError as e6:
522             (enum, estr) = e6.args
523             self.assertEquals(enum, ERR_UNWILLING_TO_PERFORM)
524
525     def test_duplicate_explicit_ldapdisplayname_with_class(self):
526         """Testing creating a duplicate attribute ldapdisplayname between
527         and attribute and a class"""
528         rand = str(random.randint(1, 100000))
529         attr_name = "test-Attr" + time.strftime("%s", time.gmtime()) + "-" + rand
530         attr_ldap_display_name = attr_name.replace("-", "")
531         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.7." + rand
532         governsID   = "1.3.6.1.4.1.7165.4.6.2.6.4." + rand
533         ldif = """
534 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
535 objectClass: top
536 objectClass: attributeSchema
537 adminDescription: """ + attr_name + """
538 adminDisplayName: """ + attr_name + """
539 cn: """ + attr_name + """
540 attributeId: """ + attributeID + """
541 attributeSyntax: 2.5.5.12
542 ldapDisplayName: """ + attr_ldap_display_name + """
543 omSyntax: 64
544 instanceType: 4
545 isSingleValued: TRUE
546 systemOnly: FALSE
547 """
548         self.ldb.add_ldif(ldif)
549
550         ldif = """
551 dn: CN=%s-dup,%s""" % (attr_name, self.schema_dn) + """
552 objectClass: top
553 objectClass: classSchema
554 adminDescription: """ + attr_name + """dup
555 adminDisplayName: """ + attr_name + """dup
556 cn: """ + attr_name + """-dup
557 ldapDisplayName: """ + attr_ldap_display_name + """
558 governsID: """ + governsID + """
559 instanceType: 4
560 objectClassCategory: 1
561 subClassOf: organizationalPerson
562 rDNAttID: cn
563 systemMustContain: cn
564 systemOnly: FALSE
565 """
566         try:
567             self.ldb.add_ldif(ldif)
568             self.fail("Should have failed to add class with duplicate ldapDisplayName")
569         except LdbError as e7:
570             (enum, estr) = e7.args
571             self.assertEquals(enum, ERR_UNWILLING_TO_PERFORM)
572
573     def test_duplicate_via_rename_ldapdisplayname(self):
574         """Testing creating a duplicate attribute ldapdisplayname"""
575         rand = str(random.randint(1, 100000))
576         attr_name = "test-Attr" + time.strftime("%s", time.gmtime()) + "-" + rand
577         attr_ldap_display_name = attr_name.replace("-", "")
578         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.8." + rand
579         ldif = """
580 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
581 objectClass: top
582 objectClass: attributeSchema
583 adminDescription: """ + attr_name + """
584 adminDisplayName: """ + attr_name + """
585 cn: """ + attr_name + """
586 attributeId: """ + attributeID + """
587 attributeSyntax: 2.5.5.12
588 ldapDisplayName: """ + attr_ldap_display_name + """
589 omSyntax: 64
590 instanceType: 4
591 isSingleValued: TRUE
592 systemOnly: FALSE
593 """
594         self.ldb.add_ldif(ldif)
595
596         ldif = """
597 dn: CN=%s-dup,%s""" % (attr_name, self.schema_dn) + """
598 objectClass: top
599 objectClass: attributeSchema
600 adminDescription: """ + attr_name + """dup
601 adminDisplayName: """ + attr_name + """dup
602 cn: """ + attr_name + """-dup
603 ldapDisplayName: """ + attr_ldap_display_name + """dup
604 attributeId: """ + attributeID + """.1
605 attributeSyntax: 2.5.5.12
606 omSyntax: 64
607 instanceType: 4
608 isSingleValued: TRUE
609 systemOnly: FALSE
610 """
611         self.ldb.add_ldif(ldif)
612
613         ldif = """
614 dn: CN=%s-dup,%s""" % (attr_name, self.schema_dn) + """
615 changetype: modify
616 replace: ldapDisplayName
617 ldapDisplayName: """ + attr_ldap_display_name + """
618 -
619 """
620         try:
621             self.ldb.modify_ldif(ldif)
622             self.fail("Should have failed to modify schema to have attribute with duplicate ldapDisplayName")
623         except LdbError as e8:
624             (enum, estr) = e8.args
625             self.assertEquals(enum, ERR_UNWILLING_TO_PERFORM)
626
627     def test_duplicate_via_rename_attributeID(self):
628         """Testing creating a duplicate attributeID"""
629         rand = str(random.randint(1, 100000))
630         attr_name = "test-Attr" + time.strftime("%s", time.gmtime()) + "-" + rand
631         attr_ldap_display_name = attr_name.replace("-", "")
632         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.9." + rand
633         ldif = """
634 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
635 objectClass: top
636 objectClass: attributeSchema
637 adminDescription: """ + attr_name + """
638 adminDisplayName: """ + attr_name + """
639 cn: """ + attr_name + """
640 attributeId: """ + attributeID + """
641 attributeSyntax: 2.5.5.12
642 ldapDisplayName: """ + attr_ldap_display_name + """
643 omSyntax: 64
644 instanceType: 4
645 isSingleValued: TRUE
646 systemOnly: FALSE
647 """
648         self.ldb.add_ldif(ldif)
649
650         ldif = """
651 dn: CN=%s-dup,%s""" % (attr_name, self.schema_dn) + """
652 objectClass: top
653 objectClass: attributeSchema
654 adminDescription: """ + attr_name + """dup
655 adminDisplayName: """ + attr_name + """dup
656 cn: """ + attr_name + """-dup
657 ldapDisplayName: """ + attr_ldap_display_name + """dup
658 attributeId: """ + attributeID + """.1
659 attributeSyntax: 2.5.5.12
660 omSyntax: 64
661 instanceType: 4
662 isSingleValued: TRUE
663 systemOnly: FALSE
664 """
665         self.ldb.add_ldif(ldif)
666
667         ldif = """
668 dn: CN=%s-dup,%s""" % (attr_name, self.schema_dn) + """
669 changetype: modify
670 replace: attributeId
671 attributeId: """ + attributeID + """
672 -
673 """
674         try:
675             self.ldb.modify_ldif(ldif)
676             self.fail("Should have failed to modify schema to have attribute with duplicate attributeID")
677         except LdbError as e9:
678             (enum, estr) = e9.args
679             self.assertEquals(enum, ERR_CONSTRAINT_VIOLATION)
680
681     def test_remove_ldapdisplayname(self):
682         """Testing removing the ldapdisplayname"""
683         rand = str(random.randint(1, 100000))
684         attr_name = "test-Attr" + time.strftime("%s", time.gmtime()) + "-" + rand
685         attr_ldap_display_name = attr_name.replace("-", "")
686         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.10." + rand
687         ldif = """
688 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
689 objectClass: top
690 objectClass: attributeSchema
691 adminDescription: """ + attr_name + """
692 adminDisplayName: """ + attr_name + """
693 cn: """ + attr_name + """
694 attributeId: """ + attributeID + """
695 attributeSyntax: 2.5.5.12
696 ldapDisplayName: """ + attr_ldap_display_name + """
697 omSyntax: 64
698 instanceType: 4
699 isSingleValued: TRUE
700 systemOnly: FALSE
701 """
702         self.ldb.add_ldif(ldif)
703
704         ldif = """
705 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
706 changetype: modify
707 replace: ldapDisplayName
708 -
709 """
710         try:
711             self.ldb.modify_ldif(ldif)
712             self.fail("Should have failed to remove the ldapdisplayname")
713         except LdbError as e10:
714             (enum, estr) = e10.args
715             self.assertEquals(enum, ERR_OBJECT_CLASS_VIOLATION)
716
717     def test_rename_ldapdisplayname(self):
718         """Testing renaming ldapdisplayname"""
719         rand = str(random.randint(1, 100000))
720         attr_name = "test-Attr" + time.strftime("%s", time.gmtime()) + "-" + rand
721         attr_ldap_display_name = attr_name.replace("-", "")
722         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.11." + rand
723         ldif = """
724 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
725 objectClass: top
726 objectClass: attributeSchema
727 adminDescription: """ + attr_name + """
728 adminDisplayName: """ + attr_name + """
729 cn: """ + attr_name + """
730 attributeId: """ + attributeID + """
731 attributeSyntax: 2.5.5.12
732 ldapDisplayName: """ + attr_ldap_display_name + """
733 omSyntax: 64
734 instanceType: 4
735 isSingleValued: TRUE
736 systemOnly: FALSE
737 """
738         self.ldb.add_ldif(ldif)
739
740         ldif = """
741 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
742 changetype: modify
743 replace: ldapDisplayName
744 ldapDisplayName: """ + attr_ldap_display_name + """2
745 -
746 """
747         self.ldb.modify_ldif(ldif)
748
749     def test_change_attributeID(self):
750         """Testing change the attributeID"""
751         rand = str(random.randint(1, 100000))
752         attr_name = "test-Attr" + time.strftime("%s", time.gmtime()) + "-" + rand
753         attr_ldap_display_name = attr_name.replace("-", "")
754         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.12." + rand
755         ldif = """
756 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
757 objectClass: top
758 objectClass: attributeSchema
759 adminDescription: """ + attr_name + """
760 adminDisplayName: """ + attr_name + """
761 cn: """ + attr_name + """
762 attributeId: """ + attributeID + """
763 attributeSyntax: 2.5.5.12
764 ldapDisplayName: """ + attr_ldap_display_name + """
765 omSyntax: 64
766 instanceType: 4
767 isSingleValued: TRUE
768 systemOnly: FALSE
769 """
770         self.ldb.add_ldif(ldif)
771
772         ldif = """
773 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
774 changetype: modify
775 replace: attributeID
776 attributeId: """ + attributeID + """.1
777
778 """
779         try:
780             self.ldb.modify_ldif(ldif)
781             self.fail("Should have failed to modify schema to have different attributeID")
782         except LdbError as e11:
783             (enum, estr) = e11.args
784             self.assertEquals(enum, ERR_CONSTRAINT_VIOLATION)
785
786     def test_change_attributeID_same(self):
787         """Testing change the attributeID to the same value"""
788         rand = str(random.randint(1, 100000))
789         attr_name = "test-Attr" + time.strftime("%s", time.gmtime()) + "-" + rand
790         attr_ldap_display_name = attr_name.replace("-", "")
791         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.13." + rand
792         ldif = """
793 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
794 objectClass: top
795 objectClass: attributeSchema
796 adminDescription: """ + attr_name + """
797 adminDisplayName: """ + attr_name + """
798 cn: """ + attr_name + """
799 attributeId: """ + attributeID + """
800 attributeSyntax: 2.5.5.12
801 ldapDisplayName: """ + attr_ldap_display_name + """
802 omSyntax: 64
803 instanceType: 4
804 isSingleValued: TRUE
805 systemOnly: FALSE
806 """
807         self.ldb.add_ldif(ldif)
808
809         ldif = """
810 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
811 changetype: modify
812 replace: attributeID
813 attributeId: """ + attributeID + """
814
815 """
816         try:
817             self.ldb.modify_ldif(ldif)
818             self.fail("Should have failed to modify schema to have the same attributeID")
819         except LdbError as e12:
820             (enum, estr) = e12.args
821             self.assertEquals(enum, ERR_CONSTRAINT_VIOLATION)
822
823     def test_generated_linkID(self):
824         """
825         Test that we automatically generate a linkID if the
826         OID "1.2.840.113556.1.2.50" is given as the linkID
827         of a new attribute, and that we don't get/can't add
828         duplicate linkIDs. Also test that we can add a backlink
829         by providing the attributeID or ldapDisplayName of
830         a forwards link in the linkID attribute.
831         """
832
833         # linkID generation isn't available before 2003
834         res = self.ldb.search(base="", expression="", scope=SCOPE_BASE,
835                               attrs=["domainControllerFunctionality"])
836         self.assertEquals(len(res), 1)
837         dc_level = int(res[0]["domainControllerFunctionality"][0])
838         if dc_level < DS_DOMAIN_FUNCTION_2003:
839             return
840
841         rand = str(random.randint(1, 100000))
842
843         attr_name_1 = "test-generated-linkID" + time.strftime("%s", time.gmtime()) + "-" + rand
844         attr_ldap_display_name_1 = attr_name_1.replace("-", "")
845         attributeID_1 = "1.3.6.1.4.1.7165.4.6.1.6.16." + rand
846         ldif = """
847 dn: CN=%s,%s""" % (attr_name_1, self.schema_dn) + """
848 objectClass: top
849 objectClass: attributeSchema
850 adminDescription: """ + attr_name_1 + """
851 adminDisplayName: """ + attr_name_1 + """
852 cn: """ + attr_name_1 + """
853 attributeId: """ + attributeID_1 + """
854 linkID: 1.2.840.113556.1.2.50
855 attributeSyntax: 2.5.5.1
856 ldapDisplayName: """ + attr_ldap_display_name_1 + """
857 omSyntax: 127
858 instanceType: 4
859 isSingleValued: TRUE
860 systemOnly: FALSE
861 """
862
863         try:
864             self.ldb.add_ldif(ldif)
865         except LdbError as e13:
866             (enum, estr) = e13.args
867             self.fail(estr)
868
869         attr_name_2 = "test-generated-linkID-2" + time.strftime("%s", time.gmtime()) + "-" + rand
870         attr_ldap_display_name_2 = attr_name_2.replace("-", "")
871         attributeID_2 = "1.3.6.1.4.1.7165.4.6.1.6.17." + rand
872         ldif = """
873 dn: CN=%s,%s""" % (attr_name_2, self.schema_dn) + """
874 objectClass: top
875 objectClass: attributeSchema
876 adminDescription: """ + attr_name_2 + """
877 adminDisplayName: """ + attr_name_2 + """
878 cn: """ + attr_name_2 + """
879 attributeId: """ + attributeID_2 + """
880 linkID: 1.2.840.113556.1.2.50
881 attributeSyntax: 2.5.5.1
882 ldapDisplayName: """ + attr_ldap_display_name_2 + """
883 omSyntax: 127
884 instanceType: 4
885 isSingleValued: TRUE
886 systemOnly: FALSE
887 """
888
889         try:
890             self.ldb.add_ldif(ldif)
891         except LdbError as e14:
892             (enum, estr) = e14.args
893             self.fail(estr)
894
895         res = self.ldb.search("CN=%s,%s" % (attr_name_1, self.schema_dn),
896                               scope=SCOPE_BASE,
897                               attrs=["linkID"])
898         self.assertEquals(len(res), 1)
899         linkID_1 = int(res[0]["linkID"][0])
900
901         res = self.ldb.search("CN=%s,%s" % (attr_name_2, self.schema_dn),
902                               scope=SCOPE_BASE,
903                               attrs=["linkID"])
904         self.assertEquals(len(res), 1)
905         linkID_2 = int(res[0]["linkID"][0])
906
907         # 0 should never be generated as a linkID
908         self.assertFalse(linkID_1 == 0)
909         self.assertFalse(linkID_2 == 0)
910
911         # The generated linkID should always be even, because
912         # it should assume we're adding a forward link.
913         self.assertTrue(linkID_1 % 2 == 0)
914         self.assertTrue(linkID_2 % 2 == 0)
915
916         self.assertFalse(linkID_1 == linkID_2)
917
918         # This is only necessary against Windows, since we depend
919         # on the previously added links in the next ones and Windows
920         # won't refresh the schema as we add them.
921         ldif = """
922 dn:
923 changetype: modify
924 replace: schemaupdatenow
925 schemaupdatenow: 1
926 """
927         self.ldb.modify_ldif(ldif)
928
929         # If we add a new link with the same linkID, it should fail
930         attr_name = "test-generated-linkID-duplicate" + time.strftime("%s", time.gmtime()) + "-" + rand
931         attr_ldap_display_name = attr_name.replace("-", "")
932         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.18." + rand
933         ldif = """
934 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
935 objectClass: top
936 objectClass: attributeSchema
937 adminDescription: """ + attr_name + """
938 adminDisplayName: """ + attr_name + """
939 cn: """ + attr_name + """
940 attributeId: """ + attributeID + """
941 linkID: """ + str(linkID_1) + """
942 attributeSyntax: 2.5.5.1
943 ldapDisplayName: """ + attr_ldap_display_name + """
944 omSyntax: 127
945 instanceType: 4
946 isSingleValued: TRUE
947 systemOnly: FALSE
948 """
949
950         try:
951             self.ldb.add_ldif(ldif)
952             self.fail("Should have failed to add duplicate linkID value")
953         except LdbError as e15:
954             (enum, estr) = e15.args
955             self.assertEquals(enum, ERR_UNWILLING_TO_PERFORM)
956
957         # If we add another attribute with the attributeID or lDAPDisplayName
958         # of a forward link in its linkID field, it should add as a backlink
959
960         attr_name_3 = "test-generated-linkID-backlink" + time.strftime("%s", time.gmtime()) + "-" + rand
961         attr_ldap_display_name_3 = attr_name_3.replace("-", "")
962         attributeID_3 = "1.3.6.1.4.1.7165.4.6.1.6.19." + rand
963         ldif = """
964 dn: CN=%s,%s""" % (attr_name_3, self.schema_dn) + """
965 objectClass: top
966 objectClass: attributeSchema
967 adminDescription: """ + attr_name_3 + """
968 adminDisplayName: """ + attr_name_3 + """
969 cn: """ + attr_name_3 + """
970 attributeId: """ + attributeID_3 + """
971 linkID: """ + str(linkID_1 + 1) + """
972 attributeSyntax: 2.5.5.1
973 ldapDisplayName: """ + attr_ldap_display_name_3 + """
974 omSyntax: 127
975 instanceType: 4
976 isSingleValued: TRUE
977 systemOnly: FALSE
978 """
979
980         try:
981             self.ldb.add_ldif(ldif)
982         except LdbError as e16:
983             (enum, estr) = e16.args
984             self.fail(estr)
985
986         res = self.ldb.search("CN=%s,%s" % (attr_name_3, self.schema_dn),
987                               scope=SCOPE_BASE,
988                               attrs=["linkID"])
989         self.assertEquals(len(res), 1)
990         linkID = int(res[0]["linkID"][0])
991         self.assertEquals(linkID, linkID_1 + 1)
992
993         attr_name_4 = "test-generated-linkID-backlink-2" + time.strftime("%s", time.gmtime()) + "-" + rand
994         attr_ldap_display_name_4 = attr_name_4.replace("-", "")
995         attributeID_4 = "1.3.6.1.4.1.7165.4.6.1.6.20." + rand
996         ldif = """
997 dn: CN=%s,%s""" % (attr_name_4, self.schema_dn) + """
998 objectClass: top
999 objectClass: attributeSchema
1000 adminDescription: """ + attr_name_4 + """
1001 adminDisplayName: """ + attr_name_4 + """
1002 cn: """ + attr_name_4 + """
1003 attributeId: """ + attributeID_4 + """
1004 linkID: """ + attr_ldap_display_name_2 + """
1005 attributeSyntax: 2.5.5.1
1006 ldapDisplayName: """ + attr_ldap_display_name_4 + """
1007 omSyntax: 127
1008 instanceType: 4
1009 isSingleValued: TRUE
1010 systemOnly: FALSE
1011 """
1012
1013         try:
1014             self.ldb.add_ldif(ldif)
1015         except LdbError as e17:
1016             (enum, estr) = e17.args
1017             self.fail(estr)
1018
1019         res = self.ldb.search("CN=%s,%s" % (attr_name_4, self.schema_dn),
1020                               scope=SCOPE_BASE,
1021                               attrs=["linkID"])
1022         self.assertEquals(len(res), 1)
1023         linkID = int(res[0]["linkID"][0])
1024         self.assertEquals(linkID, linkID_2 + 1)
1025
1026         # If we then try to add another backlink in the same way
1027         # for the same forwards link, we should fail.
1028
1029         attr_name = "test-generated-linkID-backlink-duplicate" + time.strftime("%s", time.gmtime()) + "-" + rand
1030         attr_ldap_display_name = attr_name.replace("-", "")
1031         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.21." + rand
1032         ldif = """
1033 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
1034 objectClass: top
1035 objectClass: attributeSchema
1036 adminDescription: """ + attr_name + """
1037 adminDisplayName: """ + attr_name + """
1038 cn: """ + attr_name + """
1039 attributeId: """ + attributeID + """
1040 linkID: """ + attributeID_1 + """
1041 attributeSyntax: 2.5.5.1
1042 ldapDisplayName: """ + attr_ldap_display_name + """
1043 omSyntax: 127
1044 instanceType: 4
1045 isSingleValued: TRUE
1046 systemOnly: FALSE
1047 """
1048
1049         try:
1050             self.ldb.add_ldif(ldif)
1051             self.fail("Should have failed to add duplicate backlink")
1052         except LdbError as e18:
1053             (enum, estr) = e18.args
1054             self.assertEquals(enum, ERR_UNWILLING_TO_PERFORM)
1055
1056         # If we try to supply the attributeID or ldapDisplayName
1057         # of an existing backlink in the linkID field of a new link,
1058         # it should fail.
1059
1060         attr_name = "test-generated-linkID-backlink-invalid" + time.strftime("%s", time.gmtime()) + "-" + rand
1061         attr_ldap_display_name = attr_name.replace("-", "")
1062         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.22." + rand
1063         ldif = """
1064 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
1065 objectClass: top
1066 objectClass: attributeSchema
1067 adminDescription: """ + attr_name + """
1068 adminDisplayName: """ + attr_name + """
1069 cn: """ + attr_name + """
1070 attributeId: """ + attributeID + """
1071 linkID: """ + attributeID_3 + """
1072 attributeSyntax: 2.5.5.1
1073 ldapDisplayName: """ + attr_ldap_display_name + """
1074 omSyntax: 127
1075 instanceType: 4
1076 isSingleValued: TRUE
1077 systemOnly: FALSE
1078 """
1079
1080         try:
1081             self.ldb.add_ldif(ldif)
1082             self.fail("Should have failed to add backlink of backlink")
1083         except LdbError as e19:
1084             (enum, estr) = e19.args
1085             self.assertEquals(enum, ERR_UNWILLING_TO_PERFORM)
1086
1087         attr_name = "test-generated-linkID-backlink-invalid-2" + time.strftime("%s", time.gmtime()) + "-" + rand
1088         attr_ldap_display_name = attr_name.replace("-", "")
1089         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.23." + rand
1090         ldif = """
1091 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
1092 objectClass: top
1093 objectClass: attributeSchema
1094 adminDescription: """ + attr_name + """
1095 adminDisplayName: """ + attr_name + """
1096 cn: """ + attr_name + """
1097 attributeId: """ + attributeID + """
1098 linkID: """ + attr_ldap_display_name_4 + """
1099 attributeSyntax: 2.5.5.1
1100 ldapDisplayName: """ + attr_ldap_display_name + """
1101 omSyntax: 127
1102 instanceType: 4
1103 isSingleValued: TRUE
1104 systemOnly: FALSE
1105 """
1106
1107         try:
1108             self.ldb.add_ldif(ldif)
1109             self.fail("Should have failed to add backlink of backlink")
1110         except LdbError as e20:
1111             (enum, estr) = e20.args
1112             self.assertEquals(enum, ERR_UNWILLING_TO_PERFORM)
1113
1114     def test_generated_mAPIID(self):
1115         """
1116         Test that we automatically generate a mAPIID if the
1117         OID "1.2.840.113556.1.2.49" is given as the mAPIID
1118         of a new attribute, and that we don't get/can't add
1119         duplicate mAPIIDs.
1120         """
1121
1122         rand = str(random.randint(1, 100000))
1123
1124         attr_name_1 = "test-generated-mAPIID" + time.strftime("%s", time.gmtime()) + "-" + rand
1125         attr_ldap_display_name_1 = attr_name_1.replace("-", "")
1126         attributeID_1 = "1.3.6.1.4.1.7165.4.6.1.6.24." + rand
1127         ldif = """
1128 dn: CN=%s,%s""" % (attr_name_1, self.schema_dn) + """
1129 objectClass: top
1130 objectClass: attributeSchema
1131 adminDescription: """ + attr_name_1 + """
1132 adminDisplayName: """ + attr_name_1 + """
1133 cn: """ + attr_name_1 + """
1134 attributeId: """ + attributeID_1 + """
1135 mAPIID: 1.2.840.113556.1.2.49
1136 attributeSyntax: 2.5.5.1
1137 ldapDisplayName: """ + attr_ldap_display_name_1 + """
1138 omSyntax: 127
1139 instanceType: 4
1140 isSingleValued: TRUE
1141 systemOnly: FALSE
1142 """
1143
1144         try:
1145             self.ldb.add_ldif(ldif)
1146         except LdbError as e21:
1147             (enum, estr) = e21.args
1148             self.fail(estr)
1149
1150         res = self.ldb.search("CN=%s,%s" % (attr_name_1, self.schema_dn),
1151                               scope=SCOPE_BASE,
1152                               attrs=["mAPIID"])
1153         self.assertEquals(len(res), 1)
1154         mAPIID_1 = int(res[0]["mAPIID"][0])
1155
1156         ldif = """
1157 dn:
1158 changetype: modify
1159 replace: schemaupdatenow
1160 schemaupdatenow: 1
1161 """
1162         self.ldb.modify_ldif(ldif)
1163
1164         # If we add a new attribute with the same mAPIID, it should fail
1165         attr_name = "test-generated-mAPIID-duplicate" + time.strftime("%s", time.gmtime()) + "-" + rand
1166         attr_ldap_display_name = attr_name.replace("-", "")
1167         attributeID = "1.3.6.1.4.1.7165.4.6.1.6.25." + rand
1168         ldif = """
1169 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
1170 objectClass: top
1171 objectClass: attributeSchema
1172 adminDescription: """ + attr_name + """
1173 adminDisplayName: """ + attr_name + """
1174 cn: """ + attr_name + """
1175 attributeId: """ + attributeID + """
1176 mAPIID: """ + str(mAPIID_1) + """
1177 attributeSyntax: 2.5.5.1
1178 ldapDisplayName: """ + attr_ldap_display_name + """
1179 omSyntax: 127
1180 instanceType: 4
1181 isSingleValued: TRUE
1182 systemOnly: FALSE
1183 """
1184
1185         try:
1186             self.ldb.add_ldif(ldif)
1187             self.fail("Should have failed to add duplicate mAPIID value")
1188         except LdbError as e22:
1189             (enum, estr) = e22.args
1190             self.assertEquals(enum, ERR_UNWILLING_TO_PERFORM)
1191
1192     def test_change_governsID(self):
1193         """Testing change the governsID"""
1194         rand = str(random.randint(1, 100000))
1195         class_name = "test-Class" + time.strftime("%s", time.gmtime()) + "-" + rand
1196         class_ldap_display_name = class_name.replace("-", "")
1197         governsID = "1.3.6.1.4.1.7165.4.6.2.6.5." + rand
1198         ldif = """
1199 dn: CN=%s,%s""" % (class_name, self.schema_dn) + """
1200 objectClass: top
1201 objectClass: classSchema
1202 adminDescription: """ + class_name + """
1203 adminDisplayName: """ + class_name + """
1204 cn: """ + class_name + """
1205 governsId: """ + governsID + """
1206 ldapDisplayName: """ + class_ldap_display_name + """
1207 instanceType: 4
1208 objectClassCategory: 1
1209 subClassOf: organizationalPerson
1210 rDNAttID: cn
1211 systemMustContain: cn
1212 systemOnly: FALSE
1213 """
1214         self.ldb.add_ldif(ldif)
1215
1216         ldif = """
1217 dn: CN=%s,%s""" % (class_name, self.schema_dn) + """
1218 changetype: modify
1219 replace: governsID
1220 governsId: """ + governsID + """.1
1221
1222 """
1223         try:
1224             self.ldb.modify_ldif(ldif)
1225             self.fail("Should have failed to modify schema to have different governsID")
1226         except LdbError as e23:
1227             (enum, estr) = e23.args
1228             self.assertEquals(enum, ERR_CONSTRAINT_VIOLATION)
1229
1230     def test_change_governsID_same(self):
1231         """Testing change the governsID"""
1232         rand = str(random.randint(1, 100000))
1233         class_name = "test-Class" + time.strftime("%s", time.gmtime()) + "-" + rand
1234         class_ldap_display_name = class_name.replace("-", "")
1235         governsID = "1.3.6.1.4.1.7165.4.6.2.6.6." + rand
1236         ldif = """
1237 dn: CN=%s,%s""" % (class_name, self.schema_dn) + """
1238 objectClass: top
1239 objectClass: classSchema
1240 adminDescription: """ + class_name + """
1241 adminDisplayName: """ + class_name + """
1242 cn: """ + class_name + """
1243 governsId: """ + governsID + """
1244 ldapDisplayName: """ + class_ldap_display_name + """
1245 instanceType: 4
1246 objectClassCategory: 1
1247 subClassOf: organizationalPerson
1248 rDNAttID: cn
1249 systemMustContain: cn
1250 systemOnly: FALSE
1251 """
1252         self.ldb.add_ldif(ldif)
1253
1254         ldif = """
1255 dn: CN=%s,%s""" % (class_name, self.schema_dn) + """
1256 changetype: modify
1257 replace: governsID
1258 governsId: """ + governsID + """.1
1259
1260 """
1261         try:
1262             self.ldb.modify_ldif(ldif)
1263             self.fail("Should have failed to modify schema to have the same governsID")
1264         except LdbError as e24:
1265             (enum, estr) = e24.args
1266             self.assertEquals(enum, ERR_CONSTRAINT_VIOLATION)
1267
1268     def test_subClassOf(self):
1269         """ Testing usage of custom child classSchema
1270         """
1271
1272         class_name = "my-Class" + time.strftime("%s", time.gmtime())
1273         class_ldap_display_name = class_name.replace("-", "")
1274
1275         ldif = """
1276 dn: CN=%s,%s""" % (class_name, self.schema_dn) + """
1277 objectClass: top
1278 objectClass: classSchema
1279 adminDescription: """ + class_name + """
1280 adminDisplayName: """ + class_name + """
1281 cn: """ + class_name + """
1282 governsId: 1.3.6.1.4.1.7165.4.6.2.6.7.""" + str(random.randint(1, 100000)) + """
1283 instanceType: 4
1284 objectClassCategory: 1
1285 subClassOf: organizationalUnit
1286 systemFlags: 16
1287 systemOnly: FALSE
1288 """
1289         self.ldb.add_ldif(ldif)
1290
1291         # Search for created objectclass
1292         res = []
1293         res = self.ldb.search("cn=%s,%s" % (class_name, self.schema_dn), scope=SCOPE_BASE,
1294                               attrs=["lDAPDisplayName", "defaultObjectCategory",
1295                                      "schemaIDGUID", "distinguishedName"])
1296         self.assertEquals(len(res), 1)
1297         self.assertEquals(res[0]["lDAPDisplayName"][0], class_ldap_display_name)
1298         self.assertEquals(res[0]["defaultObjectCategory"][0], res[0]["distinguishedName"][0])
1299         self.assertTrue("schemaIDGUID" in res[0])
1300
1301         ldif = """
1302 dn:
1303 changetype: modify
1304 add: schemaUpdateNow
1305 schemaUpdateNow: 1
1306 """
1307         self.ldb.modify_ldif(ldif)
1308
1309         object_name = "org" + time.strftime("%s", time.gmtime())
1310
1311         ldif = """
1312 dn: OU=%s,%s""" % (object_name, self.base_dn) + """
1313 objectClass: """ + class_ldap_display_name + """
1314 ou: """ + object_name + """
1315 instanceType: 4
1316 """
1317         self.ldb.add_ldif(ldif)
1318
1319         # Search for created object
1320         res = []
1321         res = self.ldb.search("ou=%s,%s" % (object_name, self.base_dn), scope=SCOPE_BASE, attrs=["dn"])
1322         self.assertEquals(len(res), 1)
1323         # Delete the object
1324         delete_force(self.ldb, "ou=%s,%s" % (object_name, self.base_dn))
1325
1326
1327 class SchemaTests_msDS_IntId(samba.tests.TestCase):
1328
1329     def setUp(self):
1330         super(SchemaTests_msDS_IntId, self).setUp()
1331         self.ldb = SamDB(host, credentials=creds,
1332                          session_info=system_session(lp), lp=lp, options=ldb_options)
1333         res = self.ldb.search(base="", expression="", scope=SCOPE_BASE,
1334                               attrs=["schemaNamingContext", "defaultNamingContext",
1335                                      "forestFunctionality"])
1336         self.assertEquals(len(res), 1)
1337         self.schema_dn = res[0]["schemaNamingContext"][0]
1338         self.base_dn = res[0]["defaultNamingContext"][0]
1339         self.forest_level = int(res[0]["forestFunctionality"][0])
1340
1341     def _ldap_schemaUpdateNow(self):
1342         ldif = """
1343 dn:
1344 changetype: modify
1345 add: schemaUpdateNow
1346 schemaUpdateNow: 1
1347 """
1348         self.ldb.modify_ldif(ldif)
1349
1350     def _make_obj_names(self, prefix):
1351         class_name = prefix + time.strftime("%s", time.gmtime())
1352         class_ldap_name = class_name.replace("-", "")
1353         class_dn = "CN=%s,%s" % (class_name, self.schema_dn)
1354         return (class_name, class_ldap_name, class_dn)
1355
1356     def _is_schema_base_object(self, ldb_msg):
1357         """Test systemFlags for SYSTEM_FLAG_SCHEMA_BASE_OBJECT (16)"""
1358         systemFlags = 0
1359         if "systemFlags" in ldb_msg:
1360             systemFlags = int(ldb_msg["systemFlags"][0])
1361         return (systemFlags & 16) != 0
1362
1363     def _make_attr_ldif(self, attr_name, attr_dn):
1364         ldif = """
1365 dn: """ + attr_dn + """
1366 objectClass: top
1367 objectClass: attributeSchema
1368 adminDescription: """ + attr_name + """
1369 adminDisplayName: """ + attr_name + """
1370 cn: """ + attr_name + """
1371 attributeId: 1.3.6.1.4.1.7165.4.6.1.6.14.""" + str(random.randint(1, 100000)) + """
1372 attributeSyntax: 2.5.5.12
1373 omSyntax: 64
1374 instanceType: 4
1375 isSingleValued: TRUE
1376 systemOnly: FALSE
1377 """
1378         return ldif
1379
1380     def test_msDS_IntId_on_attr(self):
1381         """Testing msDs-IntId creation for Attributes.
1382         See MS-ADTS - 3.1.1.Attributes
1383
1384         This test should verify that:
1385         - Creating attribute with 'msDS-IntId' fails with ERR_UNWILLING_TO_PERFORM
1386         - Adding 'msDS-IntId' on existing attribute fails with ERR_CONSTRAINT_VIOLATION
1387         - Creating attribute with 'msDS-IntId' set and FLAG_SCHEMA_BASE_OBJECT flag
1388           set fails with ERR_UNWILLING_TO_PERFORM
1389         - Attributes created with FLAG_SCHEMA_BASE_OBJECT not set have
1390           'msDS-IntId' attribute added internally
1391         """
1392
1393         # 1. Create attribute without systemFlags
1394         # msDS-IntId should be created if forest functional
1395         # level is >= DS_DOMAIN_FUNCTION_2003
1396         # and missing otherwise
1397         (attr_name, attr_ldap_name, attr_dn) = self._make_obj_names("msDS-IntId-Attr-1-")
1398         ldif = self._make_attr_ldif(attr_name, attr_dn)
1399
1400         # try to add msDS-IntId during Attribute creation
1401         ldif_fail = ldif + "msDS-IntId: -1993108831\n"
1402         try:
1403             self.ldb.add_ldif(ldif_fail)
1404             self.fail("Adding attribute with preset msDS-IntId should fail")
1405         except LdbError as e25:
1406             (num, _) = e25.args
1407             self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
1408
1409         # add the new attribute and update schema
1410         self.ldb.add_ldif(ldif)
1411         self._ldap_schemaUpdateNow()
1412
1413         # Search for created attribute
1414         res = []
1415         res = self.ldb.search(attr_dn, scope=SCOPE_BASE,
1416                               attrs=["lDAPDisplayName", "msDS-IntId", "systemFlags"])
1417         self.assertEquals(len(res), 1)
1418         self.assertEquals(res[0]["lDAPDisplayName"][0], attr_ldap_name)
1419         if self.forest_level >= DS_DOMAIN_FUNCTION_2003:
1420             if self._is_schema_base_object(res[0]):
1421                 self.assertTrue("msDS-IntId" not in res[0])
1422             else:
1423                 self.assertTrue("msDS-IntId" in res[0])
1424         else:
1425             self.assertTrue("msDS-IntId" not in res[0])
1426
1427         msg = Message()
1428         msg.dn = Dn(self.ldb, attr_dn)
1429         msg["msDS-IntId"] = MessageElement("-1993108831", FLAG_MOD_REPLACE, "msDS-IntId")
1430         try:
1431             self.ldb.modify(msg)
1432             self.fail("Modifying msDS-IntId should return error")
1433         except LdbError as e26:
1434             (num, _) = e26.args
1435             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1436
1437         # 2. Create attribute with systemFlags = FLAG_SCHEMA_BASE_OBJECT
1438         # msDS-IntId should be created if forest functional
1439         # level is >= DS_DOMAIN_FUNCTION_2003
1440         # and missing otherwise
1441         (attr_name, attr_ldap_name, attr_dn) = self._make_obj_names("msDS-IntId-Attr-2-")
1442         ldif = self._make_attr_ldif(attr_name, attr_dn)
1443         ldif += "systemFlags: 16\n"
1444
1445         # try to add msDS-IntId during Attribute creation
1446         ldif_fail = ldif + "msDS-IntId: -1993108831\n"
1447         try:
1448             self.ldb.add_ldif(ldif_fail)
1449             self.fail("Adding attribute with preset msDS-IntId should fail")
1450         except LdbError as e27:
1451             (num, _) = e27.args
1452             self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
1453
1454         # add the new attribute and update schema
1455         self.ldb.add_ldif(ldif)
1456         self._ldap_schemaUpdateNow()
1457
1458         # Search for created attribute
1459         res = []
1460         res = self.ldb.search(attr_dn, scope=SCOPE_BASE,
1461                               attrs=["lDAPDisplayName", "msDS-IntId"])
1462         self.assertEquals(len(res), 1)
1463         self.assertEquals(res[0]["lDAPDisplayName"][0], attr_ldap_name)
1464         if self.forest_level >= DS_DOMAIN_FUNCTION_2003:
1465             if self._is_schema_base_object(res[0]):
1466                 self.assertTrue("msDS-IntId" not in res[0])
1467             else:
1468                 self.assertTrue("msDS-IntId" in res[0])
1469         else:
1470             self.assertTrue("msDS-IntId" not in res[0])
1471
1472         msg = Message()
1473         msg.dn = Dn(self.ldb, attr_dn)
1474         msg["msDS-IntId"] = MessageElement("-1993108831", FLAG_MOD_REPLACE, "msDS-IntId")
1475         try:
1476             self.ldb.modify(msg)
1477             self.fail("Modifying msDS-IntId should return error")
1478         except LdbError as e28:
1479             (num, _) = e28.args
1480             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1481
1482     def _make_class_ldif(self, class_dn, class_name, sub_oid):
1483         ldif = """
1484 dn: """ + class_dn + """
1485 objectClass: top
1486 objectClass: classSchema
1487 adminDescription: """ + class_name + """
1488 adminDisplayName: """ + class_name + """
1489 cn: """ + class_name + """
1490 governsId: 1.3.6.1.4.1.7165.4.6.2.6.%d.""" % sub_oid + str(random.randint(1, 100000)) + """
1491 instanceType: 4
1492 objectClassCategory: 1
1493 subClassOf: organizationalPerson
1494 rDNAttID: cn
1495 systemMustContain: cn
1496 systemOnly: FALSE
1497 """
1498         return ldif
1499
1500     def test_msDS_IntId_on_class(self):
1501         """Testing msDs-IntId creation for Class
1502            Reference: MS-ADTS - 3.1.1.2.4.8 Class classSchema"""
1503
1504         # 1. Create Class without systemFlags
1505         # msDS-IntId should be created if forest functional
1506         # level is >= DS_DOMAIN_FUNCTION_2003
1507         # and missing otherwise
1508         (class_name, class_ldap_name, class_dn) = self._make_obj_names("msDS-IntId-Class-1-")
1509         ldif = self._make_class_ldif(class_dn, class_name, 8)
1510
1511         # try to add msDS-IntId during Class creation
1512         ldif_add = ldif + "msDS-IntId: -1993108831\n"
1513         self.ldb.add_ldif(ldif_add)
1514         self._ldap_schemaUpdateNow()
1515
1516         res = self.ldb.search(class_dn, scope=SCOPE_BASE, attrs=["msDS-IntId"])
1517         self.assertEquals(len(res), 1)
1518         self.assertEquals(res[0]["msDS-IntId"][0], "-1993108831")
1519
1520         # add a new Class and update schema
1521         (class_name, class_ldap_name, class_dn) = self._make_obj_names("msDS-IntId-Class-2-")
1522         ldif = self._make_class_ldif(class_dn, class_name, 9)
1523
1524         self.ldb.add_ldif(ldif)
1525         self._ldap_schemaUpdateNow()
1526
1527         # Search for created Class
1528         res = self.ldb.search(class_dn, scope=SCOPE_BASE, attrs=["msDS-IntId"])
1529         self.assertEquals(len(res), 1)
1530         self.assertFalse("msDS-IntId" in res[0])
1531
1532         msg = Message()
1533         msg.dn = Dn(self.ldb, class_dn)
1534         msg["msDS-IntId"] = MessageElement("-1993108831", FLAG_MOD_REPLACE, "msDS-IntId")
1535         try:
1536             self.ldb.modify(msg)
1537             self.fail("Modifying msDS-IntId should return error")
1538         except LdbError as e29:
1539             (num, _) = e29.args
1540             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1541
1542         # 2. Create Class with systemFlags = FLAG_SCHEMA_BASE_OBJECT
1543         # msDS-IntId should be created if forest functional
1544         # level is >= DS_DOMAIN_FUNCTION_2003
1545         # and missing otherwise
1546         (class_name, class_ldap_name, class_dn) = self._make_obj_names("msDS-IntId-Class-3-")
1547         ldif = self._make_class_ldif(class_dn, class_name, 10)
1548         ldif += "systemFlags: 16\n"
1549
1550         # try to add msDS-IntId during Class creation
1551         ldif_add = ldif + "msDS-IntId: -1993108831\n"
1552         self.ldb.add_ldif(ldif_add)
1553
1554         res = self.ldb.search(class_dn, scope=SCOPE_BASE, attrs=["msDS-IntId"])
1555         self.assertEquals(len(res), 1)
1556         self.assertEquals(res[0]["msDS-IntId"][0], "-1993108831")
1557
1558         # add the new Class and update schema
1559         (class_name, class_ldap_name, class_dn) = self._make_obj_names("msDS-IntId-Class-4-")
1560         ldif = self._make_class_ldif(class_dn, class_name, 11)
1561         ldif += "systemFlags: 16\n"
1562
1563         self.ldb.add_ldif(ldif)
1564         self._ldap_schemaUpdateNow()
1565
1566         # Search for created Class
1567         res = self.ldb.search(class_dn, scope=SCOPE_BASE, attrs=["msDS-IntId"])
1568         self.assertEquals(len(res), 1)
1569         self.assertFalse("msDS-IntId" in res[0])
1570
1571         msg = Message()
1572         msg.dn = Dn(self.ldb, class_dn)
1573         msg["msDS-IntId"] = MessageElement("-1993108831", FLAG_MOD_REPLACE, "msDS-IntId")
1574         try:
1575             self.ldb.modify(msg)
1576             self.fail("Modifying msDS-IntId should return error")
1577         except LdbError as e30:
1578             (num, _) = e30.args
1579             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1580         res = self.ldb.search(class_dn, scope=SCOPE_BASE, attrs=["msDS-IntId"])
1581         self.assertEquals(len(res), 1)
1582         self.assertFalse("msDS-IntId" in res[0])
1583
1584     def test_verify_msDS_IntId(self):
1585         """Verify msDS-IntId exists only on attributes without FLAG_SCHEMA_BASE_OBJECT flag set"""
1586         count = 0
1587         res = self.ldb.search(self.schema_dn, scope=SCOPE_ONELEVEL,
1588                               expression="objectClass=attributeSchema",
1589                               attrs=["systemFlags", "msDS-IntId", "attributeID", "cn"])
1590         self.assertTrue(len(res) > 1)
1591         for ldb_msg in res:
1592             if self.forest_level >= DS_DOMAIN_FUNCTION_2003:
1593                 if self._is_schema_base_object(ldb_msg):
1594                     self.assertTrue("msDS-IntId" not in ldb_msg)
1595                 else:
1596                     # don't assert here as there are plenty of
1597                     # attributes under w2k8 that are not part of
1598                     # Base Schema (SYSTEM_FLAG_SCHEMA_BASE_OBJECT flag not set)
1599                     # has not msDS-IntId attribute set
1600                     #self.assertTrue("msDS-IntId" in ldb_msg, "msDS-IntId expected on: %s" % ldb_msg.dn)
1601                     if "msDS-IntId" not in ldb_msg:
1602                         count = count + 1
1603                         print("%3d warning: msDS-IntId expected on: %-30s %s" % (count, ldb_msg["attributeID"], ldb_msg["cn"]))
1604             else:
1605                 self.assertTrue("msDS-IntId" not in ldb_msg)
1606
1607
1608 class SchemaTests_msDS_isRODC(samba.tests.TestCase):
1609
1610     def setUp(self):
1611         super(SchemaTests_msDS_isRODC, self).setUp()
1612         self.ldb = SamDB(host, credentials=creds,
1613                           session_info=system_session(lp), lp=lp, options=ldb_options)
1614         res = self.ldb.search(base="", expression="", scope=SCOPE_BASE, attrs=["defaultNamingContext"])
1615         self.assertEquals(len(res), 1)
1616         self.base_dn = res[0]["defaultNamingContext"][0]
1617
1618     def test_objectClass_ntdsdsa(self):
1619         res = self.ldb.search(self.base_dn, expression="objectClass=nTDSDSA",
1620                               attrs=["msDS-isRODC"], controls=["search_options:1:2"])
1621         for ldb_msg in res:
1622             self.assertTrue("msDS-isRODC" in ldb_msg)
1623
1624     def test_objectClass_server(self):
1625         res = self.ldb.search(self.base_dn, expression="objectClass=server",
1626                               attrs=["msDS-isRODC"], controls=["search_options:1:2"])
1627         for ldb_msg in res:
1628             ntds_search_dn = "CN=NTDS Settings,%s" % ldb_msg['dn']
1629             try:
1630                 res_check = self.ldb.search(ntds_search_dn, attrs=["objectCategory"])
1631             except LdbError as e:
1632                 (num, _) = e.args
1633                 self.assertEquals(num, ERR_NO_SUCH_OBJECT)
1634                 print("Server entry %s doesn't have a NTDS settings object" % res[0]['dn'])
1635             else:
1636                 self.assertTrue("objectCategory" in res_check[0])
1637                 self.assertTrue("msDS-isRODC" in ldb_msg)
1638
1639     def test_objectClass_computer(self):
1640         res = self.ldb.search(self.base_dn, expression="objectClass=computer",
1641                               attrs=["serverReferenceBL", "msDS-isRODC"], controls=["search_options:1:2"])
1642         for ldb_msg in res:
1643             if "serverReferenceBL" not in ldb_msg:
1644                 print("Computer entry %s doesn't have a serverReferenceBL attribute" % ldb_msg['dn'])
1645             else:
1646                 self.assertTrue("msDS-isRODC" in ldb_msg)
1647
1648 if not "://" in host:
1649     if os.path.isfile(host):
1650         host = "tdb://%s" % host
1651     else:
1652         host = "ldap://%s" % host
1653
1654 ldb_options = []
1655 if host.startswith("ldap://"):
1656     # user 'paged_search' module when connecting remotely
1657     ldb_options = ["modules:paged_searches"]
1658
1659 TestProgram(module=__name__, opts=subunitopts)