s4-dsdb: Tests for security checks on undelete operation
authorNadezhda Ivanova <nivanova@symas.com>
Tue, 4 Nov 2014 18:08:58 +0000 (20:08 +0200)
committerAndrew Bartlett <abartlet@samba.org>
Tue, 3 Feb 2015 04:02:11 +0000 (05:02 +0100)
Implemented according to MS-ADTS 3.1.1.5.3.7.1. Unfortunately it appears
LC is also necessary, and it is not granted by default to anyone but
System and Administrator, so tests had to be done negatively

Signed-off-by: Nadezhda Ivanova <nivanova@symas.com>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Garming Sam <garming@catalyst.net.nz>
Change-Id: Ic03b8fc4e222e7842ec8a9645a1bb33e7df9c438

python/samba/sd_utils.py
source4/dsdb/tests/python/acl.py

index ded9bfc19262aa975f3b1e1c7c6476c6896b7c3d..7592a2982a4c08e80c82fecf41d5303e8affae58 100644 (file)
@@ -62,7 +62,7 @@ class SDUtils(object):
     def dacl_add_ace(self, object_dn, ace):
         """Add an ACE to an objects security descriptor
         """
-        desc = self.read_sd_on_dn(object_dn)
+        desc = self.read_sd_on_dn(object_dn,["show_deleted:1"])
         desc_sddl = desc.as_sddl(self.domain_sid)
         if ace in desc_sddl:
             return
@@ -71,10 +71,10 @@ class SDUtils(object):
                          desc_sddl[desc_sddl.index("("):])
         else:
             desc_sddl = desc_sddl + ace
-        self.modify_sd_on_dn(object_dn, desc_sddl)
+        self.modify_sd_on_dn(object_dn, desc_sddl, ["show_deleted:1"])
 
-    def get_sd_as_sddl(self, object_dn, controls=None):
+    def get_sd_as_sddl(self, object_dn, controls=[]):
         """Return object nTSecutiryDescriptor in SDDL format
         """
-        desc = self.read_sd_on_dn(object_dn, controls=controls)
+        desc = self.read_sd_on_dn(object_dn, controls + ["show_deleted:1"])
         return desc.as_sddl(self.domain_sid)
