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