2 # -*- coding: utf-8 -*-
3 # This is a port of the original in testprogs/ejs/ldap.js
11 sys.path.insert(0, "bin/python")
13 samba.ensure_external_module("testtools", "testtools")
14 samba.ensure_external_module("subunit", "subunit/python")
16 import samba.getopt as options
18 from samba.auth import system_session
19 from ldb import SCOPE_ONELEVEL, SCOPE_BASE, LdbError
20 from ldb import ERR_NO_SUCH_OBJECT
21 from ldb import ERR_UNWILLING_TO_PERFORM
22 from ldb import ERR_CONSTRAINT_VIOLATION
23 from ldb import Message, MessageElement, Dn
24 from ldb import FLAG_MOD_REPLACE
25 from samba.samdb import SamDB
26 from samba.dsdb import DS_DOMAIN_FUNCTION_2003
27 from samba.tests import delete_force
29 from subunit.run import SubunitTestRunner
32 parser = optparse.OptionParser("ldap_schema.py [options] <host>")
33 sambaopts = options.SambaOptions(parser)
34 parser.add_option_group(sambaopts)
35 parser.add_option_group(options.VersionOptions(parser))
36 # use command line creds if available
37 credopts = options.CredentialsOptions(parser)
38 parser.add_option_group(credopts)
39 opts, args = parser.parse_args()
47 lp = sambaopts.get_loadparm()
48 creds = credopts.get_credentials(lp)
51 class SchemaTests(samba.tests.TestCase):
54 super(SchemaTests, self).setUp()
56 self.base_dn = ldb.domain_dn()
57 self.schema_dn = ldb.get_schema_basedn().get_linearized()
59 def test_generated_schema(self):
60 """Testing we can read the generated schema via LDAP"""
61 res = self.ldb.search("cn=aggregate,"+self.schema_dn, scope=SCOPE_BASE,
62 attrs=["objectClasses", "attributeTypes", "dITContentRules"])
63 self.assertEquals(len(res), 1)
64 self.assertTrue("dITContentRules" in res[0])
65 self.assertTrue("objectClasses" in res[0])
66 self.assertTrue("attributeTypes" in res[0])
68 def test_generated_schema_is_operational(self):
69 """Testing we don't get the generated schema via LDAP by default"""
70 res = self.ldb.search("cn=aggregate,"+self.schema_dn, scope=SCOPE_BASE,
72 self.assertEquals(len(res), 1)
73 self.assertFalse("dITContentRules" in res[0])
74 self.assertFalse("objectClasses" in res[0])
75 self.assertFalse("attributeTypes" in res[0])
77 def test_schemaUpdateNow(self):
78 """Testing schemaUpdateNow"""
79 attr_name = "test-Attr" + time.strftime("%s", time.gmtime())
80 attr_ldap_display_name = attr_name.replace("-", "")
83 dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
85 objectClass: attributeSchema
86 adminDescription: """ + attr_name + """
87 adminDisplayName: """ + attr_name + """
88 cn: """ + attr_name + """
89 attributeId: 1.2.840.""" + str(random.randint(1,100000)) + """.1.5.9940
90 attributeSyntax: 2.5.5.12
96 self.ldb.add_ldif(ldif)
98 # Search for created attribute
100 res = self.ldb.search("cn=%s,%s" % (attr_name, self.schema_dn), scope=SCOPE_BASE, attrs=["*"])
101 self.assertEquals(len(res), 1)
102 self.assertEquals(res[0]["lDAPDisplayName"][0], attr_ldap_display_name)
103 self.assertTrue("schemaIDGUID" in res[0])
105 class_name = "test-Class" + time.strftime("%s", time.gmtime())
106 class_ldap_display_name = class_name.replace("-", "")
108 # First try to create a class with a wrong "defaultObjectCategory"
110 dn: CN=%s,%s""" % (class_name, self.schema_dn) + """
112 objectClass: classSchema
113 defaultObjectCategory: CN=_
114 adminDescription: """ + class_name + """
115 adminDisplayName: """ + class_name + """
116 cn: """ + class_name + """
117 governsId: 1.2.840.""" + str(random.randint(1,100000)) + """.1.5.9939
119 objectClassCategory: 1
120 subClassOf: organizationalPerson
123 systemMustContain: cn
124 systemMustContain: """ + attr_ldap_display_name + """
128 self.ldb.add_ldif(ldif)
130 except LdbError, (num, _):
131 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
134 dn: CN=%s,%s""" % (class_name, self.schema_dn) + """
136 objectClass: classSchema
137 adminDescription: """ + class_name + """
138 adminDisplayName: """ + class_name + """
139 cn: """ + class_name + """
140 governsId: 1.2.840.""" + str(random.randint(1,100000)) + """.1.5.9939
142 objectClassCategory: 1
143 subClassOf: organizationalPerson
146 systemMustContain: cn
147 systemMustContain: """ + attr_ldap_display_name + """
150 self.ldb.add_ldif(ldif)
152 # Search for created objectclass
154 res = self.ldb.search("cn=%s,%s" % (class_name, self.schema_dn), scope=SCOPE_BASE, attrs=["*"])
155 self.assertEquals(len(res), 1)
156 self.assertEquals(res[0]["lDAPDisplayName"][0], class_ldap_display_name)
157 self.assertEquals(res[0]["defaultObjectCategory"][0], res[0]["distinguishedName"][0])
158 self.assertTrue("schemaIDGUID" in res[0])
166 self.ldb.modify_ldif(ldif)
168 object_name = "obj" + time.strftime("%s", time.gmtime())
171 dn: CN=%s,CN=Users,%s"""% (object_name, self.base_dn) + """
172 objectClass: organizationalPerson
174 objectClass: """ + class_ldap_display_name + """
176 cn: """ + object_name + """
178 objectCategory: CN=%s,%s"""% (class_name, self.schema_dn) + """
179 distinguishedName: CN=%s,CN=Users,%s"""% (object_name, self.base_dn) + """
180 name: """ + object_name + """
181 """ + attr_ldap_display_name + """: test
183 self.ldb.add_ldif(ldif)
185 # Search for created object
187 res = self.ldb.search("cn=%s,cn=Users,%s" % (object_name, self.base_dn), scope=SCOPE_BASE, attrs=["*"])
188 self.assertEquals(len(res), 1)
190 delete_force(self.ldb, "cn=%s,cn=Users,%s" % (object_name, self.base_dn))
192 def test_subClassOf(self):
193 """ Testing usage of custom child schamaClass
196 class_name = "my-Class" + time.strftime("%s", time.gmtime())
197 class_ldap_display_name = class_name.replace("-", "")
200 dn: CN=%s,%s""" % (class_name, self.schema_dn) + """
202 objectClass: classSchema
203 adminDescription: """ + class_name + """
204 adminDisplayName: """ + class_name + """
205 cn: """ + class_name + """
206 governsId: 1.2.840.""" + str(random.randint(1,100000)) + """.1.5.9939
208 objectClassCategory: 1
209 subClassOf: organizationalUnit
213 self.ldb.add_ldif(ldif)
215 # Search for created objectclass
217 res = self.ldb.search("cn=%s,%s" % (class_name, self.schema_dn), scope=SCOPE_BASE, attrs=["*"])
218 self.assertEquals(len(res), 1)
219 self.assertEquals(res[0]["lDAPDisplayName"][0], class_ldap_display_name)
220 self.assertEquals(res[0]["defaultObjectCategory"][0], res[0]["distinguishedName"][0])
221 self.assertTrue("schemaIDGUID" in res[0])
229 self.ldb.modify_ldif(ldif)
231 object_name = "org" + time.strftime("%s", time.gmtime())
234 dn: OU=%s,%s""" % (object_name, self.base_dn) + """
235 objectClass: """ + class_ldap_display_name + """
236 ou: """ + object_name + """
239 self.ldb.add_ldif(ldif)
241 # Search for created object
243 res = self.ldb.search("ou=%s,%s" % (object_name, self.base_dn), scope=SCOPE_BASE, attrs=["*"])
244 self.assertEquals(len(res), 1)
246 delete_force(self.ldb, "ou=%s,%s" % (object_name, self.base_dn))
249 class SchemaTests_msDS_IntId(samba.tests.TestCase):
252 super(SchemaTests_msDS_IntId, self).setUp()
254 res = ldb.search(base="", expression="", scope=SCOPE_BASE, attrs=["*"])
255 self.assertEquals(len(res), 1)
256 self.schema_dn = res[0]["schemaNamingContext"][0]
257 self.base_dn = res[0]["defaultNamingContext"][0]
258 self.forest_level = int(res[0]["forestFunctionality"][0])
260 def _ldap_schemaUpdateNow(self):
267 self.ldb.modify_ldif(ldif)
269 def _make_obj_names(self, prefix):
270 class_name = prefix + time.strftime("%s", time.gmtime())
271 class_ldap_name = class_name.replace("-", "")
272 class_dn = "CN=%s,%s" % (class_name, self.schema_dn)
273 return (class_name, class_ldap_name, class_dn)
275 def _is_schema_base_object(self, ldb_msg):
276 """Test systemFlags for SYSTEM_FLAG_SCHEMA_BASE_OBJECT (16)"""
278 if "systemFlags" in ldb_msg:
279 systemFlags = int(ldb_msg["systemFlags"][0])
280 return (systemFlags & 16) != 0
282 def _make_attr_ldif(self, attr_name, attr_dn):
284 dn: """ + attr_dn + """
286 objectClass: attributeSchema
287 adminDescription: """ + attr_name + """
288 adminDisplayName: """ + attr_name + """
289 cn: """ + attr_name + """
290 attributeId: 1.2.840.""" + str(random.randint(1,100000)) + """.1.5.9940
291 attributeSyntax: 2.5.5.12
299 def test_msDS_IntId_on_attr(self):
300 """Testing msDs-IntId creation for Attributes.
301 See MS-ADTS - 3.1.1.Attributes
303 This test should verify that:
304 - Creating attribute with 'msDS-IntId' fails with ERR_UNWILLING_TO_PERFORM
305 - Adding 'msDS-IntId' on existing attribute fails with ERR_CONSTRAINT_VIOLATION
306 - Creating attribute with 'msDS-IntId' set and FLAG_SCHEMA_BASE_OBJECT flag
307 set fails with ERR_UNWILLING_TO_PERFORM
308 - Attributes created with FLAG_SCHEMA_BASE_OBJECT not set have
309 'msDS-IntId' attribute added internally
312 # 1. Create attribute without systemFlags
313 # msDS-IntId should be created if forest functional
314 # level is >= DS_DOMAIN_FUNCTION_2003
315 # and missing otherwise
316 (attr_name, attr_ldap_name, attr_dn) = self._make_obj_names("msDS-IntId-Attr-1-")
317 ldif = self._make_attr_ldif(attr_name, attr_dn)
319 # try to add msDS-IntId during Attribute creation
320 ldif_fail = ldif + "msDS-IntId: -1993108831\n"
322 self.ldb.add_ldif(ldif_fail)
323 self.fail("Adding attribute with preset msDS-IntId should fail")
324 except LdbError, (num, _):
325 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
327 # add the new attribute and update schema
328 self.ldb.add_ldif(ldif)
329 self._ldap_schemaUpdateNow()
331 # Search for created attribute
333 res = self.ldb.search(attr_dn, scope=SCOPE_BASE, attrs=["*"])
334 self.assertEquals(len(res), 1)
335 self.assertEquals(res[0]["lDAPDisplayName"][0], attr_ldap_name)
336 if self.forest_level >= DS_DOMAIN_FUNCTION_2003:
337 if self._is_schema_base_object(res[0]):
338 self.assertTrue("msDS-IntId" not in res[0])
340 self.assertTrue("msDS-IntId" in res[0])
342 self.assertTrue("msDS-IntId" not in res[0])
345 msg.dn = Dn(self.ldb, attr_dn)
346 msg["msDS-IntId"] = MessageElement("-1993108831", FLAG_MOD_REPLACE, "msDS-IntId")
349 self.fail("Modifying msDS-IntId should return error")
350 except LdbError, (num, _):
351 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
353 # 2. Create attribute with systemFlags = FLAG_SCHEMA_BASE_OBJECT
354 # msDS-IntId should be created if forest functional
355 # level is >= DS_DOMAIN_FUNCTION_2003
356 # and missing otherwise
357 (attr_name, attr_ldap_name, attr_dn) = self._make_obj_names("msDS-IntId-Attr-2-")
358 ldif = self._make_attr_ldif(attr_name, attr_dn)
359 ldif += "systemFlags: 16\n"
361 # try to add msDS-IntId during Attribute creation
362 ldif_fail = ldif + "msDS-IntId: -1993108831\n"
364 self.ldb.add_ldif(ldif_fail)
365 self.fail("Adding attribute with preset msDS-IntId should fail")
366 except LdbError, (num, _):
367 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
369 # add the new attribute and update schema
370 self.ldb.add_ldif(ldif)
371 self._ldap_schemaUpdateNow()
373 # Search for created attribute
375 res = self.ldb.search(attr_dn, scope=SCOPE_BASE, attrs=["*"])
376 self.assertEquals(len(res), 1)
377 self.assertEquals(res[0]["lDAPDisplayName"][0], attr_ldap_name)
378 if self.forest_level >= DS_DOMAIN_FUNCTION_2003:
379 if self._is_schema_base_object(res[0]):
380 self.assertTrue("msDS-IntId" not in res[0])
382 self.assertTrue("msDS-IntId" in res[0])
384 self.assertTrue("msDS-IntId" not in res[0])
387 msg.dn = Dn(self.ldb, attr_dn)
388 msg["msDS-IntId"] = MessageElement("-1993108831", FLAG_MOD_REPLACE, "msDS-IntId")
391 self.fail("Modifying msDS-IntId should return error")
392 except LdbError, (num, _):
393 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
396 def _make_class_ldif(self, class_dn, class_name):
398 dn: """ + class_dn + """
400 objectClass: classSchema
401 adminDescription: """ + class_name + """
402 adminDisplayName: """ + class_name + """
403 cn: """ + class_name + """
404 governsId: 1.2.840.""" + str(random.randint(1,100000)) + """.1.5.9939
406 objectClassCategory: 1
407 subClassOf: organizationalPerson
409 systemMustContain: cn
414 def test_msDS_IntId_on_class(self):
415 """Testing msDs-IntId creation for Class
416 Reference: MS-ADTS - 3.1.1.2.4.8 Class classSchema"""
418 # 1. Create Class without systemFlags
419 # msDS-IntId should be created if forest functional
420 # level is >= DS_DOMAIN_FUNCTION_2003
421 # and missing otherwise
422 (class_name, class_ldap_name, class_dn) = self._make_obj_names("msDS-IntId-Class-1-")
423 ldif = self._make_class_ldif(class_dn, class_name)
425 # try to add msDS-IntId during Class creation
426 ldif_add = ldif + "msDS-IntId: -1993108831\n"
427 self.ldb.add_ldif(ldif_add)
428 self._ldap_schemaUpdateNow()
430 res = self.ldb.search(class_dn, scope=SCOPE_BASE, attrs=["*"])
431 self.assertEquals(len(res), 1)
432 self.assertEquals(res[0]["msDS-IntId"][0], "-1993108831")
434 # add a new Class and update schema
435 (class_name, class_ldap_name, class_dn) = self._make_obj_names("msDS-IntId-Class-2-")
436 ldif = self._make_class_ldif(class_dn, class_name)
438 self.ldb.add_ldif(ldif)
439 self._ldap_schemaUpdateNow()
441 # Search for created Class
442 res = self.ldb.search(class_dn, scope=SCOPE_BASE, attrs=["*"])
443 self.assertEquals(len(res), 1)
444 self.assertFalse("msDS-IntId" in res[0])
447 msg.dn = Dn(self.ldb, class_dn)
448 msg["msDS-IntId"] = MessageElement("-1993108831", FLAG_MOD_REPLACE, "msDS-IntId")
451 self.fail("Modifying msDS-IntId should return error")
452 except LdbError, (num, _):
453 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
455 # 2. Create Class with systemFlags = FLAG_SCHEMA_BASE_OBJECT
456 # msDS-IntId should be created if forest functional
457 # level is >= DS_DOMAIN_FUNCTION_2003
458 # and missing otherwise
459 (class_name, class_ldap_name, class_dn) = self._make_obj_names("msDS-IntId-Class-3-")
460 ldif = self._make_class_ldif(class_dn, class_name)
461 ldif += "systemFlags: 16\n"
463 # try to add msDS-IntId during Class creation
464 ldif_add = ldif + "msDS-IntId: -1993108831\n"
465 self.ldb.add_ldif(ldif_add)
467 res = self.ldb.search(class_dn, scope=SCOPE_BASE, attrs=["*"])
468 self.assertEquals(len(res), 1)
469 self.assertEquals(res[0]["msDS-IntId"][0], "-1993108831")
471 # add the new Class and update schema
472 (class_name, class_ldap_name, class_dn) = self._make_obj_names("msDS-IntId-Class-4-")
473 ldif = self._make_class_ldif(class_dn, class_name)
474 ldif += "systemFlags: 16\n"
476 self.ldb.add_ldif(ldif)
477 self._ldap_schemaUpdateNow()
479 # Search for created Class
480 res = self.ldb.search(class_dn, scope=SCOPE_BASE, attrs=["*"])
481 self.assertEquals(len(res), 1)
482 self.assertFalse("msDS-IntId" in res[0])
485 msg.dn = Dn(self.ldb, class_dn)
486 msg["msDS-IntId"] = MessageElement("-1993108831", FLAG_MOD_REPLACE, "msDS-IntId")
489 self.fail("Modifying msDS-IntId should return error")
490 except LdbError, (num, _):
491 self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
492 res = self.ldb.search(class_dn, scope=SCOPE_BASE, attrs=["*"])
493 self.assertEquals(len(res), 1)
494 self.assertFalse("msDS-IntId" in res[0])
497 def test_verify_msDS_IntId(self):
498 """Verify msDS-IntId exists only on attributes without FLAG_SCHEMA_BASE_OBJECT flag set"""
500 res = self.ldb.search(self.schema_dn, scope=SCOPE_ONELEVEL,
501 expression="objectClass=attributeSchema",
502 attrs=["systemFlags", "msDS-IntId", "attributeID", "cn"])
503 self.assertTrue(len(res) > 1)
505 if self.forest_level >= DS_DOMAIN_FUNCTION_2003:
506 if self._is_schema_base_object(ldb_msg):
507 self.assertTrue("msDS-IntId" not in ldb_msg)
509 # don't assert here as there are plenty of
510 # attributes under w2k8 that are not part of
511 # Base Schema (SYSTEM_FLAG_SCHEMA_BASE_OBJECT flag not set)
512 # has not msDS-IntId attribute set
513 #self.assertTrue("msDS-IntId" in ldb_msg, "msDS-IntId expected on: %s" % ldb_msg.dn)
514 if "msDS-IntId" not in ldb_msg:
516 print "%3d warning: msDS-IntId expected on: %-30s %s" % (count, ldb_msg["attributeID"], ldb_msg["cn"])
518 self.assertTrue("msDS-IntId" not in ldb_msg)
521 class SchemaTests_msDS_isRODC(samba.tests.TestCase):
524 super(SchemaTests_msDS_isRODC, self).setUp()
526 res = ldb.search(base="", expression="", scope=SCOPE_BASE, attrs=["*"])
527 self.assertEquals(len(res), 1)
528 self.base_dn = res[0]["defaultNamingContext"][0]
530 def test_objectClass_ntdsdsa(self):
531 res = self.ldb.search(self.base_dn, expression="objectClass=nTDSDSA",
532 attrs=["msDS-isRODC"], controls=["search_options:1:2"])
534 self.assertTrue("msDS-isRODC" in ldb_msg)
536 def test_objectClass_server(self):
537 res = self.ldb.search(self.base_dn, expression="objectClass=server",
538 attrs=["msDS-isRODC"], controls=["search_options:1:2"])
540 ntds_search_dn = "CN=NTDS Settings,%s" % ldb_msg['dn']
542 res_check = self.ldb.search(ntds_search_dn, attrs=["objectCategory"])
543 except LdbError, (num, _):
544 self.assertEquals(num, ERR_NO_SUCH_OBJECT)
545 print("Server entry %s doesn't have a NTDS settings object" % res[0]['dn'])
547 self.assertTrue("objectCategory" in res_check[0])
548 self.assertTrue("msDS-isRODC" in ldb_msg)
550 def test_objectClass_computer(self):
551 res = self.ldb.search(self.base_dn, expression="objectClass=computer",
552 attrs=["serverReferenceBL","msDS-isRODC"], controls=["search_options:1:2"])
554 if "serverReferenceBL" not in ldb_msg:
555 print("Computer entry %s doesn't have a serverReferenceBL attribute" % ldb_msg['dn'])
557 self.assertTrue("msDS-isRODC" in ldb_msg)
559 if not "://" in host:
560 if os.path.isfile(host):
561 host = "tdb://%s" % host
563 host = "ldap://%s" % host
566 if host.startswith("ldap://"):
567 # user 'paged_search' module when connecting remotely
568 ldb_options = ["modules:paged_searches"]
570 ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp, options=ldb_options)
572 runner = SubunitTestRunner()
574 if not runner.run(unittest.makeSuite(SchemaTests)).wasSuccessful():
576 if not runner.run(unittest.makeSuite(SchemaTests_msDS_IntId)).wasSuccessful():
578 if not runner.run(unittest.makeSuite(SchemaTests_msDS_isRODC)).wasSuccessful():