index 4acc12349f41e1a2408b8d847dbb68146b1fc080..d8e896299390e978a9fd6616dfdcffc7fa549c3a 100755 (executable)
@@ -20,7 +20,7 @@ from ldb import (
 from ldb import ERR_CONSTRAINT_VIOLATION
 from ldb import ERR_OPERATIONS_ERROR
 from ldb import Message, MessageElement, Dn
-from ldb import FLAG_MOD_REPLACE, FLAG_MOD_ADD
+from ldb import FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE
 from samba.dcerpc import security, drsuapi, misc
 
 from samba.auth import system_session
@@ -1637,6 +1637,136 @@ class AclExtendedTests(AclTests):
         self.assertEqual(len(res),1)
         self.assertTrue("nTSecurityDescriptor" in res[0].keys())
 
+class AclUndeleteTests(AclTests):
+
+    def setUp(self):
+        super(AclUndeleteTests, self).setUp()
+        self.regular_user = "undeleter1"
+        self.ou1 = "OU=undeleted_ou,"
+        self.testuser1 = "to_be_undeleted1"
+        self.testuser2 = "to_be_undeleted2"
+        self.testuser3 = "to_be_undeleted3"
+        self.testuser4 = "to_be_undeleted4"
+        self.testuser5 = "to_be_undeleted5"
+        self.testuser6 = "to_be_undeleted6"
+
+        self.new_dn_ou = "CN="+ self.testuser4 + "," + self.ou1 + self.base_dn
+
+        # Create regular user
+        self.testuser1_dn = self.get_user_dn(self.testuser1)
+        self.testuser2_dn = self.get_user_dn(self.testuser2)
+        self.testuser3_dn = self.get_user_dn(self.testuser3)
+        self.testuser4_dn = self.get_user_dn(self.testuser4)
+        self.testuser5_dn = self.get_user_dn(self.testuser5)
+        self.deleted_dn1 = self.create_delete_user(self.testuser1)
+        self.deleted_dn2 = self.create_delete_user(self.testuser2)
+        self.deleted_dn3 = self.create_delete_user(self.testuser3)
+        self.deleted_dn4 = self.create_delete_user(self.testuser4)
+        self.deleted_dn5 = self.create_delete_user(self.testuser5)
+
+        self.ldb_admin.create_ou(self.ou1 + self.base_dn)
+
+        self.ldb_admin.newuser(self.regular_user, self.user_pass)
+        self.ldb_admin.add_remove_group_members("Domain Admins", [self.regular_user],
+                       add_members_operation=True)
+        self.ldb_user = self.get_ldb_connection(self.regular_user, self.user_pass)
+        self.sid = self.sd_utils.get_object_sid(self.get_user_dn(self.regular_user))
+
+    def tearDown(self):
+        super(AclUndeleteTests, self).tearDown()
+        delete_force(self.ldb_admin, self.get_user_dn(self.regular_user))
+        delete_force(self.ldb_admin, self.get_user_dn(self.testuser1))
+        delete_force(self.ldb_admin, self.get_user_dn(self.testuser2))
+        delete_force(self.ldb_admin, self.get_user_dn(self.testuser3))
+        delete_force(self.ldb_admin, self.get_user_dn(self.testuser4))
+        delete_force(self.ldb_admin, self.get_user_dn(self.testuser5))
+        delete_force(self.ldb_admin, self.new_dn_ou)
+        delete_force(self.ldb_admin, self.ou1 + self.base_dn)
+
+    def GUID_string(self, guid):
+        return ldb.schema_format_value("objectGUID", guid)
+
+    def create_delete_user(self, new_user):
+        self.ldb_admin.newuser(new_user, self.user_pass)
+
+        res = self.ldb_admin.search(expression="(objectClass=*)",
+                                    base=self.get_user_dn(new_user),
+                                    scope=SCOPE_BASE,
+                                    controls=["show_deleted:1"])
+        guid = res[0]["objectGUID"][0]
+        self.ldb_admin.delete(self.get_user_dn(new_user))
+        res = self.ldb_admin.search(base="<GUID=%s>" % self.GUID_string(guid),
+                         scope=SCOPE_BASE, controls=["show_deleted:1"])
+        self.assertEquals(len(res), 1)
+        return str(res[0].dn)
+
+    def undelete_deleted(self, olddn, newdn):
+        msg = Message()
+        msg.dn = Dn(self.ldb_user, olddn)
+        msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted")
+        msg["distinguishedName"] = MessageElement([newdn], FLAG_MOD_REPLACE, "distinguishedName")
+        res = self.ldb_user.modify(msg, ["show_recycled:1"])
+
+    def undelete_deleted_with_mod(self, olddn, newdn):
+        msg = Message()
+        msg.dn = Dn(ldb, olddn)
+        msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted")
+        msg["distinguishedName"] = MessageElement([newdn], FLAG_MOD_REPLACE, "distinguishedName")
+        msg["url"] = MessageElement(["www.samba.org"], FLAG_MOD_REPLACE, "url")
+        res = self.ldb_user.modify(msg, ["show_deleted:1"])
+
+    def test_undelete(self):
+        # it appears the user has to have LC on the old parent to be able to move the object
+        # otherwise we get no such object. Since only System can modify the SD on deleted object
+        # we cannot grant this permission via LDAP, and this leaves us with "negative" tests at the moment
+
+        # deny write property on rdn, should fail
+        mod = "(OD;;WP;bf967a0e-0de6-11d0-a285-00aa003049e2;;%s)" % str(self.sid)
+        self.sd_utils.dacl_add_ace(self.deleted_dn1, mod)
+        try:
+            self.undelete_deleted(self.deleted_dn1, self.testuser1_dn)
+            self.fail()
+        except LdbError, (num, _):
+            self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
+
+        # seems that permissions on isDeleted and distinguishedName are irrelevant
+        mod = "(OD;;WP;bf96798f-0de6-11d0-a285-00aa003049e2;;%s)" % str(self.sid)
+        self.sd_utils.dacl_add_ace(self.deleted_dn2, mod)
+        mod = "(OD;;WP;bf9679e4-0de6-11d0-a285-00aa003049e2;;%s)" % str(self.sid)
+        self.sd_utils.dacl_add_ace(self.deleted_dn2, mod)
+        self.undelete_deleted(self.deleted_dn2, self.testuser2_dn)
+
+        # attempt undelete with simultanious addition of url, WP to which is denied
+        mod = "(OD;;WP;9a9a0221-4a5b-11d1-a9c3-0000f80367c1;;%s)" % str(self.sid)
+        self.sd_utils.dacl_add_ace(self.deleted_dn3, mod)
+        try:
+            self.undelete_deleted_with_mod(self.deleted_dn3, self.testuser3_dn)
+            self.fail()
+        except LdbError, (num, _):
+            self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
+
+        # undelete in an ou, in which we have no right to create children
+        mod = "(D;;CC;;;%s)" % str(self.sid)
+        self.sd_utils.dacl_add_ace(self.ou1 + self.base_dn, mod)
+        try:
+            self.undelete_deleted(self.deleted_dn4, self.new_dn_ou)
+            self.fail()
+        except LdbError, (num, _):
+            self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
+
+        # delete is not required
+        mod = "(D;;SD;;;%s)" % str(self.sid)
+        self.sd_utils.dacl_add_ace(self.deleted_dn5, mod)
+        self.undelete_deleted(self.deleted_dn5, self.testuser5_dn)
+
+        # deny Reanimate-Tombstone, should fail
+        mod = "(OD;;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;%s)" % str(self.sid)
+        self.sd_utils.dacl_add_ace(self.base_dn, mod)
+        try:
+            self.undelete_deleted(self.deleted_dn4, self.testuser4_dn)
+            self.fail()
+        except LdbError, (num, _):
+            self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
 
 class AclSPNTests(AclTests):