2 # -*- coding: utf-8 -*-
4 from __future__ import print_function
9 sys.path.insert(0, "bin/python")
12 from samba.tests.subunitrun import SubunitOptions, TestProgram
14 import samba.getopt as options
16 from samba.auth import system_session
17 from ldb import SCOPE_BASE, LdbError, Message, MessageElement, Dn, FLAG_MOD_ADD, FLAG_MOD_DELETE, FLAG_MOD_REPLACE
18 from ldb import ERR_NO_SUCH_OBJECT, ERR_NOT_ALLOWED_ON_NON_LEAF, ERR_ENTRY_ALREADY_EXISTS, ERR_ATTRIBUTE_OR_VALUE_EXISTS
19 from ldb import ERR_UNWILLING_TO_PERFORM, ERR_OPERATIONS_ERROR
20 from samba.samdb import SamDB
21 from samba.tests import delete_force
22 from samba import dsdb
23 from samba.compat import get_string
25 parser = optparse.OptionParser("deletetest.py [options] <host|file>")
26 sambaopts = options.SambaOptions(parser)
27 parser.add_option_group(sambaopts)
28 parser.add_option_group(options.VersionOptions(parser))
29 # use command line creds if available
30 credopts = options.CredentialsOptions(parser)
31 parser.add_option_group(credopts)
32 subunitopts = SubunitOptions(parser)
33 parser.add_option_group(subunitopts)
34 opts, args = parser.parse_args()
42 lp = sambaopts.get_loadparm()
43 creds = credopts.get_credentials(lp)
46 class BaseDeleteTests(samba.tests.TestCase):
48 def GUID_string(self, guid):
49 return get_string(self.ldb.schema_format_value("objectGUID", guid))
52 super(BaseDeleteTests, self).setUp()
53 self.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp)
55 self.base_dn = self.ldb.domain_dn()
56 self.configuration_dn = self.ldb.get_config_basedn().get_linearized()
58 def search_guid(self, guid):
59 print("SEARCH by GUID %s" % self.GUID_string(guid))
61 res = self.ldb.search(base="<GUID=%s>" % self.GUID_string(guid),
63 controls=["show_deleted:1"],
64 attrs=["*", "parentGUID"])
65 self.assertEquals(len(res), 1)
68 def search_dn(self, dn):
69 print("SEARCH by DN %s" % dn)
71 res = self.ldb.search(expression="(objectClass=*)",
74 controls=["show_deleted:1"],
75 attrs=["*", "parentGUID"])
76 self.assertEquals(len(res), 1)
80 class BasicDeleteTests(BaseDeleteTests):
83 super(BasicDeleteTests, self).setUp()
85 def del_attr_values(self, delObj):
86 print("Checking attributes for %s" % delObj["dn"])
88 self.assertEquals(str(delObj["isDeleted"][0]), "TRUE")
89 self.assertTrue(not("objectCategory" in delObj))
90 self.assertTrue(not("sAMAccountType" in delObj))
92 def preserved_attributes_list(self, liveObj, delObj):
93 print("Checking for preserved attributes list")
95 preserved_list = ["nTSecurityDescriptor", "attributeID", "attributeSyntax", "dNReferenceUpdate", "dNSHostName",
96 "flatName", "governsID", "groupType", "instanceType", "lDAPDisplayName", "legacyExchangeDN",
97 "isDeleted", "isRecycled", "lastKnownParent", "msDS-LastKnownRDN", "mS-DS-CreatorSID",
98 "mSMQOwnerID", "nCName", "objectClass", "distinguishedName", "objectGUID", "objectSid",
99 "oMSyntax", "proxiedObjectName", "name", "replPropertyMetaData", "sAMAccountName",
100 "securityIdentifier", "sIDHistory", "subClassOf", "systemFlags", "trustPartner", "trustDirection",
101 "trustType", "trustAttributes", "userAccountControl", "uSNChanged", "uSNCreated", "whenCreated"]
104 if a in preserved_list:
105 self.assertTrue(a in delObj)
107 def check_rdn(self, liveObj, delObj, rdnName):
108 print("Checking for correct rDN")
109 rdn = liveObj[rdnName][0]
110 rdn2 = delObj[rdnName][0]
111 name2 = delObj["name"][0]
112 dn_rdn = delObj.dn.get_rdn_value()
113 guid = liveObj["objectGUID"][0]
114 self.assertEquals(str(rdn2), ("%s\nDEL:%s" % (rdn, self.GUID_string(guid))))
115 self.assertEquals(str(name2), ("%s\nDEL:%s" % (rdn, self.GUID_string(guid))))
116 self.assertEquals(str(name2), dn_rdn)
118 def delete_deleted(self, ldb, dn):
119 print("Testing the deletion of the already deleted dn %s" % dn)
124 except LdbError as e:
126 self.assertEquals(num, ERR_NO_SUCH_OBJECT)
128 def test_delete_protection(self):
129 """Delete protection tests"""
133 delete_force(self.ldb, "cn=entry1,cn=ldaptestcontainer," + self.base_dn)
134 delete_force(self.ldb, "cn=entry2,cn=ldaptestcontainer," + self.base_dn)
135 delete_force(self.ldb, "cn=ldaptestcontainer," + self.base_dn)
138 "dn": "cn=ldaptestcontainer," + self.base_dn,
139 "objectclass": "container"})
141 "dn": "cn=entry1,cn=ldaptestcontainer," + self.base_dn,
142 "objectclass": "container"})
144 "dn": "cn=entry2,cn=ldaptestcontainer," + self.base_dn,
145 "objectclass": "container"})
148 self.ldb.delete("cn=ldaptestcontainer," + self.base_dn)
150 except LdbError as e1:
152 self.assertEquals(num, ERR_NOT_ALLOWED_ON_NON_LEAF)
154 self.ldb.delete("cn=ldaptestcontainer," + self.base_dn, ["tree_delete:1"])
157 res = self.ldb.search("cn=ldaptestcontainer," + self.base_dn,
158 scope=SCOPE_BASE, attrs=[])
160 except LdbError as e2:
162 self.assertEquals(num, ERR_NO_SUCH_OBJECT)
164 res = self.ldb.search("cn=entry1,cn=ldaptestcontainer," + self.base_dn,
165 scope=SCOPE_BASE, attrs=[])
167 except LdbError as e3:
169 self.assertEquals(num, ERR_NO_SUCH_OBJECT)
171 res = self.ldb.search("cn=entry2,cn=ldaptestcontainer," + self.base_dn,
172 scope=SCOPE_BASE, attrs=[])
174 except LdbError as e4:
176 self.assertEquals(num, ERR_NO_SUCH_OBJECT)
178 delete_force(self.ldb, "cn=entry1,cn=ldaptestcontainer," + self.base_dn)
179 delete_force(self.ldb, "cn=entry2,cn=ldaptestcontainer," + self.base_dn)
180 delete_force(self.ldb, "cn=ldaptestcontainer," + self.base_dn)
182 # Performs some protected object delete testing
184 res = self.ldb.search(base="", expression="", scope=SCOPE_BASE,
185 attrs=["dsServiceName", "dNSHostName"])
186 self.assertEquals(len(res), 1)
188 # Delete failing since DC's nTDSDSA object is protected
190 self.ldb.delete(res[0]["dsServiceName"][0])
192 except LdbError as e5:
194 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
196 res = self.ldb.search(self.base_dn, attrs=["rIDSetReferences"],
197 expression="(&(objectClass=computer)(dNSHostName=" + str(res[0]["dNSHostName"][0]) + "))")
198 self.assertEquals(len(res), 1)
200 # Deletes failing since DC's rIDSet object is protected
202 self.ldb.delete(res[0]["rIDSetReferences"][0])
204 except LdbError as e6:
206 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
208 self.ldb.delete(res[0]["rIDSetReferences"][0], ["tree_delete:1"])
210 except LdbError as e7:
212 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
214 # Deletes failing since three main crossRef objects are protected
217 self.ldb.delete("cn=Enterprise Schema,cn=Partitions," + self.configuration_dn)
219 except LdbError as e8:
221 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
223 self.ldb.delete("cn=Enterprise Schema,cn=Partitions," + self.configuration_dn, ["tree_delete:1"])
225 except LdbError as e9:
227 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
230 self.ldb.delete("cn=Enterprise Configuration,cn=Partitions," + self.configuration_dn)
232 except LdbError as e10:
234 self.assertEquals(num, ERR_NOT_ALLOWED_ON_NON_LEAF)
236 self.ldb.delete("cn=Enterprise Configuration,cn=Partitions," + self.configuration_dn, ["tree_delete:1"])
238 except LdbError as e11:
240 self.assertEquals(num, ERR_NOT_ALLOWED_ON_NON_LEAF)
242 res = self.ldb.search("cn=Partitions," + self.configuration_dn, attrs=[],
243 expression="(nCName=%s)" % self.base_dn)
244 self.assertEquals(len(res), 1)
247 self.ldb.delete(res[0].dn)
249 except LdbError as e12:
251 self.assertEquals(num, ERR_NOT_ALLOWED_ON_NON_LEAF)
253 self.ldb.delete(res[0].dn, ["tree_delete:1"])
255 except LdbError as e13:
257 self.assertEquals(num, ERR_NOT_ALLOWED_ON_NON_LEAF)
259 # Delete failing since "SYSTEM_FLAG_DISALLOW_DELETE"
261 self.ldb.delete("CN=Users," + self.base_dn)
263 except LdbError as e14:
265 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
267 # Tree-delete failing since "isCriticalSystemObject"
269 self.ldb.delete("CN=Computers," + self.base_dn, ["tree_delete:1"])
271 except LdbError as e15:
273 self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
276 class BasicTreeDeleteTests(BasicDeleteTests):
279 super(BasicTreeDeleteTests, self).setUp()
281 # user current time in ms to make unique objects
283 marker = str(int(round(time.time() * 1000)))
284 usr1_name = "u_" + marker
285 usr2_name = "u2_" + marker
286 grp_name = "g1_" + marker
287 site_name = "s1_" + marker
289 self.usr1 = "cn=%s,cn=users,%s" % (usr1_name, self.base_dn)
290 self.usr2 = "cn=%s,cn=users,%s" % (usr2_name, self.base_dn)
291 self.grp1 = "cn=%s,cn=users,%s" % (grp_name, self.base_dn)
292 self.sit1 = "cn=%s,cn=sites,%s" % (site_name, self.configuration_dn)
293 self.ss1 = "cn=NTDS Site Settings,cn=%s,cn=sites,%s" % (site_name, self.configuration_dn)
294 self.srv1 = "cn=Servers,cn=%s,cn=sites,%s" % (site_name, self.configuration_dn)
295 self.srv2 = "cn=TESTSRV,cn=Servers,cn=%s,cn=sites,%s" % (site_name, self.configuration_dn)
297 delete_force(self.ldb, self.usr1)
298 delete_force(self.ldb, self.usr2)
299 delete_force(self.ldb, self.grp1)
300 delete_force(self.ldb, self.ss1)
301 delete_force(self.ldb, self.srv2)
302 delete_force(self.ldb, self.srv1)
303 delete_force(self.ldb, self.sit1)
307 "objectclass": "user",
308 "description": "test user description",
309 "samaccountname": usr1_name})
313 "objectclass": "user",
314 "description": "test user 2 description",
315 "samaccountname": usr2_name})
319 "objectclass": "group",
320 "description": "test group",
321 "samaccountname": grp_name,
322 "member": [self.usr1, self.usr2],
323 "isDeleted": "FALSE"})
327 "objectclass": "site"})
331 "objectclass": ["applicationSiteSettings", "nTDSSiteSettings"]})
335 "objectclass": "serversContainer"})
339 "objectClass": "server"})
341 self.objLive1 = self.search_dn(self.usr1)
342 self.guid1 = self.objLive1["objectGUID"][0]
344 self.objLive2 = self.search_dn(self.usr2)
345 self.guid2 = self.objLive2["objectGUID"][0]
347 self.objLive3 = self.search_dn(self.grp1)
348 self.guid3 = self.objLive3["objectGUID"][0]
350 self.objLive4 = self.search_dn(self.sit1)
351 self.guid4 = self.objLive4["objectGUID"][0]
353 self.objLive5 = self.search_dn(self.ss1)
354 self.guid5 = self.objLive5["objectGUID"][0]
356 self.objLive6 = self.search_dn(self.srv1)
357 self.guid6 = self.objLive6["objectGUID"][0]
359 self.objLive7 = self.search_dn(self.srv2)
360 self.guid7 = self.objLive7["objectGUID"][0]
362 self.deleted_objects_config_dn \
363 = self.ldb.get_wellknown_dn(self.ldb.get_config_basedn(),
364 dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
365 deleted_objects_config_obj \
366 = self.search_dn(self.deleted_objects_config_dn)
368 self.deleted_objects_config_guid \
369 = deleted_objects_config_obj["objectGUID"][0]
371 self.deleted_objects_domain_dn \
372 = self.ldb.get_wellknown_dn(self.ldb.get_default_basedn(),
373 dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
374 deleted_objects_domain_obj \
375 = self.search_dn(self.deleted_objects_domain_dn)
377 self.deleted_objects_domain_guid \
378 = deleted_objects_domain_obj["objectGUID"][0]
380 self.deleted_objects_domain_dn \
381 = self.ldb.get_wellknown_dn(self.ldb.get_default_basedn(),
382 dsdb.DS_GUID_DELETED_OBJECTS_CONTAINER)
383 sites_obj = self.search_dn("cn=sites,%s"
384 % self.ldb.get_config_basedn())
385 self.sites_dn = sites_obj.dn
387 = sites_obj["objectGUID"][0]
390 """Basic delete tests"""
392 self.ldb.delete(self.usr1)
393 self.ldb.delete(self.usr2)
394 self.ldb.delete(self.grp1)
395 self.ldb.delete(self.srv1, ["tree_delete:1"])
396 self.ldb.delete(self.sit1, ["tree_delete:1"])
400 def test_tree_delete(self):
401 """Basic delete tests,
402 but use just one tree delete for the config records
405 self.ldb.delete(self.usr1)
406 self.ldb.delete(self.usr2)
407 self.ldb.delete(self.grp1)
408 self.ldb.delete(self.sit1, ["tree_delete:1"])
413 objDeleted1 = self.search_guid(self.guid1)
414 objDeleted2 = self.search_guid(self.guid2)
415 objDeleted3 = self.search_guid(self.guid3)
416 objDeleted4 = self.search_guid(self.guid4)
417 objDeleted5 = self.search_guid(self.guid5)
418 objDeleted6 = self.search_guid(self.guid6)
419 objDeleted7 = self.search_guid(self.guid7)
421 self.del_attr_values(objDeleted1)
422 self.del_attr_values(objDeleted2)
423 self.del_attr_values(objDeleted3)
424 self.del_attr_values(objDeleted4)
425 self.del_attr_values(objDeleted5)
426 self.del_attr_values(objDeleted6)
427 self.del_attr_values(objDeleted7)
429 self.preserved_attributes_list(self.objLive1, objDeleted1)
430 self.preserved_attributes_list(self.objLive2, objDeleted2)
431 self.preserved_attributes_list(self.objLive3, objDeleted3)
432 self.preserved_attributes_list(self.objLive4, objDeleted4)
433 self.preserved_attributes_list(self.objLive5, objDeleted5)
434 self.preserved_attributes_list(self.objLive6, objDeleted6)
435 self.preserved_attributes_list(self.objLive7, objDeleted7)
437 self.check_rdn(self.objLive1, objDeleted1, "cn")
438 self.check_rdn(self.objLive2, objDeleted2, "cn")
439 self.check_rdn(self.objLive3, objDeleted3, "cn")
440 self.check_rdn(self.objLive4, objDeleted4, "cn")
441 self.check_rdn(self.objLive5, objDeleted5, "cn")
442 self.check_rdn(self.objLive6, objDeleted6, "cn")
443 self.check_rdn(self.objLive7, objDeleted7, "cn")
445 self.delete_deleted(self.ldb, self.usr1)
446 self.delete_deleted(self.ldb, self.usr2)
447 self.delete_deleted(self.ldb, self.grp1)
448 self.delete_deleted(self.ldb, self.sit1)
449 self.delete_deleted(self.ldb, self.ss1)
450 self.delete_deleted(self.ldb, self.srv1)
451 self.delete_deleted(self.ldb, self.srv2)
453 self.assertTrue("CN=Deleted Objects" in str(objDeleted1.dn))
454 self.assertEqual(objDeleted1.dn.parent(),
455 self.deleted_objects_domain_dn)
456 self.assertEqual(objDeleted1["parentGUID"][0],
457 self.deleted_objects_domain_guid)
459 self.assertTrue("CN=Deleted Objects" in str(objDeleted2.dn))
460 self.assertEqual(objDeleted2.dn.parent(),
461 self.deleted_objects_domain_dn)
462 self.assertEqual(objDeleted2["parentGUID"][0],
463 self.deleted_objects_domain_guid)
465 self.assertTrue("CN=Deleted Objects" in str(objDeleted3.dn))
466 self.assertEqual(objDeleted3.dn.parent(),
467 self.deleted_objects_domain_dn)
468 self.assertEqual(objDeleted3["parentGUID"][0],
469 self.deleted_objects_domain_guid)
471 self.assertFalse("CN=Deleted Objects" in str(objDeleted4.dn))
472 self.assertEqual(objDeleted4.dn.parent(),
474 self.assertEqual(objDeleted4["parentGUID"][0],
477 self.assertTrue("CN=Deleted Objects" in str(objDeleted5.dn))
478 self.assertEqual(objDeleted5.dn.parent(),
479 self.deleted_objects_config_dn)
480 self.assertEqual(objDeleted5["parentGUID"][0],
481 self.deleted_objects_config_guid)
483 self.assertFalse("CN=Deleted Objects" in str(objDeleted6.dn))
484 self.assertEqual(objDeleted6.dn.parent(),
486 self.assertEqual(objDeleted6["parentGUID"][0],
487 objDeleted4["objectGUID"][0])
489 self.assertFalse("CN=Deleted Objects" in str(objDeleted7.dn))
490 self.assertEqual(objDeleted7.dn.parent(),
492 self.assertEqual(objDeleted7["parentGUID"][0],
493 objDeleted6["objectGUID"][0])
495 objDeleted1 = self.search_guid(self.guid1)
496 objDeleted2 = self.search_guid(self.guid2)
497 objDeleted3 = self.search_guid(self.guid3)
498 objDeleted4 = self.search_guid(self.guid4)
499 objDeleted5 = self.search_guid(self.guid5)
500 objDeleted6 = self.search_guid(self.guid6)
501 objDeleted7 = self.search_guid(self.guid7)
503 self.del_attr_values(objDeleted1)
504 self.del_attr_values(objDeleted2)
505 self.del_attr_values(objDeleted3)
506 self.del_attr_values(objDeleted4)
507 self.del_attr_values(objDeleted5)
508 self.del_attr_values(objDeleted6)
509 self.del_attr_values(objDeleted7)
511 self.preserved_attributes_list(self.objLive1, objDeleted1)
512 self.preserved_attributes_list(self.objLive2, objDeleted2)
513 self.preserved_attributes_list(self.objLive3, objDeleted3)
514 self.preserved_attributes_list(self.objLive4, objDeleted4)
515 self.preserved_attributes_list(self.objLive5, objDeleted5)
516 self.preserved_attributes_list(self.objLive6, objDeleted6)
517 self.preserved_attributes_list(self.objLive7, objDeleted7)
519 self.check_rdn(self.objLive1, objDeleted1, "cn")
520 self.check_rdn(self.objLive2, objDeleted2, "cn")
521 self.check_rdn(self.objLive3, objDeleted3, "cn")
522 self.check_rdn(self.objLive4, objDeleted4, "cn")
523 self.check_rdn(self.objLive5, objDeleted5, "cn")
524 self.check_rdn(self.objLive6, objDeleted6, "cn")
525 self.check_rdn(self.objLive7, objDeleted7, "cn")
527 self.delete_deleted(self.ldb, self.usr1)
528 self.delete_deleted(self.ldb, self.usr2)
529 self.delete_deleted(self.ldb, self.grp1)
530 self.delete_deleted(self.ldb, self.sit1)
531 self.delete_deleted(self.ldb, self.ss1)
532 self.delete_deleted(self.ldb, self.srv1)
533 self.delete_deleted(self.ldb, self.srv2)
535 self.assertTrue("CN=Deleted Objects" in str(objDeleted1.dn))
536 self.assertEqual(objDeleted1.dn.parent(),
537 self.deleted_objects_domain_dn)
538 self.assertEqual(objDeleted1["parentGUID"][0],
539 self.deleted_objects_domain_guid)
540 self.assertTrue("CN=Deleted Objects" in str(objDeleted2.dn))
541 self.assertEqual(objDeleted2.dn.parent(),
542 self.deleted_objects_domain_dn)
543 self.assertEqual(objDeleted2["parentGUID"][0],
544 self.deleted_objects_domain_guid)
545 self.assertTrue("CN=Deleted Objects" in str(objDeleted3.dn))
546 self.assertEqual(objDeleted3.dn.parent(),
547 self.deleted_objects_domain_dn)
548 self.assertEqual(objDeleted3["parentGUID"][0],
549 self.deleted_objects_domain_guid)
550 self.assertFalse("CN=Deleted Objects" in str(objDeleted4.dn))
551 self.assertTrue("CN=Deleted Objects" in str(objDeleted5.dn))
552 self.assertEqual(objDeleted5.dn.parent(),
553 self.deleted_objects_config_dn)
554 self.assertEqual(objDeleted5["parentGUID"][0],
555 self.deleted_objects_config_guid)
556 self.assertFalse("CN=Deleted Objects" in str(objDeleted6.dn))
557 self.assertFalse("CN=Deleted Objects" in str(objDeleted7.dn))
560 if "://" not in host:
561 if os.path.isfile(host):
562 host = "tdb://%s" % host
564 host = "ldap://%s" % host
566 TestProgram(module=__name__, opts=subunitopts)